You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IsoTpChannel.cs:148, 155: internal ctor, _ownsActor = actor is null.
src/CanKit.Pro.Actor/ProtocolActor.cs:332-339, 746-749: PostInternal throws ObjectDisposedException on a disposed actor.
Precedent: src/CanKit.Pro.J1939Tp/J1939TpChannel.cs:338-347 ("An injected actor its owner already disposed took its sessions with it … (Bugbot on test(uds): drive functional response windows from an injected clock #183)") and src/CanKit.Pro.J1939/J1939NodeImpl.cs:1639-1664 both catch ODE at the same call.
Problem
If the channel was built with an injected actor (ownsActor: false) and the caller disposed that actor first, Dispose() throws ObjectDisposedException. That breaks the .NET convention that Dispose is safe to call.
It is worse than just throwing. _disposed is set to 1 first (:478), so everything after the Post is skipped for good: the reader wait (:505), _subscription.Dispose() (:507), the handler unsubscribe (:513), and disposal of _readerCts/_sendGate and the owned _service (:526-527). A second Dispose() returns early.
J1939Tp and J1939Node already guard this exact call; ISO-TP is the odd one out.
Proposed fix
Wrap the Post in catch (ObjectDisposedException), mirroring J1939TpChannel. When the actor is gone, fail _tx and release _busTxIdleWaiter inline, since no loop is left to race with. Add a test that disposes the injected actor, then the channel, and asserts that no exception is thrown and the subscription is released.
Review
From the CanKit.Pro deep review of main @ 2e7beb6 (2026-09-28), finding 6. Re-verified against main @ ff0cbe7. The review suggested checking J1939Tp/J1939Node for the same pattern; both are already guarded.
Severity
Low. It is reachable only through the internal actor-injecting ctor, i.e. from tests today.
Location
src/CanKit.Pro.IsoTp/IsoTpChannel.cs:495-503: unguarded_actor.Post(...)inDispose().IsoTpChannel.cs:148, 155: internal ctor,_ownsActor = actor is null.src/CanKit.Pro.Actor/ProtocolActor.cs:332-339, 746-749:PostInternalthrowsObjectDisposedExceptionon a disposed actor.src/CanKit.Pro.J1939Tp/J1939TpChannel.cs:338-347("An injected actor its owner already disposed took its sessions with it … (Bugbot on test(uds): drive functional response windows from an injected clock #183)") andsrc/CanKit.Pro.J1939/J1939NodeImpl.cs:1639-1664both catch ODE at the same call.Problem
If the channel was built with an injected actor (
ownsActor: false) and the caller disposed that actor first,Dispose()throwsObjectDisposedException. That breaks the .NET convention thatDisposeis safe to call.It is worse than just throwing.
_disposedis set to 1 first (:478), so everything after thePostis skipped for good: the reader wait (:505),_subscription.Dispose()(:507), the handler unsubscribe (:513), and disposal of_readerCts/_sendGateand the owned_service(:526-527). A secondDispose()returns early.J1939Tp and J1939Node already guard this exact call; ISO-TP is the odd one out.
Proposed fix
Wrap the
Postincatch (ObjectDisposedException), mirroringJ1939TpChannel. When the actor is gone, fail_txand release_busTxIdleWaiterinline, since no loop is left to race with. Add a test that disposes the injected actor, then the channel, and asserts that no exception is thrown and the subscription is released.Review
From the CanKit.Pro deep review of
main@2e7beb6(2026-09-28), finding 6. Re-verified againstmain@ff0cbe7. The review suggested checking J1939Tp/J1939Node for the same pattern; both are already guarded.