zerodds-endpoint-julia 1.0 — Spec Coverage

Source: docs/specs/zerodds-endpoint-julia-1.0.md — the ZeroDDS Julia endpoint SDK spec. The marshalling comes from crates/lang-rules, column Language::Julia; this document covers the transport.

Implementation:

  • endpoints/julia/zerodds.jl (module ZeroDDS) — XRCE framing (write_frame/read_frame), sync Client, Task+Channel-based AsyncReader.
  • endpoints/julia/reliable.jl (module Reliable) — reliable sender/receiver state machine + HEARTBEAT/ACKNACK wire codec.
  • endpoints/julia/reliable_app.jl — channel + drain Task (ReliableAsyncWriter construct) for the live E2E + the latency bench.
  • endpoints/julia/reliable_test.jl — unit + byte-golden suite.
  • crates/endpoint-e2e/tests/julia.rs — ping-pong E2E; crates/endpoint-e2e/tests/julia_reliable.rs — reliable-stream E2E.

§1 XRCE framing

Spec: §1 — 8-byte XRCE header (session, stream, seq LE, submsg id 0x07 WRITE_DATA, flags, len LE) + body, byte-identical to crates/xrce + endpoints/c.

Repo: endpoints/julia/zerodds.jl::write_frame/read_frame, constants SESSION_NOKEY (0x80) and STREAM_BEST_EFFORT (0x01).

Tests: framing is exercised live via julia_endpoint_sync and julia_endpoint_async (§4) — no separate raw-UDP-only test the way Go/C have one (Julia has no isolated framing-only test path).

Status: done.

§2 Sync Client

Spec: §2 — polling Client: write! frames + delivers synchronously, poll is a single non-blocking receive (nothing when empty).

Repo: endpoints/julia/zerodds.jl::Transport (deliver/receive closures, the sole integration point); endpoints/julia/zerodds.jl::Client (Client(t), write!, poll, monotonic seq counter, default session SESSION_NOKEY/STREAM_BEST_EFFORT).

Tests: live E2E julia_endpoint_sync (crates/endpoint-e2e/tests/julia.rs) — full stack (generated Ping/Pong types + ZeroDDS.Client) over a real UDP socket against the Rust XRCE peer.

Status: done.

§3 Async AsyncReader

Spec: §3 — an @async Task polls the Transport and pushes unframed sample bodies onto a Channel (push); the consumer blocks on take!. No separate AsyncWriter type (sending stays write! on the sync Client).

Repo: endpoints/julia/zerodds.jl::AsyncReader (start_reader spawns the receive Task, Samples channel ch, running::Ref{Bool} flag, recv = take!, stop! sets running[] = false).

Tests: live E2E julia_endpoint_async (crates/endpoint-e2e/tests/julia.rs) — full stack via ZeroDDS.start_reader/ZeroDDS.Client.write! against the Rust XRCE peer.

Status: done.

§4 Ping-pong E2E (live)

Spec: §5.1 — a Julia app exchanges a typed sample with the shared Rust XRCE peer over a real UDP socket: full stack (generated types from zerodds_lang_rules::generate::source, the call behind zerodds-idlc generate --lang julia, + endpoint SDK) sync and async.

Repo: crates/endpoint-e2e/tests/julia.rs — JULIA_APP (wraps the generated Ping/Pong types in module Gen alongside zerodds.jl to avoid the Endian/Writer/LE name clash between generated code and the SDK; mode sync/async via CLI argument; an armed flag serializes the first recvfrom arm against a libuv datagram drop before the first send runs).

Tests: (Linux) - julia_endpoint_sync — full stack via ZeroDDS.Client. - julia_endpoint_async — full stack via ZeroDDS.start_reader.

2/2 passed (Linux).

Status: done.

§5 Reliable stream — state machine, wire, async writer

Spec: §4 (references reliable-endpoint v1.0 §3/§4) — XRCE reliable stream (stream_id 0x80, §8.4.10/§8.4.11), mirroring the reference crates/xrce/src/reliable.rs: Sender.submit!/pending_heartbeat!/ recv_acknack!/get_in_flight; Receiver.recv_data!/drain_in_order!/ pending_acknack/reset!. Window 16, receiver buffer 64, heartbeat 500 ms, payload ≤ 65535, RFC-1982 16-bit sequence numbers (seq_lt/seq_gt). Alongside it, the async-decoupled ReliableAsyncWriter construct: the producer enqueues onto a Channel, a dedicated drain Task holds the Sender state and does all the I/O (send, heartbeat, ACKNACK-driven retransmit) — the producer never enters the kernel.

Repo: endpoints/julia/reliable.jl (module Reliable) — reliable_write_frame/heartbeat_frame/acknack_frame/parse_heartbeat/ parse_acknack; Sender, Receiver; endpoints/julia/reliable_app.jl — run_reliable (producer loop, Channel + @async drain task, empty payload as the “no more samples” sentinel), run_bench (§6); endpoints/julia/example_reliable.jl (runnable in-process demo, no socket).

Tests: (Linux) - julia_reliable_unit_and_golden (crates/endpoint-e2e/tests/julia_reliable.rs) runs julia reliable_test.jl <golden_dir> — 26 check(...) assertions: monotonic seq (submit_assigns_monotonic_seq_0/_1, submit_two_in_flight), payload-too-large (submit_rejects_payload_too_large), window-full (submit_rejects_when_window_full), heartbeat first/body/silence/after- period/none (heartbeat_fires_first_time, heartbeat_body_first_last_stream, heartbeat_silenced_before_period, heartbeat_fires_after_period, heartbeat_none_when_window_empty), ACKNACK partial/full clear (acknack_clears_acked_keeps_missing, acknack_full_clear_when_no_bits_set), receiver in-order/reorder/dedup/ buffer-full (recv_data_buffers_in_order, recv_data_reorder_blocks_on_gap, recv_data_reorder_delivers, recv_data_drops_duplicates, recv_data_rejects_when_buffer_full), pending-ACKNACK bitmap (pending_acknack_marks_missing_slots), reset (reset_clears_state), in-process end-to-end loss recovery (e2e_drain_blocks_on_lost_s1, e2e_s1_retransmittable, e2e_delivers_all_after_retransmit), byte-golden against the reference goldens generated via zerodds-xrce (byte_golden_heartbeat, byte_golden_acknack) plus their parse round-trip (golden_heartbeat_parse, golden_acknack_parse). Expects stdout to contain ALL OK. - julia_reliable_loss_recovery — peer drops every 3rd sample once; the app (reliable_app.jl run_reliable) retransmits on ACKNACK; all 12 samples delivered gap-free in order. - julia_reliable_no_loss — lossless baseline; 12/12.

3 of the 4 tests in this section (latency bench in §6); combined with §6, 4/4 passed (Linux).

Status: done.

§6 Latency — channel enqueue vs. inline send

Spec: §5.3 — the producer path of the ReliableAsyncWriter construct (enqueue → Channel push) must be measurably below the inline send (UDP sendto) syscall — the evidence that async write removes syscall latency from the producer path, not that it waits for ACKNACK.

Repo: endpoints/julia/reliable_app.jl::run_bench — 20000 iterations of inline send(sock, ...) (UDP sendto) vs. 20000 iterations of put!(chan, sample) onto a sink Task, no live peer needed (an arbitrary loopback port, only local dispatch cost under measurement). Julia’s Channel is lock+condvar-based, not a wait-free SPSC ring — documented as a known limit in the spec text (reliable_app.jl header comment); the enqueue still comes in well below the inline syscall.

Tests: (Linux) julia_reliable_producer_latency (crates/endpoint-e2e/tests/julia_reliable.rs) — channel enqueue 80–106 ns vs. inline send 6.1–6.5 µs (~60–75×).

Status: done.


Audit-Status

6 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).

Test run (Linux, verified): cargo test -p zerodds-endpoint-e2e --test julia 2/2 (ping-pong: julia_endpoint_sync/julia_endpoint_async); --test julia_reliable 4/4 (julia_reliable_unit_and_golden — 26 check(...) assertions incl. byte-golden, julia_reliable_loss_recovery, julia_reliable_no_loss, julia_reliable_producer_latency); latency bench channel enqueue 80–106 ns / inline send 6.1–6.5 µs (~60–75×).

Open items: none. Known limit (not a spec violation): Julia’s Channel is lock+condvar-based, not a wait-free SPSC ring (§6).

zerodds-endpoint-julia 1.0 — Spec-Coverage

Quelle: docs/specs/zerodds-endpoint-julia-1.0.md — ZeroDDS Julia Endpoint-SDK-Spec. Das Marshalling kommt aus crates/lang-rules, Spalte Language::Julia; hier steht der Transport.

Implementation:

  • endpoints/julia/zerodds.jl (module ZeroDDS) — XRCE-Framing (write_frame/read_frame), sync Client, Task+Channel-AsyncReader.
  • endpoints/julia/reliable.jl (module Reliable) — reliable Sender/ Receiver-State-Machine + HEARTBEAT/ACKNACK-Wire-Codec.
  • endpoints/julia/reliable_app.jl — Channel + Drain-Task (ReliableAsyncWriter-Vorgang) für das Live-E2E + den Latenz-Bench.
  • endpoints/julia/reliable_test.jl — Unit- + Byte-Golden-Suite.
  • crates/endpoint-e2e/tests/julia.rs — Ping-Pong-E2E; crates/endpoint-e2e/tests/julia_reliable.rs — reliable-Stream-E2E.

§1 XRCE-Framing

Spec: §1 — 8-Byte-XRCE-Header (session, stream, seq LE, submsg id 0x07 WRITE_DATA, flags, len LE) + Body, byte-identisch zu crates/xrce + endpoints/c.

Repo: endpoints/julia/zerodds.jl::write_frame/read_frame, Konstanten SESSION_NOKEY (0x80) und STREAM_BEST_EFFORT (0x01).

Tests: Framing wird live über julia_endpoint_sync und julia_endpoint_async (§4) geübt — kein separater roher-UDP-Test wie bei Go/C (Julia hat keinen isolierten Framing-only-Testpfad).

Status: done.

§2 Sync Client

Spec: §2 — pollender Client: write! framet + liefert synchron, poll ist ein nicht-blockierender Einzel-Receive (nothing bei leer).

Repo: endpoints/julia/zerodds.jl::Transport (deliver/receive- Closures, der einzige Integrationspunkt); endpoints/julia/zerodds.jl::Client (Client(t), write!, poll, monotoner seq-Zähler, Default-Session SESSION_NOKEY/STREAM_BEST_EFFORT).

Tests: Live-E2E julia_endpoint_sync (crates/endpoint-e2e/tests/julia.rs) — voller Stack (generierte Ping/Pong-Typen + ZeroDDS.Client) über einen echten UDP-Socket gegen den Rust-XRCE-Peer.

Status: done.

§3 Async AsyncReader

Spec: §3 — ein @async-Task pollt den Transport und schiebt entrahmte Sample-Bodies auf einen Channel (push); der Consumer blockiert auf take!. Kein separater AsyncWriter-Typ (Senden bleibt write! auf dem sync Client).

Repo: endpoints/julia/zerodds.jl::AsyncReader (start_reader spawnt den Empfangs-Task, Samples-Channel ch, running::Ref{Bool}-Flag, recv = take!, stop! setzt running[] = false).

Tests: Live-E2E julia_endpoint_async (crates/endpoint-e2e/tests/julia.rs) — voller Stack über ZeroDDS.start_reader/ZeroDDS.Client.write! gegen den Rust-XRCE-Peer.

Status: done.

§4 Ping-Pong-E2E (live)

Spec: §5.1 — eine Julia-App tauscht mit dem geteilten Rust-XRCE-Peer über einen echten UDP-Socket ein typisiertes Sample aus: voller Stack (generierte Typen aus zerodds_lang_rules::generate::source, dem Aufruf von zerodds-idlc generate --lang julia, + Endpoint-SDK) sync und async.

Repo: crates/endpoint-e2e/tests/julia.rs — JULIA_APP (baut die generierten Ping/Pong-Typen als module Gen neben zerodds.jl ein, um den Namenskonflikt Endian/Writer/LE zwischen generiertem Code und der SDK zu vermeiden; Modus sync/async per CLI-Argument; ein armed-Flag serialisiert den ersten recvfrom-Arm gegen den libuv-Datagram-Drop, bevor der erste send läuft).

Tests: (Linux) - julia_endpoint_sync — voller Stack über ZeroDDS.Client. - julia_endpoint_async — voller Stack über ZeroDDS.start_reader.

2/2 grün (Linux).

Status: done.

§5 Reliable Stream — State-Machine, Wire, Async-Writer

Spec: §4 (verweist auf reliable-endpoint v1.0 §3/§4) — XRCE reliable Stream (stream_id 0x80, §8.4.10/§8.4.11), spiegelt die Referenz crates/xrce/src/reliable.rs: Sender.submit!/pending_heartbeat!/ recv_acknack!/get_in_flight; Receiver.recv_data!/drain_in_order!/ pending_acknack/reset!. Window 16, Receiver-Buffer 64, Heartbeat 500 ms, Payload ≤ 65535, RFC-1982 16-bit Sequenznummern (seq_lt/seq_gt). Dazu der async-entkoppelte ReliableAsyncWriter-Vorgang: der Producer enqueued in einen Channel, ein dedizierter Drain-Task hält den Sender-State und macht die gesamte I/O (senden, Heartbeat, ACKNACK-getriebenes Retransmit) — der Producer geht nie in den Kernel.

Repo: endpoints/julia/reliable.jl (module Reliable) — reliable_write_frame/heartbeat_frame/acknack_frame/parse_heartbeat/ parse_acknack; Sender, Receiver; endpoints/julia/reliable_app.jl — run_reliable (Producer-Loop, Channel + @async drain-Task, leerer Payload als Sentinel für „keine weiteren Samples”), run_bench (§6); endpoints/julia/example_reliable.jl (lauffähige In-Process-Demo, kein Socket).

Tests: (Linux) - julia_reliable_unit_and_golden (crates/endpoint-e2e/tests/julia_reliable.rs) läuft julia reliable_test.jl <golden_dir> — 26 check(...)-Assertions: monotone seq (submit_assigns_monotonic_seq_0/_1, submit_two_in_flight), Payload-zu-groß (submit_rejects_payload_too_large), Window-full (submit_rejects_when_window_full), Heartbeat first/body/silence/nach- Periode/leer (heartbeat_fires_first_time, heartbeat_body_first_last_stream, heartbeat_silenced_before_period, heartbeat_fires_after_period, heartbeat_none_when_window_empty), ACKNACK Teil-/Voll-Clear (acknack_clears_acked_keeps_missing, acknack_full_clear_when_no_bits_set), Receiver In-Order/Reorder/Dedup/ Buffer-full (recv_data_buffers_in_order, recv_data_reorder_blocks_on_gap, recv_data_reorder_delivers, recv_data_drops_duplicates, recv_data_rejects_when_buffer_full), Pending-ACKNACK-Bitmap (pending_acknack_marks_missing_slots), Reset (reset_clears_state), In-Process-End-to-End-Loss-Recovery (e2e_drain_blocks_on_lost_s1, e2e_s1_retransmittable, e2e_delivers_all_after_retransmit), Byte-Golden gegen die per zerodds-xrce erzeugten Referenz-Goldens (byte_golden_heartbeat, byte_golden_acknack) sowie deren Parse-Rundreise (golden_heartbeat_parse, golden_acknack_parse). Erwartet stdout enthält ALL OK. - julia_reliable_loss_recovery — Peer dropt jedes 3. Sample einmalig; die App (reliable_app.jl run_reliable) retransmittiert auf ACKNACK; alle 12 Samples lückenlos in Reihenfolge geliefert. - julia_reliable_no_loss — lossless Baseline; 12/12.

3 von 4 Tests dieses Abschnitts (Latenz-Bench in §6); zusammen mit §6 4/4 grün (Linux).

Status: done.

§6 Latenz — Channel-Enqueue vs. inline send

Spec: §5.3 — der Producer-Pfad des ReliableAsyncWriter-Vorgangs (Enqueue → Channel-Push) muss messbar unter dem inline send (UDP-sendto)-Syscall liegen — der Beleg, dass Async-Write die Syscall-Latenz aus dem Producer-Pfad nimmt, nicht das Warten auf ACKNACK.

Repo: endpoints/julia/reliable_app.jl::run_bench — 20000 Iterationen inline send(sock, ...) (UDP-sendto) vs. 20000 Iterationen put!(chan, sample) auf einen Sink-Task, kein Live-Peer nötig (beliebiger Loopback-Port, nur lokale Dispatch-Kosten unter Messung). Julias Channel ist Lock+Condvar-basiert, kein wait-free SPSC-Ring — im Spec-Text als bekannte Grenze dokumentiert (reliable_app.jl Kopfkommentar), das Enqueue bleibt trotzdem deutlich unter dem inline-Syscall.

Tests: (Linux) julia_reliable_producer_latency (crates/endpoint-e2e/tests/julia_reliable.rs) — Channel-Enqueue 80–106 ns vs. inline send 6.1–6.5 µs (~60–75×).

Status: done.


Audit-Status

6 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).

Test-Lauf: Linux, verifiziert — cargo test -p zerodds-endpoint-e2e --test julia 2/2 (Ping-Pong: julia_endpoint_sync/julia_endpoint_async); --test julia_reliable 4/4 (julia_reliable_unit_and_golden — 26 check(...)-Assertions inkl. Byte-Golden, julia_reliable_loss_recovery, julia_reliable_no_loss, julia_reliable_producer_latency); Latenz-Bench Channel-Enqueue 80–106 ns / inline send 6.1–6.5 µs (~60–75×).

Offene Punkte: keine. Bekannte Grenze (kein Spec-Verstoß): Julias Channel ist Lock+Condvar-basiert, kein wait-free SPSC-Ring (§6).