Skip to content

Publish and subscribe from any Julia task - #413

Open
tsaubergine wants to merge 3 commits into
3.0from
claude/goby3-python-api-design-j8jb40
Open

Publish and subscribe from any Julia task#413
tsaubergine wants to merge 3 commits into
3.0from
claude/goby3-python-api-design-j8jb40

Conversation

@tsaubergine

Copy link
Copy Markdown
Member

Finishes the Julia pub/sub story: Goby.publish() and Goby.subscribe() now work from any task, on every layer, rather than only from Main.

Two commits, publish then subscribe.

Publish

check_and_publish threw an AssertionError naming the task for any layer other than INTERTHREAD unless it was called on Main. It now forwards those publications to the task that owns the C++ application over the interthread channel that task already has — which is what a C++ thread's InterProcessForwarder does with its inner interthread transporter.

publish_forward_interprocess is renamed publish_forward_cxx: the layer is passed through untouched, so INTERMODULE works the same way.

Subscribe

Subscriptions on the layers the C++ side owns were registered from whichever task called subscribe(), and every message came back to Main regardless of who had asked for it (the TODO in receive_forward_interprocess).

  • check_and_cxx_subscribe() hands the registration to the application-owning task as a :cxx_subscribe channel message, so Goby.cxx_subscribe is always called on the task that owns the application.
  • cxx_subscriber_tasks records which task asked for each (layer, scheme, type, group), and receive_forward_cxx delivers there. Anything unregistered still goes to Main, as before.

This also fixes a latent bug in interprocess_callbacks: the leaf was a single Function per key and was written without a lock, so two tasks subscribing to the same group kept only the last-registered callback. It is now keyed by task as well, behind the same lock as the delivery map, and receive_dereferenced runs only the callbacks belonging to the current task.

Known cost

An interprocess publication from a task waits up to one cxx_channel_check_frequency tick (default 10 Hz) before it goes out. That is documented in the README so an application with latency-sensitive traffic knows to raise it.

Tests

src/test/julia/test_app.jl gains testsets covering both registries directly — two tasks subscribing to one group, a task replacing its own callback, and the default-to-Main routing. Moving an actual message needs a portal, so that part is covered by the example rather than here. 29 assertions, passing.

Verified end-to-end against a live gobyd with the companion goby3-examples change (GobySoft/goby3-examples#48): reports flow publisher-task -> interthread -> subscriber-task -> interprocess, reaching both a separate subscriber process and the publishing application's own task subscription, exactly once each.

Docs

New "Multi-Threaded Julia Usage" section in share/goby/Goby.jl/README.md, and a note in doc250_languages.md that Julia — unlike Python — supports multi-threaded applications.


Generated by Claude Code

claude added 2 commits August 19, 2026 04:47
Only Main could reach the layers the C++ side owns; every other task threw
an AssertionError. Forward those publications to the task that owns the
application over the interthread channel it already has, which is what a C++
thread's InterProcessForwarder does with its inner interthread transporter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nsf4QMNTfUxkyWh4xGaok
Subscriptions on the layers the C++ side owns were registered from whichever
task called subscribe(), and every message came back to Main regardless of who
had asked for it. Register them on the task that owns the application, and
remember which task each subscription belongs to so its messages are delivered
there.

The callback registry was a single Function per (layer, scheme, type, group),
so two tasks subscribing to the same thing kept only the last one, and it was
written without a lock. Key it by task as well, behind the same lock as the
delivery map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nsf4QMNTfUxkyWh4xGaok
A group the generated C++ has no case for hits GOBY_JULIA_FAIL, which logs at
glog's die level and terminates the application. The docstring claimed the
constant stops the message going nowhere; nothing was going nowhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nsf4QMNTfUxkyWh4xGaok
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