Skip to content

Combined review: WebSocket execution stack - #480

Closed
hermabr wants to merge 19 commits into
mainfrom
t3code/combine-open-prs
Closed

Combined review: WebSocket execution stack#480
hermabr wants to merge 19 commits into
mainfrom
t3code/combine-open-prs

Conversation

@hermabr

@hermabr hermabr commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Aggregate the full WebSocket execution stack into one PR against main so the complete change can be reviewed together. This branch contains the exact stacked history through #479, without extra code changes or synthetic merge commits.

Includes:

Testing

  • uv run pytest -n 0 tests/test_worker_execution_coordinator.py tests/test_slurm_backend.py tests/test_worker_subprocess.py tests/test_config.py tests/test_provenance_wiring.py — 145 passed
  • uv run ruff check . — passed
  • uv run ty check — passed
  • Full suite — 603 passed, 4 skipped, 3 failures in unchanged multiprocessing contention tests with a 0.5-second process-start deadline; serial rerun passed 2/3, with the remaining child starting just after the deadline

hermabr and others added 19 commits July 31, 2026 08:33
- Replace the FastAPI/HTTP API with a versioned WebSocket worker protocol
- Remove HTTP client dependencies and coordinate local worker pools directly
- Use ExecutionCoordinator directly for worker backend coordination
- Update protocol discriminators and tests
- Simplify hello messages and server connection handling
- Remove obsolete protocol version validation test
lease_job now blocks on the coordinator's condition until a job is
available or the run is done, deleting the "wait" token and the server's
1s wake poll (and with it a missed-wakeup race). The lock and wake
primitives merge into a single Condition notified by mutators.

The strictly sequential worker channel drops its envelopes: workers send
a bare JobResult instead of ResultMessage, hellos validate directly, and
worker_message_adapter goes away. lease_job also stops releasing prior
leases per request - connection teardown (worker_lost) is the single
release path. client.py folds into its only caller, worker_loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Idle worker handlers block in lease_job without touching the socket, so
closing their connections never woke them: an interrupted run waited out
the 10s drain timeout and leaked non-daemon handler threads that hang
interpreter exit. Server shutdown now marks the coordinator finished
first, which wakes every waiting handler, and the run loop closes the
server before joining worker pools so pool stops only reap
already-exiting workers.

With that, the explicit stop/assign envelopes are unnecessary: the
server sends Job frames directly and a normal WebSocket close is the
stop signal, removing AssignMessage, StopMessage, their kind
discriminators, and the server message adapter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only one job can be outstanding per connection and the server retains
the Job it sent until the matching result arrives, so per-member UUID
lease IDs carry no information. Key the coordinator's running table by
object id (unique among running jobs because nodes are popped from
ready) and send Job.artifacts as a plain list of ArtifactSpec.

This deletes JobMember, RunningJob.lease_id, FailedJob.lease_id, UUID
generation, and the lease-centric log details.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dynamic scaler (scale thread, polling interval, failed-thread
bookkeeping, restart budget, idle timeouts forcing workers to exit and
be recreated) was built for the HTTP era when idle workers polled. An
idle WebSocket worker is just a blocked thread, so start max_workers
threads once, keep them connected with no idle timeout, and let the
server closing the connection end them.

A crashed worker thread now fails the run immediately: job failures are
already caught inside the worker loop, so a thread-level crash means a
furu bug, not a job problem.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three failure budgets overlapped: coordinator per-object retries,
worker consecutive-job failures, and pool failed-worker restarts. The
worker threshold (5) rarely fired under the coordinator's retry
default (3), and when it did the worker exited zero, Slurm recorded a
normal completion, and the pool replaced it without consuming any
restart budget. The Slurm restart budget itself could never govern
anything because the scale loop fails the whole run on the first
failed Slurm state.

Keep two policies: the coordinator owns job failure and retry; the
pool owns worker/allocation failure. Remove max_consecutive_failures
(worker loop, CLI flag, sbatch script plumbing), the unused Slurm
restart budget, the now-unused worker.max_failed_restarts config, and
the never-read SlurmWorkerPool._server_url/_auth_token fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hermabr hermabr closed this Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant