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

Reply via email to