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 f81c256f1abbbc014ccb76627d2f5f7d11549824 Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 01:03:10 2026 +0200 nmxact/xact: Do not busy-wait at the end of image upload Once all chunks had been sent, ImageUploadCmd.Run() spun in a tight loop until the response for the last chunk arrived, burning a CPU core for up to a round trip per chunk in the window (or the whole response timeout if a response got lost). Wait for the next response notification instead. Every change of the upload state (response, error, missed chunk) is followed by a notification, so the loop re-evaluates as soon as there is something to do. The busy loop itself cannot be observed by a test; add an option to the fake session to deliver responses asynchronously, like the transceiver based sessions do, and run uploads with it to cover the waiting path, including a retransmission. Assisted-by: Claude:claude-opus-5-5 --- nmxact/xact/fake_sesn_test.go | 24 ++++++++++++++++++++---- nmxact/xact/image.go | 5 +++++ nmxact/xact/image_upload_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/nmxact/xact/fake_sesn_test.go b/nmxact/xact/fake_sesn_test.go index 0f97a02..1593f8f 100644 --- a/nmxact/xact/fake_sesn_test.go +++ b/nmxact/xact/fake_sesn_test.go @@ -84,6 +84,10 @@ type fakeSesn struct { handler fakeHandler reqs []fakeReq + // If non-zero, TxRxMgmtAsync() delivers the result from a separate + // goroutine after this delay, like the transceiver based sessions. + asyncDelay time.Duration + abortErr error abortSeqs []uint8 @@ -221,15 +225,27 @@ func (s *fakeSesn) TxRxMgmt(m *nmp.NmpMsg, // TxRxMgmtAsync behaves like the serial and naked BLE sessions: the // transaction is performed synchronously and the result is pushed to the -// provided channels before returning. +// provided channels before returning. If asyncDelay is set, the result +// is delivered later from a separate goroutine instead. func (s *fakeSesn) TxRxMgmtAsync(m *nmp.NmpMsg, timeout time.Duration, ch chan nmp.NmpRsp, errc chan error) error { rsp, err := s.TxRxMgmt(m, timeout) - if err != nil { - errc <- err + deliver := func() { + if err != nil { + errc <- err + } else { + ch <- rsp + } + } + + if s.asyncDelay == 0 { + deliver() } else { - ch <- rsp + go func() { + time.Sleep(s.asyncDelay) + deliver() + }() } return nil } diff --git a/nmxact/xact/image.go b/nmxact/xact/image.go index cc021f1..e81a2bd 100644 --- a/nmxact/xact/image.go +++ b/nmxact/xact/image.go @@ -381,6 +381,11 @@ func (c *ImageUploadCmd) Run(s sesn.Sesn) (Result, error) { t.ProcessMissedChunks() if t.Off == len(c.Data) { + // Everything has been sent; wait for a response instead of + // spinning. + if int(atomic.LoadInt32(&t.MaxRxOff)) < len(c.Data) { + <-ch + } continue } diff --git a/nmxact/xact/image_upload_test.go b/nmxact/xact/image_upload_test.go index a27c447..8e81e32 100644 --- a/nmxact/xact/image_upload_test.go +++ b/nmxact/xact/image_upload_test.go @@ -25,6 +25,7 @@ import ( "fmt" "sync" "testing" + "time" "mynewt.apache.org/newtmgr/nmxact/nmp" "mynewt.apache.org/newtmgr/nmxact/nmxutil" @@ -644,3 +645,35 @@ func TestImageUpgradeRunNoProgressCb(t *testing.T) { } checkUploaded(t, &dev.fakeImgDev, data) } + +// Responses delivered asynchronously, as with the transceiver based +// sessions: after the last chunk has been sent, Run() waits for the +// outstanding responses. +func TestImageUploadRunAsyncSession(t *testing.T) { + data := testImage(3000) + + for _, winSz := range []int{1, 2, 5} { + for _, drop := range []bool{false, true} { + dev := &fakeImgDev{} + if drop { + dev.dropOffs = map[uint32]bool{0: true} + } + s := newFakeSesn(dev.handle) + s.mtu = 200 + s.asyncDelay = time.Millisecond + + c := NewImageUploadCmd() + c.Data = data + c.MaxWinSz = winSz + + res, err := runCmd(t, c, s) + if err != nil { + t.Fatalf("win %d drop %v: %v", winSz, drop, err) + } + if st := res.Status(); st != 0 { + t.Errorf("win %d drop %v: status %d", winSz, drop, st) + } + checkUploaded(t, dev, data) + } + } +}
