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)
+}

Reply via email to