magus v0.4.3 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
3 min read

MGS3013: build slots deadlocked

Every slot in this run's concurrency pool is held by a step that is itself waiting for something, and other steps are queued for a slot. No slot can free, so nothing queued can start, so nothing can free a slot. magus refuses the wait instead of spending it:

[MGS3013] refusing to keep waiting for a build slot: all 8 of this run's slots are held
  by steps that are themselves waiting, so no slot can free and nothing queued can start.
  Holding: . build holds 1 and is waiting on 1 build slot(s); . ci holds 1 and is waiting
  on the cache lock for 4f1ac2be, held by . generate; ... Queued: . mocks-generate needs 1;
  . types-generate needs 1. The usual cause is a target reading what a target beside it
  writes with no ctx.needs between them (see ...); order the reader after the writer.
  Raising concurrency widens the window rather than closing it.

The message is the whole investigation: every holder, how many slots it has, and what each one is waiting for.

The rule this protects

magus bounds concurrency the way make's jobserver does: a slot is held while a recipe runs, and a step that stops running to wait for something hands its slots back first. A composed target yields its slots across its ctx.needs fan-out for exactly this reason, so the targets it composes can be admitted.

Two waits keep the slots: a step waiting for the per-key cache lock, and a step waiting for another slot while already holding one. Either is fine on its own. Both at once, across every holder, is a wait that nothing in the process can end.

What it usually means

An unordered reader inside one composer's chain (MGS4008). A reader that shares a step with the writer of the files it reads has no way to wait for it except to occupy its seat while the writer queues for one that will never come. MGS4008 refuses that plan before the run starts; this code catches the same shape when it arrives some other way, and names it in seconds instead of leaving the run to the stall watchdog (MGS3012).

Resolve it

  1. Read the "Holding" list. Each entry says what that step is waiting for. One of them is the wait to remove.

  2. Order the reader after the writer. When a holder is waiting for a slot while a generator is queued, they are almost certainly members of one chain: ctx.needs(<writer>) in the reader is the fix, and MGS4008 has the worked example.

  3. Check for a target that dispatches magus recursively. A target running another magus command from its body waits on a child that has to be admitted by the same pool. Compose it with ctx.needs instead, which yields.

  4. Do not reach for higher concurrency. It makes the wedge less likely on this machine and no less real: the same run on a smaller machine, or with one more project selected, deadlocks again.

What it does not fire on

A busy pool. Queuing behind steps that are working is ordinary, and holders that are running answer "not deadlocked" however long they take.

A momentary hand-off. A step releases its slots in a defer while its peer is already queued, so a sample taken in that instant looks like the wedge. The verdict requires the shape to hold continuously for a short grace before it is believed.

Slots reserved outside a step. A spell that reserves slots for a tool's own workers holds them without a step record, which leaves the accounted total short of capacity, and a wait that might still end is never refused.

See also

  • MGS4008: the plan-time refusal for the authoring mistake that causes this.
  • MGS3012: the stall watchdog, which bounds a run making no progress for any reason at all.
  • MGS3009: the machine budget, the other reason a step does not start.
  • Concurrency: slots, yielding, and the jobserver rule.
MGS3013deadlockconcurrencyslotslimiterjobserverhangstallctx.needs
Last updated (0c2c811f)
Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Server

The background process a person starts with magus server start. It serves MCP, the console, background jobs and the warm knowledge graph, and adopts nested magus calls into one pool. See server.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Pool

The concurrency pool: the shared set of slots that caps how many targets run in parallel on one machine. Its capacity defaults to MAGUS_CONCURRENCY, then 4 on GitHub-hosted runners, then min(NumCPU, 8); magus status and the dashboard report it live. See server.

Slot

One unit of the pool's capacity. A target acquires the slots it needs to run (most take one) and releases them when it finishes; the pool tracks capacity (total slots), running (acquired), and queued (blocked). See server.

Concurrency

How many targets run at once. It is bounded by the pool's capacity and set with --concurrency, MAGUS_CONCURRENCY, or the concurrency config key. See server.

Queued

A target that wants a slot while the pool is full; it blocks first-in-first-out until a slot frees. The dashboard colors a sample with queued > 0 accordingly. See server.

Window

The terminal a command runs in. It keys fire-once notices for a caller no host gave a session, and is never recorded as a session.

Job

The unit of delegated work, and one row of the job store: what an orchestrating agent handed out, with its goal, the checkpoint it was cut against, the paths it may write or must not touch, and the one check it runs. A job's holder is either a session, for work an orchestrator handed out, or the server, for its own maintenance. The store records; the agent guard is what reads those facts back when grading a write. See doctrine.

A job is not a run. magus run build web is a run, and no job exists for it. A job causes runs: its check executes as one, and a server job records the invocation of its last one. Jobs are listed with magus ls jobs and in the console's Jobs view; runs are listed in the Runs view.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate.

Lease

The grant a holder takes on a job: the write and read paths that job declared, enforced in the checkout that took it with magus job exec. A job is the piece of work; a lease is permission over it.

Conventions

Placeholders

Angle brackets mark a value you replace with your own - never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.