Skip to content

distributed: worker recycles a stopped backend's gRPC port, allowing a silent misroute #10952

Description

@localai-bot

Summary

stopBackendExact returns a stopped backend's gRPC port to freePorts synchronously, and startBackend pops it for the very next backend. A controller holding a cached NodeModel row for the old host:port can then pass probeHealth against a different backend now bound to that port, and dispatch a model load to it.

Why this is newly reachable

Before the reaping fix, backend.delete never actually stopped a process (it resolved to zero process keys and silently no-op'd), so a delete never recycled a port. Once deletes genuinely reap, they do.

The delete case is the worst one: unlike model.unload, the controller has no idea the address just became invalid, so nothing prompts it to re-probe.

Failure shape

  1. Backend A is running for model M on node N at port P; the controller has a NodeModel row pointing at N:P.
  2. Backend A is deleted. Its process is now correctly reaped, and P returns to freePorts.
  3. Backend B starts on node N and is assigned P.
  4. The controller routes a request for M, finds the cached row, probeHealth succeeds — because something is listening on P — and dispatches to backend B.

The probe verifies liveness, not identity, so this is silent. The request goes to the wrong backend rather than failing.

Scope

Narrow: requires a delete immediately followed by an install on the same worker, with an in-flight route still holding the old address. It is also strictly better than the status quo it replaces, which was routing into a backend whose directory had been deleted out from under it (observed in production: a certifi path resolving into a removed directory).

But it converts a loud failure into a potentially silent misroute, which is worth closing deliberately rather than leaving implicit.

Options

  1. Quarantine freed ports for a grace period before returning them to freePorts. Simple, narrows the window, does not eliminate it.
  2. Verify backend identity in probeHealth rather than mere liveness. Fixes the class of bug rather than the window, and would also catch any other stale-address path. Larger change.

Option 2 is the more complete fix; option 1 is the cheaper one.

Related

  • Reaping fix: deleting a backend previously orphaned its process (the change that makes this reachable).
  • Deliberately deferred alternative: BackendDeleteReply could return the stopped process keys so the controller drops matching NodeModel rows eagerly rather than waiting for the next probeHealth. That was skipped because it adds a message-schema change and cross-version compat surface, but it would also mitigate this.

Found while debugging a distributed LongCat-Video load failure on an ARM64 L4T worker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions