feat(adapter): add Adapter.Reset() for in-process recovery#446
Open
retr0h wants to merge 2 commits intotinygo-org:devfrom
Open
feat(adapter): add Adapter.Reset() for in-process recovery#446retr0h wants to merge 2 commits intotinygo-org:devfrom
retr0h wants to merge 2 commits intotinygo-org:devfrom
Conversation
Three small fixes to adapter_darwin.go's Enable / DidUpdateState that surfaced while building a recovery flow that needs to re-Enable the adapter after an error. 1. Clear `poweredChan` on every Enable exit path (both success and timeout). Previously the field was set at the start of Enable and never cleared, so any second call to Enable on the same Adapter returned "already calling Enable function" even though no Enable was actually in flight. Treats `poweredChan != nil` as a true "Enable currently running" sentinel. 2. Move `cm.SetDelegate(...)` ahead of the `cm.State()` check. A freshly-constructed CBCentralManager can fire DidUpdateState asynchronously before the delegate is attached; when that happens, Enable waits the full 10s timeout for a powered-on callback that already fired. Wiring the delegate first guarantees the callback path is in place before any state transition can occur. 3. Nil-guard the channel send in DidUpdateState. Now that Enable clears `poweredChan`, a late or repeated DidUpdateState (which CoreBluetooth occasionally emits on state toggles) would otherwise block forever on a nil-channel send. Non-blocking send + nil check keeps the delegate goroutine from parking. No behavior change for existing single-Enable callers.
Adds Reset() to *Adapter on darwin and linux. Reset tears down the
underlying transport-specific state (CoreBluetooth managers on
darwin, D-Bus handles on linux) so a subsequent Enable() rebuilds
them from scratch.
Targeted at callers building recovery flows that need to throw away
adapter state and start fresh — adapter switching, error recovery
after a stale CBPeripheral handle, test cleanup, etc.
Honest about the limits: on darwin, Reset does NOT clear
CoreBluetooth's process-level advertisement-deduplication table.
That state survives recreating the CBCentralManager and only goes
away when the process exits. Documented in the doc comment so
callers don't reach for Reset expecting it to fix the
post-deep-sleep "peripheral won't re-discover" wedge — that one
needs process-level recovery (self-exec or subprocess), which is
outside the scope of this library.
Implementation:
- cancels in-flight scan via existing StopScan
- drains pending Connect waiters via close(ch) so callers
unblock and return error rather than parking on a callback
that the new central will never deliver
- replaces cm/pm with fresh instances; ARC reclaims old ones
- zeros the per-Adapter channels and handlers
- depends on Enable being callable after first invocation
(preceding commit)
Linux Reset is mostly a no-op — BlueZ doesn't carry the kind of
cached peripheral state that wedges CoreBluetooth — but keeps the
API symmetric so callers can write platform-agnostic recovery
code.
No behavior change for existing callers (Reset is purely additive).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
Reset()to*Adapteron darwin and linux.Resettears down the underlying transport-specific state (CoreBluetooth managers on darwin, D-Bus handles on linux) so a subsequentEnable()rebuilds them from scratch.Depends on #445 (Enable callable after first invocation) — once that lands I'll rebase and remove this note.
Motivation
There's currently no API to throw away adapter state and start fresh on the same
*Adapter. Useful for:What it does
StopScanConnectwaiters viaclose(ch)so callers unblock and return an error rather than parking on a callback that a fresh central will never delivercm/pmwith freshcbgo.NewCentralManager(nil)/cbgo.NewPeripheralManager(nil); ARC reclaims the old onespoweredChan,scanChan,peripheralFoundHandler)After Reset, calling
Enable()again is required to set up fresh delegates and wait for powered-on. This relies on Enable'spoweredChan-cleanup behavior in #445, which is why the two PRs are stacked.Honest about the limits
The doc comment is explicit about what Reset does not do:
I empirically validated this in a real recovery flow: after a long macOS sleep, ~20 Reset cycles over 10 hours never restored the ability to re-discover a known peripheral. The dedup table outlives any in-process API we have access to. Recovery from that specific failure mode requires self-exec or a subprocess BLE helper — out of scope for this library.
That said, Reset still has a useful niche:
Linux
Mostly a no-op — BlueZ is naturally per-call and doesn't carry the kind of cached peripheral state that wedges CoreBluetooth — but keeps the API symmetric so callers can write platform-agnostic recovery code.
Caller contract
Caller MUST ensure no
Scan/Connect/DiscoverServicesis in flight when Reset is called — there is no internal locking. Documented in the comment. The intended usage pattern is:Verification
go build .clean on darwin/arm64Connect. Post-Reset,Connectreturns an error from the closed connectMap channel, the caller logs it and retries the full scan-and-connect path on the new central.Backwards compatibility
Purely additive. Existing callers see no change.
🤖 Generated with Claude Code