fix(render): avoid duplicate floating first paint
- commit
- 1a1fa2b5cf2bef6ab217fd007d3ce6799054ff68
- author
- brice · · 2026-07-13 07:05 UTC
- committer
- brice · · 2026-07-13 07:17 UTC
- parent
- c58a7c1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +83 -22
M internal/usecase/daemon/client.go +11 -11
| @@ -499,16 +499,16 @@ func (d *Daemon) firstPaint(sess *session, ac *attachedClient, clientSize domain | |||
| 499 | 499 | wsz := tb.size | |
| 500 | 500 | tb.mu.Unlock() | |
| 501 | 501 | ||
| 502 | - | resized := clientSize.Valid() && wsz != tabSize(clientSize) | |
| 503 | - | if resized { | |
| 504 | - | d.resizeForFirstPaint(sess, ac, clientSize) | |
| 502 | + | outerResizeAccepted := false | |
| 503 | + | if clientSize.Valid() && wsz != tabSize(clientSize) { | |
| 504 | + | outerResizeAccepted = d.resizeForFirstPaint(sess, ac, clientSize) | |
| 505 | 505 | } | |
| 506 | 506 | d.refreshBarScriptsIfDue(sess, d.clock.Now(), true) | |
| 507 | - | // Activation can synchronously resize a retained floating pane through the | |
| 508 | - | // same transaction. A resize already emitted the mandatory reset, so avoid | |
| 509 | - | // queuing a second source-session frame that could outlive a hand-off. | |
| 510 | - | d.activateTab(sess, tb) | |
| 511 | - | if !resized { | |
| 507 | + | // Activation can synchronously resize a retained floating pane. An accepted | |
| 508 | + | // synchronous outer request already includes that pane and emits the reset, | |
| 509 | + | // but activation still performs its warmup work. | |
| 510 | + | activationResized := d.activateTabAfterResize(sess, tb, outerResizeAccepted) | |
| 511 | + | if !outerResizeAccepted && !activationResized { | |
| 512 | 512 | d.invalidateRenderNow(sess, ac, true, "client.go") | |
| 513 | 513 | } | |
| 514 | 514 | } | |
| @@ -646,9 +646,9 @@ func (d *Daemon) resize(sess *session, ac *attachedClient, sz domain.Size) { | |||
| 646 | 646 | } | |
| 647 | 647 | ||
| 648 | 648 | // resizeForFirstPaint retains attach's synchronous geometry guarantee. The | |
| 649 | - | // firstPaint caller emits its full frame immediately after this commit. | |
| 650 | - | func (d *Daemon) resizeForFirstPaint(sess *session, ac *attachedClient, sz domain.Size) { | |
| 651 | - | d.requestTransactionalResize(sess, ac, sz, true) | |
| 649 | + | // returned value reports whether the synchronous request was accepted. | |
| 650 | + | func (d *Daemon) resizeForFirstPaint(sess *session, ac *attachedClient, sz domain.Size) bool { | |
| 651 | + | return d.requestTransactionalResize(sess, ac, sz, true) | |
| 652 | 652 | } | |
| 653 | 653 | ||
| 654 | 654 | // detachOnSendError drops a client whose transport failed, leaving the session | |
| @@ -499,16 +499,16 @@ func (d *Daemon) firstPaint(sess *session, ac *attachedClient, clientSize domain | |||||
| 499 | wsz := tb.size | 499 | wsz := tb.size | ||
| 500 | tb.mu.Unlock() | 500 | tb.mu.Unlock() | ||
| 501 | 501 | ||||
| 502 | - | resized := clientSize.Valid() && wsz != tabSize(clientSize) | 502 | + | outerResizeAccepted := false |
| 503 | - | if resized { | 503 | + | if clientSize.Valid() && wsz != tabSize(clientSize) { |
| 504 | - | d.resizeForFirstPaint(sess, ac, clientSize) | 504 | + | outerResizeAccepted = d.resizeForFirstPaint(sess, ac, clientSize) |
| 505 | } | 505 | } | ||
| 506 | d.refreshBarScriptsIfDue(sess, d.clock.Now(), true) | 506 | d.refreshBarScriptsIfDue(sess, d.clock.Now(), true) | ||
| 507 | - | // Activation can synchronously resize a retained floating pane through the | 507 | + | // Activation can synchronously resize a retained floating pane. An accepted |
| 508 | - | // same transaction. A resize already emitted the mandatory reset, so avoid | 508 | + | // synchronous outer request already includes that pane and emits the reset, |
| 509 | - | // queuing a second source-session frame that could outlive a hand-off. | 509 | + | // but activation still performs its warmup work. |
| 510 | - | d.activateTab(sess, tb) | 510 | + | activationResized := d.activateTabAfterResize(sess, tb, outerResizeAccepted) |
| 511 | - | if !resized { | 511 | + | if !outerResizeAccepted && !activationResized { |
| 512 | d.invalidateRenderNow(sess, ac, true, "client.go") | 512 | d.invalidateRenderNow(sess, ac, true, "client.go") | ||
| 513 | } | 513 | } | ||
| 514 | } | 514 | } | ||
| @@ -646,9 +646,9 @@ func (d *Daemon) resize(sess *session, ac *attachedClient, sz domain.Size) { | |||||
| 646 | } | 646 | } | ||
| 647 | 647 | ||||
| 648 | // resizeForFirstPaint retains attach's synchronous geometry guarantee. The | 648 | // resizeForFirstPaint retains attach's synchronous geometry guarantee. The | ||
| 649 | - | // firstPaint caller emits its full frame immediately after this commit. | 649 | + | // returned value reports whether the synchronous request was accepted. |
| 650 | - | func (d *Daemon) resizeForFirstPaint(sess *session, ac *attachedClient, sz domain.Size) { | 650 | + | func (d *Daemon) resizeForFirstPaint(sess *session, ac *attachedClient, sz domain.Size) bool { |
| 651 | - | d.requestTransactionalResize(sess, ac, sz, true) | 651 | + | return d.requestTransactionalResize(sess, ac, sz, true) |
| 652 | } | 652 | } | ||
| 653 | 653 | ||||
| 654 | // detachOnSendError drops a client whose transport failed, leaving the session | 654 | // detachOnSendError drops a client whose transport failed, leaving the session | ||
M internal/usecase/daemon/floating.go +14 -4
| @@ -154,9 +154,15 @@ func (d *Daemon) ensureFloatingWarm(sess *session, tb *tab) { | |||
| 154 | 154 | // activateTab performs the work associated with making a tab the destination. | |
| 155 | 155 | // It is deliberately idempotent: every tab transition can use this single | |
| 156 | 156 | // hook, while inactive restored tabs remain cold until actually selected. | |
| 157 | - | func (d *Daemon) activateTab(sess *session, tb *tab) { | |
| 157 | + | func (d *Daemon) activateTab(sess *session, tb *tab) bool { | |
| 158 | + | return d.activateTabAfterResize(sess, tb, false) | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // activateTabAfterResize retains tab activation's warmup work while avoiding a | |
| 162 | + | // second resize when a synchronous outer resize request was already accepted. | |
| 163 | + | func (d *Daemon) activateTabAfterResize(sess *session, tb *tab, outerResizeAccepted bool) bool { | |
| 158 | 164 | if d == nil || sess == nil || tb == nil { | |
| 159 | - | return | |
| 165 | + | return false | |
| 160 | 166 | } | |
| 161 | 167 | // A headless session has no actual terminal destination. Deferring warmup | |
| 162 | 168 | // until firstPaint keeps restored tabs cold and avoids launching children | |
| @@ -166,16 +172,20 @@ func (d *Daemon) activateTab(sess *session, tb *tab) { | |||
| 166 | 172 | sess.mu.Unlock() | |
| 167 | 173 | d.exitCopyMode(ac) | |
| 168 | 174 | if ac == nil { | |
| 169 | - | return | |
| 175 | + | return false | |
| 170 | 176 | } | |
| 171 | 177 | d.ensureFloatingWarm(sess, tb) | |
| 178 | + | if outerResizeAccepted { | |
| 179 | + | return false | |
| 180 | + | } | |
| 172 | 181 | tb.mu.Lock() | |
| 173 | 182 | hasFloating := tb.floating.pane != nil | |
| 174 | 183 | size := domain.Size{Cols: tb.size.Cols, Rows: tb.size.Rows + 2} | |
| 175 | 184 | tb.mu.Unlock() | |
| 176 | 185 | if hasFloating { | |
| 177 | - | d.requestTransactionalResize(sess, ac, size, true) | |
| 186 | + | return d.requestTransactionalResize(sess, ac, size, true) | |
| 178 | 187 | } | |
| 188 | + | return false | |
| 179 | 189 | } | |
| 180 | 190 | ||
| 181 | 191 | // toggleFloating changes only this tab's slot. Opening an uninitialized slot | |
| @@ -154,9 +154,15 @@ func (d *Daemon) ensureFloatingWarm(sess *session, tb *tab) { | |||||
| 154 | // activateTab performs the work associated with making a tab the destination. | 154 | // activateTab performs the work associated with making a tab the destination. | ||
| 155 | // It is deliberately idempotent: every tab transition can use this single | 155 | // It is deliberately idempotent: every tab transition can use this single | ||
| 156 | // hook, while inactive restored tabs remain cold until actually selected. | 156 | // hook, while inactive restored tabs remain cold until actually selected. | ||
| 157 | - | func (d *Daemon) activateTab(sess *session, tb *tab) { | 157 | + | func (d *Daemon) activateTab(sess *session, tb *tab) bool { |
| 158 | + | return d.activateTabAfterResize(sess, tb, false) | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // activateTabAfterResize retains tab activation's warmup work while avoiding a | |||
| 162 | + | // second resize when a synchronous outer resize request was already accepted. | |||
| 163 | + | func (d *Daemon) activateTabAfterResize(sess *session, tb *tab, outerResizeAccepted bool) bool { | |||
| 158 | if d == nil || sess == nil || tb == nil { | 164 | if d == nil || sess == nil || tb == nil { | ||
| 159 | - | return | 165 | + | return false |
| 160 | } | 166 | } | ||
| 161 | // A headless session has no actual terminal destination. Deferring warmup | 167 | // A headless session has no actual terminal destination. Deferring warmup | ||
| 162 | // until firstPaint keeps restored tabs cold and avoids launching children | 168 | // until firstPaint keeps restored tabs cold and avoids launching children | ||
| @@ -166,16 +172,20 @@ func (d *Daemon) activateTab(sess *session, tb *tab) { | |||||
| 166 | sess.mu.Unlock() | 172 | sess.mu.Unlock() | ||
| 167 | d.exitCopyMode(ac) | 173 | d.exitCopyMode(ac) | ||
| 168 | if ac == nil { | 174 | if ac == nil { | ||
| 169 | - | return | 175 | + | return false |
| 170 | } | 176 | } | ||
| 171 | d.ensureFloatingWarm(sess, tb) | 177 | d.ensureFloatingWarm(sess, tb) | ||
| 178 | + | if outerResizeAccepted { | |||
| 179 | + | return false | |||
| 180 | + | } | |||
| 172 | tb.mu.Lock() | 181 | tb.mu.Lock() | ||
| 173 | hasFloating := tb.floating.pane != nil | 182 | hasFloating := tb.floating.pane != nil | ||
| 174 | size := domain.Size{Cols: tb.size.Cols, Rows: tb.size.Rows + 2} | 183 | size := domain.Size{Cols: tb.size.Cols, Rows: tb.size.Rows + 2} | ||
| 175 | tb.mu.Unlock() | 184 | tb.mu.Unlock() | ||
| 176 | if hasFloating { | 185 | if hasFloating { | ||
| 177 | - | d.requestTransactionalResize(sess, ac, size, true) | 186 | + | return d.requestTransactionalResize(sess, ac, size, true) |
| 178 | } | 187 | } | ||
| 188 | + | return false | |||
| 179 | } | 189 | } | ||
| 180 | 190 | ||||
| 181 | // toggleFloating changes only this tab's slot. Opening an uninitialized slot | 191 | // toggleFloating changes only this tab's slot. Opening an uninitialized slot | ||
M internal/usecase/daemon/render_coordinator_test.go +11 -0
| @@ -1558,4 +1558,15 @@ func TestRenderCoordinatorResizeEpochDispatch(t *testing.T) { | |||
| 1558 | 1558 | requireNoInvalidation(t, invs) | |
| 1559 | 1559 | requireNoCoordinatorOutputFrame(t, sends) | |
| 1560 | 1560 | }) | |
| 1561 | + | ||
| 1562 | + | t.Run("async request reports coordinator schedule acceptance", func(t *testing.T) { | |
| 1563 | + | d, sess, ac, _, clk, _ := newResizeFixture(t) | |
| 1564 | + | ||
| 1565 | + | require.True(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 120, Rows: 24}, false)) | |
| 1566 | + | awaitCoordinatorScheduledTimer(t, clk) | |
| 1567 | + | ||
| 1568 | + | // A torn-down coordinator cannot accept a stale attachment's schedule. | |
| 1569 | + | sess.renderCoordinator().noteSessionTeardown() | |
| 1570 | + | require.False(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 140, Rows: 30}, false)) | |
| 1571 | + | }) | |
| 1561 | 1572 | } | |
| @@ -1558,4 +1558,15 @@ func TestRenderCoordinatorResizeEpochDispatch(t *testing.T) { | |||||
| 1558 | requireNoInvalidation(t, invs) | 1558 | requireNoInvalidation(t, invs) | ||
| 1559 | requireNoCoordinatorOutputFrame(t, sends) | 1559 | requireNoCoordinatorOutputFrame(t, sends) | ||
| 1560 | }) | 1560 | }) | ||
| 1561 | + | ||||
| 1562 | + | t.Run("async request reports coordinator schedule acceptance", func(t *testing.T) { | |||
| 1563 | + | d, sess, ac, _, clk, _ := newResizeFixture(t) | |||
| 1564 | + | ||||
| 1565 | + | require.True(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 120, Rows: 24}, false)) | |||
| 1566 | + | awaitCoordinatorScheduledTimer(t, clk) | |||
| 1567 | + | ||||
| 1568 | + | // A torn-down coordinator cannot accept a stale attachment's schedule. | |||
| 1569 | + | sess.renderCoordinator().noteSessionTeardown() | |||
| 1570 | + | require.False(t, d.requestTransactionalResize(sess, ac, domain.Size{Cols: 140, Rows: 30}, false)) | |||
| 1571 | + | }) | |||
| 1561 | } | 1572 | } | ||
M internal/usecase/daemon/render_test.go +37 -0
| @@ -55,6 +55,43 @@ func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) { | |||
| 55 | 55 | require.True(t, paletteVisible, "palette must remain composed above copy mode") | |
| 56 | 56 | } | |
| 57 | 57 | ||
| 58 | + | func TestFirstPaintRetainedFloatingPaneEmitsOneReset(t *testing.T) { | |
| 59 | + | for _, tc := range []struct { | |
| 60 | + | name string | |
| 61 | + | clientSize domain.Size | |
| 62 | + | }{ | |
| 63 | + | {name: "matching outer size", clientSize: domain.Size{Cols: 80, Rows: 25}}, | |
| 64 | + | {name: "different outer size", clientSize: domain.Size{Cols: 100, Rows: 40}}, | |
| 65 | + | } { | |
| 66 | + | t.Run(tc.name, func(t *testing.T) { | |
| 67 | + | pty, releasePTY := newBlockingPTY(t) | |
| 68 | + | defer releasePTY() | |
| 69 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | |
| 70 | + | ac.sendMu.Lock() | |
| 71 | + | ac.size = tc.clientSize | |
| 72 | + | ac.sendMu.Unlock() | |
| 73 | + | ||
| 74 | + | // A retained visible popup still needs activation warmup. When the | |
| 75 | + | // outer terminal also changes, its completed transaction must cover | |
| 76 | + | // that popup rather than emitting a second reset-producing frame. | |
| 77 | + | floating := newPane(layout.PaneID("floating"), nil, domain.Size{Cols: 20, Rows: 8}) | |
| 78 | + | installTestFloating(sess.activeTab(), floating, true) | |
| 79 | + | ||
| 80 | + | d.firstPaint(sess, ac, tc.clientSize) | |
| 81 | + | ||
| 82 | + | frame := awaitFrame(t, sends, ports.MsgOutput) | |
| 83 | + | output, err := ports.UnmarshalOutput(frame.Payload) | |
| 84 | + | require.NoError(t, err) | |
| 85 | + | require.Zero(t, output.BaseStateNum, "first paint must be a mandatory reset") | |
| 86 | + | select { | |
| 87 | + | case extra := <-sends: | |
| 88 | + | t.Fatalf("first paint emitted duplicate frame after floating activation resize: %#v", extra) | |
| 89 | + | default: | |
| 90 | + | } | |
| 91 | + | }) | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | ||
| 58 | 95 | func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | |
| 59 | 96 | p, release := newBlockingPTY(t) | |
| 60 | 97 | defer release() | |
| @@ -55,6 +55,43 @@ func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) { | |||||
| 55 | require.True(t, paletteVisible, "palette must remain composed above copy mode") | 55 | require.True(t, paletteVisible, "palette must remain composed above copy mode") | ||
| 56 | } | 56 | } | ||
| 57 | 57 | ||||
| 58 | + | func TestFirstPaintRetainedFloatingPaneEmitsOneReset(t *testing.T) { | |||
| 59 | + | for _, tc := range []struct { | |||
| 60 | + | name string | |||
| 61 | + | clientSize domain.Size | |||
| 62 | + | }{ | |||
| 63 | + | {name: "matching outer size", clientSize: domain.Size{Cols: 80, Rows: 25}}, | |||
| 64 | + | {name: "different outer size", clientSize: domain.Size{Cols: 100, Rows: 40}}, | |||
| 65 | + | } { | |||
| 66 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 67 | + | pty, releasePTY := newBlockingPTY(t) | |||
| 68 | + | defer releasePTY() | |||
| 69 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, pty) | |||
| 70 | + | ac.sendMu.Lock() | |||
| 71 | + | ac.size = tc.clientSize | |||
| 72 | + | ac.sendMu.Unlock() | |||
| 73 | + | ||||
| 74 | + | // A retained visible popup still needs activation warmup. When the | |||
| 75 | + | // outer terminal also changes, its completed transaction must cover | |||
| 76 | + | // that popup rather than emitting a second reset-producing frame. | |||
| 77 | + | floating := newPane(layout.PaneID("floating"), nil, domain.Size{Cols: 20, Rows: 8}) | |||
| 78 | + | installTestFloating(sess.activeTab(), floating, true) | |||
| 79 | + | ||||
| 80 | + | d.firstPaint(sess, ac, tc.clientSize) | |||
| 81 | + | ||||
| 82 | + | frame := awaitFrame(t, sends, ports.MsgOutput) | |||
| 83 | + | output, err := ports.UnmarshalOutput(frame.Payload) | |||
| 84 | + | require.NoError(t, err) | |||
| 85 | + | require.Zero(t, output.BaseStateNum, "first paint must be a mandatory reset") | |||
| 86 | + | select { | |||
| 87 | + | case extra := <-sends: | |||
| 88 | + | t.Fatalf("first paint emitted duplicate frame after floating activation resize: %#v", extra) | |||
| 89 | + | default: | |||
| 90 | + | } | |||
| 91 | + | }) | |||
| 92 | + | } | |||
| 93 | + | } | |||
| 94 | + | ||||
| 58 | func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | 95 | func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | ||
| 59 | p, release := newBlockingPTY(t) | 96 | p, release := newBlockingPTY(t) | ||
| 60 | defer release() | 97 | defer release() | ||
M internal/usecase/daemon/transactional_resize.go +10 -7
| @@ -242,9 +242,11 @@ func (d *Daemon) retryResizeMembers(sess *session, ac *attachedClient, epoch uin | |||
| 242 | 242 | } | |
| 243 | 243 | } | |
| 244 | 244 | ||
| 245 | - | func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, size domain.Size, immediate bool) { | |
| 245 | + | // requestTransactionalResize reports whether the coordinator accepted the | |
| 246 | + | // request. Immediate requests complete synchronously before it returns. | |
| 247 | + | func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, size domain.Size, immediate bool) bool { | |
| 246 | 248 | if sess == nil || !size.Valid() { | |
| 247 | - | return | |
| 249 | + | return false | |
| 248 | 250 | } | |
| 249 | 251 | if ac == nil { | |
| 250 | 252 | // Headless geometry has no coordinator/transport to coalesce, but keeps | |
| @@ -252,15 +254,16 @@ func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, s | |||
| 252 | 254 | plan := d.prepareResize(sess, size) | |
| 253 | 255 | d.applyResize(&plan) | |
| 254 | 256 | d.commitResize(sess, nil, plan) | |
| 255 | - | return | |
| 257 | + | return true | |
| 256 | 258 | } | |
| 257 | 259 | rc := d.attachCoordinator(sess, nil, ac, true) | |
| 258 | 260 | if immediate { | |
| 259 | 261 | epoch := rc.recordResizeRequest(size, ac) | |
| 260 | - | if epoch != 0 { | |
| 261 | - | d.runResizeTransaction(sess, ac, epoch) | |
| 262 | + | if epoch == 0 { | |
| 263 | + | return false | |
| 262 | 264 | } | |
| 263 | - | return | |
| 265 | + | d.runResizeTransaction(sess, ac, epoch) | |
| 266 | + | return true | |
| 264 | 267 | } | |
| 265 | - | rc.scheduleResize(size, ac, func(epoch uint64) { d.runResizeTransaction(sess, ac, epoch) }) | |
| 268 | + | return rc.scheduleResize(size, ac, func(epoch uint64) { d.runResizeTransaction(sess, ac, epoch) }) != 0 | |
| 266 | 269 | } | |
| @@ -242,9 +242,11 @@ func (d *Daemon) retryResizeMembers(sess *session, ac *attachedClient, epoch uin | |||||
| 242 | } | 242 | } | ||
| 243 | } | 243 | } | ||
| 244 | 244 | ||||
| 245 | - | func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, size domain.Size, immediate bool) { | 245 | + | // requestTransactionalResize reports whether the coordinator accepted the |
| 246 | + | // request. Immediate requests complete synchronously before it returns. | |||
| 247 | + | func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, size domain.Size, immediate bool) bool { | |||
| 246 | if sess == nil || !size.Valid() { | 248 | if sess == nil || !size.Valid() { | ||
| 247 | - | return | 249 | + | return false |
| 248 | } | 250 | } | ||
| 249 | if ac == nil { | 251 | if ac == nil { | ||
| 250 | // Headless geometry has no coordinator/transport to coalesce, but keeps | 252 | // Headless geometry has no coordinator/transport to coalesce, but keeps | ||
| @@ -252,15 +254,16 @@ func (d *Daemon) requestTransactionalResize(sess *session, ac *attachedClient, s | |||||
| 252 | plan := d.prepareResize(sess, size) | 254 | plan := d.prepareResize(sess, size) | ||
| 253 | d.applyResize(&plan) | 255 | d.applyResize(&plan) | ||
| 254 | d.commitResize(sess, nil, plan) | 256 | d.commitResize(sess, nil, plan) | ||
| 255 | - | return | 257 | + | return true |
| 256 | } | 258 | } | ||
| 257 | rc := d.attachCoordinator(sess, nil, ac, true) | 259 | rc := d.attachCoordinator(sess, nil, ac, true) | ||
| 258 | if immediate { | 260 | if immediate { | ||
| 259 | epoch := rc.recordResizeRequest(size, ac) | 261 | epoch := rc.recordResizeRequest(size, ac) | ||
| 260 | - | if epoch != 0 { | 262 | + | if epoch == 0 { |
| 261 | - | d.runResizeTransaction(sess, ac, epoch) | 263 | + | return false |
| 262 | } | 264 | } | ||
| 263 | - | return | 265 | + | d.runResizeTransaction(sess, ac, epoch) |
| 266 | + | return true | |||
| 264 | } | 267 | } | ||
| 265 | - | rc.scheduleResize(size, ac, func(epoch uint64) { d.runResizeTransaction(sess, ac, epoch) }) | 268 | + | return rc.scheduleResize(size, ac, func(epoch uint64) { d.runResizeTransaction(sess, ac, epoch) }) != 0 |
| 266 | } | 269 | } | ||