@@ -60,6 +60,8 @@ func New[T comparable](name string, o ...Opt[T]) PriorityQueue[T] {
60
60
items : map [T ]* item [T ]{},
61
61
queue : btree .NewG (32 , less [T ]),
62
62
metrics : newQueueMetrics [T ](opts .MetricProvider , name , clock.RealClock {}),
63
+ // Use a buffered channel to try and not block callers
64
+ adds : make (chan * itemsWithOpts [T ], 1000 ),
63
65
// itemOrWaiterAdded indicates that an item or
64
66
// waiter was added. It must be buffered, because
65
67
// if we currently process items we can't tell
@@ -88,6 +90,7 @@ type priorityqueue[T comparable] struct {
88
90
items map [T ]* item [T ]
89
91
queue bTree [* item [T ]]
90
92
metrics queueMetrics [T ]
93
+ adds chan * itemsWithOpts [T ]
91
94
92
95
// addedCounter is a counter of elements added, we need it
93
96
// because unixNano is not guaranteed to be unique.
@@ -117,54 +120,67 @@ type priorityqueue[T comparable] struct {
117
120
}
118
121
119
122
func (w * priorityqueue [T ]) AddWithOpts (o AddOpts , items ... T ) {
120
- w .lock . Lock ()
121
- defer w . lock . Unlock ()
123
+ w .adds <- & itemsWithOpts [ T ]{ opts : o , items : items }
124
+ }
122
125
123
- for _ , key := range items {
124
- if o .RateLimited {
125
- after := w .rateLimiter .When (key )
126
- if o .After == 0 || after < o .After {
127
- o .After = after
126
+ func (w * priorityqueue [T ]) drainAddsLocked (added * itemsWithOpts [T ]) {
127
+ for {
128
+ for _ , key := range added .items {
129
+ if added .opts .RateLimited {
130
+ after := w .rateLimiter .When (key )
131
+ if added .opts .After == 0 || after < added .opts .After {
132
+ added .opts .After = after
133
+ }
128
134
}
129
- }
130
135
131
- var readyAt * time.Time
132
- if o .After > 0 {
133
- readyAt = ptr .To (w .now ().Add (o .After ))
134
- w .metrics .retry ()
135
- }
136
- if _ , ok := w .items [key ]; ! ok {
137
- item := & item [T ]{
138
- key : key ,
139
- addedAtUnixNano : w .now ().UnixNano (),
140
- addedCounter : w .addedCounter ,
141
- priority : o .Priority ,
142
- readyAt : readyAt ,
136
+ var readyAt * time.Time
137
+ if added .opts .After > 0 {
138
+ readyAt = ptr .To (w .now ().Add (added .opts .After ))
139
+ w .metrics .retry ()
140
+ }
141
+ if _ , ok := w .items [key ]; ! ok {
142
+ item := & item [T ]{
143
+ key : key ,
144
+ addedAtUnixNano : w .now ().UnixNano (),
145
+ addedCounter : w .addedCounter ,
146
+ priority : added .opts .Priority ,
147
+ readyAt : readyAt ,
148
+ }
149
+ w .items [key ] = item
150
+ w .queue .ReplaceOrInsert (item )
151
+ w .metrics .add (key )
152
+ w .addedCounter ++
153
+ continue
143
154
}
144
- w .items [key ] = item
145
- w .queue .ReplaceOrInsert (item )
146
- w .metrics .add (key )
147
- w .addedCounter ++
148
- continue
149
- }
150
155
151
- // The b-tree de-duplicates based on ordering and any change here
152
- // will affect the order - Just delete and re-add.
153
- item , _ := w .queue .Delete (w .items [key ])
154
- if o .Priority > item .priority {
155
- item .priority = o .Priority
156
- }
156
+ // The b-tree de-duplicates based on ordering and any change here
157
+ // will affect the order - Just delete and re-add.
158
+ item , _ := w .queue .Delete (w .items [key ])
159
+ if added .opts .Priority > item .priority {
160
+ item .priority = added .opts .Priority
161
+ }
162
+
163
+ if item .readyAt != nil && (readyAt == nil || readyAt .Before (* item .readyAt )) {
164
+ item .readyAt = readyAt
165
+ }
157
166
158
- if item .readyAt != nil && (readyAt == nil || readyAt .Before (* item .readyAt )) {
159
- item .readyAt = readyAt
167
+ w .queue .ReplaceOrInsert (item )
160
168
}
161
169
162
- w .queue .ReplaceOrInsert (item )
170
+ // Drain the remainder of the channel. Has to be at the end,
171
+ // because the first item is read in spin() and passed on
172
+ // to us.
173
+ select {
174
+ case added = <- w .adds :
175
+ default :
176
+ return
177
+ }
163
178
}
179
+ }
164
180
165
- if len ( items ) > 0 {
166
- w . notifyItemOrWaiterAdded ()
167
- }
181
+ type itemsWithOpts [ T comparable ] struct {
182
+ opts AddOpts
183
+ items [] T
168
184
}
169
185
170
186
func (w * priorityqueue [T ]) notifyItemOrWaiterAdded () {
@@ -179,11 +195,13 @@ func (w *priorityqueue[T]) spin() {
179
195
var nextReady <- chan time.Time
180
196
nextReady = blockForever
181
197
198
+ var addedItem * itemsWithOpts [T ]
182
199
for {
183
200
select {
184
201
case <- w .done :
185
202
return
186
203
case <- w .itemOrWaiterAdded :
204
+ case addedItem = <- w .adds :
187
205
case <- nextReady :
188
206
}
189
207
@@ -193,6 +211,11 @@ func (w *priorityqueue[T]) spin() {
193
211
w .lock .Lock ()
194
212
defer w .lock .Unlock ()
195
213
214
+ if addedItem != nil {
215
+ w .drainAddsLocked (addedItem )
216
+ }
217
+ addedItem = nil
218
+
196
219
w .lockedLock .Lock ()
197
220
defer w .lockedLock .Unlock ()
198
221
0 commit comments