Skip to content

fix(ffi)!: request draining shutdown synchronously - #6436

Merged
kixelated merged 8 commits into
mainfrom
quest/m1/ffi-shutdown-sync
Oct 24, 2026
Merged

kixelated merged 8 commits into
mainfrom
quest/m1/ffi-shutdown-sync

Conversation

@kixelated

@kixelated kixelated commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

An unpolled or dropped shutdown() future could cancel a session instead of draining its finished tracks. OBS compensated by retaining futures. Accepted C sessions also aborted after a local close request despite the documented drain contract.

Approach

Register one synchronous, owned drain on the FFI runtime, retaining the native session and origins. closed() shares its completion and delivery error across waiters. Serialize publication with existing waiters so stopping the client loop cannot hide the drain result. Update every binding, wrapper, and affected example; OBS no longer needs a retained future to keep the drain alive; its destructor still waits on each retired session's closed(), because module unload stops the runtime and would cancel a drain still in flight.

Impact

  • Breaking on main: UniFFI shutdown() returns synchronously without a result; call it, then await closed(). Python, Swift, Kotlin, Go, Dart, and C++ follow that shape. Convenience close/context-manager paths still await the drain. Kotlin's moq.shutdown() no longer releases the client; close the Moq after closed().
  • closed() reports a requested drain's result, including delivery timeout, for client and accepted sessions. Dropping a waiter or disposing the session does not abort the owned drain.
  • Rust MoqError gains Clone for stable repeated completion results.
  • C ABI signatures stay unchanged; accepted moq_session_close() now drains and reports failures through its terminal callback.
  • Wire unchanged. Explicit lite-07-wip confirms application receipt; older Lite versions and IETF still wait only for transport ACKs.

Migration

Each package's note, for its MIGRATE.md at the breaking cut. Upgrading from the release line's additive async drain() backport: replace the awaited drain() with shutdown() followed by awaiting closed(); main has no separate drain() method.

  • rs/moq-ffi: MoqSession::shutdown() requests the drain synchronously. Use shutdown(); closed().await? to wait and report delivery failures, or cancel(0) for immediate cancellation.
  • py/moq-ffi, swift/ffi, kt/moq-ffi, go/ffi, dart/moq_ffi: shutdown() requests a drain synchronously; awaiting closed() (Closed(ctx) in Go, suspending closed() in Kotlin) waits for its result and reports delivery failures. Regenerate bindings and link the matching native library together.
  • py/moq-rs: session.shutdown() requests the drain synchronously. Use session.shutdown(); await session.closed() to wait and raise delivery failures, or session.cancel(0) for immediate cancellation. A clean async with exit now waits for the drain.
  • swift: session.shutdown() is synchronous and requests a drain. Follow it with try await session.closed() to wait and report delivery failures; use session.cancel(code: 0) for immediate cancellation. The 0.4 section's "Session.shutdown() is async throws and drains before returning" becomes "Session.shutdown() requests a drain synchronously; try await session.closed() waits for its result."
  • kt/moq: session.shutdown() and moq.shutdown() request a drain synchronously. Await session.closed() (or moq.session.closed()) to wait and report delivery failures. moq.shutdown() no longer releases the client, so close the Moq (or leave its use { }) after closed() returns; Moq.close() still cancels immediately.
  • go/wrapper: session.Shutdown() requests a drain synchronously with no context or error result. Call session.Closed(ctx) to wait and report delivery failures, or session.Cancel(0) for immediate cancellation. Client.Close() now waits for the drain and returns an error.
  • dart/moq: session.shutdown() requests a drain synchronously; await session.closed() waits for its result. Moq.close() returns Future<void> and drains before stopping the client, so await it. Use session.cancel(code: 0) for immediate cancellation.
  • cpp/moq: session->shutdown() returns void and requests the drain synchronously. Use session->closed().get() to wait and report delivery failures, or session->cancel(0) to cancel immediately. A shutdown request survives session disposal and discarded waiters.
  • rs/moq-c: moq_session_close() keeps its synchronous request/terminal-callback ABI; no function is renamed. Accepted sessions now drain finished tracks too; a failed drain is reported by the terminal MOQ_CONNECTION_PHASE_CLOSED state snapshot. Finish or abort live published tracks before requesting close.

Alternatives

Keeping an async method still requires its first poll. Adding begin_shutdown() duplicates one action. Retaining futures in OBS only leaves every other caller exposed.

Validation

  • Both original lifecycle bugs reproduced before their fixes. Paused-clock tests cover disposal without a waiter and a discarded waiter preserving delivery failure; socket tests cover waiters started before shutdown on client and accepted sessions.
  • Final nix develop --command just ci passed: 375 Rust/C tests, 104 Python tests, Kotlin JVM checks, Go race tests, Dart tests, C++ probes, OBS compilation, and JS/docs/lints. Swift's check explicitly skipped because this host has no Swift toolchain.
  • Final nix develop --command just test interop --all passed, including the full language matrix, FETCH, and finite/compressed/dropped tails.
  • Final nix develop --command bash sh/obs/unit.sh passed, including stopping and immediately destroying an output with a queued 2 MiB tail. CI, interop, and OBS builds ran sequentially against the final source.
  • After merging main (fac614f) and the review fixes (8c68d5d): just check passed; just obs test (TSan) passed every MoQOutput test, then stalled in the unrelated moq-source-test on a loaded host and was stopped; just test interop --all passed 171 of 172 cases; the one failure (Rust publisher to js-native-node, slow-group skip at load average 34) touches no FFI code and passed on rerun.
  • After merging main again (a8ffb9f; 2c4e073 is a further clean merge of main), dropping this PR's MIGRATE.md hunks per docs: write MIGRATE.md at the breaking cut, not per PR #6610 (notes moved to Migration above), adapting to the session state() API from refactor: observe binding connection state coherently #6487, and regenerating the Dart bindings: just check passed; just test interop --all passed 171 of 172; the one failure was the known rust -> js-native-node audio timestamp gap (quest).

Follow-ups

The existing release backport quest remains separate. Its plan and the affected migrations now map the additive async drain() backport to shutdown() followed by awaited closed() at the breaking cut. No new quests or open decisions.

(Written by GPT-6; updated by Claude Opus 5.5)

kixelated and others added 3 commits October 22, 2026 18:17
Co-Authored-By: GPT-6 <noreply@openai.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
@kixelated kixelated changed the title FFI shutdown requests the drain synchronously fix(ffi): request draining shutdown synchronously Oct 23, 2026
@kixelated
kixelated marked this pull request as ready for review October 23, 2026 02:17
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 27 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0483c2d8-8216-4771-afb7-8afe81ac22bf
Commits

Reviewing files that changed from the base of the PR and between f52848d and 2c4e073.

Files selected for processing: 29
  • cpp/obs/src/moq-output.cpp
  • cpp/obs/src/moq-output.h
  • dart/moq/lib/src/client.dart
  • dart/moq_ffi/lib/src/moq.dart
  • doc/lib/c/index.md
  • doc/lib/cpp/index.md
  • doc/lib/dart/index.md
  • doc/lib/go/index.md
  • doc/lib/kt/index.md
  • doc/lib/py/index.md
  • doc/lib/swift/index.md
  • go/wrapper/client.go
  • go/wrapper/session.go
  • kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Moq.kt
  • py/moq-rs/README.md
  • py/moq-rs/moq/client.py
  • py/moq-rs/moq/session.py
  • quest/m0/ffi-shutdown-backport.md
  • quest/m1/README.md
  • quest/m1/api-audit/migrate.md
  • quest/m1/ffi-shutdown-sync.md
  • rs/moq-c/src/session.rs
  • rs/moq-c/src/test.rs
  • rs/moq-ffi/src/error.rs
  • rs/moq-ffi/src/session.rs
  • rs/moq-ffi/src/test.rs
  • swift/README.md
  • swift/Sources/Moq/Client.swift
  • test/interop/clients/cpp/main.cpp
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5131954c-da72-4f49-81fa-fe716f094ed5
📥 Commits

Reviewing files that changed from the base of the PR and between b103413 and 8c68d5d.

📒 Files selected for processing: 42
  • cpp/moq/MIGRATE.md
  • cpp/obs/src/moq-output.cpp
  • cpp/obs/src/moq-output.h
  • dart/moq/MIGRATE.md
  • dart/moq/lib/src/client.dart
  • dart/moq_ffi/MIGRATE.md
  • dart/moq_ffi/lib/src/moq.dart
  • doc/lib/c/index.md
  • doc/lib/cpp/index.md
  • doc/lib/dart/index.md
  • doc/lib/go/index.md
  • doc/lib/kt/index.md
  • doc/lib/py/index.md
  • doc/lib/swift/index.md
  • go/ffi/MIGRATE.md
  • go/wrapper/MIGRATE.md
  • go/wrapper/client.go
  • go/wrapper/session.go
  • kt/moq-ffi/MIGRATE.md
  • kt/moq/MIGRATE.md
  • kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Moq.kt
  • py/moq-ffi/MIGRATE.md
  • py/moq-rs/MIGRATE.md
  • py/moq-rs/README.md
  • py/moq-rs/moq/client.py
  • py/moq-rs/moq/session.py
  • quest/m0/ffi-shutdown-backport.md
  • quest/m1/README.md
  • quest/m1/api-audit/migrate.md
  • quest/m1/ffi-shutdown-sync.md
  • rs/moq-c/MIGRATE.md
  • rs/moq-c/src/session.rs
  • rs/moq-c/src/test.rs
  • rs/moq-ffi/MIGRATE.md
  • rs/moq-ffi/src/error.rs
  • rs/moq-ffi/src/session.rs
  • rs/moq-ffi/src/test.rs
  • swift/MIGRATE.md
  • swift/README.md
  • swift/Sources/Moq/Client.swift
  • swift/ffi/MIGRATE.md
  • test/interop/clients/cpp/main.cpp
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 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-10-24T08:02:16.217399Z — 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.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Completed at 95d9a36 and marked ready. Final CI passed (375 Rust/C tests, 104 Python tests, binding and documentation checks), followed sequentially by full cross-language interop and the real OBS runtime suite. Swift validation was explicitly skipped because this host has no Swift toolchain. The final OBS check confirms a stopped, immediately destroyed output still delivers its queued finished tail.

Synchronous owned shutdown and awaited closed() are synchronized across bindings; the C ABI keeps its request/terminal callback shape. Wire unchanged. No open decisions or new follow-up quests; the existing release backport remains separate. Worktree is clean and no task commands remain running.

(Written by GPT-6)

@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 23, 2026
# Conflicts:
#	cpp/moq/MIGRATE.md
#	dart/moq/MIGRATE.md
#	dart/moq_ffi/MIGRATE.md
#	go/ffi/MIGRATE.md
#	go/wrapper/MIGRATE.md
#	kt/moq-ffi/MIGRATE.md
#	kt/moq/MIGRATE.md
#	py/moq-ffi/MIGRATE.md
#	py/moq-rs/MIGRATE.md
#	rs/moq-c/MIGRATE.md
#	rs/moq-ffi/MIGRATE.md
#	swift/MIGRATE.md
#	swift/ffi/MIGRATE.md
@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026

@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: e62e4ccab3

ℹ️ 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 cpp/obs/src/moq-output.cpp Resolved
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 23, 2026
@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 23, 2026
@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 23, 2026
kixelated and others added 2 commits October 23, 2026 16:47
…-sync

# Conflicts:
#	cpp/moq/MIGRATE.md
#	dart/moq/MIGRATE.md
#	dart/moq_ffi/MIGRATE.md
#	dart/moq_ffi/lib/src/moq.dart
#	doc/lib/dart/index.md
#	doc/lib/go/index.md
#	doc/lib/kt/index.md
#	go/ffi/MIGRATE.md
#	go/wrapper/MIGRATE.md
#	kt/moq-ffi/MIGRATE.md
#	kt/moq/MIGRATE.md
#	py/moq-ffi/MIGRATE.md
#	py/moq-rs/MIGRATE.md
#	quest/m1/README.md
#	rs/moq-c/MIGRATE.md
#	rs/moq-ffi/MIGRATE.md
#	swift/MIGRATE.md
#	swift/ffi/MIGRATE.md
moq_ffi_shutdown() drops every runtime task, so an owned drain still
running when OBS unloads the module on exit was cancelled. Keep each
retired session's closed() waiter and let the destructor wait them out.
Note in the Kotlin migration that moq.shutdown() no longer releases the
client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merged main (resolved the MIGRATE, binding-doc, and quest README conflicts; regenerated the Dart bindings) and addressed both Codex findings:

  • OBS: moq_ffi_shutdown() drops every runtime task, so MoQOutput again waits out each retired session's drain in its destructor, now through closed() rather than a load-bearing shutdown future. The OBS MIGRATE and doc edits are reverted since user-visible behavior is unchanged.
  • Kotlin: Moq.close() still releases the client; the MIGRATE entry and KDoc now say to close the Moq after closed(), since moq.shutdown() no longer does.

Checks: just check passed; just obs test passed every MoQOutput test under TSan (an unrelated moq-source-test stalled on a loaded host and was stopped); just test interop --all passed except one load-induced Rust to js-native-node case, which passed on rerun. Codex reviewed 8c68d5d with no findings. No open decisions.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 24, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 24, 2026
# Conflicts:
#	cpp/moq/MIGRATE.md
#	dart/moq/MIGRATE.md
#	dart/moq_ffi/MIGRATE.md
#	dart/moq_ffi/lib/src/moq.dart
#	doc/lib/c/index.md
#	go/ffi/MIGRATE.md
#	go/wrapper/MIGRATE.md
#	kt/moq-ffi/MIGRATE.md
#	kt/moq/MIGRATE.md
#	py/moq-ffi/MIGRATE.md
#	py/moq-rs/MIGRATE.md
#	py/moq-rs/moq/session.py
#	quest/m1/README.md
#	quest/m1/ffi-shutdown-sync.md
#	rs/moq-c/MIGRATE.md
#	rs/moq-c/src/test.rs
#	rs/moq-ffi/MIGRATE.md
#	rs/moq-ffi/src/session.rs
#	swift/MIGRATE.md
#	swift/Sources/Moq/Client.swift
#	swift/ffi/MIGRATE.md
@kixelated kixelated changed the title fix(ffi): request draining shutdown synchronously fix(ffi)!: request draining shutdown synchronously Oct 24, 2026
@kixelated
kixelated added this pull request to the merge queue Oct 24, 2026

@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.

ℹ️ 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 on lines +129 to +130
public func shutdown() {
ffi.shutdown()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the migration guidance for synchronous shutdown

For users upgrading through swift/MIGRATE.md, the unreleased instructions still say that Session.shutdown() is async throws and drains before returning (line 194), which this change makes false. Following that guidance no longer waits for delivery or observes a drain failure, and exiting immediately afterward can discard queued tails. Update that existing paragraph to require shutdown() followed by try await closed(); the API-audit quest also incorrectly claims these migration entries are already synchronized. The repository explicitly requires outdated documentation to be fixed inline.

AGENTS.md reference: AGENTS.md:L32-L32

Useful? React with 👍 / 👎.

Merged via the queue into main with commit 2ad9752 Oct 24, 2026
11 checks passed
@kixelated
kixelated deleted the quest/m1/ffi-shutdown-sync branch October 24, 2026 09:40
kixelated added a commit that referenced this pull request Oct 24, 2026
…grate

Keeps this PR's deletion of quest/m1/api-audit/migrate.md and main's
deletion of quest/m1/ffi-shutdown-sync.md (finished by #6436).

Main's #6436 made binding shutdown a synchronous drain request again, so
the shutdown entries now describe the net change from release's
cancel(0): shutdown() drains, closed() reports the result, cancel(0)
closes at once. Also drops the js/publish linger entry #6522 made moot
and names the abort error #6482 left behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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