begin_unbind() assumes that any binding not yet in Ready/Ready is already being torn down, and returns as if unbind is pending. That is too coarse. If NmrRegisterProvider() is in the middle of binding a provider to an existing client, the new binding can already be inserted in the bindings map with status Start, but the attach callback has not yet completed. If NmrDeregisterClient() runs in that window, it can find that Start binding, skip the real unbind work, and return as though teardown is already underway. The provider registration path then finishes, flips the binding to Ready/Ready, and leaves it stranded against a client that is already marked deregistering, so the binding count never drops to zero and client deregistration can hang or assert in remove().
Thread A: provider register Thread B: client deregister
--------------------------- ---------------------------
NmrRegisterProvider(P)
perform_bind(P, C)
bind(C, P)
binding_count++ on C and P
create B_new with status Start/Start
insert B_new into bindings
unlock
NmrDeregisterClient(C)
deactivate(C)
C.deregistering = true
perform_unbind(C)
sees B_new in bindings
begin_unbind(B_new)
status != Ready
returns "pending"
but does not start unbind
action() // deferred attach callback
ClientAttachProvider(...)
ProviderAttachClient(...)
B_new -> Ready/Ready
wait_for_deregister_client(C)
remove(C)
binding_count still 1
waits forever / asserts
begin_unbind()assumes that any binding not yet inReady/Readyis already being torn down, and returns as if unbind is pending. That is too coarse. IfNmrRegisterProvider()is in the middle of binding a provider to an existing client, the new binding can already be inserted in the bindings map with statusStart, but the attach callback has not yet completed. IfNmrDeregisterClient()runs in that window, it can find thatStartbinding, skip the real unbind work, and return as though teardown is already underway. The provider registration path then finishes, flips the binding toReady/Ready, and leaves it stranded against a client that is already marked deregistering, so the binding count never drops to zero and client deregistration can hang or assert inremove().