Search documentation of Metastatic

# Metastatic.Analysis.BusinessLogic.UnmanagedTask (Metastatic v0.13.2)

View Source

Detects unsupervised async operations that can cause memory leaks.

This analyzer identifies spawning of async tasks/threads without proper supervision or error handling - a common concurrency anti-pattern.

## Cross-Language Applicability

This is a universal concurrency anti-pattern across all async/concurrent systems:

- Python/asyncio: `asyncio.create_task()` without task group
- Python/threading: `Thread(target=fn).start()` without join
- JavaScript: `new Promise()` without `.catch()`
- JavaScript/Node: Promise without rejection handler
- Elixir: `Task.async()` without `Task.Supervisor`
- Go: `go func()` without wait group or context
- C#: `Task.Run()` without continuation or error handling
- Java: `CompletableFuture.runAsync()` without exception handling
- Rust: `tokio::spawn()` without join handle management

## Problem

Unmanaged async operations can:

- Leak memory if tasks never complete
- Silently fail without error propagation
- Cause race conditions without synchronization
- Leave orphaned processes after parent exits
- Exhaust resources (threads, file handles, connections)

## Examples

### Bad (Python)

    async def handler():
        asyncio.create_task(background_work())  # Fire and forget!
        # Task may fail silently, no cleanup

### Good (Python)

    async def handler():
        async with asyncio.TaskGroup() as tg:
            tg.create_task(background_work())
        # Tasks are supervised, errors propagate

### Bad (JavaScript)

    function handler() {
        new Promise(async (resolve) => {
            await riskyWork();  // No error handling!
        });
    }

### Good (JavaScript)

    function handler() {
        Promise.resolve()
            .then(() => riskyWork())
            .catch(handleError);
    }

### Bad (Elixir)

    def handle_event(event) do
      Task.async(fn -> process(event) end)  # Unmanaged!
    end

### Good (Elixir)

    def handle_event(event) do
      Task.Supervisor.async_nolink(MySupervisor, fn ->
        process(event)
      end)
    end

### Bad (Go)

    func handler() {
        go riskyWork()  // No wait, no error handling
    }

### Good (Go)

    func handler(ctx context.Context) {
        var wg sync.WaitGroup
        wg.Add(1)
        go func() {
            defer wg.Done()
            riskyWork(ctx)
        }()
        wg.Wait()
    }

### Bad (C#)

    void Handler() {
        Task.Run(() => RiskyWork());  // Fire and forget
    }

### Good (C#)

    async Task Handler() {
        await Task.Run(() => RiskyWork())
            .ContinueWith(t => HandleError(t.Exception));
    }

## Detection Strategy

Detects MetaAST pattern:

    {:async_operation, :spawn, lambda}

Or function calls matching async spawn patterns:

- `Task.async`, `asyncio.create_task`, `Promise.resolve`
- `go func()`, `Task.Run`, `spawn`

Without subsequent supervision/await/join in same scope.

## Limitations

- Cannot detect if supervision exists in parent scope
- Heuristic-based: may have false positives
- Difficult to detect await in different block