Skip to content

feat(swift): add the consumer - #4641

Merged
hubcio merged 3 commits into
apache:masterfrom
RustToMetal:swift/08-consumer-v2
Oct 26, 2026
Merged

hubcio merged 3 commits into
apache:masterfrom
RustToMetal:swift/08-consumer-v2

Conversation

@RustToMetal

@RustToMetal RustToMetal commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Relates to #4001

Rationale

The Swift client can poll messages and store offsets (#4553) and produce with IggyProducer (#4577), but an application that reads a topic steadily needs what the Rust IggyConsumer gives it: tracking each partition's position, committing it at the right moment, and coming back after an outage or a rebalance. This is the next step of #4001: the consumer.

What changed?

IggyConsumer is a port of the current Rust consumer, as an actor that is also an AsyncSequence. client.consumer(name:streamID:topicID:partitionID:) builds a standalone consumer of one partition and client.consumerGroup(name:streamID:topicID:) a group member that joins its group, creating it when allowed, and reads the server's assignment. initialize() looks the stream and topic up with the configured retries. next() hands messages over one at a time from a buffer that a poll refills; each poll of a partition continues after the last message handed over, under the partition context that delivered it, and a message already handed over from the same partition history is skipped unless allowReplay is set. A failed poll is thrown after the retry interval and leaves the consumer usable; a member that lost its membership joins again before the next poll.

The auto-commit modes follow Rust: with the poll, on an interval, while messages are handed over (each, every nth offset, end of batch), and after a handler of consumeMessages(_:) returned. Queued commits collapse to the latest per partition, a position the last store already covers is not sent again, and shutdown() drains the commit tasks, stores the reading position of every partition read unless auto-commit is disabled, and leaves the group. Concurrent initialize() and shutdown() calls share one run, a dropped consumer ends its tasks, and options out of range are refused when the consumer is built. The client gains an internal canRecover, which consumeMessages(_:) uses to stop once the client cannot come back without the caller.

The Swift client has no encryptor and no operation lanes yet, so the consumer does not decrypt and does not resolve a group create or join whose reply was lost; the docs and README say so.

Of the 1,588 added lines, 664 are tests.

Local Execution

  • Passed
  • Pre-commit hooks ran

Run on macOS (Swift 6.4) on the final commit: swift format lint --strict, swift build -Xswiftc -warnings-as-errors (sources and tests), swift build -c release, swift test (395 tests, three runs), the golden lane (regenerate, no diff), and prek run over the commit. The same build and tests passed in Docker on Linux with Swift 6.4 and with Swift 6.1, the toolchain of the macOS lane.

The end-to-end suite (50 tests, 2 of them for the consumer) passed against iggy-server built from current master, started the way the CI lane starts it: plain, with TLS using the certificates in core/certs, and as a two-node cluster.

AI Usage

  1. Claude Code.
  2. Implementation and tests were written with it, as a port of core/sdk/src/clients/consumer.rs, consumer_builder.rs, and consumer_ext/consumer_message_ext.rs, applying the patterns from the review rounds on the earlier Swift PRs.
  3. The local runs listed above, plus mutation checks: the replay filter, the continuation, dropping a refused continuation, the covered-position skip, the shared shutdown and initialization, the weak task captures, the rejoin after a revoked membership, the end of consumeMessages for a client that cannot come back, and the after-handler triggers were each broken on purpose and a test failed or hung for each.
  4. Yes.

An application reading a topic needs more than pollMessages: it has to
keep each partition's position, continue after the last message it
handled, commit offsets at the right moment, and come back after an
outage or a rebalance. The Rust SDK does this in IggyConsumer, and the
Swift SDK had no counterpart.

IggyConsumer ports it as an actor that is also an AsyncSequence. A
standalone consumer reads one partition, a group member joins its
group and reads the server's assignment. Each poll continues after the
last message handed over under the context that delivered it, a
message already handed over from the same partition history is
skipped unless replay is allowed, and the auto-commit modes, interval
task, queued commits, and shutdown flush follow the Rust consumer.
consumeMessages carries the after-handler commits and stops once the
client cannot come back on its own. Encryption and resolving a lost
group create or join need client features Swift does not have yet.
@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

If AI was used to create this PR, you must run the team-review skill before opening it.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer
  • /pin - exempt the PR from the stale bot, /unpin to undo

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Oct 25, 2026
@hubcio

hubcio commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

/skill team-review

Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Oct 26, 2026

@github-actions github-actions Bot 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.

Summary: The new Swift consumer has three shutdown defects: a pending next() can outlive shutdown(), a join can leave a group member behind, and the interval drain can stall past its timeout. The review also finds canRecover wrong when the heartbeat is off, two simplifications, and two documentation nits.

Counts: critical 1, warning 3, nit 2, simplification 2


This review was generated by Claude Code 2.1.310 on deepseek-flash[1m]. Review the output before you act on it.

Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
Comment thread foreign/swift/Sources/Iggy/Client/ClientCore.swift
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift Outdated
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift Outdated
Comment thread foreign/swift/README.md Outdated
Comment thread foreign/swift/Sources/Iggy/Consumer/IggyConsumer.swift
A consumer outlived its shutdown. A join in flight ended after the
final leave. Without a heartbeat, a poll that waited for a lost
client never woke. The drain also waited past offsetDrainTimeout.
A tick that stored slept one more interval, and a waiter that gives
up on task.value does not cancel the task.

Joins and the final leave now share a lock. The poll wait now signs
the client in and ends at shutdown. A tick ends after its store,
and the drain cancels a task at its deadline.

Overlapping stores moved the stored offset back, so stores and
deletes now run one at a time. Under the next strategy, a poll
that did not commit returned the same offset again and stalled
the read. The consumer now continues after the last message,
unless the poll commits. The iterator ends at its first error,
as AsyncIteratorProtocol requires.
@hubcio
hubcio merged commit f43ecc4 into apache:master Oct 26, 2026
45 checks passed
@github-actions github-actions Bot removed the S-waiting-on-author PR is waiting on author response label Oct 26, 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.

5 participants