@@ -35,6 +35,7 @@ import (
35
35
fcmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics"
36
36
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
37
37
"k8s.io/klog/v2"
38
+ utilsclock "k8s.io/utils/clock"
38
39
)
39
40
40
41
// PriorityAndFairnessClassification identifies the results of
@@ -78,6 +79,10 @@ type priorityAndFairnessHandler struct {
78
79
// the purpose of computing RetryAfter header to avoid system
79
80
// overload.
80
81
droppedRequests utilflowcontrol.DroppedRequestsTracker
82
+
83
+ // newReqWaitCtxFn creates a derived context with a deadline
84
+ // of how long a given request can wait in its queue.
85
+ newReqWaitCtxFn func (context.Context ) (context.Context , context.CancelFunc )
81
86
}
82
87
83
88
func (h * priorityAndFairnessHandler ) Handle (w http.ResponseWriter , r * http.Request ) {
@@ -240,16 +245,17 @@ func (h *priorityAndFairnessHandler) Handle(w http.ResponseWriter, r *http.Reque
240
245
resultCh <- err
241
246
}()
242
247
243
- // We create handleCtx with explicit cancelation function.
244
- // The reason for it is that Handle() underneath may start additional goroutine
248
+ // We create handleCtx with an adjusted deadline, for two reasons.
249
+ // One is to limit the time the request waits before its execution starts.
250
+ // The other reason for it is that Handle() underneath may start additional goroutine
245
251
// that is blocked on context cancellation. However, from APF point of view,
246
252
// we don't want to wait until the whole watch request is processed (which is
247
253
// when it context is actually cancelled) - we want to unblock the goroutine as
248
254
// soon as the request is processed from the APF point of view.
249
255
//
250
256
// Note that we explicitly do NOT call the actuall handler using that context
251
257
// to avoid cancelling request too early.
252
- handleCtx , handleCtxCancel := context . WithCancel (ctx )
258
+ handleCtx , handleCtxCancel := h . newReqWaitCtxFn (ctx )
253
259
defer handleCtxCancel ()
254
260
255
261
// Note that Handle will return irrespective of whether the request
@@ -286,7 +292,11 @@ func (h *priorityAndFairnessHandler) Handle(w http.ResponseWriter, r *http.Reque
286
292
h .handler .ServeHTTP (w , r )
287
293
}
288
294
289
- h .fcIfc .Handle (ctx , digest , noteFn , estimateWork , queueNote , execute )
295
+ func () {
296
+ handleCtx , cancelFn := h .newReqWaitCtxFn (ctx )
297
+ defer cancelFn ()
298
+ h .fcIfc .Handle (handleCtx , digest , noteFn , estimateWork , queueNote , execute )
299
+ }()
290
300
}
291
301
292
302
if ! served {
@@ -309,6 +319,7 @@ func WithPriorityAndFairness(
309
319
longRunningRequestCheck apirequest.LongRunningRequestCheck ,
310
320
fcIfc utilflowcontrol.Interface ,
311
321
workEstimator flowcontrolrequest.WorkEstimatorFunc ,
322
+ defaultRequestWaitLimit time.Duration ,
312
323
) http.Handler {
313
324
if fcIfc == nil {
314
325
klog .Warningf ("priority and fairness support not found, skipping" )
@@ -322,12 +333,18 @@ func WithPriorityAndFairness(
322
333
waitingMark .mutatingObserver = fcmetrics .GetWaitingMutatingConcurrency ()
323
334
})
324
335
336
+ clock := & utilsclock.RealClock {}
337
+ newReqWaitCtxFn := func (ctx context.Context ) (context.Context , context.CancelFunc ) {
338
+ return getRequestWaitContext (ctx , defaultRequestWaitLimit , clock )
339
+ }
340
+
325
341
priorityAndFairnessHandler := & priorityAndFairnessHandler {
326
342
handler : handler ,
327
343
longRunningRequestCheck : longRunningRequestCheck ,
328
344
fcIfc : fcIfc ,
329
345
workEstimator : workEstimator ,
330
346
droppedRequests : utilflowcontrol .NewDroppedRequestsTracker (),
347
+ newReqWaitCtxFn : newReqWaitCtxFn ,
331
348
}
332
349
return http .HandlerFunc (priorityAndFairnessHandler .Handle )
333
350
}
@@ -356,3 +373,48 @@ func tooManyRequests(req *http.Request, w http.ResponseWriter, retryAfter string
356
373
w .Header ().Set ("Retry-After" , retryAfter )
357
374
http .Error (w , "Too many requests, please try again later." , http .StatusTooManyRequests )
358
375
}
376
+
377
+ // getRequestWaitContext returns a new context with a deadline of how
378
+ // long the request is allowed to wait before it is removed from its
379
+ // queue and rejected.
380
+ // The context.CancelFunc returned must never be nil and the caller is
381
+ // responsible for calling the CancelFunc function for cleanup.
382
+ // - ctx: the context associated with the request (it may or may
383
+ // not have a deadline).
384
+ // - defaultRequestWaitLimit: the default wait duration that is used
385
+ // if the request context does not have any deadline.
386
+ // (a) initialization of a watch or
387
+ // (b) a request whose context has no deadline
388
+ //
389
+ // clock comes in handy for testing the function
390
+ func getRequestWaitContext (ctx context.Context , defaultRequestWaitLimit time.Duration , clock utilsclock.PassiveClock ) (context.Context , context.CancelFunc ) {
391
+ if ctx .Err () != nil {
392
+ return ctx , func () {}
393
+ }
394
+
395
+ reqArrivedAt := clock .Now ()
396
+ if reqReceivedTimestamp , ok := apirequest .ReceivedTimestampFrom (ctx ); ok {
397
+ reqArrivedAt = reqReceivedTimestamp
398
+ }
399
+
400
+ // a) we will allow the request to wait in the queue for one
401
+ // fourth of the time of its allotted deadline.
402
+ // b) if the request context does not have any deadline
403
+ // then we default to 'defaultRequestWaitLimit'
404
+ // in any case, the wait limit for any request must not
405
+ // exceed the hard limit of 1m
406
+ //
407
+ // request has deadline:
408
+ // wait-limit = min(remaining deadline / 4, 1m)
409
+ // request has no deadline:
410
+ // wait-limit = min(defaultRequestWaitLimit, 1m)
411
+ thisReqWaitLimit := defaultRequestWaitLimit
412
+ if deadline , ok := ctx .Deadline (); ok {
413
+ thisReqWaitLimit = deadline .Sub (reqArrivedAt ) / 4
414
+ }
415
+ if thisReqWaitLimit > time .Minute {
416
+ thisReqWaitLimit = time .Minute
417
+ }
418
+
419
+ return context .WithDeadline (ctx , reqArrivedAt .Add (thisReqWaitLimit ))
420
+ }
0 commit comments