Skip to content

paper: match 0.6.4 - #34

Open
trissim wants to merge 1 commit into
mainfrom
paper/match-0.6.4
Open

trissim wants to merge 1 commit into
mainfrom
paper/match-0.6.4

Conversation

@trissim

@trissim trissim commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Brings paper.md in line with the code at v0.6.4 (1d8160b, which is also main). No library code changes. Paths below are under src/zmqruntime/ unless they start with tests/ or openhcs/. The repository has no CHANGELOG.md, so the source was the only reference.

Nothing was executed: the machine was loaded, no environment was created and no test was run. Every name and call signature in the code example was checked by reading the source: ExecutionClient and ExecutionSubmissionResponse are importable from zmqruntime.execution (execution/init.py:11, 26-30), ExecuteRequest from zmqruntime.messages (messages.py:591-604), the three abstract methods the subclass must define are serialize_task (execution/client.py:388), _spawn_server_process and send_data (client.py:1473-1479), the constructor takes port (execution/client.py:52-61), and launch().to_argument(), submit_execution(task, config), ExecutionSubmissionResponse.from_wire(...).require_execution_id(context) and wait_for_completion(execution_id) exist with those signatures (client.py:927; messages.py:1022; execution/client.py:94, 118; execution/responses.py:74, 86). The example was not run. serve.py, pipeline_code and config_code in it are placeholders, as in the old example.

Claim ledger

# Old claim Status What it became Source
1 "distributed execution framework for Python using ZMQ as the transport layer" true as far as it goes "runs tasks in a separate server process over ZeroMQ"; title kept server.py:40; execution/server.py:51
2 Dual-channel pattern: a control channel and a data channel true kept server.py:136-137, 156-182
3 Control channel is REQ/REP true kept server.py:179; execution/client.py:212
4 Data channel is PUSH/PULL false "PUB/SUB by default". The server's data socket defaults to PUB and the client connects a SUB socket. PUSH/PULL appears only in the per-image acknowledgement route of viewers server.py:98; client.py:1296; streaming/server.py:99; ack_listener.py:226
5 Data channel carries "results and streaming output" false The data channel carries numbered progress events and the final status record. Task results are not sent: the server keeps only a count and the keys of a returned dict execution/server.py:134-154, 271-281, 323-327, 450-465
6 "Streaming results: large arrays stream directly to disk/memory without buffering in memory" false removed. No array crosses the execution data channel. The viewer base class copies images out of shared memory, which is a different path and is not "without buffering" execution/server.py:153; streaming/server.py:34-64
7 "Non-blocking execution: submit a job, get an execution ID, poll for results" true, with other method names submit_execution returns the ID; poll_status and wait_for_completion follow it execution/client.py:94-116, 118-159; execution/server.py:236-256
8 from zmqruntime import ExecutionClient false ExecutionClient is not exported from the package root; the example imports it from zmqruntime.execution init.py:17-192; execution/init.py:11
9 ExecutionClient(port=5555) can be instantiated false It is abstract (serialize_task, _spawn_server_process, send_data); the example now defines a subclass execution/client.py:49, 388; client.py:1473-1479
10 client.execute(...) is non-blocking and "returns execution_id immediately" (twice) false execute submits and then waits for completion. The example uses submit_execution execution/client.py:161-171
11 client.get_status(exec_id), client.get_results(exec_id) (three uses) false Neither method exists anywhere in the source. Replaced by poll_status / wait_for_completion; the second code block was removed grep -rn "get_results|def get_status" src is empty
12 "Graceful cancellation: cancel running jobs without killing the server" true, mechanism unstated "cancelling terminates the server's worker processes, not the server" execution/server.py:347-370, 381-384, 510-564
13 "Auto-spawning servers: if no server is running, the client spawns one automatically" (twice) partly "connect attaches to a usable server or starts one". How the process is started is the application's abstract _spawn_server_process; the library owns the lock, the readiness wait and the check that the server which answered is the one just started client.py:1110-1210, 945-952, 1473
14 "prevents head-of-line blocking where a slow result transfer blocks incoming commands" false as worded (there is no result transfer) "The server answers control requests in its main loop and runs executions one at a time on a worker thread, so status and cancel requests are answered while a task runs" server.py:202-220; execution/server.py:180-217; runner.py:28-31
15 "Enum-based message types with automatic handler dispatch"; listing with ControlMessageType.dispatch and get_handler_method false ControlMessageType is a plain enum with eight members and no methods. ExecutionServer holds a dict from member to bound handler. The listing was removed messages.py:391-399; execution/server.py:96-104, 156-165
16 "Adding a new command requires only defining the enum and implementing the handler method" false removed. A new command also needs a request dataclass and an entry in the handler dict execution/server.py:96-104; messages.py:591-655
17 "Each execution gets a unique ID. Clients poll for status without blocking." true folded into the Summary bullet execution/server.py:240; execution/client.py:107-116
18 "Results are streamed to disk or memory as they arrive" false removed (see 5, 6)
19 "Traditional approaches either use heavyweight frameworks (Celery, Dask) or hand-written socket code" unverifiable in the repo "Task frameworks such as Celery and Dask need a broker or scheduler service". See "For the owner"
20 "automatic server spawning for development convenience"; "users don't need to manually start servers" true covered by 13 client.py:1161-1171
21 OpenHCS: "thousands of images are processed across multiple worker nodes" unverifiable; nothing in zmqruntime distributes work across nodes removed. One server runs one execution at a time; its workers are its own child processes execution/server.py:186-217, 510-515
22 "allows the orchestrator to submit new jobs while previous jobs are still streaming results" misleading removed. New submissions are accepted while one runs and wait in a queue execution/server.py:236-256
23 zmqruntime "powers distributed execution in OpenHCS" true for execution in a separate process "runs pipeline execution in OpenHCS: the interface submits pipelines to an execution server and follows their progress" openhcs/runtime/zmq_execution_client.py:964; openhcs/runtime/zmq_execution_server.py:134, 176

Found false or misleading: 13 of 23 (4, 5, 6, 8, 9, 10, 11, 14, 15, 16, 18, 21, 22). Unverifiable: 19 (and the node count in 21).

New statements, with their source

  • Transports are a family: TransportDeclaration (abstract) with TcpTransportDeclaration and IpcTransportDeclaration; TransportMode resolves a member to its class through the registry (transport_modes.py:32, 264, 426; config.py:17-29).
  • IPC is the default; on Windows only TCP is supported (config.py:31-38; transport_modes.py:268, 430, 449-451).
  • An IPC endpoint is the file <prefix>-<port>.sock in socket_root() / ipc_socket_dir; socket_root() defaults to ~/.<app_name> and is the override point (transport_modes.py:454-463; config.py:95-106, 128-136). OpenHCS overrides it per checkout (openhcs/runtime/zmq_config.py:85).
  • For IPC, private_endpoint yields a config whose socket directory is a new private-* directory under the socket root, removed on exit (transport_modes.py:588-600; tests/test_transport.py:199-223).
  • Every pong carries an EndpointLaunch with the code root, the starting process and the audience (server.py:102-104, 289-303; messages.py:953-996).
  • A client attaches to a local server only when the code roots match and the audience admits it; otherwise EndpointLaunchMismatchError, and the refused server is left running (client.py:954-971, 1212-1223; messages.py:1002-1015, 1032-1041; tests/test_endpoint_launch.py:63-88).
  • A server started by a non-persistent client admits only that process (messages.py:909-912, 925-950; client.py:927-937; tests/test_endpoint_launch.py:91-110).
  • A server stops once its starter has exited; a persistent one only when it has no queued or running execution (server.py:308-339; execution/server.py:386-392; messages.py:934-950, 1017-1020).
  • Requests and responses are dataclasses; ExecutionServer maps each ControlMessageType member to a handler (messages.py:404-416, 591-710; execution/server.py:96-104).
  • Progress events are numbered per execution and the client reports a gap as an error (execution/server.py:457-465; execution/client.py:333-349).

For the owner

Places where the code does less than "isolation" might suggest. These are findings, and the paper does not claim otherwise:

  1. The launch check guards attaching, not shutdown. ZMQClient.shutdown_endpoint_on_port (client.py:1404-1431) sends a shutdown to whatever answers at the address if it advertises the capability; it compares no code root or audience. A process that knows the address can stop another session's server.
  2. connect can still kill a process it did not start. When the address is occupied but nothing answers a ready ping, and no live startup owner is recorded, _connect_locked calls kill_processes_on_port for both ports (client.py:1140-1152). For TCP preserve_unresponsive_endpoint is always False and the kill targets any local process listening on that port number, also when the client's host is remote (transport_modes.py:336-368). For IPC a socket with a live binder is preserved (transport_modes.py:547-552). A server that answers and is refused is never killed.
  3. The launch check is skipped for non-local hosts (client.py:961-962), by design; the paper says "a local server".
  4. TCP private_endpoint reserves nothing. It returns the first unbound port pair; the docstring says another process can bind it first (transport_modes.py:412-423). The paper states the private directory only "for IPC".
  5. The orphan stop needs the launch to be handed over. A server that was not given the client's launch through launched_by records itself as owner and never sees an orphan (server.py:66-70, 100-104). Passing it is the application's job in _spawn_server_process.
  6. Cancellation is not per execution at the process level. _interrupt_execution terminates every Python child of the server that is not a preserved kind (execution/server.py:381-384, 494-508). With one execution running at a time this is the running one's workers. The thread running execute_task is not interrupted.
  7. ZMQServer.kill_processes_on_port (server.py:341-373, lsof / kill -9 / taskkill) has no caller in the library.
  8. Control messages are unpickled from the socket (server.py:211). With the TCP transport and the default bind host * this trusts every peer that can reach the port. The paper does not discuss security; you may want a sentence.

Could not verify:

  • The Celery and Dask comparison (ledger 19). I kept one factual sentence (they need a broker or scheduler service) and dropped "heavyweight". Neither tool is cited in paper.bib; JOSS reviewers usually ask for citations here. I added none because I could not confirm DOIs from this machine.
  • Cross-platform behaviour. CI runs on ubuntu-latest only (.github/workflows/tests.yml:11, 27). The Windows statement in the paper is read from is_supported, not from a test run.
  • Any claim about scale (images, nodes). Removed.

Bib changes: Hintjens2013 changed from @article to @book (it is the O'Reilly book; fields unchanged) and is now cited once at the first mention of ZeroMQ. The old paper cited nothing, so all three entries were unused. Beazley2009 and Gorelick2020 remain unused and will not render; delete them or cite them. No reference was added.

Not changed: title, front matter (the date is still 15 January 2026), authors, section headings. The old paper had no acknowledgements or AI-usage section.

Word count (wc -w paper.md): 420 before, 481 after.

🤖 Generated with Claude Code

Rewrite the statements the released code no longer supports: the client
API in the examples, the data-channel socket pattern, result streaming,
enum dispatch, and the multi-node claim. Describe the transport family,
the socket root and the launch check that keeps servers apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 20:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
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.

2 participants