Skip to content

feat(native): PR1C — teardown ownership, operation barrier, typed disconnect#5

Open
mrmidi wants to merge 1 commit into
pr1b/queue-boundsfrom
pr1c/teardown-ownership
Open

feat(native): PR1C — teardown ownership, operation barrier, typed disconnect#5
mrmidi wants to merge 1 commit into
pr1b/queue-boundsfrom
pr1c/teardown-ownership

Conversation

@mrmidi

@mrmidi mrmidi commented Jul 20, 2026

Copy link
Copy Markdown
Owner

PR1C — Native Teardown Ownership

Base: pr1b/queue-bounds (stacked PR chain)
fptn submodule: aad9923 (pr1c/teardown-ownership)

Problem solved

Three races in the previous teardown:

  1. Stop() mutated socket/SSL from the stop-caller's thread while coroutines still executed on the strand
  2. Run() exited before coroutines completed (detached, no barrier)
  3. shared_from_this() prevented destruction but provided no ordering

Architecture: strand-only teardown + operation barrier

Stop-caller thread:     RequestStop(code, origin)
                          → claimTerminalReason (CAS, first wins)
                          → stop_requested_ = true
                          → post StopOnExecutor onto strand (tracked)

io_context thread:      Run() poll loop
                          → StopOnExecutor: cancel timer/signal/channel/resolver,
                            close socket (NO SSL shutdown)
                          → coroutines exit via cancellation
                          → completion handlers decrement active_operations_
                          → barrier: stop_cleanup_completed_ && ops == 0
                          → Run() returns

Wrapper thread:         client_run_thread()
                          → Run() returned → all coroutines provably done
                          → reads terminal_reason_ from local strong pointer
                          → stores under wrapper mutex
                          → calls disconnected callback

Stop-caller thread:     stop() joins thread → all native state gone

Key design decisions

Decision Rationale
RequestStop() posts onto strand Never touch Asio/Beast/SSL from caller's thread
No SSL shutdown in forced teardown Socket closure completes pending ops with errors; SSL shutdown races with composed Beast operations
Packed terminal_reason_ (uint32 CAS) First-writer-wins; no data race between concurrent terminal paths
run_started_ guard, no reset of stop_requested_ Stop-before-Run() preserved
Watchdog as tracked coroutine Was untracked recursive async_wait; could outlive the barrier
Completion handlers capture shared_from_this() Not raw this; safe regardless of coroutine frame lifetime
Local strong pointer in client_run_thread() stop() may reset wrapper->client before join

Disconnect classification

Failure site Code Origin
Resolve/connect failure tcp_error native_failure
Peer close (websocket::error::closed) peer_closed peer
WebSocket read/write error websocket_error native_failure
Watchdog expiry watchdog native_failure
External stop() local_stop caller-specified

Invariants

Connected steady state:
  active_operations_         = 3 (reader + sender + watchdog)
  active_reader_coroutines_  = 1
  active_sender_coroutines_  = 1
  terminal_reason_           = 0

After stop() joins:
  active_operations_         = 0
  active_reader_coroutines_  = 0
  active_sender_coroutines_  = 0
  stop_cleanup_completed_    = true
  terminal_reason_           = packed(code, origin)

Swift API

  • WebsocketDisconnectCode / WebsocketStopOrigin enums (matching C++ ABI)
  • stop(origin: .swiftTunnelStop) / stop(origin: .swiftReconnect)
  • Status: disconnectCode, stopOrigin, stopCleanupCompleted, activeOperations

Verification

  • Native framework (Debug, C++23): built
  • Xcode Debug build: succeeded
  • Tests: 24/24 passed

Stack

PR-1  Measurement safety
PR0   Production native build
PR1A  Native fixed baseline
PR1B  Queue and batch bounds      (base)
PR1C  Native teardown ownership   ← this PR
PR2   Wrapper/provider integration
PR3   C-based binary flight recorder

@mrmidi
mrmidi force-pushed the pr1c/teardown-ownership branch from 4a7c5ca to e05646a Compare July 20, 2026 16:09
…connect

Pin fptn submodule to aad9923 (pr1c/teardown-ownership).

C++ bridge:
- stop() accepts origin (uint16): 1=swift_tunnel_stop, 2=swift_reconnect,
  3=native_failure, 4=peer, 255=unknown
- Status struct: +disconnect_code, stop_origin, stop_cleanup_completed,
  active_operations
- client_run_thread: local strong pointer, terminal reason stored after
  Run() returns

Swift:
- WebsocketDisconnectCode + WebsocketStopOrigin enums in
  SharedTunnelRuntime (matching C++ ABI values)
- TunnelWebSocketTransport protocol: stop(origin:)
- Both wrappers: stop(origin:), status mapping for 4 new fields
- Provider: existing stop() calls use default .swiftTunnelStop

Tests: 24/24 passed. Debug build succeeded.
@mrmidi
mrmidi force-pushed the pr1c/teardown-ownership branch from e05646a to 933305e Compare July 20, 2026 16:51
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