detached 33a90cd

fix(mr): arm one retry timer per earliest retry in the mergeability scheduler

commit
33a90cd8090c83698b8b0a6daea5f664552e4e47
author
brice · · 2026-10-19 15:14 UTC
committer
brice · · 2026-10-19 18:30 UTC
parent
41023de
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +12 -2
M internal/usecase/mergeability.go +12 -2
@@ -198,6 +198,12 @@ func (s *Mergeability) retry(key retryKey) {
198198 // earliest is due and puts it back in the normal queue.
199199 func (s *Mergeability) scheduler() {
200200 defer s.wg.Done()
201+ // The timer armed for waited; a kick re-arms it only when the earliest
202+ // retry changed, so a stale kick never starts a second timer.
203+ var (
204+ after <-chan time.Time
205+ waited retryItem
206+ )
201207 for {
202208 s.retryMu.Lock()
203209 var head retryItem
@@ -207,6 +213,7 @@ func (s *Mergeability) scheduler() {
207213 }
208214 s.retryMu.Unlock()
209215 if !has {
216+ after = nil
210217 select {
211218 case <-s.ctx.Done():
212219 return
@@ -214,13 +221,16 @@ func (s *Mergeability) scheduler() {
214221 continue
215222 }
216223 }
217- after := s.cfg.After(max(0, head.due.Sub(s.cfg.Clock.Now())))
224+ if after == nil || head.key != waited.key || !head.due.Equal(waited.due) {
225+ after, waited = s.cfg.After(max(0, head.due.Sub(s.cfg.Clock.Now()))), head
226+ }
218227 select {
219228 case <-s.ctx.Done():
220229 return
221230 case <-s.retryKick:
222- continue // an earlier retry arrived
231+ continue // an earlier retry may have arrived
223232 case <-after:
233+ after = nil
224234 }
225235 for _, k := range s.popDue(head.key) {
226236 if s.ctx.Err() != nil {
@@ -198,6 +198,12 @@ func (s *Mergeability) retry(key retryKey) {
198 // earliest is due and puts it back in the normal queue.198 // earliest is due and puts it back in the normal queue.
199 func (s *Mergeability) scheduler() {199 func (s *Mergeability) scheduler() {
200 defer s.wg.Done()200 defer s.wg.Done()
201+ // The timer armed for waited; a kick re-arms it only when the earliest
202+ // retry changed, so a stale kick never starts a second timer.
203+ var (
204+ after <-chan time.Time
205+ waited retryItem
206+ )
201 for {207 for {
202 s.retryMu.Lock()208 s.retryMu.Lock()
203 var head retryItem209 var head retryItem
@@ -207,6 +213,7 @@ func (s *Mergeability) scheduler() {
207 }213 }
208 s.retryMu.Unlock()214 s.retryMu.Unlock()
209 if !has {215 if !has {
216+ after = nil
210 select {217 select {
211 case <-s.ctx.Done():218 case <-s.ctx.Done():
212 return219 return
@@ -214,13 +221,16 @@ func (s *Mergeability) scheduler() {
214 continue221 continue
215 }222 }
216 }223 }
217- after := s.cfg.After(max(0, head.due.Sub(s.cfg.Clock.Now())))224+ if after == nil || head.key != waited.key || !head.due.Equal(waited.due) {
225+ after, waited = s.cfg.After(max(0, head.due.Sub(s.cfg.Clock.Now()))), head
226+ }
218 select {227 select {
219 case <-s.ctx.Done():228 case <-s.ctx.Done():
220 return229 return
221 case <-s.retryKick:230 case <-s.retryKick:
222- continue // an earlier retry arrived231+ continue // an earlier retry may have arrived
223 case <-after:232 case <-after:
233+ after = nil
224 }234 }
225 for _, k := range s.popDue(head.key) {235 for _, k := range s.popDue(head.key) {
226 if s.ctx.Err() != nil {236 if s.ctx.Err() != nil {

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 2

Tags 43

↑↓ moveenter openesc close