-
Notifications
You must be signed in to change notification settings - Fork 21
/
workerPool.go
148 lines (129 loc) · 4.42 KB
/
workerPool.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
146
147
148
// SPDX-FileCopyrightText: 2017 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
)
// WorkerPool describes a pool of goroutines that dispatch http.Request objects to
// a transactor function
type WorkerPool struct {
logger *zap.Logger
outbounds <-chan outboundEnvelope
workerPoolSize uint
queueSize prometheus.Gauge
droppedMessages CounterVec
transactor func(*http.Request) (*http.Response, error)
runOnce sync.Once
}
func NewWorkerPool(om OutboundMeasures, o *Outbounder, outbounds <-chan outboundEnvelope) *WorkerPool {
logger := o.logger()
return &WorkerPool{
logger: logger,
outbounds: outbounds,
workerPoolSize: o.workerPoolSize(),
queueSize: om.QueueSize,
droppedMessages: om.DroppedMessages,
transactor: (&http.Client{
Transport: NewOutboundRoundTripper(om, o),
Timeout: o.clientTimeout(),
}).Do,
}
}
// Run spawns the configured number of goroutines to service the outbound channel.
// This method is idempotent.
func (wp *WorkerPool) Run() {
wp.runOnce.Do(func() {
for repeat := uint(0); repeat < wp.workerPoolSize; repeat++ {
go wp.worker()
}
})
}
// transact performs all the logic necessary to fulfill an outbound request.
// This method ensures that the Context associated with the request is properly canceled.
func (wp *WorkerPool) transact(e outboundEnvelope) {
defer e.cancel()
scheme, ok := e.request.Context().Value(schemeContextKey{}).(string)
if !ok {
scheme = unknown
}
// bail out early if the request has been on the queue too long
if err := e.request.Context().Err(); err != nil {
reason := getDroppedMessageReason(err)
wp.droppedMessages.With(prometheus.Labels{schemeLabel: scheme, codeLabel: messageDroppedCode, reasonLabel: reason}).Add(1)
wp.logger.Error("Outbound message expired while on queue", zap.String(schemeLabel, scheme), zap.String("reason", reason), zap.Error(err))
return
}
response, err := wp.transactor(e.request)
if err != nil {
reason := getDroppedMessageReason(err)
code := messageDroppedCode
if response != nil {
code = strconv.Itoa(response.StatusCode)
}
wp.droppedMessages.With(prometheus.Labels{schemeLabel: scheme, codeLabel: code, reasonLabel: reason}).Add(1)
wp.logger.Error("HTTP transaction error", zap.String(schemeLabel, scheme), zap.String(codeLabel, code), zap.String(reasonLabel, reason), zap.Error(err))
return
}
code := strconv.Itoa(response.StatusCode)
switch response.StatusCode {
case http.StatusAccepted:
wp.logger.Debug("HTTP response", zap.String("status", response.Status), zap.String(schemeLabel, scheme), zap.String(codeLabel, code), zap.String(reasonLabel, expectedCodeReason))
default:
wp.droppedMessages.With(prometheus.Labels{schemeLabel: scheme, codeLabel: code, reasonLabel: non202CodeReason}).Add(1)
wp.logger.Warn("HTTP response", zap.String(schemeLabel, scheme), zap.String(codeLabel, code), zap.String(reasonLabel, non202CodeReason))
}
io.Copy(io.Discard, response.Body)
response.Body.Close()
}
// worker represents a single goroutine that processes the outbounds channel.
// This method simply invokes transact for each *outboundEnvelope
func (wp *WorkerPool) worker() {
for e := range wp.outbounds {
wp.queueSize.Add(-1.0)
wp.transact(e)
}
}
func getDoErrReason(err error) string {
var d *net.DNSError
if err == nil {
return noErrReason
} else if errors.Is(err, context.DeadlineExceeded) {
return deadlineExceededReason
} else if errors.Is(err, context.Canceled) {
return contextCanceledReason
} else if errors.Is(err, &net.AddrError{}) {
return addressErrReason
} else if errors.Is(err, &net.ParseError{}) {
return parseAddrErrReason
} else if errors.Is(err, net.InvalidAddrError("")) {
return invalidAddrReason
} else if errors.As(err, &d) {
if d.IsNotFound {
return hostNotFoundReason
}
return dnsErrReason
} else if errors.Is(err, net.ErrClosed) {
return connClosedReason
} else if errors.Is(err, &net.OpError{}) {
return opErrReason
} else if errors.Is(err, net.UnknownNetworkError("")) {
return networkErrReason
}
// nolint: errorlint
if err, ok := err.(*url.Error); ok {
if strings.TrimSpace(strings.ToLower(err.Unwrap().Error())) == "eof" {
return connectionUnexpectedlyClosedEOFReason
}
}
return unknown
}