Metastatic.Analysis.BusinessLogic.UnmanagedTask (Metastatic v0.10.4)

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