]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1996-2025 The Squid Software Foundation and contributors | |
3 | * | |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
9 | /* DEBUG: section 84 Helper process maintenance */ | |
10 | ||
11 | #ifndef SQUID_SRC_HELPER_H | |
12 | #define SQUID_SRC_HELPER_H | |
13 | ||
14 | #include "base/AsyncCall.h" | |
15 | #include "base/InstanceId.h" | |
16 | #include "base/RefCount.h" | |
17 | #include "cbdata.h" | |
18 | #include "comm/forward.h" | |
19 | #include "dlink.h" | |
20 | #include "helper/ChildConfig.h" | |
21 | #include "helper/forward.h" | |
22 | #include "helper/Reply.h" | |
23 | #include "helper/Request.h" | |
24 | #include "helper/ReservationId.h" | |
25 | #include "ip/Address.h" | |
26 | #include "sbuf/SBuf.h" | |
27 | ||
28 | #include <list> | |
29 | #include <map> | |
30 | #include <queue> | |
31 | #include <unordered_map> | |
32 | ||
33 | class CommTimeoutCbParams; | |
34 | class MemBuf; | |
35 | class Packable; | |
36 | class wordlist; | |
37 | ||
38 | namespace Helper | |
39 | { | |
40 | /// Holds the required data to serve a helper request. | |
41 | class Xaction { | |
42 | MEMPROXY_CLASS(Helper::Xaction); | |
43 | public: | |
44 | Xaction(HLPCB *c, void *d, const char *b): request(c, d, b) {} | |
45 | Helper::Request request; | |
46 | Helper::Reply reply; | |
47 | }; | |
48 | ||
49 | class SessionBase; | |
50 | ||
51 | /** | |
52 | * Managers a set of individual helper processes with a common queue of requests. | |
53 | * | |
54 | * With respect to load, a helper goes through these states (roughly): | |
55 | * idle: no processes are working on requests (and no requests are queued); | |
56 | * normal: some, but not all processes are working (and no requests are queued); | |
57 | * busy: all processes are working (and some requests are possibly queued); | |
58 | * overloaded: a busy helper with more than queue-size requests in the queue. | |
59 | * | |
60 | * A busy helper queues new requests and issues a WARNING every 10 minutes or so. | |
61 | * An overloaded helper either drops new requests or keeps queuing them, depending on | |
62 | * whether the caller can handle dropped requests (trySubmit vs helperSubmit APIs). | |
63 | * If an overloaded helper has been overloaded for 3+ minutes, an attempt to use | |
64 | * it results in on-persistent-overload action, which may kill worker. | |
65 | */ | |
66 | class Client: public RefCountable | |
67 | { | |
68 | public: | |
69 | using Pointer = RefCount<Client>; | |
70 | ||
71 | /// \returns a newly created instance of the named helper client | |
72 | /// \param name admin-visible helper category (with this process lifetime) | |
73 | static Pointer Make(const char *name); | |
74 | ||
75 | virtual ~Client(); | |
76 | ||
77 | /// \returns next request in the queue, or nil. | |
78 | Xaction *nextRequest(); | |
79 | ||
80 | /// If possible, submit request. Otherwise, either kill Squid or return false. | |
81 | bool trySubmit(const char *buf, HLPCB * callback, void *data); | |
82 | ||
83 | /// Submits a request to the helper or add it to the queue if none of | |
84 | /// the servers is available. | |
85 | void submitRequest(Xaction *); | |
86 | ||
87 | /// Dump some stats about the helper state to a Packable object | |
88 | void packStatsInto(Packable *p, const char *label = nullptr) const; | |
89 | /// whether the helper will be in "overloaded" state after one more request | |
90 | /// already overloaded helpers return true | |
91 | bool willOverload() const; | |
92 | ||
93 | /// Updates internal statistics and starts new helper processes after | |
94 | /// an unexpected server exit | |
95 | void handleKilledServer(SessionBase *); | |
96 | ||
97 | /// Reacts to unexpected helper process death(s), including a failure to start helper(s) | |
98 | /// and an unexpected exit of a previously started helper. \sa handleKilledServer() | |
99 | /// \param madeProgress whether the died helper(s) responded to any requests | |
100 | void handleFewerServers(bool madeProgress); | |
101 | ||
102 | /// satisfies all queued requests with a Helper::Unknown answer | |
103 | /// \prec no existing servers will be able to process queued requests | |
104 | /// \sa SessionBase::dropQueued() | |
105 | void dropQueued(); | |
106 | ||
107 | /// sends transaction response to the transaction initiator | |
108 | void callBack(Xaction &); | |
109 | ||
110 | /// Starts required helper process(es). | |
111 | /// The caller is responsible for checking that new processes are needed. | |
112 | virtual void openSessions(); | |
113 | ||
114 | public: | |
115 | wordlist *cmdline = nullptr; | |
116 | dlink_list servers; | |
117 | std::queue<Xaction *> queue; | |
118 | const char *id_name = nullptr; | |
119 | ChildConfig childs; ///< Configuration settings for number running. | |
120 | int ipc_type = 0; | |
121 | Ip::Address addr; | |
122 | unsigned int droppedRequests = 0; ///< requests not sent during helper overload | |
123 | time_t overloadStart = 0; ///< when the helper became overloaded (zero if it is not) | |
124 | time_t last_queue_warn = 0; | |
125 | time_t last_restart = 0; | |
126 | time_t timeout = 0; ///< Requests timeout | |
127 | bool retryTimedOut = false; ///< Whether the timed-out requests must retried | |
128 | bool retryBrokenHelper = false; ///< Whether the requests must retried on BH replies | |
129 | SBuf onTimedOutResponse; ///< The response to use when helper response timedout | |
130 | char eom = '\n'; ///< The char which marks the end of (response) message, normally '\n' | |
131 | ||
132 | struct _stats { | |
133 | int requests = 0; | |
134 | int replies = 0; | |
135 | int timedout = 0; | |
136 | int queue_size = 0; | |
137 | int avg_svc_time = 0; | |
138 | } stats; | |
139 | ||
140 | protected: | |
141 | /// \param name admin-visible helper category (with this process lifetime) | |
142 | explicit Client(const char * const name): id_name(name) {} | |
143 | ||
144 | bool queueFull() const; | |
145 | bool overloaded() const; | |
146 | void syncQueueStats(); | |
147 | bool prepSubmit(); | |
148 | void submit(const char *buf, HLPCB * callback, void *data); | |
149 | }; | |
150 | ||
151 | } // namespace Helper | |
152 | ||
153 | // TODO: Rename to a *Client. | |
154 | class statefulhelper: public Helper::Client | |
155 | { | |
156 | public: | |
157 | using Pointer = RefCount<statefulhelper>; | |
158 | typedef std::unordered_map<Helper::ReservationId, helper_stateful_server *> Reservations; | |
159 | ||
160 | ~statefulhelper() override = default; | |
161 | ||
162 | static Pointer Make(const char *name); | |
163 | ||
164 | /// reserve the given server | |
165 | void reserveServer(helper_stateful_server * srv); | |
166 | ||
167 | /// undo reserveServer(), clear the reservation and kick the queue | |
168 | void cancelReservation(const Helper::ReservationId reservation); | |
169 | ||
170 | /* Helper::Client API */ | |
171 | void openSessions() override; | |
172 | ||
173 | private: | |
174 | friend void helperStatefulSubmit(const statefulhelper::Pointer &, const char *buf, HLPCB *, void *cbData, const Helper::ReservationId &); | |
175 | ||
176 | explicit statefulhelper(const char * const name): Helper::Client(name) {} | |
177 | ||
178 | /// \return the previously reserved server (if the reservation is still valid) or nil | |
179 | helper_stateful_server *findServer(const Helper::ReservationId & reservation); | |
180 | ||
181 | void submit(const char *buf, HLPCB * callback, void *data, const Helper::ReservationId & reservation); | |
182 | bool trySubmit(const char *buf, HLPCB * callback, void *data, const Helper::ReservationId & reservation); | |
183 | ||
184 | ///< reserved servers indexed by reservation IDs | |
185 | Reservations reservations; | |
186 | }; | |
187 | ||
188 | namespace Helper | |
189 | { | |
190 | ||
191 | /// represents a single helper process | |
192 | class SessionBase: public CbdataParent | |
193 | { | |
194 | public: | |
195 | ~SessionBase() override; | |
196 | ||
197 | /// close handler to handle exited server processes | |
198 | static void HelperServerClosed(SessionBase *); | |
199 | ||
200 | /** Closes pipes to the helper safely. | |
201 | * Handles the case where the read and write pipes are the same FD. | |
202 | */ | |
203 | void closePipesSafely(); | |
204 | ||
205 | /** Closes the reading pipe. | |
206 | * If the read and write sockets are the same the write pipe will | |
207 | * also be closed. Otherwise its left open for later handling. | |
208 | */ | |
209 | void closeWritePipeSafely(); | |
210 | ||
211 | // TODO: Teach each child to report its child-specific state instead. | |
212 | /// whether the server is locked for exclusive use by a client | |
213 | virtual bool reserved() = 0; | |
214 | ||
215 | /// our creator (parent) object | |
216 | virtual Client &helper() const = 0; | |
217 | ||
218 | /// dequeues and sends an Unknown answer to all queued requests | |
219 | virtual void dropQueued(); | |
220 | ||
221 | public: | |
222 | /// Helper program identifier; does not change when contents do, | |
223 | /// including during assignment | |
224 | const InstanceId<SessionBase> index; | |
225 | ||
226 | int pid; | |
227 | Ip::Address addr; | |
228 | Comm::ConnectionPointer readPipe; | |
229 | Comm::ConnectionPointer writePipe; | |
230 | void *hIpc; | |
231 | ||
232 | char *rbuf; | |
233 | size_t rbuf_sz; | |
234 | size_t roffset; | |
235 | ||
236 | struct timeval dispatch_time; | |
237 | struct timeval answer_time; | |
238 | ||
239 | dlink_node link; | |
240 | ||
241 | struct _helper_flags { | |
242 | bool writing; | |
243 | bool closing; | |
244 | bool shutdown; | |
245 | } flags; | |
246 | ||
247 | using Requests = std::list<Xaction *>; | |
248 | Requests requests; ///< requests in order of submission/expiration | |
249 | ||
250 | struct { | |
251 | uint64_t uses; //< requests sent to this helper | |
252 | uint64_t replies; //< replies received from this helper | |
253 | uint64_t pending; //< queued lookups waiting to be sent to this helper | |
254 | uint64_t releases; //< times release() has been called on this helper (if stateful) | |
255 | uint64_t timedout; //< requests which timed-out | |
256 | } stats; | |
257 | void initStats(); | |
258 | }; | |
259 | ||
260 | /// represents a single "stateless helper" process; | |
261 | /// supports concurrent helper requests | |
262 | class Session: public SessionBase | |
263 | { | |
264 | CBDATA_CHILD(Session); | |
265 | ||
266 | public: | |
267 | uint64_t nextRequestId; | |
268 | ||
269 | MemBuf *wqueue; | |
270 | MemBuf *writebuf; | |
271 | ||
272 | Client::Pointer parent; | |
273 | ||
274 | /// The helper request Xaction object for the current reply . | |
275 | /// A helper reply may be distributed to more than one of the retrieved | |
276 | /// packets from helper. This member stores the Xaction object as long as | |
277 | /// the end-of-message for current reply is not retrieved. | |
278 | Xaction *replyXaction; | |
279 | ||
280 | /// Whether to ignore current message, because it is timed-out or other reason | |
281 | bool ignoreToEom; | |
282 | ||
283 | // STL says storing std::list iterators is safe when changing the list | |
284 | typedef std::map<uint64_t, Requests::iterator> RequestIndex; | |
285 | RequestIndex requestsIndex; ///< maps request IDs to requests | |
286 | ||
287 | ~Session() override; | |
288 | ||
289 | /// Search in queue for the request with requestId, return the related | |
290 | /// Xaction object and remove it from queue. | |
291 | /// If concurrency is disabled then the requestId is ignored and the | |
292 | /// Xaction of the next request in queue is retrieved. | |
293 | Xaction *popRequest(int requestId); | |
294 | ||
295 | /// Run over the active requests lists and forces a retry, or timedout reply | |
296 | /// or the configured "on timeout response" for timedout requests. | |
297 | void checkForTimedOutRequests(bool const retry); | |
298 | ||
299 | /* SessionBase API */ | |
300 | bool reserved() override {return false;} | |
301 | void dropQueued() override; | |
302 | Client &helper() const override { return *parent; } | |
303 | ||
304 | /// Read timeout handler | |
305 | static void requestTimeout(const CommTimeoutCbParams &io); | |
306 | }; | |
307 | ||
308 | } // namespace Helper | |
309 | ||
310 | // TODO: Rename to a *Session, matching renamed statefulhelper. | |
311 | /// represents a single "stateful helper" process; | |
312 | /// supports exclusive transaction reservations | |
313 | class helper_stateful_server: public Helper::SessionBase | |
314 | { | |
315 | CBDATA_CHILD(helper_stateful_server); | |
316 | ||
317 | public: | |
318 | ~helper_stateful_server() override; | |
319 | void reserve(); | |
320 | void clearReservation(); | |
321 | ||
322 | /* Helper::SessionBase API */ | |
323 | bool reserved() override {return reservationId.reserved();} | |
324 | Helper::Client &helper() const override { return *parent; } | |
325 | ||
326 | statefulhelper::Pointer parent; | |
327 | ||
328 | // Reservations temporary lock the server for an exclusive "client" use. The | |
329 | // client keeps the reservation ID as a proof of her reservation. If a | |
330 | // reservation expires, and the server is reserved for another client, then | |
331 | // the reservation ID presented by the late client will not match ours. | |
332 | Helper::ReservationId reservationId; ///< "confirmation ID" of the last | |
333 | time_t reservationStart; ///< when the last `reservation` was made | |
334 | }; | |
335 | ||
336 | void helperSubmit(const Helper::Client::Pointer &, const char *buf, HLPCB *, void *cbData); | |
337 | void helperStatefulSubmit(const statefulhelper::Pointer &, const char *buf, HLPCB *, void *cbData, uint64_t reservation); | |
338 | void helperShutdown(const Helper::Client::Pointer &); | |
339 | void helperStatefulShutdown(const statefulhelper::Pointer &); | |
340 | ||
341 | #endif /* SQUID_SRC_HELPER_H */ | |
342 |