@@ -33,10 +33,14 @@ type PoolConfig interface {
3333
3434// waiter represents a client waiting for a connection in the waitlist
3535type waiter [C Connection ] struct {
36+ // setting is the connection Setting that we'd like, or nil if we'd like a
37+ // a connection with no Setting applied
38+ setting * Setting
3639 // conn is a channel that will receive the connection when it's ready
37- conn chan * Pooled [C ]
38- request * loadshed.Request [* waiter [C ]]
39- err error
40+ conn chan * Pooled [C ]
41+ err error
42+ // age is the amount of cycles this client has been on the waitlist
43+ age uint32
4044}
4145
4246type waitlist [C Connection ] struct {
@@ -62,7 +66,7 @@ func (wl *waitlist[C]) waitForConn(ctx context.Context, setting *Setting, closeC
6266 defer wl .nodes .Put (elem )
6367
6468 conn := elem .conn
65- * elem = waiter [C ]{conn : conn }
69+ * elem = waiter [C ]{conn : conn , setting : setting }
6670
6771 // Fast path: reject early using an atomic read of the list length to avoid
6872 // contending on the mutex under high query rates. This is racy — the count
@@ -99,9 +103,12 @@ func (wl *waitlist[C]) waitForConn(ctx context.Context, setting *Setting, closeC
99103 }
100104 return nil , ErrPoolWaiterCapReached
101105 }
102- request , dropped := wl .snake .Enqueue (elem , valveID , snakePriority (priority ))
103- elem .request = request
104- wl .syncTimer ()
106+ if priority != loadshed .PriorityUndroppable {
107+ // Translate the Vitess proto priority (0 = most important) into Snake's
108+ // convention (higher priority shed last).
109+ priority = float64 (sqlparser .MaxPriorityValue ) - priority
110+ }
111+ request , dropped := wl .snake .Enqueue (elem , valveID , priority )
105112 wl .mu .Unlock ()
106113 wl .reject (dropped )
107114
@@ -110,8 +117,7 @@ func (wl *waitlist[C]) waitForConn(ctx context.Context, setting *Setting, closeC
110117 // Pool was closed while we were waiting.
111118 wl .mu .Lock ()
112119 // Try to find and remove ourselves from the list.
113- removed , dropped := wl .snake .Cancel (elem .request )
114- wl .syncTimer ()
120+ removed , dropped := wl .snake .Cancel (request )
115121 wl .mu .Unlock ()
116122 wl .reject (dropped )
117123
@@ -128,8 +134,7 @@ func (wl *waitlist[C]) waitForConn(ctx context.Context, setting *Setting, closeC
128134 // prevent another goroutine from trying to hand us a connection later on.
129135 wl .mu .Lock ()
130136 // Try to find and remove ourselves from the list.
131- removed , dropped := wl .snake .Cancel (elem .request )
132- wl .syncTimer ()
137+ removed , dropped := wl .snake .Cancel (request )
133138 wl .mu .Unlock ()
134139 wl .reject (dropped )
135140
@@ -150,15 +155,9 @@ func (wl *waitlist[C]) aboveWaiterCap(maxWaiters uint) bool {
150155 return maxWaiters > 0 && wl .snake .Len () >= int (maxWaiters )
151156}
152157
153- func snakePriority (priority float64 ) float64 {
154- if priority == loadshed .PriorityUndroppable {
155- return priority
156- }
157- return float64 (sqlparser .MaxPriorityValue ) - priority
158- }
159-
160158func (wl * waitlist [C ]) maybeStarvingCount () int {
161- return wl .snake .Len ()
159+ // TODO: Remove the age/starvation code since Snake guarantees prompt grant-or-shed.
160+ return 0
162161}
163162
164163// tryReturnConn tries handing over a connection to one of the waiters in the pool.
@@ -172,9 +171,25 @@ func (wl *waitlist[D]) tryReturnConn(conn *Pooled[D]) bool {
172171}
173172
174173func (wl * waitlist [D ]) tryReturnConnSlow (conn * Pooled [D ]) bool {
174+ const maxAge = 8
175+ connSetting := conn .Conn .Setting ()
176+
175177 wl .mu .Lock ()
176- waiter , ok , dropped := wl .snake .Dequeue ()
177- wl .syncTimer ()
178+ // iterate through the waitlist looking for either waiters that have been
179+ // here too long, or a waiter that is looking exactly for the same Setting
180+ // as the one we have in our connection.
181+ waiter , ok , dropped := wl .snake .DequeueMatching (func (waiter * waiter [D ]) bool {
182+ if waiter .age > maxAge || waiter .setting == connSetting {
183+ return true
184+ }
185+ // this only ages the waiters that are being skipped over: we'll start
186+ // aging the waiters in the back once they get to the front of the pool.
187+ // the maxAge of 8 has been set empirically: smaller values cause clients
188+ // with a specific setting to slightly starve, and aging all the clients
189+ // in the list every time leads to unfairness when the system is at capacity
190+ waiter .age ++
191+ return false
192+ })
178193 wl .mu .Unlock ()
179194 wl .reject (dropped )
180195
@@ -200,16 +215,9 @@ func (wl *waitlist[C]) reject(waiters []*waiter[C]) {
200215 }
201216}
202217
203- func (wl * waitlist [C ]) syncTimer () {
204- if delay , ok := wl .snake .LockedTimerUpdate (); ok {
205- time .AfterFunc (delay , wl .runDropTimer )
206- }
207- }
208-
209218func (wl * waitlist [C ]) runDropTimer () {
210219 wl .mu .Lock ()
211220 dropped := wl .snake .LockedDropTimerFired ()
212- wl .syncTimer ()
213221 wl .mu .Unlock ()
214222 wl .reject (dropped )
215223}
@@ -228,6 +236,7 @@ func (wl *waitlist[C]) init(poolName string, config PoolConfig) {
228236
229237 wl .snake = loadshed.NewSnake [* waiter [C ]](loadshed.SnakeConfig {
230238 LoadsheddingAllowed : enabled ,
239+ DropTimerFired : wl .runDropTimer ,
231240 CoDel : loadshed.CoDelConfig {
232241 IntervalNs : func () int64 { return interval ().Nanoseconds () },
233242 TargetNs : func () int64 { return target ().Nanoseconds () },
0 commit comments