Scheduling
Interactive work stays snappy while long jobs keep moving.
Ten agents on one daemon only work if a whole-program analysis can't stall a 10 ms decompile, and an agent that sends a hundred requests can't starve one that sends one. Two lanes, per-program locks and deadlines take care of both.
Two lanes
Every request takes a slot in a lane before it takes any lock:
| Lane | Runs | Slots |
|---|---|---|
| Quick | decompile, functions, list, open, rename | one per CPU (maxQuick) |
| Long | analyze, import, script, save | a quarter of the CPUs, at least 2 (maxLong) |
Long work can never take the slots quick queries need. When a lane is full, waiting requests are admitted round-robin by client. The daemon's own background work, such as building an upgraded copy of an old program, takes a long slot only while no client is waiting for one.
Per-program locks
Each program has a fair read/write lock. Queries and contained scripts share it, so readers run in parallel, each with its own pooled decompiler. Edits, analysis, saves and owner scripts hold it alone. Different programs, and different projects, never wait on each other.
Nothing waits forever
Each request has a wait: how many milliseconds it may wait for a slot or a lock (10,000 by default). When that runs out it fails with busy, naming the holder, what it's doing and for how long, and nothing stays queued past the deadline.
1s.call("decompile", address="rva:0x1000", wait=30000)Imports are the exception: by default a queued import waits until it runs or is cancelled, unless you give it a numeric wait.
The import queue
Up to maxImports imports run at once (one per four CPUs, 1 to 4), in any projects. Four large PEs took 64 s in parallel instead of 165 s one after another. The rest wait first come, first served; while one waits it sends progress events with its position and an estimate, and status.imports shows the whole queue.
Cancelling
| Request | Cancels |
|---|---|
{"cmd": "cancel", "target": 17} | One of your requests, running or queued. |
{"cmd": "cancel", "all": true} | All of your connection's requests. |
cancel with client/harness labels | Only that label's requests. |
{"cmd": "cancel", "cacheBuild": "all"} | Background upgrade-cache builds. |
A cancelled script stops at its next monitor.checkCancelled(). A script that never checks its monitor can't be stopped from outside, because it runs inside the daemon's JVM.
Watching it
status shows each lane's running and waiting requests by label, every client's unanswered requests with their latest progress, and the import queue. It's the first thing to look at when something feels slow.
1python $GS status