Skip to content

Port Fluvio adapter to wingfoil-next - #598

Open
0-jake-0 wants to merge 2 commits into
nextfrom
fluvio-next
Open

Port Fluvio adapter to wingfoil-next#598
0-jake-0 wants to merge 2 commits into
nextfrom
fluvio-next

Conversation

@0-jake-0

Copy link
Copy Markdown
Contributor

Ports the classic wingfoil::adapters::fluvio module onto the Op model, behind a new fluvio feature. Phase 4 item 8 (aeron, iceoryx2, fluvio) — fluvio lands first of the three because, unlike the other two, it is an ordinary async network client needing no native toolchain.

Surface

  • Sourcefluvio_sub(&g, run_mode, conn, topic, partition, start_offset) over produce_async: streams one topic partition from a caller-chosen offset, emitting each record as a FluvioEvent in a Stream<Burst<FluvioEvent>>.
  • SinkFluvioSinkOps::fluvio_pub(conn, topic, buffer_size) over consume_async_bursts: sends every FluvioRecord in a burst, then issues a single flush() per burst (classic's batching semantics). Implemented for Stream<Burst<FluvioRecord>> and, for convenience, Stream<FluvioRecord>.

All classic capabilities are preserved: offset-selected partition consumption, keyed and keyless records (RecordKey::NULL), per-burst flush batching, and the single-record convenience sink.

Deviations from classic

Canonical list lives in the adapter's # Deviations from classic module-doc block; summarised:

  1. The graph owns the tokio runtime — no &Handle param (register A5).
  2. fluvio_sub rejects RunMode::HistoricalFrom at wiring — a live, unbounded consumer that tails forever after the retained records, with no historical timeline to replay; the historical channel path would block-collect it and deadlock at start (register B2, ruling (a)). Classic technically permitted a wall-clock historical run.
  3. The sink is a trait onlyFluvioSinkOps replaces the classic free fluvio_pub + FluvioPubOperators pair, per the sink-as-trait convention.
  4. The sink connects lazily, on the first burst — wiring does no I/O; an unreachable cluster aborts the run rather than graph construction (register A1/A4).
  5. A negative start_offset is rejected at wiring rather than deferred into the producer future (the validation is pure).

The sink also takes a buffer_size for the consume_async_bursts bound.

Tests

  • tests/fluvio_adapter.rs (fluvio) — no-service wiring tests: historical rejection, negative-offset rejection, connection-refused aborting the run with adapter-named context for both source and sink, and the single-record sink form. 5 tests, green.
  • tests/fluvio_integration.rs (fluvio-integration-test) — parity port of the classic integration suite: connection_refused, sub_from_beginning, pub_round_trip, sub_live_stream, pub_keyless_record, pub_keyed_record, sub_from_absolute_offset. Uses testcontainers infinyon/fluvio:0.18.1 with host networking and the classic SC-then-register-SPU-then-exec-SPU startup dance, carried over verbatim.

Docker is unavailable in the dev sandbox, so the container-backed suite was not executed locally; it runs in CI.

Other changes

  • examples/fluvio/{main.rs,README.md} — the classic round-trip example ported (seed → consume → uppercase → write to a second topic).
  • .github/workflows/fluvio-next-integration.yml + registration in integration-tests.yml.
  • next/docs/port-plan.md Phase 4 item 8 and next/docs/deviation-register.md (B2 scope) updated.

Checks

cargo fmt --all -- --check, cargo lint, cargo clippy -p wingfoil-next --all-features --all-targets -- -D warnings, and cargo test -p wingfoil-next --features fluvio all pass. The scoped clippy was substituted for the workspace cargo lint-all per the skill's sandbox caveat (the classic aeron adapter's C library does not build here); CI runs the full pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FKhf2tiM8nwtSBzC1St3ga


Generated by Claude Code

Ports the classic `wingfoil::adapters::fluvio` module onto the Op model,
behind a new `fluvio` feature:

- `fluvio_sub` — a streaming topic-partition consume source over
  `produce_async`, emitting `Burst<FluvioEvent>` from a caller-chosen
  start offset.
- `FluvioSinkOps::fluvio_pub` — a topic-produce sink over
  `consume_async_bursts`, sending every record in a burst then issuing a
  single flush per burst (classic's batching semantics).

All classic capabilities are preserved: offset-selected partition
consumption, keyed and keyless records, per-burst flush batching, and the
single-record convenience sink. Deviations (documented in the module's
`# Deviations from classic` block and the deviation register): the graph
owns the tokio runtime, the sink connects and creates its producer lazily
on the first burst, the sink is an extension trait rather than a free fn +
operator trait pair, `fluvio_sub` rejects `RunMode::HistoricalFrom` at
wiring (a live unbounded tail with no historical timeline), and a negative
`start_offset` is rejected at wiring instead of inside the producer future.

Tests: no-service wiring tests in `tests/fluvio_adapter.rs`; a parity port
of the classic integration suite in `tests/fluvio_integration.rs`
(testcontainers `infinyon/fluvio:0.18.1`, host networking, SC + SPU
registration) behind `fluvio-integration-test`, wired into CI via
`fluvio-next-integration.yml`. The classic round-trip example is ported to
`examples/fluvio/`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKhf2tiM8nwtSBzC1St3ga
The main `Build, Test, & Lint` job runs
`cargo test -p wingfoil-next --all-features --lib --tests`, which enables
`fluvio-integration-test` and so runs this suite *without*
`--test-threads=1`. Five of seven tests failed with
`SPU register failed: spu 'spu-5001(5001)' already defined`.

The cause is a real defect in the suite, not just a CI-config quirk: the
cluster uses host networking on fixed ports (the SPU public endpoint the SC
hands to clients has to be reachable from the host as `127.0.0.1:9010`), so
only one cluster can exist per machine. Every test nonetheless tried to
stand up its own container — the second one silently attached to the first
cluster's SC and then failed registering an SPU that was already defined.

Make the constraint explicit instead of accidental:

- `fluvio_cluster()` hands out one shared cluster via `Mutex<Weak<..>>`.
  Each test holds an `Arc` for its duration, so the container is stopped by
  `Drop` once the last test finishes — no leaked container, and the suite is
  correct at any thread count.
- `start_fluvio` attaches to a cluster that is already listening rather than
  racing it for the fixed ports.
- SPU registration and topic creation treat "already defined" / "already
  exists" as success, since the cluster now outlives a single test.

Docker is unavailable in the dev sandbox, so this was verified by
compilation and clippy only; the suite itself runs in CI.

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