detached 4fbce1b

docs(syncdispatch): clarify timeout state transitions

commit
4fbce1b364ba423c75a8f813bc6af56a5a730636
author
brice · · 2026-06-03 05:17 UTC
parent
8bbb568
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +9 -0
M internal/infrastructure/snapshot/service.go +2 -0
@@ -172,6 +172,8 @@ func (s *Service) saveSnapshot(ctx context.Context) error {
172172 }
173173
174174 windows, activeWindowIndex := s.provider.GetWindowSnapshotState()
175+ // A nil window list with no active index means the snapshot is truly unavailable,
176+ // not merely empty. Keep the session dirty so a later capture can retry.
175177 if windows == nil && activeWindowIndex < 0 {
176178 s.markDirty()
177179 logging.FromContext(ctx).Warn().Msg("window snapshot unavailable; keeping session snapshot dirty")
@@ -172,6 +172,8 @@ func (s *Service) saveSnapshot(ctx context.Context) error {
172 }172 }
173 173
174 windows, activeWindowIndex := s.provider.GetWindowSnapshotState()174 windows, activeWindowIndex := s.provider.GetWindowSnapshotState()
175+ // A nil window list with no active index means the snapshot is truly unavailable,
176+ // not merely empty. Keep the session dirty so a later capture can retry.
175 if windows == nil && activeWindowIndex < 0 {177 if windows == nil && activeWindowIndex < 0 {
176 s.markDirty()178 s.markDirty()
177 logging.FromContext(ctx).Warn().Msg("window snapshot unavailable; keeping session snapshot dirty")179 logging.FromContext(ctx).Warn().Msg("window snapshot unavailable; keeping session snapshot dirty")
M internal/shared/syncdispatch/sync_dispatch.go +7 -0
@@ -122,6 +122,9 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
122122 if state != dispatchStateInit {
123123 return false
124124 }
125+ // Check deadline and transition to dispatchStateStarted under one lock so
126+ // the timer cannot mark dispatchStateTimedOut after this callback is allowed
127+ // to start. Late-start cleanup opts out through AllowLateStartAfterTimeout.
125128 if !opts.AllowLateStartAfterTimeout && time.Now().After(deadline) {
126129 state = dispatchStateTimedOut
127130 return false
@@ -159,6 +162,8 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
159162 stateMu.Lock()
160163 currentState := state
161164 if opts.AllowLateStartAfterTimeout && currentState == dispatchStateInit {
165+ // Cleanup work may remain queued after timer expiry; decision callbacks
166+ // instead fall through and mark dispatchStateTimedOut while still unstarted.
162167 stateMu.Unlock()
163168 result.Status = SyncDispatchQueuedAfterTimeout
164169 result.Elapsed = time.Since(start)
@@ -172,6 +177,8 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
172177 return result
173178 }
174179 stateMu.Unlock()
180+ // dispatchStateStarted means the worker already owns fn side effects; wait on
181+ // done so the caller reports completed-after-timeout only after completion.
175182 <-done
176183 if loadState() == dispatchStateTimedOut {
177184 result.Status = SyncDispatchTimedOut
@@ -122,6 +122,9 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
122 if state != dispatchStateInit {122 if state != dispatchStateInit {
123 return false123 return false
124 }124 }
125+ // Check deadline and transition to dispatchStateStarted under one lock so
126+ // the timer cannot mark dispatchStateTimedOut after this callback is allowed
127+ // to start. Late-start cleanup opts out through AllowLateStartAfterTimeout.
125 if !opts.AllowLateStartAfterTimeout && time.Now().After(deadline) {128 if !opts.AllowLateStartAfterTimeout && time.Now().After(deadline) {
126 state = dispatchStateTimedOut129 state = dispatchStateTimedOut
127 return false130 return false
@@ -159,6 +162,8 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
159 stateMu.Lock()162 stateMu.Lock()
160 currentState := state163 currentState := state
161 if opts.AllowLateStartAfterTimeout && currentState == dispatchStateInit {164 if opts.AllowLateStartAfterTimeout && currentState == dispatchStateInit {
165+ // Cleanup work may remain queued after timer expiry; decision callbacks
166+ // instead fall through and mark dispatchStateTimedOut while still unstarted.
162 stateMu.Unlock()167 stateMu.Unlock()
163 result.Status = SyncDispatchQueuedAfterTimeout168 result.Status = SyncDispatchQueuedAfterTimeout
164 result.Elapsed = time.Since(start)169 result.Elapsed = time.Since(start)
@@ -172,6 +177,8 @@ func RunSynchronousDispatch(opts SyncDispatchOptions, fn func()) SyncDispatchRes
172 return result177 return result
173 }178 }
174 stateMu.Unlock()179 stateMu.Unlock()
180+ // dispatchStateStarted means the worker already owns fn side effects; wait on
181+ // done so the caller reports completed-after-timeout only after completion.
175 <-done182 <-done
176 if loadState() == dispatchStateTimedOut {183 if loadState() == dispatchStateTimedOut {
177 result.Status = SyncDispatchTimedOut184 result.Status = SyncDispatchTimedOut

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close