forked from go-gitea/gitea
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqos.go
145 lines (122 loc) · 3.92 KB
/
qos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"context"
"fmt"
"net/http"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
giteacontext "code.gitea.io/gitea/services/context"
"github.com/bohde/codel"
"github.com/go-chi/chi/v5"
)
const tplStatus503 templates.TplName = "status/503"
type Priority int
func (p Priority) String() string {
switch p {
case HighPriority:
return "high"
case DefaultPriority:
return "default"
case LowPriority:
return "low"
default:
return fmt.Sprintf("%d", p)
}
}
const (
LowPriority = Priority(-10)
DefaultPriority = Priority(0)
HighPriority = Priority(10)
)
// QoS implements quality of service for requests, based upon whether
// or not the user is logged in. All traffic may get dropped, and
// anonymous users are deprioritized.
func QoS() func(next http.Handler) http.Handler {
if !setting.Service.QoS.Enabled {
return nil
}
maxOutstanding := setting.Service.QoS.MaxInFlightRequests
if maxOutstanding <= 0 {
maxOutstanding = 10
}
c := codel.NewPriority(codel.Options{
// The maximum number of waiting requests.
MaxPending: setting.Service.QoS.MaxWaitingRequests,
// The maximum number of in-flight requests.
MaxOutstanding: maxOutstanding,
// The target latency that a blocked request should wait
// for. After this, it might be dropped.
TargetLatency: setting.Service.QoS.TargetWaitTime,
})
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
priority := requestPriority(ctx)
// Check if the request can begin processing.
err := c.Acquire(ctx, int(priority))
if err != nil {
log.Error("QoS error, dropping request of priority %s: %v", priority, err)
renderServiceUnavailable(w, req)
return
}
// Release long-polling immediately, so they don't always
// take up an in-flight request
if strings.Contains(req.URL.Path, "/user/events") {
c.Release()
} else {
defer c.Release()
}
next.ServeHTTP(w, req)
})
}
}
// requestPriority assigns a priority value for a request based upon
// whether the user is logged in and how expensive the endpoint is
func requestPriority(ctx context.Context) Priority {
// If the user is logged in, assign high priority.
data := middleware.GetContextData(ctx)
if _, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
return HighPriority
}
rctx := chi.RouteContext(ctx)
if rctx == nil {
return DefaultPriority
}
// If we're operating in the context of a repo, assign low priority
routePattern := rctx.RoutePattern()
if strings.HasPrefix(routePattern, "/{username}/{reponame}/") {
return LowPriority
}
return DefaultPriority
}
// renderServiceUnavailable will render an HTTP 503 Service
// Unavailable page, providing HTML if the client accepts it.
func renderServiceUnavailable(w http.ResponseWriter, req *http.Request) {
acceptsHTML := false
for _, part := range req.Header["Accept"] {
if strings.Contains(part, "text/html") {
acceptsHTML = true
break
}
}
// If the client doesn't accept HTML, then render a plain text response
if !acceptsHTML {
http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
return
}
tmplCtx := giteacontext.TemplateContext{}
tmplCtx["Locale"] = middleware.Locale(w, req)
ctxData := middleware.GetContextData(req.Context())
err := templates.HTMLRenderer().HTML(w, http.StatusServiceUnavailable, tplStatus503, ctxData, tmplCtx)
if err != nil {
log.Error("Error occurs again when rendering service unavailable page: %v", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal server error, please collect error logs and report to Gitea issue tracker"))
}
}