Skip to content

feat(bindings): end a broadcast with close() - #4126

Merged
kixelated merged 8 commits into
quest/m1/broadcast-close/READMEfrom
quest/m1/broadcast-close/bindings
Sep 25, 2026
Merged

kixelated merged 8 commits into
quest/m1/broadcast-close/READMEfrom
quest/m1/broadcast-close/bindings

Conversation

@kixelated

@kixelated kixelated commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Rust ends a broadcast with broadcast::Producer::close() (#4047), but every binding still spells it finish(), a name that implies a clean-versus-aborted distinction the wire cannot carry.

Approach

  • moq-ffi: MoqBroadcastProducer::close() closes the broadcast, then finalizes the catalog, so a catalog error cannot stop the broadcast from ending. It holds the state lock throughout, so a concurrent or second call returns only after shutdown and is a no-op. finish() delegates to it and is documented as deprecated. A Rust #[deprecated] is not used because the uniffi scaffolding calls the method and would warn.
  • libmoq: moq_publish_close. moq_publish_finish is a deprecated alias. OBS and the C tests move to close.
  • Python BroadcastProducer.close() (finish emits DeprecationWarning; its docstring wrongly said it closed the tracks), Swift close() (finish is @available(*, deprecated, renamed: "close")), Go Close() (Finish is // Deprecated:). Dart picks close() up from the generated type.
  • Kotlin: a generated close() would collide with uniffi's AutoCloseable.close() (two override fun close()), so rs/moq-ffi/uniffi.toml excludes it from Kotlin. Kotlin's close() / use {} releases the producer handle, which ends the broadcast like dropping the last Rust producer (unless a dynamic() handle is still held). Kotlin keeps finish() as the forced end for now.
  • Each binding has a double-close test; binding tests and docs move to close. Fixes the moq-ffi available() doc that pointed at a nonexistent broadcast.closed().
  • Deletes the bindings quest and notes the Kotlin gap on remove.md.

Impact

  • moq-ffi: new MoqBroadcastProducer::close(); finish() now also returns Ok on a second call instead of Closed.
  • libmoq: new moq_publish_close(broadcast); moq_publish_finish deprecated (doc only, same behavior). A second close fails like any released handle, matching moq_origin_close and friends.
  • Python/Swift/Go/Dart: new close() / Close(); finish / Finish deprecated.
  • Kotlin: unchanged surface (close excluded from bindgen).
  • No wire change.

Alternatives

  • Kotlin: rename the generated method (e.g. closeBroadcast()) via [bindings.kotlin.rename]. That keeps a forced end on Kotlin after finish is removed but breaks the "one name everywhere" goal. Or make the ffi object close the broadcast on drop, so AutoCloseable.close() always forces the end; rejected because a GC finalizer (Go, Python) would then end a broadcast still served through a held dynamic().
  • libmoq: make a second moq_publish_close return 0. Rejected: libmoq handles are freed on close, and every other moq_*_close errors on an unknown id.
  • A #[deprecated] attribute plus cbindgen deprecation macros for C. Skipped as extra machinery for an alias removed on dev.

Follow-ups

  • Remove finish now notes that Kotlin loses its forced end when finish goes; decide there whether it needs one.
  • The Swift toolchain was not available locally; Swift is covered by CI only.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 6 commits September 25, 2026 00:22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review September 25, 2026 08:01
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T12:52:56.354504Z 21fa044 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 422b2edb22

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-ffi/src/producer.rs Outdated
@kixelated

Copy link
Copy Markdown
Collaborator Author

MERGE

Positive improvement, complexity justified, approach looks right.

Rust already ends broadcasts with close() (#4047); bindings still saying finish() implied a clean-vs-aborted distinction the wire cannot carry. Renaming to close() across moq-ffi / libmoq / Python / Swift / Go / Dart (with deprecated aliases) is the right alignment, and closing the broadcast before finalizing the catalog so a catalog error cannot strand it open is a real bugfix in the same change.

Concrete strengths:

  • Second close() is a no-op on ffi/higher bindings (idempotent end); libmoq still errors on a freed handle like other moq_*_close APIs—consistent per-surface semantics, documented.
  • Kotlin excluding generated close() via uniffi.toml avoids the AutoCloseable clash without inventing closeBroadcast(); finish() remains the forced end until the remove quest decides. Honest trade-off, noted on remove.md.
  • Double-close tests and docs/callers (OBS, examples) moved over; Swift covered by CI.

Not worth a different approach: this is naming + ordering hygiene completing the broadcast-close line. Behavior tweak that ffi finish() now returns Ok on a second call (was Closed) is intentional with the new close semantics.

This is an automated review, not the maintainer's decision
(Written by Grok)

A concurrent close() returned Ok before the first caller had closed the
broadcast. Also end the data-track tests merged from main with
moq_publish_close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated merged commit 4cc3a7f into quest/m1/broadcast-close/README Sep 25, 2026
26 checks passed
@kixelated
kixelated deleted the quest/m1/broadcast-close/bindings branch September 25, 2026 14:16
@kixelated

Copy link
Copy Markdown
Collaborator Author

Landed. Changes made while merging:

  • Merged main into the line quest/m1/broadcast-close/README, then the line into this branch; resolved a doc/lib/c/index.md conflict by keeping both the new data-track wording and moq_publish_close.
  • Moved the new libmoq data-track tests from moq_publish_finish to moq_publish_close.
  • Addressed the Codex finding: MoqBroadcastProducer::close() holds the state lock through shutdown, so a concurrent call returns only after the broadcast is closed.

(Written by Claude Opus 5.5)

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.

1 participant