@@ -97,6 +97,72 @@ type childBucket struct {
9797// cooldownQueue is the blocked auth collection ordered by next retry time during rebuilds.
9898type cooldownQueue []* scheduledAuth
9999
100+ type readyViewCursorState struct {
101+ cursor int
102+ parentCursor int
103+ childCursors map [string ]int
104+ }
105+
106+ type readyBucketCursorState struct {
107+ all readyViewCursorState
108+ ws readyViewCursorState
109+ }
110+
111+ func snapshotReadyViewCursors (view readyView ) readyViewCursorState {
112+ state := readyViewCursorState {
113+ cursor : view .cursor ,
114+ parentCursor : view .parentCursor ,
115+ }
116+ if len (view .children ) == 0 {
117+ return state
118+ }
119+ state .childCursors = make (map [string ]int , len (view .children ))
120+ for parent , child := range view .children {
121+ if child == nil {
122+ continue
123+ }
124+ state .childCursors [parent ] = child .cursor
125+ }
126+ return state
127+ }
128+
129+ func restoreReadyViewCursors (view * readyView , state readyViewCursorState ) {
130+ if view == nil {
131+ return
132+ }
133+ if len (view .flat ) > 0 {
134+ view .cursor = normalizeCursor (state .cursor , len (view .flat ))
135+ }
136+ if len (view .parentOrder ) == 0 || len (view .children ) == 0 {
137+ return
138+ }
139+ view .parentCursor = normalizeCursor (state .parentCursor , len (view .parentOrder ))
140+ if len (state .childCursors ) == 0 {
141+ return
142+ }
143+ for parent , child := range view .children {
144+ if child == nil || len (child .items ) == 0 {
145+ continue
146+ }
147+ cursor , ok := state .childCursors [parent ]
148+ if ! ok {
149+ continue
150+ }
151+ child .cursor = normalizeCursor (cursor , len (child .items ))
152+ }
153+ }
154+
155+ func normalizeCursor (cursor , size int ) int {
156+ if size <= 0 || cursor <= 0 {
157+ return 0
158+ }
159+ cursor = cursor % size
160+ if cursor < 0 {
161+ cursor += size
162+ }
163+ return cursor
164+ }
165+
100166// newAuthScheduler constructs an empty scheduler configured for the supplied selector strategy.
101167func newAuthScheduler (selector Selector ) * authScheduler {
102168 return & authScheduler {
@@ -824,6 +890,17 @@ func (m *modelScheduler) availabilitySummaryLocked(predicate func(*scheduledAuth
824890
825891// rebuildIndexesLocked reconstructs ready and blocked views from the current entry map.
826892func (m * modelScheduler ) rebuildIndexesLocked () {
893+ cursorStates := make (map [int ]readyBucketCursorState , len (m .readyByPriority ))
894+ for priority , bucket := range m .readyByPriority {
895+ if bucket == nil {
896+ continue
897+ }
898+ cursorStates [priority ] = readyBucketCursorState {
899+ all : snapshotReadyViewCursors (bucket .all ),
900+ ws : snapshotReadyViewCursors (bucket .ws ),
901+ }
902+ }
903+
827904 m .readyByPriority = make (map [int ]* readyBucket )
828905 m .priorityOrder = m .priorityOrder [:0 ]
829906 m .blocked = m .blocked [:0 ]
@@ -844,7 +921,12 @@ func (m *modelScheduler) rebuildIndexesLocked() {
844921 sort .Slice (entries , func (i , j int ) bool {
845922 return entries [i ].auth .ID < entries [j ].auth .ID
846923 })
847- m .readyByPriority [priority ] = buildReadyBucket (entries )
924+ bucket := buildReadyBucket (entries )
925+ if cursorState , ok := cursorStates [priority ]; ok && bucket != nil {
926+ restoreReadyViewCursors (& bucket .all , cursorState .all )
927+ restoreReadyViewCursors (& bucket .ws , cursorState .ws )
928+ }
929+ m .readyByPriority [priority ] = bucket
848930 m .priorityOrder = append (m .priorityOrder , priority )
849931 }
850932 sort .Slice (m .priorityOrder , func (i , j int ) bool {
0 commit comments