This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git
commit 55c46dbd0a2936276c4649f45a6c0dd52bcd9071 Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:33:20 2026 +0200 nmxact/nmble: Wake up primary removed by StopWaitingPrimary Master.StopWaitingPrimary() removed the waiter from the primary queue but never signalled it, and ignored its err argument. The goroutine blocked in AcquirePrimary() on the waiter's channel therefore stayed blocked forever, since nothing else holds a reference to that channel anymore. Send the error to the removed waiter, the same way servicePrimary() and StopWaitingSecondary() do. The function is exported through BleXport.StopWaitingForMasterPrimary(); it currently has no in-tree callers. Assisted-by: Claude:claude-opus-5-5 --- nmxact/nmble/master.go | 4 ++++ nmxact/nmble/master_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/nmxact/nmble/master.go b/nmxact/nmble/master.go index 273b419..25370d0 100644 --- a/nmxact/nmble/master.go +++ b/nmxact/nmble/master.go @@ -267,8 +267,12 @@ func (m *Master) StopWaitingPrimary(token interface{}, err error) { return } + c := m.primaries[idx] m.primaries = append( m.primaries[0:idx], m.primaries[idx+1:len(m.primaries)]...) + + // Wake up the waiter blocked in AcquirePrimary(). + c.ch <- err } // Removes the specified secondary from the wait queue. diff --git a/nmxact/nmble/master_test.go b/nmxact/nmble/master_test.go index 49a2625..e657e4f 100644 --- a/nmxact/nmble/master_test.go +++ b/nmxact/nmble/master_test.go @@ -333,3 +333,39 @@ func TestMasterAbortPreemptsSecondary(t *testing.T) { m.Release() checkMasterState(t, m, MASTER_STATE_IDLE, 0) } + +// A primary removed from the wait queue must be woken up with the given +// error rather than left blocked in AcquirePrimary forever. +func TestMasterStopWaitingPrimary(t *testing.T) { + m, _ := newTestMaster(t) + prim := make(chan acqResult, 2) + + if err := m.AcquirePrimary(0); err != nil { + t.Fatal(err) + } + + acquirePrimaryAsync(m, 1, prim) + waitMasterState(t, m, MASTER_STATE_PRIMARY, 1) + acquirePrimaryAsync(m, 2, prim) + waitMasterState(t, m, MASTER_STATE_PRIMARY, 2) + + want := errors.New("stop waiting") + m.StopWaitingPrimary(1, want) + if r := recvAcq(t, prim); r.id != 1 || r.err != want { + t.Fatalf("got %+v, want id 1 with error %v", r, want) + } + checkMasterState(t, m, MASTER_STATE_PRIMARY, 1) + + // Unknown token: no-op. + m.StopWaitingPrimary(42, want) + checkMasterState(t, m, MASTER_STATE_PRIMARY, 1) + + // The remaining waiter is still served. + m.Release() + if r := recvAcq(t, prim); r.id != 2 || r.err != nil { + t.Fatalf("got %+v, want id 2 with nil error", r) + } + + m.Release() + checkMasterState(t, m, MASTER_STATE_IDLE, 0) +}
