/src/freeradius-server/src/lib/server/trunk.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or (at |
5 | | * your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: fc85e0cd7a091e18670aa5b6916ffb5d84743c69 $ |
19 | | * |
20 | | * @file src/lib/server/trunk.c |
21 | | * @brief A management API for bonding multiple connections together. |
22 | | * |
23 | | * @copyright 2019-2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | * @copyright 2019-2020 The FreeRADIUS server project |
25 | | */ |
26 | | |
27 | 0 | #define LOG_PREFIX trunk->log_prefix |
28 | | |
29 | | #ifdef NDEBUG |
30 | | # define TALLOC_GET_TYPE_ABORT_NOOP 1 |
31 | | #endif |
32 | | |
33 | | typedef struct trunk_request_s trunk_request_t; |
34 | | typedef struct trunk_connection_s trunk_connection_t; |
35 | | typedef struct trunk_s trunk_t; |
36 | | #define _TRUNK_PRIVATE 1 |
37 | | #include <freeradius-devel/server/trunk.h> |
38 | | |
39 | | #include <freeradius-devel/server/trigger.h> |
40 | | #include <freeradius-devel/util/debug.h> |
41 | | #include <freeradius-devel/util/misc.h> |
42 | | #include <freeradius-devel/util/syserror.h> |
43 | | #include <freeradius-devel/util/minmax_heap.h> |
44 | | |
45 | | #ifdef HAVE_STDATOMIC_H |
46 | | # include <stdatomic.h> |
47 | | # ifndef ATOMIC_VAR_INIT |
48 | | # define ATOMIC_VAR_INIT(_x) (_x) |
49 | | # endif |
50 | | #else |
51 | | # include <freeradius-devel/util/stdatomic.h> |
52 | | #endif |
53 | | |
54 | | static atomic_uint_fast64_t request_counter = ATOMIC_VAR_INIT(1); |
55 | | |
56 | | #ifdef TESTING_TRUNK |
57 | | static fr_time_t test_time_base = fr_time_wrap(1); |
58 | | |
59 | | static fr_time_t test_time(void) |
60 | | { |
61 | | return test_time_base; |
62 | | } |
63 | | |
64 | | #define fr_time test_time |
65 | | #endif |
66 | | |
67 | | #ifndef NDEBUG |
68 | | /** The maximum number of state logs to record per request |
69 | | * |
70 | | */ |
71 | 0 | #define TRUNK_REQUEST_STATE_LOG_MAX 20 |
72 | | |
73 | | /** Trace state machine changes for a particular request |
74 | | * |
75 | | */ |
76 | | typedef struct { |
77 | | fr_dlist_head_t *log_head; //!< To allow the log entry to remove itself on free. |
78 | | fr_dlist_t entry; //!< Entry in the linked list. |
79 | | trunk_request_state_t from; //!< What state we transitioned from. |
80 | | trunk_request_state_t to; //!< What state we transitioned to. |
81 | | |
82 | | trunk_connection_t *tconn; //!< The request was associated with. |
83 | | ///< Pointer may now be invalid, do no de-reference. |
84 | | |
85 | | uint64_t tconn_id; //!< If the treq was associated with a connection |
86 | | ///< the connection ID. |
87 | | trunk_connection_state_t tconn_state; //!< If the treq was associated with a connection |
88 | | ///< the connection state at the time of the |
89 | | ///< state transition. |
90 | | |
91 | | char const *function; //!< State change occurred in. |
92 | | int line; //!< Line change occurred on. |
93 | | } trunk_request_state_log_t; |
94 | | #endif |
95 | | |
96 | | /** Wraps a normal request |
97 | | * |
98 | | */ |
99 | | struct trunk_request_s { |
100 | | struct trunk_request_pub_s pub; //!< Public fields in the trunk request. |
101 | | ///< This *MUST* be the first field in this |
102 | | ///< structure. |
103 | | |
104 | | uint64_t id; //!< Trunk request ID. |
105 | | |
106 | | fr_heap_index_t heap_id; //!< Used to track the request conn->pending heap. |
107 | | |
108 | | fr_dlist_t entry; //!< Used to track the trunk request in the conn->sent |
109 | | ///< or trunk->backlog request. |
110 | | |
111 | | trunk_cancel_reason_t cancel_reason; //!< Why this request was cancelled. |
112 | | |
113 | | fr_time_t last_freed; //!< Last time this request was freed. |
114 | | |
115 | | bool bound_to_conn; //!< Fail the request if there's an attempt to |
116 | | ///< re-enqueue it. |
117 | | |
118 | | bool sent; //!< Trunk request has been sent at least once. |
119 | | ///< Used so that re-queueing doesn't increase trunk |
120 | | ///< `sent` count. |
121 | | |
122 | | #ifndef NDEBUG |
123 | | fr_dlist_head_t log; //!< State change log. |
124 | | #endif |
125 | | }; |
126 | | |
127 | | |
128 | | /** Associates request queues with a connection |
129 | | * |
130 | | * @dotfile src/lib/server/trunk_conn.gv "Trunk connection state machine" |
131 | | * @dotfile src/lib/server/trunk_req.gv "Trunk request state machine" |
132 | | */ |
133 | | struct trunk_connection_s { |
134 | | struct trunk_connection_pub_s pub; //!< Public fields in the trunk connection. |
135 | | ///< This *MUST* be the first field in this |
136 | | ///< structure. |
137 | | |
138 | | fr_heap_index_t heap_id; //!< Used to track the connection in the connected |
139 | | ///< heap. |
140 | | |
141 | | fr_dlist_t entry; //!< Used to track the connection in the connecting, |
142 | | ///< full and failed lists. |
143 | | |
144 | | /** @name State |
145 | | * @{ |
146 | | */ |
147 | | trunk_connection_event_t events; //!< The current events we expect to be notified on. |
148 | | /** @} */ |
149 | | |
150 | | /** @name Request lists |
151 | | * @{ |
152 | | */ |
153 | | fr_heap_t *pending; //!< Requests waiting to be sent. |
154 | | |
155 | | trunk_request_t *partial; //!< Partially written request. |
156 | | |
157 | | fr_dlist_head_t sent; //!< Sent request. |
158 | | |
159 | | fr_dlist_head_t reapable; //!< Idle request. |
160 | | |
161 | | fr_dlist_head_t cancel; //!< Requests in the cancel state. |
162 | | |
163 | | trunk_request_t *cancel_partial; //!< Partially written cancellation request. |
164 | | |
165 | | fr_dlist_head_t cancel_sent; //!< Sent cancellation request. |
166 | | /** @} */ |
167 | | |
168 | | /** @name Statistics |
169 | | * @{ |
170 | | */ |
171 | | uint64_t sent_count; //!< The number of requests that have been sent using |
172 | | ///< this connection. |
173 | | /** @} */ |
174 | | |
175 | | /** @name Timers |
176 | | * @{ |
177 | | */ |
178 | | fr_timer_t *lifetime_ev; //!< Maximum time this connection can be open. |
179 | | /** @} */ |
180 | | }; |
181 | | |
182 | | /** An entry in a trunk watch function list |
183 | | * |
184 | | */ |
185 | | typedef struct trunk_watch_entry_s { |
186 | | fr_dlist_t entry; //!< List entry. |
187 | | trunk_watch_t func; //!< Function to call when a trunk enters |
188 | | ///< the state this list belongs to |
189 | | bool oneshot; //!< Remove the function after it's called once. |
190 | | bool enabled; //!< Whether the watch entry is enabled. |
191 | | void *uctx; //!< User data to pass to the function. |
192 | | } trunk_watch_entry_t; |
193 | | |
194 | | /** Map connection states to trigger names |
195 | | * |
196 | | * Must stay in the same order as #trunk_connection_state_t |
197 | | */ |
198 | | static fr_table_num_indexed_bit_pos_t const trunk_conn_trigger_names[] = { |
199 | | { L("pool.connection_halted"), TRUNK_CONN_HALTED }, /* 0x0000 - bit 0 */ |
200 | | { L("pool.connection_init"), TRUNK_CONN_INIT }, /* 0x0001 - bit 1 */ |
201 | | { L("pool.connection_connecting"), TRUNK_CONN_CONNECTING }, /* 0x0002 - bit 2 */ |
202 | | { L("pool.connection_active"), TRUNK_CONN_ACTIVE }, /* 0x0004 - bit 3 */ |
203 | | { L("pool.connection_closed"), TRUNK_CONN_CLOSED }, /* 0x0008 - bit 4 */ |
204 | | { L("pool.connection_full"), TRUNK_CONN_FULL }, /* 0x0010 - bit 5 */ |
205 | | { L("pool.connection_inactive"), TRUNK_CONN_INACTIVE }, /* 0x0020 - bit 6 */ |
206 | | { L("pool.connection_inactive_draining"), TRUNK_CONN_INACTIVE_DRAINING }, /* 0x0040 - bit 7 */ |
207 | | { L("pool.connection_draining"), TRUNK_CONN_DRAINING }, /* 0x0080 - bit 8 */ |
208 | | { L("pool.connection_draining_to_free"), TRUNK_CONN_DRAINING_TO_FREE } /* 0x0100 - bit 9 */ |
209 | | }; |
210 | | static size_t trunk_conn_trigger_names_len = NUM_ELEMENTS(trunk_conn_trigger_names); |
211 | | |
212 | | /** Main trunk management handle |
213 | | * |
214 | | */ |
215 | | struct trunk_s { |
216 | | struct trunk_pub_s pub; //!< Public fields in the trunk connection. |
217 | | ///< This *MUST* be the first field in this |
218 | | ///< structure. |
219 | | |
220 | | char const *log_prefix; //!< What to prepend to messages. |
221 | | |
222 | | fr_event_list_t *el; //!< Event list used by this trunk and the connection. |
223 | | |
224 | | trunk_conf_t conf; //!< Trunk common configuration. |
225 | | |
226 | | fr_dlist_head_t free_requests; //!< Requests in the unassigned state. Waiting to be |
227 | | ///< enqueued. |
228 | | |
229 | | fr_heap_t *backlog; //!< The request backlog. Requests we couldn't |
230 | | ///< immediately assign to a connection. |
231 | | |
232 | | /** @name Connection lists |
233 | | * |
234 | | * A connection must always be in exactly one of these lists |
235 | | * or trees. |
236 | | * |
237 | | * @{ |
238 | | */ |
239 | | fr_dlist_head_t init; //!< Connections which have not yet started |
240 | | ///< connecting. |
241 | | |
242 | | fr_dlist_head_t connecting; //!< Connections which are not yet in the open state. |
243 | | |
244 | | fr_minmax_heap_t *active; //!< Connections which can service requests. |
245 | | |
246 | | fr_dlist_head_t full; //!< Connections which have too many outstanding |
247 | | ///< requests. |
248 | | |
249 | | fr_dlist_head_t inactive; //!< Connections which have been signalled to be |
250 | | ///< inactive by the API client. |
251 | | |
252 | | fr_dlist_head_t inactive_draining; //!< Connections which have been signalled to be |
253 | | ///< inactive by the API client, which the trunk |
254 | | ///< manager is draining to close. |
255 | | |
256 | | fr_dlist_head_t failed; //!< Connections that'll be reconnected shortly. |
257 | | |
258 | | fr_dlist_head_t closed; //!< Connections that have closed. Either due to |
259 | | ///< shutdown, reconnection or failure. |
260 | | |
261 | | fr_dlist_head_t draining; //!< Connections that will be freed once all their |
262 | | ///< requests are complete, but can be reactivated. |
263 | | |
264 | | fr_dlist_head_t draining_to_free; //!< Connections that will be freed once all their |
265 | | ///< requests are complete. |
266 | | |
267 | | fr_dlist_head_t to_free; //!< Connections we're done with and will free on |
268 | | //!< the next call to trunk_manage. |
269 | | //!< This prevents connections from being freed |
270 | | //!< whilst we're inside callbacks. |
271 | | /** @} */ |
272 | | |
273 | | /** @name Callbacks |
274 | | * @{ |
275 | | */ |
276 | | trunk_io_funcs_t funcs; //!< I/O functions. |
277 | | |
278 | | void *in_handler; //!< Which handler we're inside. |
279 | | |
280 | | void *uctx; //!< Uctx data to pass to alloc. |
281 | | |
282 | | fr_dlist_head_t watch[TRUNK_STATE_MAX]; //!< To be called when trunk changes state. |
283 | | |
284 | | trunk_watch_entry_t *next_watcher; //!< Watcher about to be run. Used to prevent nested watchers. |
285 | | /** @} */ |
286 | | |
287 | | /** @name Timers |
288 | | * @{ |
289 | | */ |
290 | | fr_timer_t *manage_ev; //!< Periodic connection management event. |
291 | | /** @} */ |
292 | | |
293 | | /** @name Log rate limiting entries |
294 | | * @{ |
295 | | */ |
296 | | fr_rate_limit_t limit_max_requests_alloc_log; //!< Rate limit on "Refusing to alloc requests - Limit of * requests reached" |
297 | | |
298 | | fr_rate_limit_t limit_last_failure_log; //!< Rate limit on "Refusing to enqueue requests - No active conns" |
299 | | /** @} */ |
300 | | |
301 | | /** @name State |
302 | | * @{ |
303 | | */ |
304 | | bool freeing; //!< Trunk is being freed, don't spawn new |
305 | | ///< connections or re-enqueue. |
306 | | |
307 | | bool started; //!< Has the trunk been started. |
308 | | |
309 | | bool managing_connections; //!< Whether the trunk is allowed to manage |
310 | | ///< (open/close) connections. |
311 | | |
312 | | uint64_t last_req_per_conn; //!< The last request to connection ratio we calculated. |
313 | | /** @} */ |
314 | | |
315 | | fr_pair_list_t *trigger_args; //!< Passed to trigger |
316 | | |
317 | | bool trigger_undef[NUM_ELEMENTS(trunk_conn_trigger_names)]; //!< Record that a specific trigger is undefined. |
318 | | |
319 | | CONF_PAIR *trigger_cp[NUM_ELEMENTS(trunk_conn_trigger_names)]; //!< Cached trigger CONF_PAIRs |
320 | | }; |
321 | | |
322 | | int trunk_trigger_cf_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule); |
323 | | |
324 | | static conf_parser_t const trunk_config_request[] = { |
325 | | { FR_CONF_OFFSET("per_connection_max", trunk_conf_t, max_req_per_conn), .dflt = "2000" }, |
326 | | { FR_CONF_OFFSET("per_connection_target", trunk_conf_t, target_req_per_conn), .dflt = "1000" }, |
327 | | { FR_CONF_OFFSET("free_delay", trunk_conf_t, req_cleanup_delay), .dflt = "10.0" }, |
328 | | { FR_CONF_OFFSET("triggers", trunk_conf_t, req_triggers), .func = trunk_trigger_cf_parse }, |
329 | | |
330 | | CONF_PARSER_TERMINATOR |
331 | | }; |
332 | | |
333 | | static conf_parser_t const trunk_config_connection[] = { |
334 | | { FR_CONF_OFFSET("connect_timeout", connection_conf_t, connection_timeout), .dflt = "3.0" }, |
335 | | { FR_CONF_OFFSET("reconnect_delay", connection_conf_t, reconnection_delay), .dflt = "1" }, |
336 | | |
337 | | CONF_PARSER_TERMINATOR |
338 | | }; |
339 | | |
340 | | #ifndef TRUNK_TESTS |
341 | | conf_parser_t const trunk_config[] = { |
342 | | { FR_CONF_OFFSET("start", trunk_conf_t, start), .dflt = "1" }, |
343 | | { FR_CONF_OFFSET("min", trunk_conf_t, min), .dflt = "1" }, |
344 | | { FR_CONF_OFFSET("max", trunk_conf_t, max), .dflt = "5" }, |
345 | | { FR_CONF_OFFSET("connecting", trunk_conf_t, connecting), .dflt = "2" }, |
346 | | { FR_CONF_OFFSET("uses", trunk_conf_t, max_uses), .dflt = "0" }, |
347 | | { FR_CONF_OFFSET("lifetime", trunk_conf_t, lifetime), .dflt = "0" }, |
348 | | { FR_CONF_OFFSET("idle_timeout", trunk_conf_t, idle_timeout), .dflt = "0" }, |
349 | | |
350 | | { FR_CONF_OFFSET("open_delay", trunk_conf_t, open_delay), .dflt = "0.2" }, |
351 | | { FR_CONF_OFFSET("close_delay", trunk_conf_t, close_delay), .dflt = "10.0" }, |
352 | | |
353 | | { FR_CONF_OFFSET("manage_interval", trunk_conf_t, manage_interval), .dflt = "0.2" }, |
354 | | |
355 | | { FR_CONF_OFFSET("max_backlog", trunk_conf_t, max_backlog), .dflt = "1000" }, |
356 | | |
357 | | { FR_CONF_OFFSET("backlog_on_failed_conn", trunk_conf_t, backlog_on_failed_conn), }, |
358 | | |
359 | | { FR_CONF_OFFSET("triggers", trunk_conf_t, conn_triggers), .func = trunk_trigger_cf_parse }, |
360 | | |
361 | | { FR_CONF_OFFSET_SUBSECTION("connection", 0, trunk_conf_t, conn_conf, trunk_config_connection), .subcs_size = sizeof(trunk_config_connection) }, |
362 | | { FR_CONF_POINTER("request", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) trunk_config_request }, |
363 | | |
364 | | CONF_PARSER_TERMINATOR |
365 | | }; |
366 | | #endif |
367 | | |
368 | | #ifndef NDEBUG |
369 | | /** Map request states to trigger names |
370 | | * |
371 | | * Must stay in the same order as #trunk_connection_state_t |
372 | | */ |
373 | | static fr_table_num_indexed_bit_pos_t const trunk_req_trigger_names[] = { |
374 | | { L("pool.request_init"), TRUNK_REQUEST_STATE_INIT }, /* 0x0000 - bit 0 */ |
375 | | { L("pool.request_unassigned"), TRUNK_REQUEST_STATE_UNASSIGNED }, /* 0x0001 - bit 1 */ |
376 | | { L("pool.request_backlog"), TRUNK_REQUEST_STATE_BACKLOG }, /* 0x0002 - bit 2 */ |
377 | | { L("pool.request_pending"), TRUNK_REQUEST_STATE_PENDING }, /* 0x0004 - bit 3 */ |
378 | | { L("pool.request_partial"), TRUNK_REQUEST_STATE_PARTIAL }, /* 0x0008 - bit 4 */ |
379 | | { L("pool.request_sent"), TRUNK_REQUEST_STATE_SENT }, /* 0x0010 - bit 5 */ |
380 | | { L("pool.request_state_reapable"), TRUNK_REQUEST_STATE_REAPABLE }, /* 0x0020 - bit 6 */ |
381 | | { L("pool.request_complete"), TRUNK_REQUEST_STATE_COMPLETE }, /* 0x0040 - bit 7 */ |
382 | | { L("pool.request_state_failed"), TRUNK_REQUEST_STATE_FAILED }, /* 0x0080 - bit 8 */ |
383 | | { L("pool.request_state_cancel"), TRUNK_REQUEST_STATE_CANCEL }, /* 0x0100 - bit 9 */ |
384 | | { L("pool.request_state_cancel_sent"), TRUNK_REQUEST_STATE_CANCEL_SENT }, /* 0x0200 - bit 10 */ |
385 | | { L("pool.request_state_cancel_partial"), TRUNK_REQUEST_STATE_CANCEL_PARTIAL }, /* 0x0400 - bit 11 */ |
386 | | { L("pool.request_state_cancel_complete"), TRUNK_REQUEST_STATE_CANCEL_COMPLETE }, /* 0x0800 - bit 12 */ |
387 | | }; |
388 | | static size_t trunk_req_trigger_names_len = NUM_ELEMENTS(trunk_req_trigger_names); |
389 | | #endif |
390 | | |
391 | | static fr_table_num_ordered_t const trunk_request_states[] = { |
392 | | { L("INIT"), TRUNK_REQUEST_STATE_INIT }, |
393 | | { L("UNASSIGNED"), TRUNK_REQUEST_STATE_UNASSIGNED }, |
394 | | { L("BACKLOG"), TRUNK_REQUEST_STATE_BACKLOG }, |
395 | | { L("PENDING"), TRUNK_REQUEST_STATE_PENDING }, |
396 | | { L("PARTIAL"), TRUNK_REQUEST_STATE_PARTIAL }, |
397 | | { L("SENT"), TRUNK_REQUEST_STATE_SENT }, |
398 | | { L("REAPABLE"), TRUNK_REQUEST_STATE_REAPABLE }, |
399 | | { L("COMPLETE"), TRUNK_REQUEST_STATE_COMPLETE }, |
400 | | { L("FAILED"), TRUNK_REQUEST_STATE_FAILED }, |
401 | | { L("CANCEL"), TRUNK_REQUEST_STATE_CANCEL }, |
402 | | { L("CANCEL-SENT"), TRUNK_REQUEST_STATE_CANCEL_SENT }, |
403 | | { L("CANCEL-PARTIAL"), TRUNK_REQUEST_STATE_CANCEL_PARTIAL }, |
404 | | { L("CANCEL-COMPLETE"), TRUNK_REQUEST_STATE_CANCEL_COMPLETE } |
405 | | }; |
406 | | static size_t trunk_request_states_len = NUM_ELEMENTS(trunk_request_states); |
407 | | |
408 | | static fr_table_num_ordered_t const trunk_states[] = { |
409 | | { L("IDLE"), TRUNK_STATE_IDLE }, |
410 | | { L("ACTIVE"), TRUNK_STATE_ACTIVE }, |
411 | | { L("PENDING"), TRUNK_STATE_PENDING }, |
412 | | { L("FULL"), TRUNK_STATE_FULL }, |
413 | | { L("FAILED"), TRUNK_STATE_FAILED } |
414 | | }; |
415 | | static size_t trunk_states_len = NUM_ELEMENTS(trunk_states); |
416 | | |
417 | | static fr_table_num_ordered_t const trunk_connection_states[] = { |
418 | | { L("INIT"), TRUNK_CONN_INIT }, |
419 | | { L("HALTED"), TRUNK_CONN_HALTED }, |
420 | | { L("CONNECTING"), TRUNK_CONN_CONNECTING }, |
421 | | { L("ACTIVE"), TRUNK_CONN_ACTIVE }, |
422 | | { L("CLOSED"), TRUNK_CONN_CLOSED }, |
423 | | { L("FULL"), TRUNK_CONN_FULL }, |
424 | | { L("INACTIVE"), TRUNK_CONN_INACTIVE }, |
425 | | { L("INACTIVE-DRAINING"), TRUNK_CONN_INACTIVE_DRAINING }, |
426 | | { L("DRAINING"), TRUNK_CONN_DRAINING }, |
427 | | { L("DRAINING-TO-FREE"), TRUNK_CONN_DRAINING_TO_FREE } |
428 | | }; |
429 | | static size_t trunk_connection_states_len = NUM_ELEMENTS(trunk_connection_states); |
430 | | |
431 | | static fr_table_num_ordered_t const trunk_cancellation_reasons[] = { |
432 | | { L("TRUNK_CANCEL_REASON_NONE"), TRUNK_CANCEL_REASON_NONE }, |
433 | | { L("TRUNK_CANCEL_REASON_SIGNAL"), TRUNK_CANCEL_REASON_SIGNAL }, |
434 | | { L("TRUNK_CANCEL_REASON_MOVE"), TRUNK_CANCEL_REASON_MOVE }, |
435 | | { L("TRUNK_CANCEL_REASON_REQUEUE"), TRUNK_CANCEL_REASON_REQUEUE } |
436 | | }; |
437 | | static size_t trunk_cancellation_reasons_len = NUM_ELEMENTS(trunk_cancellation_reasons); |
438 | | |
439 | | static fr_table_num_ordered_t const trunk_connection_events[] = { |
440 | | { L("TRUNK_CONN_EVENT_NONE"), TRUNK_CONN_EVENT_NONE }, |
441 | | { L("TRUNK_CONN_EVENT_READ"), TRUNK_CONN_EVENT_READ }, |
442 | | { L("TRUNK_CONN_EVENT_WRITE"), TRUNK_CONN_EVENT_WRITE }, |
443 | | { L("TRUNK_CONN_EVENT_BOTH"), TRUNK_CONN_EVENT_BOTH }, |
444 | | }; |
445 | | static size_t trunk_connection_events_len = NUM_ELEMENTS(trunk_connection_events); |
446 | | |
447 | 0 | #define CONN_TRIGGER(_state) do { \ |
448 | 0 | uint8_t idx = fr_high_bit_pos(_state); \ |
449 | 0 | if (trunk->conf.conn_triggers && !trunk->trigger_undef[idx]) { \ |
450 | 0 | if (trigger(unlang_interpret_get_thread_default(), trunk->conf.conn_trigger_cs, \ |
451 | 0 | &trunk->trigger_cp[idx], \ |
452 | 0 | fr_table_str_by_value(trunk_conn_trigger_names, _state, \ |
453 | 0 | "<INVALID>"), true, trunk->trigger_args) == -1) { \ |
454 | 0 | trunk->trigger_undef[idx] = true; \ |
455 | 0 | } \ |
456 | 0 | } \ |
457 | 0 | } while (0) |
458 | | |
459 | 0 | #define CONN_STATE_TRANSITION(_new, _log) \ |
460 | 0 | do { \ |
461 | 0 | _log("[%" PRIu64 "] Trunk connection changed state %s -> %s", \ |
462 | 0 | tconn->pub.conn->id, \ |
463 | 0 | fr_table_str_by_value(trunk_connection_states, tconn->pub.state, "<INVALID>"), \ |
464 | 0 | fr_table_str_by_value(trunk_connection_states, _new, "<INVALID>")); \ |
465 | 0 | tconn->pub.state = _new; \ |
466 | 0 | CONN_TRIGGER(_new); \ |
467 | 0 | trunk_requests_per_connection(NULL, NULL, trunk, fr_time(), false); \ |
468 | 0 | } while (0) |
469 | | |
470 | 0 | #define CONN_BAD_STATE_TRANSITION(_new) \ |
471 | 0 | do { \ |
472 | 0 | if (!fr_cond_assert_msg(0, "[%" PRIu64 "] Trunk connection invalid transition %s -> %s", \ |
473 | 0 | tconn->pub.conn->id, \ |
474 | 0 | fr_table_str_by_value(trunk_connection_states, tconn->pub.state, "<INVALID>"), \ |
475 | 0 | fr_table_str_by_value(trunk_connection_states, _new, "<INVALID>"))) return; \ |
476 | 0 | } while (0) |
477 | | |
478 | | #ifndef NDEBUG |
479 | | void trunk_request_state_log_entry_add(char const *function, int line, |
480 | | trunk_request_t *treq, trunk_request_state_t new) CC_HINT(nonnull); |
481 | | |
482 | 0 | #define REQUEST_TRIGGER(_state) do { \ |
483 | 0 | if (trunk->conf.req_triggers) { \ |
484 | 0 | trigger(unlang_interpret_get_thread_default(), \ |
485 | 0 | trunk->conf.req_trigger_cs, NULL, fr_table_str_by_value(trunk_req_trigger_names, _state, \ |
486 | 0 | "<INVALID>"), true, trunk->trigger_args); \ |
487 | 0 | } \ |
488 | 0 | } while (0) |
489 | | |
490 | | /** Record a request state transition and log appropriate output |
491 | | * |
492 | | */ |
493 | 0 | #define REQUEST_STATE_TRANSITION(_new) \ |
494 | 0 | do { \ |
495 | 0 | request_t *request = treq->pub.request; \ |
496 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Trunk request %" PRIu64 " changed state %s -> %s", \ |
497 | 0 | treq->id, \ |
498 | 0 | fr_table_str_by_value(trunk_request_states, treq->pub.state, "<INVALID>"), \ |
499 | 0 | fr_table_str_by_value(trunk_request_states, _new, "<INVALID>")); \ |
500 | 0 | trunk_request_state_log_entry_add(__FUNCTION__, __LINE__, treq, _new); \ |
501 | 0 | treq->pub.state = _new; \ |
502 | 0 | REQUEST_TRIGGER(_new); \ |
503 | 0 | } while (0) |
504 | 0 | #define REQUEST_BAD_STATE_TRANSITION(_new) \ |
505 | 0 | do { \ |
506 | 0 | trunk_request_state_log(&default_log, L_ERR, __FILE__, __LINE__, treq); \ |
507 | 0 | if (!fr_cond_assert_msg(0, "Trunk request %" PRIu64 " invalid transition %s -> %s", \ |
508 | 0 | treq->id, \ |
509 | 0 | fr_table_str_by_value(trunk_request_states, treq->pub.state, "<INVALID>"), \ |
510 | 0 | fr_table_str_by_value(trunk_request_states, _new, "<INVALID>"))) return; \ |
511 | 0 | } while (0) |
512 | | #else |
513 | | /** Record a request state transition |
514 | | * |
515 | | */ |
516 | | #define REQUEST_STATE_TRANSITION(_new) \ |
517 | | do { \ |
518 | | request_t *request = treq->pub.request; \ |
519 | | ROPTIONAL(RDEBUG3, DEBUG3, "Trunk request %" PRIu64 " changed state %s -> %s", \ |
520 | | treq->id, \ |
521 | | fr_table_str_by_value(trunk_request_states, treq->pub.state, "<INVALID>"), \ |
522 | | fr_table_str_by_value(trunk_request_states, _new, "<INVALID>")); \ |
523 | | treq->pub.state = _new; \ |
524 | | } while (0) |
525 | | #define REQUEST_BAD_STATE_TRANSITION(_new) \ |
526 | | do { \ |
527 | | if (!fr_cond_assert_msg(0, "Trunk request %" PRIu64 " invalid transition %s -> %s", \ |
528 | | treq->id, \ |
529 | | fr_table_str_by_value(trunk_request_states, treq->pub.state, "<INVALID>"), \ |
530 | | fr_table_str_by_value(trunk_request_states, _new, "<INVALID>"))) return; \ |
531 | | } while (0) |
532 | | #endif |
533 | | |
534 | | |
535 | | /** Call the cancel callback if set |
536 | | * |
537 | | */ |
538 | 0 | #define DO_REQUEST_CANCEL(_treq, _reason) \ |
539 | 0 | do { \ |
540 | 0 | if ((_treq)->pub.trunk->funcs.request_cancel) { \ |
541 | 0 | request_t *request = (_treq)->pub.request; \ |
542 | 0 | void *_prev = (_treq)->pub.trunk->in_handler; \ |
543 | 0 | (_treq)->pub.trunk->in_handler = (void *)(_treq)->pub.trunk->funcs.request_cancel; \ |
544 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Calling request_cancel(conn=%p, preq=%p, reason=%s, uctx=%p)", \ |
545 | 0 | (_treq)->pub.tconn->pub.conn, \ |
546 | 0 | (_treq)->pub.preq, \ |
547 | 0 | fr_table_str_by_value(trunk_cancellation_reasons, \ |
548 | 0 | (_reason), \ |
549 | 0 | "<INVALID>"), \ |
550 | 0 | (_treq)->pub.trunk->uctx); \ |
551 | 0 | (_treq)->pub.trunk->funcs.request_cancel((_treq)->pub.tconn->pub.conn, (_treq)->pub.preq, (_reason), (_treq)->pub.trunk->uctx); \ |
552 | 0 | (_treq)->pub.trunk->in_handler = _prev; \ |
553 | 0 | } \ |
554 | 0 | } while(0) |
555 | | |
556 | | /** Call the "conn_release" callback (if set) |
557 | | * |
558 | | */ |
559 | 0 | #define DO_REQUEST_CONN_RELEASE(_treq) \ |
560 | 0 | do { \ |
561 | 0 | if ((_treq)->pub.trunk->funcs.request_conn_release) { \ |
562 | 0 | request_t *request = (_treq)->pub.request; \ |
563 | 0 | void *_prev = (_treq)->pub.trunk->in_handler; \ |
564 | 0 | (_treq)->pub.trunk->in_handler = (void *)(_treq)->pub.trunk->funcs.request_conn_release; \ |
565 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Calling request_conn_release(conn=%p, preq=%p, uctx=%p)", \ |
566 | 0 | (_treq)->pub.tconn->pub.conn, \ |
567 | 0 | (_treq)->pub.preq, \ |
568 | 0 | (_treq)->pub.trunk->uctx); \ |
569 | 0 | (_treq)->pub.trunk->funcs.request_conn_release((_treq)->pub.tconn->pub.conn, (_treq)->pub.preq, (_treq)->pub.trunk->uctx); \ |
570 | 0 | (_treq)->pub.trunk->in_handler = _prev; \ |
571 | 0 | } \ |
572 | 0 | } while(0) |
573 | | |
574 | | /** Call the complete callback (if set) |
575 | | * |
576 | | */ |
577 | 0 | #define DO_REQUEST_COMPLETE(_treq) \ |
578 | 0 | do { \ |
579 | 0 | if ((_treq)->pub.trunk->funcs.request_complete) { \ |
580 | 0 | request_t *request = (_treq)->pub.request; \ |
581 | 0 | void *_prev = (_treq)->pub.trunk->in_handler; \ |
582 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Calling request_complete(request=%p, preq=%p, rctx=%p, uctx=%p)", \ |
583 | 0 | (_treq)->pub.request, \ |
584 | 0 | (_treq)->pub.preq, \ |
585 | 0 | (_treq)->pub.rctx, \ |
586 | 0 | (_treq)->pub.trunk->uctx); \ |
587 | 0 | (_treq)->pub.trunk->in_handler = (void *)(_treq)->pub.trunk->funcs.request_complete; \ |
588 | 0 | (_treq)->pub.trunk->funcs.request_complete((_treq)->pub.request, (_treq)->pub.preq, (_treq)->pub.rctx, (_treq)->pub.trunk->uctx); \ |
589 | 0 | (_treq)->pub.trunk->in_handler = _prev; \ |
590 | 0 | } \ |
591 | 0 | } while(0) |
592 | | |
593 | | /** Call the fail callback (if set) |
594 | | * |
595 | | */ |
596 | 0 | #define DO_REQUEST_FAIL(_treq, _prev_state) \ |
597 | 0 | do { \ |
598 | 0 | if ((_treq)->pub.trunk->funcs.request_fail) { \ |
599 | 0 | request_t *request = (_treq)->pub.request; \ |
600 | 0 | void *_prev = (_treq)->pub.trunk->in_handler; \ |
601 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Calling request_fail(request=%p, preq=%p, rctx=%p, state=%s uctx=%p)", \ |
602 | 0 | (_treq)->pub.request, \ |
603 | 0 | (_treq)->pub.preq, \ |
604 | 0 | (_treq)->pub.rctx, \ |
605 | 0 | fr_table_str_by_value(trunk_request_states, (_prev_state), "<INVALID>"), \ |
606 | 0 | (_treq)->pub.trunk->uctx); \ |
607 | 0 | (_treq)->pub.trunk->in_handler = (void *)(_treq)->pub.trunk->funcs.request_fail; \ |
608 | 0 | (_treq)->pub.trunk->funcs.request_fail((_treq)->pub.request, (_treq)->pub.preq, (_treq)->pub.rctx, _prev_state, (_treq)->pub.trunk->uctx); \ |
609 | 0 | (_treq)->pub.trunk->in_handler = _prev; \ |
610 | 0 | } \ |
611 | 0 | } while(0) |
612 | | |
613 | | /** Call the free callback (if set) |
614 | | * |
615 | | */ |
616 | 0 | #define DO_REQUEST_FREE(_treq) \ |
617 | 0 | do { \ |
618 | 0 | if ((_treq)->pub.trunk->funcs.request_free) { \ |
619 | 0 | request_t *request = (_treq)->pub.request; \ |
620 | 0 | void *_prev = (_treq)->pub.trunk->in_handler; \ |
621 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "Calling request_free(request=%p, preq=%p, uctx=%p)", \ |
622 | 0 | (_treq)->pub.request, \ |
623 | 0 | (_treq)->pub.preq, \ |
624 | 0 | (_treq)->pub.trunk->uctx); \ |
625 | 0 | (_treq)->pub.trunk->in_handler = (void *)(_treq)->pub.trunk->funcs.request_free; \ |
626 | 0 | (_treq)->pub.trunk->funcs.request_free((_treq)->pub.request, (_treq)->pub.preq, (_treq)->pub.trunk->uctx); \ |
627 | 0 | (_treq)->pub.trunk->in_handler = _prev; \ |
628 | 0 | } \ |
629 | 0 | } while(0) |
630 | | |
631 | | /** Write one or more requests to a connection |
632 | | * |
633 | | */ |
634 | 0 | #define DO_REQUEST_MUX(_tconn) \ |
635 | 0 | do { \ |
636 | 0 | void *_prev = (_tconn)->pub.trunk->in_handler; \ |
637 | 0 | DEBUG3("[%" PRIu64 "] Calling request_mux(el=%p, tconn=%p, conn=%p, uctx=%p)", \ |
638 | 0 | (_tconn)->pub.conn->id, \ |
639 | 0 | (_tconn)->pub.trunk->el, \ |
640 | 0 | (_tconn), \ |
641 | 0 | (_tconn)->pub.conn, \ |
642 | 0 | (_tconn)->pub.trunk->uctx); \ |
643 | 0 | (_tconn)->pub.trunk->in_handler = (void *)(_tconn)->pub.trunk->funcs.request_mux; \ |
644 | 0 | (_tconn)->pub.trunk->funcs.request_mux((_tconn)->pub.trunk->el, (_tconn), (_tconn)->pub.conn, (_tconn)->pub.trunk->uctx); \ |
645 | 0 | (_tconn)->pub.trunk->in_handler = _prev; \ |
646 | 0 | } while(0) |
647 | | |
648 | | /** Read one or more requests from a connection |
649 | | * |
650 | | */ |
651 | 0 | #define DO_REQUEST_DEMUX(_tconn) \ |
652 | 0 | do { \ |
653 | 0 | void *_prev = (_tconn)->pub.trunk->in_handler; \ |
654 | 0 | DEBUG3("[%" PRIu64 "] Calling request_demux(tconn=%p, conn=%p, uctx=%p)", \ |
655 | 0 | (_tconn)->pub.conn->id, \ |
656 | 0 | (_tconn), \ |
657 | 0 | (_tconn)->pub.conn, \ |
658 | 0 | (_tconn)->pub.trunk->uctx); \ |
659 | 0 | (_tconn)->pub.trunk->in_handler = (void *)(_tconn)->pub.trunk->funcs.request_demux; \ |
660 | 0 | (_tconn)->pub.trunk->funcs.request_demux((_tconn)->pub.trunk->el, (_tconn), (_tconn)->pub.conn, (_tconn)->pub.trunk->uctx); \ |
661 | 0 | (_tconn)->pub.trunk->in_handler = _prev; \ |
662 | 0 | } while(0) |
663 | | |
664 | | /** Write one or more cancellation requests to a connection |
665 | | * |
666 | | */ |
667 | 0 | #define DO_REQUEST_CANCEL_MUX(_tconn) \ |
668 | 0 | do { \ |
669 | 0 | if ((_tconn)->pub.trunk->funcs.request_cancel_mux) { \ |
670 | 0 | void *_prev = (_tconn)->pub.trunk->in_handler; \ |
671 | 0 | DEBUG3("[%" PRIu64 "] Calling request_cancel_mux(tconn=%p, conn=%p, uctx=%p)", \ |
672 | 0 | (_tconn)->pub.conn->id, \ |
673 | 0 | (_tconn), \ |
674 | 0 | (_tconn)->pub.conn, \ |
675 | 0 | (_tconn)->pub.trunk->uctx); \ |
676 | 0 | (_tconn)->pub.trunk->in_handler = (void *)(_tconn)->pub.trunk->funcs.request_cancel_mux; \ |
677 | 0 | (_tconn)->pub.trunk->funcs.request_cancel_mux((_tconn)->pub.trunk->el, (_tconn), (_tconn)->pub.conn, (_tconn)->pub.trunk->uctx); \ |
678 | 0 | (_tconn)->pub.trunk->in_handler = _prev; \ |
679 | 0 | } \ |
680 | 0 | } while(0) |
681 | | |
682 | | /** Allocate a new connection |
683 | | * |
684 | | */ |
685 | 0 | #define DO_CONNECTION_ALLOC(_tconn) \ |
686 | 0 | do { \ |
687 | 0 | void *_prev = trunk->in_handler; \ |
688 | 0 | DEBUG3("Calling connection_alloc(tconn=%p, el=%p, conf=%p, log_prefix=\"%s\", uctx=%p)", \ |
689 | 0 | (_tconn), \ |
690 | 0 | (_tconn)->pub.trunk->el, \ |
691 | 0 | (_tconn)->pub.trunk->conf.conn_conf, \ |
692 | 0 | trunk->log_prefix, \ |
693 | 0 | (_tconn)->pub.trunk->uctx); \ |
694 | 0 | (_tconn)->pub.trunk->in_handler = (void *) (_tconn)->pub.trunk->funcs.connection_alloc; \ |
695 | 0 | (_tconn)->pub.conn = trunk->funcs.connection_alloc((_tconn), (_tconn)->pub.trunk->el, (_tconn)->pub.trunk->conf.conn_conf, (_tconn)->pub.trunk->log_prefix, trunk->uctx); \ |
696 | 0 | (_tconn)->pub.trunk->in_handler = _prev; \ |
697 | 0 | if (!(_tconn)->pub.conn) { \ |
698 | 0 | ERROR("Failed creating new connection"); \ |
699 | 0 | talloc_free(tconn); \ |
700 | 0 | return -1; \ |
701 | 0 | } \ |
702 | 0 | } while(0) |
703 | | |
704 | | /** Change what events the connection should be notified about |
705 | | * |
706 | | */ |
707 | 0 | #define DO_CONNECTION_NOTIFY(_tconn, _events) \ |
708 | 0 | do { \ |
709 | 0 | if ((_tconn)->pub.trunk->funcs.connection_notify) { \ |
710 | 0 | void *_prev = (_tconn)->pub.trunk->in_handler; \ |
711 | 0 | DEBUG3("[%" PRIu64 "] Calling connection_notify(tconn=%p, conn=%p, el=%p, events=%s, uctx=%p)", \ |
712 | 0 | (_tconn)->pub.conn->id, \ |
713 | 0 | (_tconn), \ |
714 | 0 | (_tconn)->pub.conn, \ |
715 | 0 | (_tconn)->pub.trunk->el, \ |
716 | 0 | fr_table_str_by_value(trunk_connection_events, (_events), "<INVALID>"), \ |
717 | 0 | (_tconn)->pub.trunk->uctx); \ |
718 | 0 | (_tconn)->pub.trunk->in_handler = (void *)(_tconn)->pub.trunk->funcs.connection_notify; \ |
719 | 0 | (_tconn)->pub.trunk->funcs.connection_notify((_tconn), (_tconn)->pub.conn, (_tconn)->pub.trunk->el, (_events), (_tconn)->pub.trunk->uctx); \ |
720 | 0 | (_tconn)->pub.trunk->in_handler = _prev; \ |
721 | 0 | } \ |
722 | 0 | } while(0) |
723 | | |
724 | | #define IN_HANDLER(_trunk) (((_trunk)->in_handler) != NULL) |
725 | | #define IN_REQUEST_MUX(_trunk) (((_trunk)->funcs.request_mux) && ((_trunk)->in_handler == (void *)(_trunk)->funcs.request_mux)) |
726 | 0 | #define IN_REQUEST_DEMUX(_trunk) (((_trunk)->funcs.request_demux) && ((_trunk)->in_handler == (void *)(_trunk)->funcs.request_demux)) |
727 | | #define IN_REQUEST_CANCEL_MUX(_trunk) (((_trunk)->funcs.request_cancel_mux) && ((_trunk)->in_handler == (void *)(_trunk)->funcs.request_cancel_mux)) |
728 | | |
729 | 0 | #define IS_SERVICEABLE(_tconn) ((_tconn)->pub.state & TRUNK_CONN_SERVICEABLE) |
730 | 0 | #define IS_PROCESSING(_tconn) ((_tconn)->pub.state & TRUNK_CONN_PROCESSING) |
731 | | |
732 | | /** Remove the current request from the backlog |
733 | | * |
734 | | */ |
735 | 0 | #define REQUEST_EXTRACT_BACKLOG(_treq) \ |
736 | 0 | do { \ |
737 | 0 | int _ret; \ |
738 | 0 | _ret = fr_heap_extract(&(_treq)->pub.trunk->backlog, _treq); \ |
739 | 0 | if (!fr_cond_assert_msg(_ret == 0, "Failed extracting conn from backlog heap: %s", fr_strerror())) break; \ |
740 | 0 | } while (0) |
741 | | |
742 | | /** Remove the current request from the pending list |
743 | | * |
744 | | */ |
745 | 0 | #define REQUEST_EXTRACT_PENDING(_treq) \ |
746 | 0 | do { \ |
747 | 0 | int _ret; \ |
748 | 0 | _ret = fr_heap_extract(&(_treq)->pub.tconn->pending, _treq); \ |
749 | 0 | if (!fr_cond_assert_msg(_ret == 0, "Failed extracting conn from pending heap: %s", fr_strerror())) break; \ |
750 | 0 | } while (0) |
751 | | |
752 | | /** Remove the current request from the partial slot |
753 | | * |
754 | | */ |
755 | 0 | #define REQUEST_EXTRACT_PARTIAL(_treq) \ |
756 | 0 | do { \ |
757 | 0 | fr_assert((_treq)->pub.tconn->partial == treq); \ |
758 | 0 | tconn->partial = NULL; \ |
759 | 0 | } while (0) |
760 | | |
761 | | /** Remove the current request from the sent list |
762 | | * |
763 | | */ |
764 | 0 | #define REQUEST_EXTRACT_SENT(_treq) fr_dlist_remove(&tconn->sent, treq) |
765 | | |
766 | | /** Remove the current request from the reapable list |
767 | | * |
768 | | */ |
769 | 0 | #define REQUEST_EXTRACT_REAPABLE(_treq) fr_dlist_remove(&tconn->reapable, treq) |
770 | | |
771 | | /** Remove the current request from the cancel list |
772 | | * |
773 | | */ |
774 | 0 | #define REQUEST_EXTRACT_CANCEL(_treq) fr_dlist_remove(&tconn->cancel, treq) |
775 | | |
776 | | /** Remove the current request from the cancel_partial slot |
777 | | * |
778 | | */ |
779 | 0 | #define REQUEST_EXTRACT_CANCEL_PARTIAL(_treq) \ |
780 | 0 | do { \ |
781 | 0 | fr_assert((_treq)->pub.tconn->cancel_partial == treq); \ |
782 | 0 | tconn->cancel_partial = NULL; \ |
783 | 0 | } while (0) |
784 | | |
785 | | /** Remove the current request from the cancel sent list |
786 | | * |
787 | | */ |
788 | 0 | #define REQUEST_EXTRACT_CANCEL_SENT(_treq) fr_dlist_remove(&tconn->cancel_sent, treq) |
789 | | |
790 | | /** Reorder the connections in the active heap |
791 | | * |
792 | | * fr_heap_extract will also error out if heap_id is bad - no need for assert |
793 | | */ |
794 | 0 | #define CONN_REORDER(_tconn) \ |
795 | 0 | do { \ |
796 | 0 | int _ret; \ |
797 | 0 | if ((fr_minmax_heap_num_elements((_tconn)->pub.trunk->active) == 1)) break; \ |
798 | 0 | if (!fr_cond_assert((_tconn)->pub.state == TRUNK_CONN_ACTIVE)) break; \ |
799 | 0 | _ret = fr_minmax_heap_extract((_tconn)->pub.trunk->active, (_tconn)); \ |
800 | 0 | if (!fr_cond_assert_msg(_ret == 0, "Failed extracting conn from active heap: %s", fr_strerror())) break; \ |
801 | 0 | fr_minmax_heap_insert((_tconn)->pub.trunk->active, (_tconn)); \ |
802 | 0 | } while (0) |
803 | | |
804 | | DIAG_OFF(unused-function) |
805 | | |
806 | | #define FR_TRUNK_LIST_FUNC(_list,_type) \ |
807 | 0 | static inline CC_HINT(nonnull, always_inline) void trunk_list_ ## _list ## _add(trunk_t *trunk, _type *arg) \ |
808 | 0 | { \ |
809 | 0 | fr_dlist_insert_head(&trunk->_list, arg); \ |
810 | 0 | } \ Unexecuted instantiation: trunk.c:trunk_list_free_requests_add Unexecuted instantiation: trunk.c:trunk_list_inactive_add Unexecuted instantiation: trunk.c:trunk_list_inactive_draining_add Unexecuted instantiation: trunk.c:trunk_list_full_add Unexecuted instantiation: trunk.c:trunk_list_draining_add |
811 | 0 | static inline CC_HINT(nonnull, always_inline) _type *trunk_list_ ## _list ##_peek(trunk_t *trunk) \ |
812 | 0 | { \ |
813 | 0 | return fr_dlist_tail(&trunk->_list); \ |
814 | 0 | } \ Unexecuted instantiation: trunk.c:trunk_list_free_requests_peek Unexecuted instantiation: trunk.c:trunk_list_inactive_peek Unexecuted instantiation: trunk.c:trunk_list_full_peek Unexecuted instantiation: trunk.c:trunk_list_inactive_draining_peek Unexecuted instantiation: trunk.c:trunk_list_draining_peek |
815 | 0 | static inline CC_HINT(nonnull, always_inline) _type *trunk_list_ ## _list ##_pop(trunk_t *trunk) \ |
816 | 0 | { \ |
817 | 0 | return fr_dlist_pop_head(&trunk->_list); \ |
818 | 0 | } \ Unexecuted instantiation: trunk.c:trunk_list_free_requests_pop Unexecuted instantiation: trunk.c:trunk_list_full_pop Unexecuted instantiation: trunk.c:trunk_list_inactive_pop Unexecuted instantiation: trunk.c:trunk_list_inactive_draining_pop Unexecuted instantiation: trunk.c:trunk_list_draining_pop |
819 | 0 | static inline CC_HINT(nonnull, always_inline) void trunk_list_ ## _list ##_remove(trunk_t *trunk, _type *arg) \ |
820 | 0 | { \ |
821 | 0 | fr_dlist_remove(&trunk->_list, arg); \ |
822 | 0 | } Unexecuted instantiation: trunk.c:trunk_list_full_remove Unexecuted instantiation: trunk.c:trunk_list_inactive_remove Unexecuted instantiation: trunk.c:trunk_list_inactive_draining_remove Unexecuted instantiation: trunk.c:trunk_list_draining_remove Unexecuted instantiation: trunk.c:trunk_list_free_requests_remove |
823 | | |
824 | | FR_TRUNK_LIST_FUNC(free_requests, trunk_request_t) |
825 | | FR_TRUNK_LIST_FUNC(full, trunk_connection_t) |
826 | | FR_TRUNK_LIST_FUNC(inactive, trunk_connection_t) |
827 | | FR_TRUNK_LIST_FUNC(inactive_draining, trunk_connection_t) |
828 | | FR_TRUNK_LIST_FUNC(draining, trunk_connection_t) |
829 | | |
830 | | DIAG_ON(unused-function) |
831 | | |
832 | | /** Call a list of watch functions associated with a state |
833 | | * |
834 | | */ |
835 | | static inline void trunk_watch_call(trunk_t *trunk, fr_dlist_head_t *list, trunk_state_t state) |
836 | 0 | { |
837 | | /* |
838 | | * Nested watcher calls are not allowed |
839 | | * and shouldn't be possible because of |
840 | | * deferred signal processing. |
841 | | */ |
842 | 0 | fr_assert(trunk->next_watcher == NULL); |
843 | |
|
844 | 0 | while ((trunk->next_watcher = fr_dlist_next(list, trunk->next_watcher))) { |
845 | 0 | trunk_watch_entry_t *entry = trunk->next_watcher; |
846 | 0 | bool oneshot = entry->oneshot; /* Watcher could be freed, so store now */ |
847 | |
|
848 | 0 | if (!entry->enabled) continue; |
849 | 0 | if (oneshot) trunk->next_watcher = fr_dlist_remove(list, entry); |
850 | |
|
851 | 0 | entry->func(trunk, trunk->pub.state, state, entry->uctx); |
852 | |
|
853 | 0 | if (oneshot) talloc_free(entry); |
854 | 0 | } |
855 | 0 | trunk->next_watcher = NULL; |
856 | 0 | } |
857 | | |
858 | | /** Call the state change watch functions |
859 | | * |
860 | | */ |
861 | 0 | #define CALL_WATCHERS(_trunk, _state) \ |
862 | 0 | do { \ |
863 | 0 | if (fr_dlist_empty(&(_trunk)->watch[_state])) break; \ |
864 | 0 | trunk_watch_call((_trunk), &(_trunk)->watch[_state], _state); \ |
865 | 0 | } while(0) |
866 | | |
867 | | /** Remove a watch function from a trunk state list |
868 | | * |
869 | | * @param[in] trunk The trunk to remove the watcher from. |
870 | | * @param[in] state to remove the watch from. |
871 | | * @param[in] watch Function to remove. |
872 | | * @return |
873 | | * - 0 if the function was removed successfully. |
874 | | * - -1 if the function wasn't present in the watch list. |
875 | | * - -2 if an invalid state was passed. |
876 | | */ |
877 | | int trunk_del_watch(trunk_t *trunk, trunk_state_t state, trunk_watch_t watch) |
878 | 0 | { |
879 | 0 | trunk_watch_entry_t *entry = NULL; |
880 | 0 | fr_dlist_head_t *list; |
881 | |
|
882 | 0 | if (state >= TRUNK_STATE_MAX) return -2; |
883 | | |
884 | 0 | list = &trunk->watch[state]; |
885 | 0 | while ((entry = fr_dlist_next(list, entry))) { |
886 | 0 | if (entry->func == watch) { |
887 | 0 | if (trunk->next_watcher == entry) { |
888 | 0 | trunk->next_watcher = fr_dlist_remove(list, entry); |
889 | 0 | } else { |
890 | 0 | fr_dlist_remove(list, entry); |
891 | 0 | } |
892 | 0 | talloc_free(entry); |
893 | 0 | return 0; |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | 0 | return -1; |
898 | 0 | } |
899 | | |
900 | | /** Add a watch entry to the trunk state list |
901 | | * |
902 | | * @param[in] trunk The trunk to add the watcher to. |
903 | | * @param[in] state to watch for. |
904 | | * @param[in] watch Function to add. |
905 | | * @param[in] oneshot Should this watcher only be run once. |
906 | | * @param[in] uctx Context to pass to function. |
907 | | * @return |
908 | | * - NULL if an invalid state is passed. |
909 | | * - A new watch entry handle on success. |
910 | | */ |
911 | | trunk_watch_entry_t *trunk_add_watch(trunk_t *trunk, trunk_state_t state, |
912 | | trunk_watch_t watch, bool oneshot, void const *uctx) |
913 | | { |
914 | | trunk_watch_entry_t *entry; |
915 | | fr_dlist_head_t *list; |
916 | | |
917 | | if (state >= TRUNK_STATE_MAX) return NULL; |
918 | | |
919 | | list = &trunk->watch[state]; |
920 | | MEM(entry = talloc_zero(trunk, trunk_watch_entry_t)); |
921 | | |
922 | | entry->func = watch; |
923 | | entry->oneshot = oneshot; |
924 | | entry->enabled = true; |
925 | | memcpy(&entry->uctx, &uctx, sizeof(entry->uctx)); |
926 | | fr_dlist_insert_tail(list, entry); |
927 | | |
928 | | return entry; |
929 | | } |
930 | | |
931 | 0 | #define TRUNK_STATE_TRANSITION(_new) \ |
932 | 0 | do { \ |
933 | 0 | DEBUG3("Trunk changed state %s -> %s", \ |
934 | 0 | fr_table_str_by_value(trunk_states, trunk->pub.state, "<INVALID>"), \ |
935 | 0 | fr_table_str_by_value(trunk_states, _new, "<INVALID>")); \ |
936 | 0 | CALL_WATCHERS(trunk, _new); \ |
937 | 0 | trunk->pub.state = _new; \ |
938 | 0 | } while (0) |
939 | | |
940 | | static void trunk_request_enter_backlog(trunk_request_t *treq, bool new); |
941 | | static void trunk_request_enter_pending(trunk_request_t *treq, trunk_connection_t *tconn, bool new); |
942 | | static void trunk_request_enter_partial(trunk_request_t *treq); |
943 | | static void trunk_request_enter_sent(trunk_request_t *treq); |
944 | | static void trunk_request_enter_reapable(trunk_request_t *treq); |
945 | | static void trunk_request_enter_failed(trunk_request_t *treq); |
946 | | static void trunk_request_enter_complete(trunk_request_t *treq); |
947 | | static void trunk_request_enter_cancel(trunk_request_t *treq, trunk_cancel_reason_t reason); |
948 | | static void trunk_request_enter_cancel_sent(trunk_request_t *treq); |
949 | | static void trunk_request_enter_cancel_complete(trunk_request_t *treq); |
950 | | |
951 | | static uint64_t trunk_requests_per_connection(uint16_t *conn_count_out, uint32_t *req_conn_out, |
952 | | trunk_t *trunk, fr_time_t now, NDEBUG_UNUSED bool verify); |
953 | | |
954 | | static int trunk_connection_spawn(trunk_t *trunk, fr_time_t now); |
955 | | static inline void trunk_connection_auto_full(trunk_connection_t *tconn); |
956 | | static inline void trunk_connection_auto_unfull(trunk_connection_t *tconn); |
957 | | static inline void trunk_connection_readable(trunk_connection_t *tconn); |
958 | | static inline void trunk_connection_writable(trunk_connection_t *tconn); |
959 | | static void trunk_connection_event_update(trunk_connection_t *tconn); |
960 | | static void trunk_connection_enter_full(trunk_connection_t *tconn); |
961 | | static void trunk_connection_enter_inactive(trunk_connection_t *tconn); |
962 | | static void trunk_connection_enter_inactive_draining(trunk_connection_t *tconn); |
963 | | static void trunk_connection_enter_draining(trunk_connection_t *tconn); |
964 | | static void trunk_connection_enter_draining_to_free(trunk_connection_t *tconn); |
965 | | static void trunk_connection_enter_active(trunk_connection_t *tconn); |
966 | | |
967 | | static void trunk_rebalance(trunk_t *trunk); |
968 | | static void trunk_manage(trunk_t *trunk, fr_time_t now); |
969 | | static void _trunk_timer(fr_timer_list_t *tl, fr_time_t now, void *uctx); |
970 | | static void trunk_backlog_drain(trunk_t *trunk); |
971 | | |
972 | | /** Compare two protocol requests |
973 | | * |
974 | | * Allows protocol requests to be prioritised with a function |
975 | | * specified by the API client. Defaults to by pointer address |
976 | | * if no function is specified. |
977 | | * |
978 | | * @param[in] a treq to compare to b. |
979 | | * @param[in] b treq to compare to a. |
980 | | * @return |
981 | | * - +1 if a > b. |
982 | | * - 0 if a == b. |
983 | | * - -1 if a < b. |
984 | | */ |
985 | | static int8_t _trunk_request_prioritise(void const *a, void const *b) |
986 | 0 | { |
987 | 0 | trunk_request_t const *treq_a = talloc_get_type_abort_const(a, trunk_request_t); |
988 | 0 | trunk_request_t const *treq_b = talloc_get_type_abort_const(b, trunk_request_t); |
989 | |
|
990 | 0 | fr_assert(treq_a->pub.trunk == treq_b->pub.trunk); |
991 | |
|
992 | 0 | return treq_a->pub.trunk->funcs.request_prioritise(treq_a->pub.preq, treq_b->pub.preq); |
993 | 0 | } |
994 | | |
995 | | /** Remove a request from all connection lists |
996 | | * |
997 | | * A common function used by init, fail, complete state functions to disassociate |
998 | | * a request from a connection in preparation for freeing or reassignment. |
999 | | * |
1000 | | * Despite its unassuming name, this function is *the* place to put calls to |
1001 | | * functions which need to be called when the number of requests associated with |
1002 | | * a connection changes. |
1003 | | * |
1004 | | * Trunk requests will always be passed to this function before they're removed |
1005 | | * from a connection, even if the requests are being freed. |
1006 | | * |
1007 | | * @param[in] treq to trigger a state change for. |
1008 | | */ |
1009 | | static void trunk_request_remove_from_conn(trunk_request_t *treq) |
1010 | 0 | { |
1011 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1012 | 0 | trunk_t *trunk = treq->pub.trunk; |
1013 | |
|
1014 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1015 | | |
1016 | 0 | switch (treq->pub.state) { |
1017 | 0 | case TRUNK_REQUEST_STATE_UNASSIGNED: |
1018 | 0 | return; /* Not associated with connection */ |
1019 | | |
1020 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1021 | 0 | REQUEST_EXTRACT_PENDING(treq); |
1022 | 0 | break; |
1023 | | |
1024 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
1025 | 0 | REQUEST_EXTRACT_PARTIAL(treq); |
1026 | 0 | break; |
1027 | | |
1028 | 0 | case TRUNK_REQUEST_STATE_SENT: |
1029 | 0 | REQUEST_EXTRACT_SENT(treq); |
1030 | 0 | break; |
1031 | | |
1032 | 0 | case TRUNK_REQUEST_STATE_REAPABLE: |
1033 | 0 | REQUEST_EXTRACT_REAPABLE(treq); |
1034 | 0 | break; |
1035 | | |
1036 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
1037 | 0 | REQUEST_EXTRACT_CANCEL(treq); |
1038 | 0 | break; |
1039 | | |
1040 | 0 | case TRUNK_REQUEST_STATE_CANCEL_PARTIAL: |
1041 | 0 | REQUEST_EXTRACT_CANCEL_PARTIAL(treq); |
1042 | 0 | break; |
1043 | | |
1044 | 0 | case TRUNK_REQUEST_STATE_CANCEL_SENT: |
1045 | 0 | REQUEST_EXTRACT_CANCEL_SENT(treq); |
1046 | 0 | break; |
1047 | | |
1048 | 0 | default: |
1049 | 0 | fr_assert(0); |
1050 | 0 | break; |
1051 | 0 | } |
1052 | | |
1053 | | /* |
1054 | | * If the request wasn't associated with a |
1055 | | * connection, then there's nothing more |
1056 | | * to do. |
1057 | | */ |
1058 | 0 | if (!tconn) return; |
1059 | | |
1060 | 0 | { |
1061 | 0 | request_t *request = treq->pub.request; |
1062 | |
|
1063 | 0 | ROPTIONAL(RDEBUG3, DEBUG3, "%s Trunk connection released request %" PRIu64, |
1064 | 0 | tconn->pub.conn->name, treq->id); |
1065 | 0 | } |
1066 | | /* |
1067 | | * Release any connection specific resources the |
1068 | | * treq holds. |
1069 | | */ |
1070 | 0 | DO_REQUEST_CONN_RELEASE(treq); |
1071 | |
|
1072 | 0 | switch (tconn->pub.state){ |
1073 | 0 | case TRUNK_CONN_FULL: |
1074 | 0 | trunk_connection_auto_unfull(tconn); /* Check if we can switch back to active */ |
1075 | 0 | if (tconn->pub.state == TRUNK_CONN_FULL) break; /* Only fallthrough if conn is now active */ |
1076 | 0 | FALL_THROUGH; |
1077 | |
|
1078 | 0 | case TRUNK_CONN_ACTIVE: |
1079 | 0 | CONN_REORDER(tconn); |
1080 | 0 | break; |
1081 | | |
1082 | 0 | default: |
1083 | 0 | break; |
1084 | 0 | } |
1085 | | |
1086 | 0 | treq->pub.tconn = NULL; |
1087 | | |
1088 | | /* |
1089 | | * Request removed from the connection |
1090 | | * see if we need up deregister I/O events. |
1091 | | */ |
1092 | 0 | trunk_connection_event_update(tconn); |
1093 | 0 | } |
1094 | | |
1095 | | /** Transition a request to the unassigned state, in preparation for re-assignment |
1096 | | * |
1097 | | * @note treq->tconn may be inviable after calling |
1098 | | * if treq->conn and connection_signals_pause are not used. |
1099 | | * This is due to call to trunk_request_remove_from_conn. |
1100 | | * |
1101 | | * @param[in] treq to trigger a state change for. |
1102 | | */ |
1103 | | static void trunk_request_enter_unassigned(trunk_request_t *treq) |
1104 | 0 | { |
1105 | 0 | trunk_t *trunk = treq->pub.trunk; |
1106 | |
|
1107 | 0 | switch (treq->pub.state) { |
1108 | 0 | case TRUNK_REQUEST_STATE_UNASSIGNED: |
1109 | 0 | return; |
1110 | | |
1111 | 0 | case TRUNK_REQUEST_STATE_BACKLOG: |
1112 | 0 | REQUEST_EXTRACT_BACKLOG(treq); |
1113 | 0 | break; |
1114 | | |
1115 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1116 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
1117 | 0 | case TRUNK_REQUEST_STATE_CANCEL_PARTIAL: |
1118 | 0 | case TRUNK_REQUEST_STATE_CANCEL_SENT: |
1119 | 0 | trunk_request_remove_from_conn(treq); |
1120 | 0 | break; |
1121 | | |
1122 | 0 | default: |
1123 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_UNASSIGNED); |
1124 | 0 | } |
1125 | | |
1126 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_UNASSIGNED); |
1127 | 0 | } |
1128 | | |
1129 | | /** Transition a request to the backlog state, adding it to the backlog of the trunk |
1130 | | * |
1131 | | * @note treq->tconn and treq may be inviable after calling |
1132 | | * if treq->conn and connection_signals_pause are not used. |
1133 | | * This is due to call to trunk_manage. |
1134 | | * |
1135 | | * @param[in] treq to trigger a state change for. |
1136 | | * @param[in] new Whether this is a new request. |
1137 | | */ |
1138 | | static void trunk_request_enter_backlog(trunk_request_t *treq, bool new) |
1139 | 0 | { |
1140 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1141 | 0 | trunk_t *trunk = treq->pub.trunk; |
1142 | |
|
1143 | 0 | switch (treq->pub.state) { |
1144 | 0 | case TRUNK_REQUEST_STATE_INIT: |
1145 | 0 | case TRUNK_REQUEST_STATE_UNASSIGNED: |
1146 | 0 | break; |
1147 | | |
1148 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1149 | 0 | REQUEST_EXTRACT_PENDING(treq); |
1150 | 0 | break; |
1151 | | |
1152 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
1153 | 0 | REQUEST_EXTRACT_CANCEL(treq); |
1154 | 0 | break; |
1155 | | |
1156 | 0 | default: |
1157 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_BACKLOG); |
1158 | 0 | } |
1159 | | |
1160 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_BACKLOG); |
1161 | 0 | fr_heap_insert(&trunk->backlog, treq); /* Insert into the backlog heap */ |
1162 | | |
1163 | | /* |
1164 | | * A new request has entered the trunk. |
1165 | | * Re-calculate request/connection ratios. |
1166 | | */ |
1167 | 0 | if (new) trunk_requests_per_connection(NULL, NULL, trunk, fr_time(), false); |
1168 | | |
1169 | | /* |
1170 | | * To reduce latency, if there's no connections |
1171 | | * in the connecting state, call the trunk manage |
1172 | | * function immediately. |
1173 | | * |
1174 | | * Likewise, if there's draining connections |
1175 | | * which could be moved back to active call |
1176 | | * the trunk manage function. |
1177 | | * |
1178 | | * Remember requests only enter the backlog if |
1179 | | * there's no connections which can service them. |
1180 | | */ |
1181 | 0 | if ((trunk_connection_count_by_state(treq->pub.trunk, TRUNK_CONN_CONNECTING) == 0) || |
1182 | 0 | (trunk_connection_count_by_state(treq->pub.trunk, TRUNK_CONN_DRAINING) > 0)) { |
1183 | 0 | trunk_connection_manage_schedule(treq->pub.trunk); |
1184 | 0 | } |
1185 | 0 | } |
1186 | | |
1187 | | /** Transition a request to the pending state, adding it to the backlog of an active connection |
1188 | | * |
1189 | | * All trunk requests being added to a connection get passed to this function. |
1190 | | * All trunk requests being removed from a connection get passed to #trunk_request_remove_from_conn. |
1191 | | * |
1192 | | * @note treq->tconn and treq may be inviable after calling |
1193 | | * if treq->conn and connection_signals_pause is not used. |
1194 | | * This is due to call to trunk_connection_event_update. |
1195 | | * |
1196 | | * @param[in] treq to trigger a state change for. |
1197 | | * @param[in] tconn to enqueue the request on. |
1198 | | * @param[in] new Whether this is a new request. |
1199 | | */ |
1200 | | static void trunk_request_enter_pending(trunk_request_t *treq, trunk_connection_t *tconn, bool new) |
1201 | | { |
1202 | | trunk_t *trunk = treq->pub.trunk; |
1203 | | |
1204 | | fr_assert(tconn->pub.trunk == trunk); |
1205 | | fr_assert(IS_PROCESSING(tconn)); |
1206 | | |
1207 | | switch (treq->pub.state) { |
1208 | | case TRUNK_REQUEST_STATE_INIT: |
1209 | | case TRUNK_REQUEST_STATE_UNASSIGNED: |
1210 | | fr_assert(!treq->pub.tconn); |
1211 | | break; |
1212 | | |
1213 | | case TRUNK_REQUEST_STATE_BACKLOG: |
1214 | | fr_assert(!treq->pub.tconn); |
1215 | | REQUEST_EXTRACT_BACKLOG(treq); |
1216 | | break; |
1217 | | |
1218 | | case TRUNK_REQUEST_STATE_CANCEL: /* Moved from another connection */ |
1219 | | REQUEST_EXTRACT_CANCEL(treq); |
1220 | | break; |
1221 | | |
1222 | | default: |
1223 | | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_PENDING); |
1224 | | } |
1225 | | |
1226 | | /* |
1227 | | * Assign the new connection first this first so |
1228 | | * it appears in the state log. |
1229 | | */ |
1230 | | treq->pub.tconn = tconn; |
1231 | | |
1232 | | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_PENDING); |
1233 | | |
1234 | | { |
1235 | | request_t *request = treq->pub.request; |
1236 | | |
1237 | | ROPTIONAL(RDEBUG, DEBUG3, "%s Trunk connection assigned request %"PRIu64, |
1238 | | tconn->pub.conn->name, treq->id); |
1239 | | } |
1240 | | fr_heap_insert(&tconn->pending, treq); |
1241 | | |
1242 | | /* |
1243 | | * A new request has entered the trunk. |
1244 | | * Re-calculate request/connection ratios. |
1245 | | */ |
1246 | | if (new) trunk_requests_per_connection(NULL, NULL, trunk, fr_time(), false); |
1247 | | |
1248 | | /* |
1249 | | * Check if we need to automatically transition the |
1250 | | * connection to full. |
1251 | | */ |
1252 | | trunk_connection_auto_full(tconn); |
1253 | | |
1254 | | /* |
1255 | | * Reorder the connection in the heap now it has an |
1256 | | * additional request. |
1257 | | */ |
1258 | | if (tconn->pub.state == TRUNK_CONN_ACTIVE) CONN_REORDER(tconn); |
1259 | | |
1260 | | /* |
1261 | | * We have a new request, see if we need to register |
1262 | | * for I/O events. |
1263 | | */ |
1264 | | trunk_connection_event_update(tconn); |
1265 | | } |
1266 | | |
1267 | | /** Transition a request to the partial state, indicating that is has been partially sent |
1268 | | * |
1269 | | * @param[in] treq to trigger a state change for. |
1270 | | */ |
1271 | | static void trunk_request_enter_partial(trunk_request_t *treq) |
1272 | 0 | { |
1273 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1274 | 0 | trunk_t *trunk = treq->pub.trunk; |
1275 | |
|
1276 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1277 | | |
1278 | 0 | switch (treq->pub.state) { |
1279 | 0 | case TRUNK_REQUEST_STATE_PENDING: /* All requests go through pending, even requeued ones */ |
1280 | 0 | REQUEST_EXTRACT_PENDING(treq); |
1281 | 0 | break; |
1282 | | |
1283 | 0 | default: |
1284 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_PARTIAL); |
1285 | 0 | } |
1286 | | |
1287 | 0 | fr_assert(!tconn->partial); |
1288 | 0 | tconn->partial = treq; |
1289 | |
|
1290 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_PARTIAL); |
1291 | 0 | } |
1292 | | |
1293 | | /** Transition a request to the sent state, indicating that it's been sent in its entirety |
1294 | | * |
1295 | | * @note treq->tconn and treq may be inviable after calling |
1296 | | * if treq->conn and connection_signals_pause is not used. |
1297 | | * This is due to call to trunk_connection_event_update. |
1298 | | * |
1299 | | * @param[in] treq to trigger a state change for. |
1300 | | */ |
1301 | | static void trunk_request_enter_sent(trunk_request_t *treq) |
1302 | 0 | { |
1303 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1304 | 0 | trunk_t *trunk = treq->pub.trunk; |
1305 | |
|
1306 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1307 | | |
1308 | 0 | switch (treq->pub.state) { |
1309 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1310 | 0 | REQUEST_EXTRACT_PENDING(treq); |
1311 | 0 | break; |
1312 | | |
1313 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
1314 | 0 | REQUEST_EXTRACT_PARTIAL(treq); |
1315 | 0 | break; |
1316 | | |
1317 | 0 | default: |
1318 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_SENT); |
1319 | 0 | } |
1320 | | |
1321 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_SENT); |
1322 | 0 | fr_dlist_insert_tail(&tconn->sent, treq); |
1323 | | |
1324 | | /* |
1325 | | * Update the connection's sent stats if this is the |
1326 | | * first time this request is being sent. |
1327 | | */ |
1328 | 0 | if (!treq->sent) { |
1329 | 0 | trunk->pub.last_write_success = fr_time(); |
1330 | |
|
1331 | 0 | tconn->pub.last_write_success = trunk->pub.last_write_success; |
1332 | 0 | tconn->sent_count++; |
1333 | 0 | treq->sent = true; |
1334 | | |
1335 | | /* |
1336 | | * Enforces max_uses |
1337 | | */ |
1338 | 0 | if ((trunk->conf.max_uses > 0) && (tconn->sent_count >= trunk->conf.max_uses)) { |
1339 | 0 | DEBUG3("Trunk hit max uses %" PRIu64 " at %d", trunk->conf.max_uses, __LINE__); |
1340 | 0 | trunk_connection_enter_draining_to_free(tconn); |
1341 | 0 | } |
1342 | 0 | } |
1343 | | |
1344 | | /* |
1345 | | * We just sent a request, we probably need |
1346 | | * to tell the event loop we want to be |
1347 | | * notified if there's data available. |
1348 | | */ |
1349 | 0 | trunk_connection_event_update(tconn); |
1350 | 0 | } |
1351 | | |
1352 | | /** Transition a request to the reapable state, indicating that it's been sent in its entirety, but no response is expected |
1353 | | * |
1354 | | * @note Largely a replica of trunk_request_enter_sent. |
1355 | | * |
1356 | | * @param[in] treq to trigger a state change for. |
1357 | | */ |
1358 | | static void trunk_request_enter_reapable(trunk_request_t *treq) |
1359 | 0 | { |
1360 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1361 | 0 | trunk_t *trunk = treq->pub.trunk; |
1362 | |
|
1363 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1364 | | |
1365 | 0 | switch (treq->pub.state) { |
1366 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1367 | 0 | REQUEST_EXTRACT_PENDING(treq); |
1368 | 0 | break; |
1369 | | |
1370 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
1371 | 0 | REQUEST_EXTRACT_PARTIAL(treq); |
1372 | 0 | break; |
1373 | | |
1374 | 0 | default: |
1375 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_REAPABLE); |
1376 | 0 | } |
1377 | | |
1378 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_REAPABLE); |
1379 | 0 | fr_dlist_insert_tail(&tconn->reapable, treq); |
1380 | |
|
1381 | 0 | if (!treq->sent) { |
1382 | 0 | tconn->sent_count++; |
1383 | 0 | treq->sent = true; |
1384 | |
|
1385 | 0 | if ((trunk->conf.max_uses > 0) && (tconn->sent_count >= trunk->conf.max_uses)) { |
1386 | 0 | DEBUG3("Trunk hit max uses %" PRIu64 " at %d", trunk->conf.max_uses, __LINE__); |
1387 | 0 | trunk_connection_enter_draining_to_free(tconn); |
1388 | 0 | } |
1389 | 0 | } |
1390 | |
|
1391 | 0 | trunk_connection_event_update(tconn); |
1392 | 0 | } |
1393 | | |
1394 | | /** Transition a request to the cancel state, placing it in a connection's cancellation list |
1395 | | * |
1396 | | * If a request_cancel_send callback is provided, that callback will |
1397 | | * be called periodically for requests which were cancelled due to |
1398 | | * a signal. |
1399 | | * |
1400 | | * The request_cancel_send callback will dequeue cancelled requests |
1401 | | * and inform a remote server that the result is no longer required. |
1402 | | * |
1403 | | * A request must enter this state before being added to the backlog |
1404 | | * of another connection if it's been sent or partially sent. |
1405 | | * |
1406 | | * @note treq->tconn and treq may be inviable after calling |
1407 | | * if treq->conn and connection_signals_pause is not used. |
1408 | | * This is due to call to trunk_connection_event_update. |
1409 | | * |
1410 | | * @param[in] treq to trigger a state change for. |
1411 | | * @param[in] reason Why the request was cancelled. |
1412 | | * Should be one of: |
1413 | | * - TRUNK_CANCEL_REASON_SIGNAL request cancelled |
1414 | | * because of a signal from the interpreter. |
1415 | | * - TRUNK_CANCEL_REASON_MOVE request cancelled |
1416 | | * because the connection failed and it needs |
1417 | | * to be assigned to a new connection. |
1418 | | * - TRUNK_CANCEL_REASON_REQUEUE request cancelled |
1419 | | * as it needs to be resent on the same connection. |
1420 | | */ |
1421 | | static void trunk_request_enter_cancel(trunk_request_t *treq, trunk_cancel_reason_t reason) |
1422 | 0 | { |
1423 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1424 | 0 | trunk_t *trunk = treq->pub.trunk; |
1425 | |
|
1426 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1427 | | |
1428 | 0 | switch (treq->pub.state) { |
1429 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
1430 | 0 | REQUEST_EXTRACT_PARTIAL(treq); |
1431 | 0 | break; |
1432 | | |
1433 | 0 | case TRUNK_REQUEST_STATE_SENT: |
1434 | 0 | REQUEST_EXTRACT_SENT(treq); |
1435 | 0 | break; |
1436 | | |
1437 | 0 | case TRUNK_REQUEST_STATE_REAPABLE: |
1438 | 0 | REQUEST_EXTRACT_REAPABLE(treq); |
1439 | 0 | break; |
1440 | | |
1441 | 0 | default: |
1442 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL); |
1443 | 0 | } |
1444 | | |
1445 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL); |
1446 | 0 | fr_dlist_insert_tail(&tconn->cancel, treq); |
1447 | 0 | treq->cancel_reason = reason; |
1448 | |
|
1449 | 0 | DO_REQUEST_CANCEL(treq, reason); |
1450 | | |
1451 | | /* |
1452 | | * Our treq is no longer bound to an actual |
1453 | | * request_t *, as we can't guarantee the |
1454 | | * lifetime of the original request_t *. |
1455 | | */ |
1456 | 0 | if (treq->cancel_reason == TRUNK_CANCEL_REASON_SIGNAL) treq->pub.request = NULL; |
1457 | | |
1458 | | /* |
1459 | | * Register for I/O write events if we need to. |
1460 | | */ |
1461 | 0 | trunk_connection_event_update(treq->pub.tconn); |
1462 | 0 | } |
1463 | | |
1464 | | /** Transition a request to the cancel_partial state, placing it in a connection's cancel_partial slot |
1465 | | * |
1466 | | * The request_demux function is then responsible for signalling |
1467 | | * that the cancel request is complete when the remote server |
1468 | | * acknowledges the cancellation request. |
1469 | | * |
1470 | | * @param[in] treq to trigger a state change for. |
1471 | | */ |
1472 | | static void trunk_request_enter_cancel_partial(trunk_request_t *treq) |
1473 | 0 | { |
1474 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1475 | 0 | trunk_t *trunk = treq->pub.trunk; |
1476 | |
|
1477 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1478 | 0 | fr_assert(trunk->funcs.request_cancel_mux); |
1479 | 0 | fr_assert(treq->cancel_reason == TRUNK_CANCEL_REASON_SIGNAL); |
1480 | |
|
1481 | 0 | switch (treq->pub.state) { |
1482 | 0 | case TRUNK_REQUEST_STATE_CANCEL: /* The only valid state cancel_sent can be reached from */ |
1483 | 0 | REQUEST_EXTRACT_CANCEL(treq); |
1484 | 0 | break; |
1485 | | |
1486 | 0 | default: |
1487 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_PARTIAL); |
1488 | 0 | } |
1489 | | |
1490 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_PARTIAL); |
1491 | 0 | fr_assert(!tconn->cancel_partial); |
1492 | 0 | tconn->cancel_partial = treq; |
1493 | 0 | } |
1494 | | |
1495 | | /** Transition a request to the cancel_sent state, placing it in a connection's cancel_sent list |
1496 | | * |
1497 | | * The request_demux function is then responsible for signalling |
1498 | | * that the cancel request is complete when the remote server |
1499 | | * acknowledges the cancellation request. |
1500 | | * |
1501 | | * @note treq->tconn and treq may be inviable after calling |
1502 | | * if treq->conn and connection_signals_pause is not used. |
1503 | | * This is due to call to trunk_connection_event_update. |
1504 | | * |
1505 | | * @param[in] treq to trigger a state change for. |
1506 | | */ |
1507 | | static void trunk_request_enter_cancel_sent(trunk_request_t *treq) |
1508 | 0 | { |
1509 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1510 | 0 | trunk_t *trunk = treq->pub.trunk; |
1511 | |
|
1512 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1513 | 0 | fr_assert(trunk->funcs.request_cancel_mux); |
1514 | 0 | fr_assert(treq->cancel_reason == TRUNK_CANCEL_REASON_SIGNAL); |
1515 | |
|
1516 | 0 | switch (treq->pub.state) { |
1517 | 0 | case TRUNK_REQUEST_STATE_CANCEL_PARTIAL: |
1518 | 0 | REQUEST_EXTRACT_CANCEL_PARTIAL(treq); |
1519 | 0 | break; |
1520 | | |
1521 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
1522 | 0 | REQUEST_EXTRACT_CANCEL(treq); |
1523 | 0 | break; |
1524 | | |
1525 | 0 | default: |
1526 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_SENT); |
1527 | 0 | } |
1528 | | |
1529 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_SENT); |
1530 | 0 | fr_dlist_insert_tail(&tconn->cancel_sent, treq); |
1531 | | |
1532 | | /* |
1533 | | * De-register for I/O write events |
1534 | | * and register the read events |
1535 | | * to drain the cancel ACKs. |
1536 | | */ |
1537 | 0 | trunk_connection_event_update(treq->pub.tconn); |
1538 | 0 | } |
1539 | | |
1540 | | /** Cancellation was acked, the request is complete, free it |
1541 | | * |
1542 | | * The API client will not be informed, as the original request_t * |
1543 | | * will likely have been freed by this point. |
1544 | | * |
1545 | | * @note treq will be inviable after a call to this function. |
1546 | | * treq->tconn may be inviable after calling |
1547 | | * if treq->conn and connection_signals_pause is not used. |
1548 | | * This is due to call to trunk_request_remove_from_conn. |
1549 | | * |
1550 | | * @param[in] treq to mark as complete. |
1551 | | */ |
1552 | | static void trunk_request_enter_cancel_complete(trunk_request_t *treq) |
1553 | 0 | { |
1554 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1555 | 0 | trunk_t *trunk = treq->pub.trunk; |
1556 | |
|
1557 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1558 | 0 | if (!fr_cond_assert(!treq->pub.request)) return; /* Only a valid state for request_t * which have been cancelled */ |
1559 | | |
1560 | 0 | switch (treq->pub.state) { |
1561 | 0 | case TRUNK_REQUEST_STATE_CANCEL_SENT: |
1562 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
1563 | 0 | break; |
1564 | | |
1565 | 0 | default: |
1566 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_COMPLETE); |
1567 | 0 | } |
1568 | | |
1569 | 0 | trunk_request_remove_from_conn(treq); |
1570 | |
|
1571 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_CANCEL_COMPLETE); |
1572 | 0 | trunk_request_free(&treq); /* Free the request */ |
1573 | 0 | } |
1574 | | |
1575 | | /** Request completed successfully, inform the API client and free the request |
1576 | | * |
1577 | | * @note treq will be inviable after a call to this function. |
1578 | | * treq->tconn may also be inviable due to call to |
1579 | | * trunk_request_remove_from_conn. |
1580 | | * |
1581 | | * @param[in] treq to mark as complete. |
1582 | | */ |
1583 | | static void trunk_request_enter_complete(trunk_request_t *treq) |
1584 | 0 | { |
1585 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1586 | 0 | trunk_t *trunk = treq->pub.trunk; |
1587 | |
|
1588 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1589 | | |
1590 | 0 | switch (treq->pub.state) { |
1591 | 0 | case TRUNK_REQUEST_STATE_SENT: |
1592 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
1593 | 0 | case TRUNK_REQUEST_STATE_REAPABLE: |
1594 | 0 | trunk_request_remove_from_conn(treq); |
1595 | 0 | break; |
1596 | | |
1597 | 0 | default: |
1598 | 0 | REQUEST_BAD_STATE_TRANSITION(TRUNK_REQUEST_STATE_COMPLETE); |
1599 | 0 | } |
1600 | | |
1601 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_COMPLETE); |
1602 | 0 | DO_REQUEST_COMPLETE(treq); |
1603 | 0 | trunk_request_free(&treq); /* Free the request */ |
1604 | 0 | } |
1605 | | |
1606 | | /** Request failed, inform the API client and free the request |
1607 | | * |
1608 | | * @note treq will be inviable after a call to this function. |
1609 | | * treq->tconn may also be inviable due to call to |
1610 | | * trunk_request_remove_from_conn. |
1611 | | * |
1612 | | * @param[in] treq to mark as failed. |
1613 | | */ |
1614 | | static void trunk_request_enter_failed(trunk_request_t *treq) |
1615 | 0 | { |
1616 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
1617 | 0 | trunk_t *trunk = treq->pub.trunk; |
1618 | 0 | trunk_request_state_t prev = treq->pub.state; |
1619 | |
|
1620 | 0 | if (!fr_cond_assert(!tconn || (tconn->pub.trunk == trunk))) return; |
1621 | | |
1622 | 0 | switch (treq->pub.state) { |
1623 | 0 | case TRUNK_REQUEST_STATE_BACKLOG: |
1624 | 0 | REQUEST_EXTRACT_BACKLOG(treq); |
1625 | 0 | break; |
1626 | | |
1627 | 0 | default: |
1628 | 0 | trunk_request_remove_from_conn(treq); |
1629 | 0 | break; |
1630 | 0 | } |
1631 | | |
1632 | 0 | REQUEST_STATE_TRANSITION(TRUNK_REQUEST_STATE_FAILED); |
1633 | 0 | DO_REQUEST_FAIL(treq, prev); |
1634 | 0 | trunk_request_free(&treq); /* Free the request */ |
1635 | 0 | } |
1636 | | |
1637 | | /** Check to see if a trunk request can be enqueued |
1638 | | * |
1639 | | * @param[out] tconn_out Connection the request may be enqueued on. |
1640 | | * @param[in] trunk To enqueue requests on. |
1641 | | * @param[in] request associated with the treq (if any). |
1642 | | * @return |
1643 | | * - TRUNK_ENQUEUE_OK caller should enqueue request on provided tconn. |
1644 | | * - TRUNK_ENQUEUE_IN_BACKLOG Request should be queued in the backlog. |
1645 | | * - TRUNK_ENQUEUE_NO_CAPACITY Unable to enqueue request as we have no spare |
1646 | | * connections or backlog space. |
1647 | | * - TRUNK_ENQUEUE_DST_UNAVAILABLE Can't enqueue because the destination is |
1648 | | * unreachable. |
1649 | | */ |
1650 | | static trunk_enqueue_t trunk_request_check_enqueue(trunk_connection_t **tconn_out, trunk_t *trunk, |
1651 | | request_t *request) |
1652 | 0 | { |
1653 | 0 | trunk_connection_t *tconn; |
1654 | | /* |
1655 | | * If we have an active connection then |
1656 | | * return that. |
1657 | | */ |
1658 | 0 | tconn = fr_minmax_heap_min_peek(trunk->active); |
1659 | 0 | if (tconn) { |
1660 | 0 | *tconn_out = tconn; |
1661 | 0 | return TRUNK_ENQUEUE_OK; |
1662 | 0 | } |
1663 | | |
1664 | | /* |
1665 | | * Unlike the connection pool, we don't need |
1666 | | * to drive any internal processes by feeding |
1667 | | * it requests. |
1668 | | * |
1669 | | * If the last event to occur was a failure |
1670 | | * we refuse to enqueue new requests until |
1671 | | * one or more connections comes online. |
1672 | | */ |
1673 | 0 | if (!trunk->conf.backlog_on_failed_conn && |
1674 | 0 | fr_time_gt(trunk->pub.last_failed, fr_time_wrap(0)) && |
1675 | 0 | fr_time_lt(trunk->pub.last_connected, trunk->pub.last_failed)) { |
1676 | 0 | RATE_LIMIT_LOCAL_ROPTIONAL(&trunk->limit_last_failure_log, |
1677 | 0 | RWARN, WARN, "Refusing to enqueue requests - " |
1678 | 0 | "No active connections and last event was a connection failure"); |
1679 | |
|
1680 | 0 | return TRUNK_ENQUEUE_DST_UNAVAILABLE; |
1681 | 0 | } |
1682 | | |
1683 | | |
1684 | | /* |
1685 | | * Only enforce if we're limiting maximum |
1686 | | * number of connections, and maximum |
1687 | | * number of requests per connection. |
1688 | | */ |
1689 | 0 | if (trunk->conf.max_req_per_conn && trunk->conf.max) { |
1690 | 0 | uint64_t limit; |
1691 | |
|
1692 | 0 | limit = trunk->conf.max * (uint64_t)trunk->conf.max_req_per_conn; |
1693 | 0 | if (limit > 0) { |
1694 | 0 | uint64_t total_reqs; |
1695 | |
|
1696 | 0 | total_reqs = trunk_request_count_by_state(trunk, TRUNK_CONN_ALL, |
1697 | 0 | TRUNK_REQUEST_STATE_ALL); |
1698 | 0 | if (total_reqs >= (limit + trunk->conf.max_backlog)) { |
1699 | 0 | RATE_LIMIT_LOCAL_ROPTIONAL(&trunk->limit_max_requests_alloc_log, |
1700 | 0 | RWARN, WARN, "Refusing to alloc requests - " |
1701 | 0 | "Limit of %"PRIu64" (max = %u * per_connection_max = %u) " |
1702 | 0 | "plus %u backlog requests reached", |
1703 | 0 | limit, trunk->conf.max, trunk->conf.max_req_per_conn, |
1704 | 0 | trunk->conf.max_backlog); |
1705 | 0 | return TRUNK_ENQUEUE_NO_CAPACITY; |
1706 | 0 | } |
1707 | 0 | } |
1708 | 0 | } |
1709 | | |
1710 | 0 | return TRUNK_ENQUEUE_IN_BACKLOG; |
1711 | 0 | } |
1712 | | |
1713 | | /** Enqueue a request which has never been assigned to a connection or was previously cancelled |
1714 | | * |
1715 | | * @param[in] treq to re enqueue. Must have been removed |
1716 | | * from its existing connection with |
1717 | | * #trunk_connection_requests_dequeue. |
1718 | | * @return |
1719 | | * - TRUNK_ENQUEUE_OK Request was re-enqueued. |
1720 | | * - TRUNK_ENQUEUE_NO_CAPACITY Request enqueueing failed because we're at capacity. |
1721 | | * - TRUNK_ENQUEUE_DST_UNAVAILABLE Enqueuing failed for some reason. |
1722 | | * Usually because the connection to the resource is down. |
1723 | | */ |
1724 | | static trunk_enqueue_t trunk_request_enqueue_existing(trunk_request_t *treq) |
1725 | 0 | { |
1726 | 0 | trunk_t *trunk = treq->pub.trunk; |
1727 | 0 | trunk_connection_t *tconn = NULL; |
1728 | 0 | trunk_enqueue_t ret; |
1729 | | |
1730 | | /* |
1731 | | * Must *NOT* still be assigned to another connection |
1732 | | */ |
1733 | 0 | fr_assert(!treq->pub.tconn); |
1734 | |
|
1735 | 0 | ret = trunk_request_check_enqueue(&tconn, trunk, treq->pub.request); |
1736 | 0 | switch (ret) { |
1737 | 0 | case TRUNK_ENQUEUE_OK: |
1738 | 0 | if (trunk->conf.always_writable) { |
1739 | 0 | connection_signals_pause(tconn->pub.conn); |
1740 | 0 | trunk_request_enter_pending(treq, tconn, false); |
1741 | 0 | trunk_connection_writable(tconn); |
1742 | 0 | connection_signals_resume(tconn->pub.conn); |
1743 | 0 | } else { |
1744 | 0 | trunk_request_enter_pending(treq, tconn, false); |
1745 | 0 | } |
1746 | 0 | break; |
1747 | | |
1748 | 0 | case TRUNK_ENQUEUE_IN_BACKLOG: |
1749 | | /* |
1750 | | * No more connections and request |
1751 | | * is already in the backlog. |
1752 | | * |
1753 | | * Signal our caller it should stop |
1754 | | * trying to drain the backlog. |
1755 | | */ |
1756 | 0 | if (treq->pub.state == TRUNK_REQUEST_STATE_BACKLOG) return TRUNK_ENQUEUE_NO_CAPACITY; |
1757 | 0 | trunk_request_enter_backlog(treq, false); |
1758 | 0 | break; |
1759 | | |
1760 | 0 | default: |
1761 | 0 | break; |
1762 | 0 | } |
1763 | | |
1764 | 0 | return ret; |
1765 | 0 | } |
1766 | | |
1767 | | /** Shift requests in the specified states onto new connections |
1768 | | * |
1769 | | * This function will blindly dequeue any requests in the specified state and get |
1770 | | * them back to the unassigned state, cancelling any sent or partially sent requests. |
1771 | | * |
1772 | | * This function does not check that dequeuing a request in a particular state is a |
1773 | | * sane or sensible thing to do, that's up to the caller! |
1774 | | * |
1775 | | * @param[out] out A list to insert the newly dequeued and unassigned |
1776 | | * requests into. |
1777 | | * @param[in] tconn to dequeue requests from. |
1778 | | * @param[in] states Dequeue request in these states. |
1779 | | * @param[in] max The maximum number of requests to dequeue. 0 for unlimited. |
1780 | | */ |
1781 | | static uint64_t trunk_connection_requests_dequeue(fr_dlist_head_t *out, trunk_connection_t *tconn, |
1782 | | int states, uint64_t max) |
1783 | 0 | { |
1784 | 0 | trunk_request_t *treq; |
1785 | 0 | uint64_t count = 0; |
1786 | |
|
1787 | 0 | if (max == 0) max = UINT64_MAX; |
1788 | |
|
1789 | 0 | #define OVER_MAX_CHECK if (++count > max) return (count - 1) |
1790 | |
|
1791 | 0 | #define DEQUEUE_ALL(_src_list, _state) do { \ |
1792 | 0 | while ((treq = fr_dlist_head(_src_list))) { \ |
1793 | 0 | OVER_MAX_CHECK; \ |
1794 | 0 | fr_assert(treq->pub.state == (_state)); \ |
1795 | 0 | trunk_request_enter_unassigned(treq); \ |
1796 | 0 | fr_dlist_insert_tail(out, treq); \ |
1797 | 0 | } } while (0) |
1798 | | |
1799 | | /* |
1800 | | * Don't need to do anything with |
1801 | | * cancellation requests. |
1802 | | */ |
1803 | 0 | if (states & TRUNK_REQUEST_STATE_CANCEL) DEQUEUE_ALL(&tconn->cancel, |
1804 | 0 | TRUNK_REQUEST_STATE_CANCEL); |
1805 | | |
1806 | | /* |
1807 | | * ...same with cancel inform |
1808 | | */ |
1809 | 0 | if (states & TRUNK_REQUEST_STATE_CANCEL_SENT) DEQUEUE_ALL(&tconn->cancel_sent, |
1810 | 0 | TRUNK_REQUEST_STATE_CANCEL_SENT); |
1811 | | |
1812 | | /* |
1813 | | * ....same with cancel partial |
1814 | | */ |
1815 | 0 | if (states & TRUNK_REQUEST_STATE_CANCEL_PARTIAL) { |
1816 | 0 | OVER_MAX_CHECK; |
1817 | 0 | treq = tconn->cancel_partial; |
1818 | 0 | if (treq) { |
1819 | 0 | fr_assert(treq->pub.state == TRUNK_REQUEST_STATE_CANCEL_PARTIAL); |
1820 | 0 | trunk_request_enter_unassigned(treq); |
1821 | 0 | fr_dlist_insert_tail(out, treq); |
1822 | 0 | } |
1823 | 0 | } |
1824 | | |
1825 | | /* |
1826 | | * ...and pending. |
1827 | | */ |
1828 | 0 | if (states & TRUNK_REQUEST_STATE_PENDING) { |
1829 | 0 | while ((treq = fr_heap_peek(tconn->pending))) { |
1830 | 0 | OVER_MAX_CHECK; |
1831 | 0 | fr_assert(treq->pub.state == TRUNK_REQUEST_STATE_PENDING); |
1832 | 0 | trunk_request_enter_unassigned(treq); |
1833 | 0 | fr_dlist_insert_tail(out, treq); |
1834 | 0 | } |
1835 | 0 | } |
1836 | | |
1837 | | /* |
1838 | | * Cancel partially sent requests |
1839 | | */ |
1840 | 0 | if (states & TRUNK_REQUEST_STATE_PARTIAL) { |
1841 | 0 | OVER_MAX_CHECK; |
1842 | 0 | treq = tconn->partial; |
1843 | 0 | if (treq) { |
1844 | 0 | fr_assert(treq->pub.state == TRUNK_REQUEST_STATE_PARTIAL); |
1845 | | |
1846 | | /* |
1847 | | * Don't allow the connection to change state whilst |
1848 | | * we're draining requests from it. |
1849 | | */ |
1850 | 0 | connection_signals_pause(tconn->pub.conn); |
1851 | 0 | trunk_request_enter_cancel(treq, TRUNK_CANCEL_REASON_MOVE); |
1852 | 0 | trunk_request_enter_unassigned(treq); |
1853 | 0 | fr_dlist_insert_tail(out, treq); |
1854 | 0 | connection_signals_resume(tconn->pub.conn); |
1855 | 0 | } |
1856 | 0 | } |
1857 | | |
1858 | | /* |
1859 | | * Cancel sent requests |
1860 | | */ |
1861 | 0 | if (states & TRUNK_REQUEST_STATE_SENT) { |
1862 | | /* |
1863 | | * Don't allow the connection to change state whilst |
1864 | | * we're draining requests from it. |
1865 | | */ |
1866 | 0 | connection_signals_pause(tconn->pub.conn); |
1867 | 0 | while ((count < max) && (treq = fr_dlist_head(&tconn->sent))) { |
1868 | 0 | fr_assert(treq->pub.state == TRUNK_REQUEST_STATE_SENT); |
1869 | |
|
1870 | 0 | trunk_request_enter_cancel(treq, TRUNK_CANCEL_REASON_MOVE); |
1871 | 0 | trunk_request_enter_unassigned(treq); |
1872 | 0 | fr_dlist_insert_tail(out, treq); |
1873 | 0 | count++; |
1874 | 0 | } |
1875 | 0 | connection_signals_resume(tconn->pub.conn); |
1876 | 0 | } |
1877 | |
|
1878 | 0 | return count; |
1879 | 0 | } |
1880 | | |
1881 | | /** Remove requests in specified states from a connection, attempting to distribute them to new connections |
1882 | | * |
1883 | | * @param[in] tconn To remove requests from. |
1884 | | * @param[in] states One or more states or'd together. |
1885 | | * @param[in] max The maximum number of requests to dequeue. |
1886 | | * 0 for unlimited. |
1887 | | * @param[in] fail_bound If true causes any requests bound to the connection to fail. |
1888 | | * If false bound requests will not be moved. |
1889 | | * |
1890 | | * @return the number of requests re-queued. |
1891 | | */ |
1892 | | static uint64_t trunk_connection_requests_requeue_priv(trunk_connection_t *tconn, int states, uint64_t max, bool fail_bound) |
1893 | 0 | { |
1894 | 0 | trunk_t *trunk = tconn->pub.trunk; |
1895 | 0 | fr_dlist_head_t to_process; |
1896 | 0 | trunk_request_t *treq = NULL; |
1897 | 0 | uint64_t moved = 0; |
1898 | |
|
1899 | 0 | if (max == 0) max = UINT64_MAX; |
1900 | |
|
1901 | 0 | fr_dlist_talloc_init(&to_process, trunk_request_t, entry); |
1902 | | |
1903 | | /* |
1904 | | * Prevent the connection changing state whilst we're |
1905 | | * working with it. |
1906 | | * |
1907 | | * There's a user callback that can be called by |
1908 | | * trunk_request_enqueue_existing which can reconnect |
1909 | | * the connection. |
1910 | | */ |
1911 | 0 | connection_signals_pause(tconn->pub.conn); |
1912 | | |
1913 | | /* |
1914 | | * Remove non-cancelled requests from the connection |
1915 | | */ |
1916 | 0 | moved += trunk_connection_requests_dequeue(&to_process, tconn, states & ~TRUNK_REQUEST_STATE_CANCEL_ALL, max); |
1917 | | |
1918 | | /* |
1919 | | * Prevent requests being requeued on the same trunk |
1920 | | * connection, which would break rebalancing. |
1921 | | * |
1922 | | * This is a bit of a hack, but nothing should test |
1923 | | * for connection/list consistency in this code, |
1924 | | * and if something is added later, it'll be flagged |
1925 | | * by the tests. |
1926 | | */ |
1927 | 0 | if (tconn->pub.state == TRUNK_CONN_ACTIVE) { |
1928 | 0 | int ret; |
1929 | |
|
1930 | 0 | ret = fr_minmax_heap_extract(trunk->active, tconn); |
1931 | 0 | if (!fr_cond_assert_msg(ret == 0, |
1932 | 0 | "Failed extracting conn from active heap: %s", fr_strerror())) goto done; |
1933 | |
|
1934 | 0 | } |
1935 | | |
1936 | | /* |
1937 | | * Loop over all the requests we gathered and |
1938 | | * redistribute them to new connections. |
1939 | | */ |
1940 | 0 | while ((treq = fr_dlist_next(&to_process, treq))) { |
1941 | 0 | trunk_request_t *prev; |
1942 | |
|
1943 | 0 | prev = fr_dlist_remove(&to_process, treq); |
1944 | | |
1945 | | /* |
1946 | | * Attempts to re-queue a request |
1947 | | * that's bound to a connection |
1948 | | * results in a failure. |
1949 | | */ |
1950 | 0 | if (treq->bound_to_conn) { |
1951 | 0 | if (fail_bound || !IS_SERVICEABLE(tconn)) { |
1952 | 0 | trunk_request_enter_failed(treq); |
1953 | 0 | } else { |
1954 | 0 | trunk_request_enter_pending(treq, tconn, false); |
1955 | 0 | } |
1956 | 0 | goto next; |
1957 | 0 | } |
1958 | | |
1959 | 0 | switch (trunk_request_enqueue_existing(treq)) { |
1960 | 0 | case TRUNK_ENQUEUE_OK: |
1961 | 0 | break; |
1962 | | |
1963 | | /* |
1964 | | * A connection failed, and |
1965 | | * there's no other connections |
1966 | | * available to deal with the |
1967 | | * load, it's been placed back |
1968 | | * in the backlog. |
1969 | | */ |
1970 | 0 | case TRUNK_ENQUEUE_IN_BACKLOG: |
1971 | 0 | break; |
1972 | | |
1973 | | /* |
1974 | | * If we fail to re-enqueue then |
1975 | | * there's nothing to do except |
1976 | | * fail the request. |
1977 | | */ |
1978 | 0 | case TRUNK_ENQUEUE_DST_UNAVAILABLE: |
1979 | 0 | case TRUNK_ENQUEUE_NO_CAPACITY: |
1980 | 0 | case TRUNK_ENQUEUE_FAIL: |
1981 | 0 | trunk_request_enter_failed(treq); |
1982 | 0 | break; |
1983 | 0 | } |
1984 | 0 | next: |
1985 | 0 | treq = prev; |
1986 | 0 | } |
1987 | | |
1988 | | /* |
1989 | | * Add the connection back into the active list |
1990 | | */ |
1991 | 0 | if (tconn->pub.state == TRUNK_CONN_ACTIVE) { |
1992 | 0 | int ret; |
1993 | |
|
1994 | 0 | ret = fr_minmax_heap_insert(trunk->active, tconn); |
1995 | 0 | if (!fr_cond_assert_msg(ret == 0, |
1996 | 0 | "Failed re-inserting conn into active heap: %s", fr_strerror())) goto done; |
1997 | 0 | } |
1998 | 0 | if (moved >= max) goto done; |
1999 | | |
2000 | | /* |
2001 | | * Deal with the cancelled requests specially we can't |
2002 | | * queue them up again as they were only valid on that |
2003 | | * specific connection. |
2004 | | * |
2005 | | * We just need to run them to completion which, as |
2006 | | * they should already be in the unassigned state, |
2007 | | * just means freeing them. |
2008 | | */ |
2009 | 0 | moved += trunk_connection_requests_dequeue(&to_process, tconn, |
2010 | 0 | states & TRUNK_REQUEST_STATE_CANCEL_ALL, max - moved); |
2011 | 0 | while ((treq = fr_dlist_next(&to_process, treq))) { |
2012 | 0 | trunk_request_t *prev; |
2013 | |
|
2014 | 0 | prev = fr_dlist_remove(&to_process, treq); |
2015 | 0 | trunk_request_free(&treq); |
2016 | 0 | treq = prev; |
2017 | 0 | } |
2018 | |
|
2019 | 0 | done: |
2020 | | |
2021 | | /* |
2022 | | * Always re-calculate the request/connection |
2023 | | * ratio at the end. |
2024 | | * |
2025 | | * This avoids having the state transition |
2026 | | * functions do it. |
2027 | | * |
2028 | | * The ratio would be wrong when they calculated |
2029 | | * it anyway, because a bunch of requests are |
2030 | | * dequeued from the connection and temporarily |
2031 | | * cease to exist from the perspective of the |
2032 | | * trunk_requests_per_connection code. |
2033 | | */ |
2034 | 0 | trunk_requests_per_connection(NULL, NULL, trunk, fr_time(), false); |
2035 | |
|
2036 | 0 | connection_signals_resume(tconn->pub.conn); |
2037 | 0 | return moved; |
2038 | 0 | } |
2039 | | |
2040 | | /** Move requests off of a connection and requeue elsewhere |
2041 | | * |
2042 | | * @note We don't re-queue on draining or draining to free, as requests should have already been |
2043 | | * moved off of the connection. It's also dangerous as the trunk management code main |
2044 | | * clean up a connection in this state when it's run on re-queue, and then the caller |
2045 | | * may try and access a now freed connection. |
2046 | | * |
2047 | | * @param[in] tconn to move requests off of. |
2048 | | * @param[in] states Only move requests in this state. |
2049 | | * @param[in] max The maximum number of requests to dequeue. 0 for unlimited. |
2050 | | * @param[in] fail_bound If true causes any requests bound to the connection to fail. |
2051 | | * If false bound requests will not be moved. |
2052 | | * @return The number of requests requeued. |
2053 | | */ |
2054 | | uint64_t trunk_connection_requests_requeue(trunk_connection_t *tconn, int states, uint64_t max, bool fail_bound) |
2055 | 0 | { |
2056 | 0 | switch (tconn->pub.state) { |
2057 | 0 | case TRUNK_CONN_ACTIVE: |
2058 | 0 | case TRUNK_CONN_FULL: |
2059 | 0 | case TRUNK_CONN_INACTIVE: |
2060 | 0 | return trunk_connection_requests_requeue_priv(tconn, states, max, fail_bound); |
2061 | | |
2062 | 0 | default: |
2063 | 0 | return 0; |
2064 | 0 | } |
2065 | 0 | } |
2066 | | |
2067 | | /** Signal a partial write |
2068 | | * |
2069 | | * Where there's high load, and the outbound write buffer is full |
2070 | | * |
2071 | | * @param[in] treq to signal state change for. |
2072 | | */ |
2073 | | void trunk_request_signal_partial(trunk_request_t *treq) |
2074 | 0 | { |
2075 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2076 | | |
2077 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_MUX(treq->pub.trunk), |
2078 | 0 | "%s can only be called from within request_mux handler", __FUNCTION__)) return; |
2079 | | |
2080 | 0 | switch (treq->pub.state) { |
2081 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
2082 | 0 | trunk_request_enter_partial(treq); |
2083 | 0 | break; |
2084 | | |
2085 | 0 | default: |
2086 | 0 | return; |
2087 | 0 | } |
2088 | 0 | } |
2089 | | |
2090 | | /** Signal that the request was written to a connection successfully |
2091 | | * |
2092 | | * @param[in] treq to signal state change for. |
2093 | | */ |
2094 | | void trunk_request_signal_sent(trunk_request_t *treq) |
2095 | 0 | { |
2096 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2097 | | |
2098 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_MUX(treq->pub.trunk), |
2099 | 0 | "%s can only be called from within request_mux handler", __FUNCTION__)) return; |
2100 | | |
2101 | 0 | switch (treq->pub.state) { |
2102 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
2103 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
2104 | 0 | trunk_request_enter_sent(treq); |
2105 | 0 | break; |
2106 | | |
2107 | 0 | default: |
2108 | 0 | return; |
2109 | 0 | } |
2110 | 0 | } |
2111 | | |
2112 | | /** Signal that the request was written to a connection successfully, but no response is expected |
2113 | | * |
2114 | | * @param[in] treq to signal state change for. |
2115 | | */ |
2116 | | void trunk_request_signal_reapable(trunk_request_t *treq) |
2117 | 0 | { |
2118 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2119 | | |
2120 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_MUX(treq->pub.trunk), |
2121 | 0 | "%s can only be called from within request_mux handler", __FUNCTION__)) return; |
2122 | | |
2123 | 0 | switch (treq->pub.state) { |
2124 | 0 | case TRUNK_REQUEST_STATE_PENDING: |
2125 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
2126 | 0 | trunk_request_enter_reapable(treq); |
2127 | 0 | break; |
2128 | | |
2129 | 0 | default: |
2130 | 0 | return; |
2131 | 0 | } |
2132 | 0 | } |
2133 | | |
2134 | | /** Signal that a trunk request is complete |
2135 | | * |
2136 | | * The API client will be informed that the request is now complete. |
2137 | | */ |
2138 | | void trunk_request_signal_complete(trunk_request_t *treq) |
2139 | 0 | { |
2140 | 0 | trunk_t *trunk = treq->pub.trunk; |
2141 | |
|
2142 | 0 | if (!fr_cond_assert_msg(trunk, "treq not associated with trunk")) return; |
2143 | | |
2144 | | /* |
2145 | | * We assume that if the request is being signalled |
2146 | | * as complete from the demux function, that it was |
2147 | | * a successful read. |
2148 | | * |
2149 | | * If this assumption turns out to be incorrect |
2150 | | * then we need to add an argument to signal_complete |
2151 | | * to indicate if this is a successful read. |
2152 | | */ |
2153 | 0 | if (IN_REQUEST_DEMUX(trunk)) { |
2154 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
2155 | |
|
2156 | 0 | trunk->pub.last_read_success = fr_time(); |
2157 | 0 | tconn->pub.last_read_success = trunk->pub.last_read_success; |
2158 | 0 | } |
2159 | |
|
2160 | 0 | switch (treq->pub.state) { |
2161 | 0 | case TRUNK_REQUEST_STATE_SENT: |
2162 | 0 | case TRUNK_REQUEST_STATE_PENDING: /* Got immediate response, i.e. cached */ |
2163 | 0 | case TRUNK_REQUEST_STATE_REAPABLE: |
2164 | 0 | trunk_request_enter_complete(treq); |
2165 | 0 | break; |
2166 | | |
2167 | 0 | default: |
2168 | 0 | return; |
2169 | 0 | } |
2170 | 0 | } |
2171 | | |
2172 | | /** Signal that a trunk request failed |
2173 | | * |
2174 | | * The API client will be informed that the request has failed. |
2175 | | */ |
2176 | | void trunk_request_signal_fail(trunk_request_t *treq) |
2177 | | { |
2178 | | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2179 | | |
2180 | | trunk_request_enter_failed(treq); |
2181 | | } |
2182 | | |
2183 | | /** Cancel a trunk request |
2184 | | * |
2185 | | * treq can be in any state, but requests to cancel if the treq is not in |
2186 | | * the TRUNK_REQUEST_STATE_PARTIAL or TRUNK_REQUEST_STATE_SENT state will be ignored. |
2187 | | * |
2188 | | * The complete or failed callbacks will not be called here, as it's assumed the request_t * |
2189 | | * is now inviable as it's being cancelled. |
2190 | | * |
2191 | | * The free function however, is called, and that should be used to perform necessary |
2192 | | * cleanup. |
2193 | | * |
2194 | | * @param[in] treq to signal state change for. |
2195 | | */ |
2196 | | void trunk_request_signal_cancel(trunk_request_t *treq) |
2197 | 0 | { |
2198 | 0 | trunk_t *trunk; |
2199 | | |
2200 | | /* |
2201 | | * Ensure treq hasn't been freed |
2202 | | */ |
2203 | 0 | (void)talloc_get_type_abort(treq, trunk_request_t); |
2204 | |
|
2205 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2206 | | |
2207 | 0 | if (!fr_cond_assert_msg(!IN_HANDLER(treq->pub.trunk), |
2208 | 0 | "%s cannot be called within a handler", __FUNCTION__)) return; |
2209 | | |
2210 | 0 | trunk = treq->pub.trunk; |
2211 | |
|
2212 | 0 | switch (treq->pub.state) { |
2213 | | /* |
2214 | | * We don't call the complete or failed callbacks |
2215 | | * as the request and rctx are no longer viable. |
2216 | | */ |
2217 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
2218 | 0 | case TRUNK_REQUEST_STATE_SENT: |
2219 | 0 | { |
2220 | 0 | trunk_connection_t *tconn = treq->pub.tconn; |
2221 | | |
2222 | | /* |
2223 | | * Don't allow connection state changes |
2224 | | */ |
2225 | 0 | connection_signals_pause(tconn->pub.conn); |
2226 | 0 | trunk_request_enter_cancel(treq, TRUNK_CANCEL_REASON_SIGNAL); |
2227 | 0 | if (!fr_cond_assert_msg(treq->pub.state == TRUNK_REQUEST_STATE_CANCEL, |
2228 | 0 | "Bad state %s after cancellation", |
2229 | 0 | fr_table_str_by_value(trunk_request_states, treq->pub.state, "<INVALID>"))) { |
2230 | 0 | connection_signals_resume(tconn->pub.conn); |
2231 | 0 | return; |
2232 | 0 | } |
2233 | | /* |
2234 | | * No cancel muxer. We're done. |
2235 | | * |
2236 | | * If we do have a cancel mux function, |
2237 | | * the next time this connection becomes |
2238 | | * writable, we'll call the cancel mux |
2239 | | * function. |
2240 | | * |
2241 | | * We don't run the complete or failed |
2242 | | * callbacks here as the request is |
2243 | | * being cancelled. |
2244 | | */ |
2245 | 0 | if (!trunk->funcs.request_cancel_mux) { |
2246 | 0 | trunk_request_enter_unassigned(treq); |
2247 | 0 | trunk_request_free(&treq); |
2248 | 0 | } |
2249 | 0 | connection_signals_resume(tconn->pub.conn); |
2250 | 0 | } |
2251 | 0 | break; |
2252 | | |
2253 | | /* |
2254 | | * We're already in the process of cancelling a |
2255 | | * request, so ignore duplicate signals. |
2256 | | */ |
2257 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
2258 | 0 | case TRUNK_REQUEST_STATE_CANCEL_PARTIAL: |
2259 | 0 | case TRUNK_REQUEST_STATE_CANCEL_SENT: |
2260 | 0 | case TRUNK_REQUEST_STATE_CANCEL_COMPLETE: |
2261 | 0 | break; |
2262 | | |
2263 | | /* |
2264 | | * For any other state, we just release the request |
2265 | | * from its current connection and free it. |
2266 | | */ |
2267 | 0 | default: |
2268 | 0 | trunk_request_enter_unassigned(treq); |
2269 | 0 | trunk_request_free(&treq); |
2270 | 0 | break; |
2271 | 0 | } |
2272 | 0 | } |
2273 | | |
2274 | | /** Signal a partial cancel write |
2275 | | * |
2276 | | * Where there's high load, and the outbound write buffer is full |
2277 | | * |
2278 | | * @param[in] treq to signal state change for. |
2279 | | */ |
2280 | | void trunk_request_signal_cancel_partial(trunk_request_t *treq) |
2281 | 0 | { |
2282 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2283 | | |
2284 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_CANCEL_MUX(treq->pub.trunk), |
2285 | 0 | "%s can only be called from within request_cancel_mux handler", __FUNCTION__)) return; |
2286 | | |
2287 | 0 | switch (treq->pub.state) { |
2288 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
2289 | 0 | trunk_request_enter_cancel_partial(treq); |
2290 | 0 | break; |
2291 | | |
2292 | 0 | default: |
2293 | 0 | return; |
2294 | 0 | } |
2295 | 0 | } |
2296 | | |
2297 | | /** Signal that a remote server has been notified of the cancellation |
2298 | | * |
2299 | | * Called from request_cancel_mux to indicate that the datastore has been informed |
2300 | | * that the response is no longer needed. |
2301 | | * |
2302 | | * @param[in] treq to signal state change for. |
2303 | | */ |
2304 | | void trunk_request_signal_cancel_sent(trunk_request_t *treq) |
2305 | 0 | { |
2306 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2307 | | |
2308 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_CANCEL_MUX(treq->pub.trunk), |
2309 | 0 | "%s can only be called from within request_cancel_mux handler", __FUNCTION__)) return; |
2310 | | |
2311 | 0 | switch (treq->pub.state) { |
2312 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
2313 | 0 | case TRUNK_REQUEST_STATE_CANCEL_PARTIAL: |
2314 | 0 | trunk_request_enter_cancel_sent(treq); |
2315 | 0 | break; |
2316 | | |
2317 | 0 | default: |
2318 | 0 | break; |
2319 | 0 | } |
2320 | 0 | } |
2321 | | |
2322 | | /** Signal that a remote server acked our cancellation |
2323 | | * |
2324 | | * Called from request_demux to indicate that it got an ack for the cancellation. |
2325 | | * |
2326 | | * @param[in] treq to signal state change for. |
2327 | | */ |
2328 | | void trunk_request_signal_cancel_complete(trunk_request_t *treq) |
2329 | 0 | { |
2330 | 0 | if (!fr_cond_assert_msg(treq->pub.trunk, "treq not associated with trunk")) return; |
2331 | | |
2332 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_DEMUX(treq->pub.trunk) || IN_REQUEST_CANCEL_MUX(treq->pub.trunk), |
2333 | 0 | "%s can only be called from within request_demux or request_cancel_mux handlers", |
2334 | 0 | __FUNCTION__)) return; |
2335 | | |
2336 | 0 | switch (treq->pub.state) { |
2337 | 0 | case TRUNK_REQUEST_STATE_CANCEL_SENT: |
2338 | | /* |
2339 | | * This is allowed, as we may not need to wait |
2340 | | * for the database to ACK our cancellation |
2341 | | * request. |
2342 | | * |
2343 | | * Note: TRUNK_REQUEST_STATE_CANCEL_PARTIAL |
2344 | | * is not allowed here, as that'd mean we'd half |
2345 | | * written the cancellation request out to the |
2346 | | * socket, and then decided to abandon it. |
2347 | | * |
2348 | | * That'd leave the socket in an unusable state. |
2349 | | */ |
2350 | 0 | case TRUNK_REQUEST_STATE_CANCEL: |
2351 | 0 | trunk_request_enter_cancel_complete(treq); |
2352 | 0 | break; |
2353 | | |
2354 | 0 | default: |
2355 | 0 | break; |
2356 | 0 | } |
2357 | 0 | } |
2358 | | |
2359 | | /** If the trunk request is freed then update the target requests |
2360 | | * |
2361 | | * gperftools showed calling the request free function directly was slightly faster |
2362 | | * than using talloc_free. |
2363 | | * |
2364 | | * @param[in] treq_to_free request. |
2365 | | */ |
2366 | | void trunk_request_free(trunk_request_t **treq_to_free) |
2367 | 0 | { |
2368 | 0 | trunk_request_t *treq = *treq_to_free; |
2369 | 0 | trunk_t *trunk; |
2370 | |
|
2371 | 0 | if (unlikely(!treq)) return; |
2372 | | |
2373 | 0 | trunk = treq->pub.trunk; |
2374 | | |
2375 | | /* |
2376 | | * The only valid states a trunk request can be |
2377 | | * freed from. |
2378 | | */ |
2379 | 0 | switch (treq->pub.state) { |
2380 | 0 | case TRUNK_REQUEST_STATE_INIT: |
2381 | 0 | case TRUNK_REQUEST_STATE_UNASSIGNED: |
2382 | 0 | case TRUNK_REQUEST_STATE_COMPLETE: |
2383 | 0 | case TRUNK_REQUEST_STATE_FAILED: |
2384 | 0 | case TRUNK_REQUEST_STATE_CANCEL_COMPLETE: |
2385 | 0 | break; |
2386 | | |
2387 | 0 | default: |
2388 | 0 | if (!fr_cond_assert(0)) return; |
2389 | 0 | } |
2390 | | |
2391 | | /* |
2392 | | * Zero out the pointer to prevent double frees |
2393 | | */ |
2394 | 0 | *treq_to_free = NULL; |
2395 | | |
2396 | | /* |
2397 | | * Call the API client callback to free |
2398 | | * any associated memory. |
2399 | | */ |
2400 | 0 | DO_REQUEST_FREE(treq); |
2401 | | |
2402 | | /* |
2403 | | * Update the last above/below target stats |
2404 | | * We only do this when we alloc or free |
2405 | | * connections, or on connection |
2406 | | * state changes. |
2407 | | */ |
2408 | 0 | trunk_requests_per_connection(NULL, NULL, treq->pub.trunk, fr_time(), false); |
2409 | | |
2410 | | /* |
2411 | | * This tracks the total number of requests |
2412 | | * allocated and not freed or returned to |
2413 | | * the free list. |
2414 | | */ |
2415 | 0 | if (fr_cond_assert(trunk->pub.req_alloc > 0)) trunk->pub.req_alloc--; |
2416 | | |
2417 | | /* |
2418 | | * No cleanup delay, means cleanup immediately |
2419 | | */ |
2420 | 0 | if (!fr_time_delta_ispos(trunk->conf.req_cleanup_delay)) { |
2421 | 0 | treq->pub.state = TRUNK_REQUEST_STATE_INIT; |
2422 | |
|
2423 | 0 | #ifndef NDEBUG |
2424 | | /* |
2425 | | * Ensure anything parented off the treq |
2426 | | * is freed. We do this to trigger |
2427 | | * the destructors for the log entries. |
2428 | | */ |
2429 | 0 | talloc_free_children(treq); |
2430 | | |
2431 | | /* |
2432 | | * State log should now be empty as entries |
2433 | | * remove themselves from the dlist |
2434 | | * on free. |
2435 | | */ |
2436 | 0 | fr_assert_msg(fr_dlist_num_elements(&treq->log) == 0, |
2437 | 0 | "Should have 0 remaining log entries, have %u", fr_dlist_num_elements(&treq->log)); |
2438 | 0 | #endif |
2439 | |
|
2440 | 0 | talloc_free(treq); |
2441 | 0 | return; |
2442 | 0 | } |
2443 | | |
2444 | | /* |
2445 | | * Ensure anything parented off the treq |
2446 | | * is freed. |
2447 | | */ |
2448 | 0 | talloc_free_children(treq); |
2449 | |
|
2450 | 0 | #ifndef NDEBUG |
2451 | | /* |
2452 | | * State log should now be empty as entries |
2453 | | * remove themselves from the dlist |
2454 | | * on free. |
2455 | | */ |
2456 | 0 | fr_assert_msg(fr_dlist_num_elements(&treq->log) == 0, |
2457 | 0 | "Should have 0 remaining log entries, have %u", fr_dlist_num_elements(&treq->log)); |
2458 | 0 | #endif |
2459 | | |
2460 | | /* |
2461 | | * |
2462 | | * Return the trunk request back to the init state. |
2463 | | */ |
2464 | 0 | *treq = (trunk_request_t){ |
2465 | 0 | .pub = { |
2466 | 0 | .state = TRUNK_REQUEST_STATE_INIT, |
2467 | 0 | .trunk = treq->pub.trunk, |
2468 | 0 | }, |
2469 | 0 | .cancel_reason = TRUNK_CANCEL_REASON_NONE, |
2470 | 0 | .last_freed = fr_time(), |
2471 | 0 | #ifndef NDEBUG |
2472 | 0 | .log = treq->log /* Keep the list head, to save reinitialisation */ |
2473 | 0 | #endif |
2474 | 0 | }; |
2475 | | |
2476 | | |
2477 | | /* |
2478 | | * Insert at the head, so that we can free |
2479 | | * requests that have been unused for N |
2480 | | * seconds from the tail. |
2481 | | */ |
2482 | 0 | trunk_list_free_requests_add(trunk, treq); |
2483 | |
|
2484 | 0 | } |
2485 | | |
2486 | | /** Actually free the trunk request |
2487 | | * |
2488 | | */ |
2489 | | static int _trunk_request_free(trunk_request_t *treq) |
2490 | 0 | { |
2491 | 0 | trunk_t *trunk = treq->pub.trunk; |
2492 | |
|
2493 | 0 | switch (treq->pub.state) { |
2494 | 0 | case TRUNK_REQUEST_STATE_INIT: |
2495 | 0 | case TRUNK_REQUEST_STATE_UNASSIGNED: |
2496 | 0 | break; |
2497 | | |
2498 | 0 | default: |
2499 | 0 | fr_assert(0); |
2500 | 0 | break; |
2501 | 0 | } |
2502 | | |
2503 | 0 | trunk_list_free_requests_remove(trunk, treq); |
2504 | |
|
2505 | 0 | return 0; |
2506 | 0 | } |
2507 | | |
2508 | | /** (Pre-)Allocate a new trunk request |
2509 | | * |
2510 | | * If trunk->conf.req_pool_headers or trunk->conf.req_pool_size are not zero then the |
2511 | | * request will be a talloc pool, which can be used to hold the preq. |
2512 | | * |
2513 | | * @note Do not use MEM to check the result of this allocated as it may fail for |
2514 | | * non-fatal reasons. |
2515 | | * |
2516 | | * @param[in] trunk to add request to. |
2517 | | * @param[in] request to wrap in a trunk request (treq). |
2518 | | * @return |
2519 | | * - A newly allocated request. |
2520 | | * - NULL if too many requests are allocated. |
2521 | | */ |
2522 | | trunk_request_t *trunk_request_alloc(trunk_t *trunk, request_t *request) |
2523 | 0 | { |
2524 | 0 | trunk_request_t *treq; |
2525 | | |
2526 | | /* |
2527 | | * The number of treqs currently allocated |
2528 | | * exceeds the maximum number allowed. |
2529 | | */ |
2530 | 0 | if (trunk->conf.max_req_per_conn && trunk->conf.max) { |
2531 | 0 | uint64_t limit; |
2532 | |
|
2533 | 0 | limit = (uint64_t) trunk->conf.max_req_per_conn * trunk->conf.max; |
2534 | 0 | if (trunk->pub.req_alloc >= (limit + trunk->conf.max_backlog)) { |
2535 | 0 | RATE_LIMIT_LOCAL_ROPTIONAL(&trunk->limit_max_requests_alloc_log, |
2536 | 0 | RWARN, WARN, "Refusing to alloc requests - " |
2537 | 0 | "Limit of %"PRIu64" (max = %u * per_connection_max = %u) " |
2538 | 0 | "plus %u backlog requests reached", |
2539 | 0 | limit, trunk->conf.max, trunk->conf.max_req_per_conn, |
2540 | 0 | trunk->conf.max_backlog); |
2541 | 0 | return NULL; |
2542 | 0 | } |
2543 | 0 | } |
2544 | | |
2545 | | /* |
2546 | | * Re-use a recently freed request, which might have some |
2547 | | * better cache locality than getting a request from the tail. |
2548 | | * |
2549 | | * If we can't do that, just allocate a new one. |
2550 | | */ |
2551 | 0 | treq = trunk_list_free_requests_pop(trunk); |
2552 | 0 | if (treq) { |
2553 | 0 | fr_assert(treq->pub.state == TRUNK_REQUEST_STATE_INIT); |
2554 | 0 | fr_assert(treq->pub.trunk == trunk); |
2555 | 0 | fr_assert(treq->pub.tconn == NULL); |
2556 | 0 | fr_assert(treq->cancel_reason == TRUNK_CANCEL_REASON_NONE); |
2557 | 0 | fr_assert(fr_time_gt(treq->last_freed, fr_time_wrap(0))); |
2558 | 0 | trunk->pub.req_alloc_reused++; |
2559 | 0 | } else { |
2560 | 0 | MEM(treq = talloc_pooled_object(trunk, trunk_request_t, |
2561 | 0 | trunk->conf.req_pool_headers, trunk->conf.req_pool_size)); |
2562 | 0 | talloc_set_destructor(treq, _trunk_request_free); |
2563 | |
|
2564 | 0 | *treq = (trunk_request_t){ |
2565 | 0 | .pub = { |
2566 | 0 | .state = TRUNK_REQUEST_STATE_INIT, |
2567 | 0 | .trunk = trunk |
2568 | 0 | }, |
2569 | 0 | .cancel_reason = TRUNK_CANCEL_REASON_NONE |
2570 | 0 | }; |
2571 | 0 | trunk->pub.req_alloc_new++; |
2572 | 0 | #ifndef NDEBUG |
2573 | 0 | fr_dlist_init(&treq->log, trunk_request_state_log_t, entry); |
2574 | 0 | #endif |
2575 | 0 | } |
2576 | |
|
2577 | 0 | trunk->pub.req_alloc++; |
2578 | 0 | treq->id = atomic_fetch_add_explicit(&request_counter, 1, memory_order_relaxed); |
2579 | | /* heap_id - initialised when treq inserted into pending */ |
2580 | | /* list - empty */ |
2581 | | /* preq - populated later */ |
2582 | | /* rctx - populated later */ |
2583 | 0 | treq->pub.request = request; |
2584 | |
|
2585 | 0 | return treq; |
2586 | 0 | } |
2587 | | |
2588 | | /** Enqueue a request that needs data written to the trunk |
2589 | | * |
2590 | | * When a request_t * needs to make an asynchronous request to an external datastore |
2591 | | * it should call this function, specifying a preq (protocol request) containing |
2592 | | * the data necessary to request information from the external datastore, and an |
2593 | | * rctx (resume ctx) used to hold the decoded response and/or any error codes. |
2594 | | * |
2595 | | * After a treq is successfully enqueued it will either be assigned immediately |
2596 | | * to the pending queue of a connection, or if no connections are available, |
2597 | | * (depending on the trunk configuration) the treq will be placed in the trunk's |
2598 | | * global backlog. |
2599 | | * |
2600 | | * After receiving a positive return code from this function the caller should |
2601 | | * immediately yield, to allow the various timers and I/O handlers that drive tconn |
2602 | | * (trunk connection) and treq state changes to be called. |
2603 | | * |
2604 | | * When a tconn becomes writable (or the trunk is configured to be always writable) |
2605 | | * the #trunk_request_mux_t callback will be called to dequeue, encode and |
2606 | | * send any pending requests for that tconn. The #trunk_request_mux_t callback |
2607 | | * is also responsible for tracking the outbound requests to allow the |
2608 | | * #trunk_request_demux_t callback to match inbound responses with the original |
2609 | | * treq. Once the #trunk_request_mux_t callback is done processing the treq |
2610 | | * it signals what state the treq should enter next using one of the |
2611 | | * trunk_request_signal_* functions. |
2612 | | * |
2613 | | * When a tconn becomes readable the user specified #trunk_request_demux_t |
2614 | | * callback is called to process any responses, match them with the original treq. |
2615 | | * and signal what state they should enter next using one of the |
2616 | | * trunk_request_signal_* functions. |
2617 | | * |
2618 | | * @param[in,out] treq_out A trunk request handle. If the memory pointed to |
2619 | | * is NULL, a new treq will be allocated. |
2620 | | * Otherwise treq should point to memory allocated |
2621 | | * with trunk_request_alloc. |
2622 | | * @param[in] trunk to enqueue request on. |
2623 | | * @param[in] request to enqueue. |
2624 | | * @param[in] preq Protocol request to write out. Will be freed when |
2625 | | * treq is freed. Should ideally be parented by the |
2626 | | * treq if possible. |
2627 | | * Use #trunk_request_alloc for pre-allocation of |
2628 | | * the treq. |
2629 | | * @param[in] rctx The resume context to write any result to. |
2630 | | * @return |
2631 | | * - TRUNK_ENQUEUE_OK. |
2632 | | * - TRUNK_ENQUEUE_IN_BACKLOG. |
2633 | | * - TRUNK_ENQUEUE_NO_CAPACITY. |
2634 | | * - TRUNK_ENQUEUE_DST_UNAVAILABLE |
2635 | | * - TRUNK_ENQUEUE_FAIL |
2636 | | */ |
2637 | | trunk_enqueue_t trunk_request_enqueue(trunk_request_t **treq_out, trunk_t *trunk, |
2638 | | request_t *request, void *preq, void *rctx) |
2639 | 0 | { |
2640 | 0 | trunk_connection_t *tconn = NULL; |
2641 | 0 | trunk_request_t *treq; |
2642 | 0 | trunk_enqueue_t ret; |
2643 | |
|
2644 | 0 | if (!fr_cond_assert_msg(!IN_HANDLER(trunk), |
2645 | 0 | "%s cannot be called within a handler", __FUNCTION__)) return TRUNK_ENQUEUE_FAIL; |
2646 | | |
2647 | 0 | if (!fr_cond_assert_msg(!*treq_out || ((*treq_out)->pub.state == TRUNK_REQUEST_STATE_INIT), |
2648 | 0 | "%s requests must be in \"init\" state", __FUNCTION__)) return TRUNK_ENQUEUE_FAIL; |
2649 | | |
2650 | | /* |
2651 | | * If delay_start was set, we may need |
2652 | | * to insert the timer for the connection manager. |
2653 | | */ |
2654 | 0 | if (unlikely(!trunk->started)) { |
2655 | 0 | if (trunk_start(trunk) < 0) return TRUNK_ENQUEUE_FAIL; |
2656 | 0 | } |
2657 | | |
2658 | 0 | ret = trunk_request_check_enqueue(&tconn, trunk, request); |
2659 | 0 | switch (ret) { |
2660 | 0 | case TRUNK_ENQUEUE_OK: |
2661 | 0 | if (*treq_out) { |
2662 | 0 | treq = *treq_out; |
2663 | 0 | } else { |
2664 | 0 | *treq_out = treq = trunk_request_alloc(trunk, request); |
2665 | 0 | if (!treq) return TRUNK_ENQUEUE_NO_CAPACITY; |
2666 | 0 | } |
2667 | 0 | treq->pub.preq = preq; |
2668 | 0 | treq->pub.rctx = rctx; |
2669 | 0 | if (trunk->conf.always_writable) { |
2670 | 0 | connection_signals_pause(tconn->pub.conn); |
2671 | 0 | trunk_request_enter_pending(treq, tconn, true); |
2672 | 0 | trunk_connection_writable(tconn); |
2673 | 0 | connection_signals_resume(tconn->pub.conn); |
2674 | 0 | } else { |
2675 | 0 | trunk_request_enter_pending(treq, tconn, true); |
2676 | 0 | } |
2677 | 0 | break; |
2678 | | |
2679 | 0 | case TRUNK_ENQUEUE_IN_BACKLOG: |
2680 | 0 | if (*treq_out) { |
2681 | 0 | treq = *treq_out; |
2682 | 0 | } else { |
2683 | 0 | *treq_out = treq = trunk_request_alloc(trunk, request); |
2684 | 0 | if (!treq) return TRUNK_ENQUEUE_NO_CAPACITY; |
2685 | 0 | } |
2686 | 0 | treq->pub.preq = preq; |
2687 | 0 | treq->pub.rctx = rctx; |
2688 | 0 | trunk_request_enter_backlog(treq, true); |
2689 | 0 | break; |
2690 | | |
2691 | 0 | default: |
2692 | | /* |
2693 | | * If a trunk request was provided |
2694 | | * populate the preq and rctx fields |
2695 | | * so that if it's freed with |
2696 | | * trunk_request_free, the free |
2697 | | * function works as intended. |
2698 | | */ |
2699 | 0 | if (*treq_out) { |
2700 | 0 | treq = *treq_out; |
2701 | 0 | treq->pub.preq = preq; |
2702 | 0 | treq->pub.rctx = rctx; |
2703 | 0 | } |
2704 | 0 | return ret; |
2705 | 0 | } |
2706 | | |
2707 | 0 | return ret; |
2708 | 0 | } |
2709 | | |
2710 | | /** Re-enqueue a request on the same connection |
2711 | | * |
2712 | | * If the treq has been sent, we assume that we're being signalled to requeue |
2713 | | * because something outside of the trunk API has determined that a retransmission |
2714 | | * is required. The easiest way to perform that retransmission is to clean up |
2715 | | * any tracking information for the request, and the requeue it for transmission. |
2716 | | * |
2717 | | * IF re-queueing fails, the request will enter the fail state. It should not be |
2718 | | * accessed if this occurs. |
2719 | | * |
2720 | | * @param[in] treq to requeue (retransmit). |
2721 | | * @return |
2722 | | * - TRUNK_ENQUEUE_OK. |
2723 | | * - TRUNK_ENQUEUE_DST_UNAVAILABLE - Connection cannot service requests. |
2724 | | * - TRUNK_ENQUEUE_FAIL - Request isn't in a valid state to be reassigned. |
2725 | | */ |
2726 | | trunk_enqueue_t trunk_request_requeue(trunk_request_t *treq) |
2727 | 0 | { |
2728 | 0 | trunk_connection_t *tconn = treq->pub.tconn; /* Existing conn */ |
2729 | |
|
2730 | 0 | if (!tconn) return TRUNK_ENQUEUE_FAIL; |
2731 | | |
2732 | 0 | if (!IS_PROCESSING(tconn)) { |
2733 | 0 | trunk_request_enter_failed(treq); |
2734 | 0 | return TRUNK_ENQUEUE_DST_UNAVAILABLE; |
2735 | 0 | } |
2736 | | |
2737 | 0 | switch (treq->pub.state) { |
2738 | 0 | case TRUNK_REQUEST_STATE_PARTIAL: |
2739 | 0 | case TRUNK_REQUEST_STATE_SENT: |
2740 | 0 | case TRUNK_REQUEST_STATE_REAPABLE: |
2741 | 0 | connection_signals_pause(tconn->pub.conn); |
2742 | 0 | trunk_request_enter_cancel(treq, TRUNK_CANCEL_REASON_REQUEUE); |
2743 | 0 | trunk_request_enter_pending(treq, tconn, false); |
2744 | 0 | if (treq->pub.trunk->conf.always_writable) { |
2745 | 0 | trunk_connection_writable(tconn); |
2746 | 0 | } |
2747 | 0 | connection_signals_resume(tconn->pub.conn); |
2748 | 0 | break; |
2749 | | |
2750 | 0 | case TRUNK_REQUEST_STATE_BACKLOG: /* Do nothing.... */ |
2751 | 0 | case TRUNK_REQUEST_STATE_PENDING: /* Do nothing.... */ |
2752 | 0 | break; |
2753 | | |
2754 | 0 | default: |
2755 | 0 | trunk_request_enter_failed(treq); |
2756 | 0 | return TRUNK_ENQUEUE_FAIL; |
2757 | 0 | } |
2758 | | |
2759 | 0 | return TRUNK_ENQUEUE_OK; |
2760 | 0 | } |
2761 | | |
2762 | | /** Enqueue additional requests on a specific connection |
2763 | | * |
2764 | | * This may be used to create a series of requests on a single connection, or to generate |
2765 | | * in-band status checks. |
2766 | | * |
2767 | | * @note If conf->always_writable, then the muxer will be called immediately. The caller |
2768 | | * must be able to handle multiple calls to its muxer gracefully. |
2769 | | * |
2770 | | * @param[in,out] treq_out A trunk request handle. If the memory pointed to |
2771 | | * is NULL, a new treq will be allocated. |
2772 | | * Otherwise treq should point to memory allocated |
2773 | | * with trunk_request_alloc. |
2774 | | * @param[in] tconn to enqueue request on. |
2775 | | * @param[in] request to enqueue. |
2776 | | * @param[in] preq Protocol request to write out. Will be freed when |
2777 | | * treq is freed. Should ideally be parented by the |
2778 | | * treq if possible. |
2779 | | * Use #trunk_request_alloc for pre-allocation of |
2780 | | * the treq. |
2781 | | * @param[in] rctx The resume context to write any result to. |
2782 | | * @param[in] ignore_limits Ignore max_req_per_conn. Useful to force status |
2783 | | * checks through even if the connection is at capacity. |
2784 | | * Will also allow enqueuing on "inactive", "draining", |
2785 | | * "draining-to-free" connections. |
2786 | | * @return |
2787 | | * - TRUNK_ENQUEUE_OK. |
2788 | | * - TRUNK_ENQUEUE_NO_CAPACITY - At max_req_per_conn_limit |
2789 | | * - TRUNK_ENQUEUE_DST_UNAVAILABLE - Connection cannot service requests. |
2790 | | */ |
2791 | | trunk_enqueue_t trunk_request_enqueue_on_conn(trunk_request_t **treq_out, trunk_connection_t *tconn, |
2792 | | request_t *request, void *preq, void *rctx, |
2793 | | bool ignore_limits) |
2794 | 0 | { |
2795 | 0 | trunk_request_t *treq; |
2796 | 0 | trunk_t *trunk = tconn->pub.trunk; |
2797 | |
|
2798 | 0 | if (!fr_cond_assert_msg(!*treq_out || ((*treq_out)->pub.state == TRUNK_REQUEST_STATE_INIT), |
2799 | 0 | "%s requests must be in \"init\" state", __FUNCTION__)) return TRUNK_ENQUEUE_FAIL; |
2800 | | |
2801 | 0 | if (!IS_SERVICEABLE(tconn)) return TRUNK_ENQUEUE_DST_UNAVAILABLE; |
2802 | | |
2803 | | /* |
2804 | | * Limits check |
2805 | | */ |
2806 | 0 | if (!ignore_limits) { |
2807 | 0 | if (trunk->conf.max_req_per_conn && |
2808 | 0 | (trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) >= |
2809 | 0 | trunk->conf.max_req_per_conn)) return TRUNK_ENQUEUE_NO_CAPACITY; |
2810 | | |
2811 | 0 | if (tconn->pub.state != TRUNK_CONN_ACTIVE) return TRUNK_ENQUEUE_NO_CAPACITY; |
2812 | 0 | } |
2813 | | |
2814 | 0 | if (*treq_out) { |
2815 | 0 | treq = *treq_out; |
2816 | 0 | } else { |
2817 | 0 | *treq_out = treq = trunk_request_alloc(trunk, request); |
2818 | 0 | if (!treq) return TRUNK_ENQUEUE_NO_CAPACITY; |
2819 | 0 | } |
2820 | | |
2821 | 0 | treq->pub.preq = preq; |
2822 | 0 | treq->pub.rctx = rctx; |
2823 | 0 | treq->bound_to_conn = true; /* Don't let the request be transferred */ |
2824 | |
|
2825 | 0 | if (trunk->conf.always_writable) { |
2826 | 0 | connection_signals_pause(tconn->pub.conn); |
2827 | 0 | trunk_request_enter_pending(treq, tconn, true); |
2828 | 0 | trunk_connection_writable(tconn); |
2829 | 0 | connection_signals_resume(tconn->pub.conn); |
2830 | 0 | } else { |
2831 | 0 | trunk_request_enter_pending(treq, tconn, true); |
2832 | 0 | } |
2833 | |
|
2834 | 0 | return TRUNK_ENQUEUE_OK; |
2835 | 0 | } |
2836 | | |
2837 | | #ifndef NDEBUG |
2838 | | /** Used for sanity checks to ensure all log entries have been freed |
2839 | | * |
2840 | | */ |
2841 | | static int _state_log_entry_free(trunk_request_state_log_t *slog) |
2842 | 0 | { |
2843 | 0 | fr_dlist_remove(slog->log_head, slog); |
2844 | |
|
2845 | 0 | return 0; |
2846 | 0 | } |
2847 | | |
2848 | | void trunk_request_state_log_entry_add(char const *function, int line, |
2849 | | trunk_request_t *treq, trunk_request_state_t new) |
2850 | 0 | { |
2851 | 0 | trunk_request_state_log_t *slog = NULL; |
2852 | |
|
2853 | 0 | if (fr_dlist_num_elements(&treq->log) >= TRUNK_REQUEST_STATE_LOG_MAX) { |
2854 | 0 | slog = fr_dlist_head(&treq->log); |
2855 | 0 | fr_assert_msg(slog, "slog list head NULL but element counter was %u", |
2856 | 0 | fr_dlist_num_elements(&treq->log)); |
2857 | 0 | (void)fr_dlist_remove(&treq->log, slog); /* Returns NULL when removing the list head */ |
2858 | 0 | memset(slog, 0, sizeof(*slog)); |
2859 | 0 | } else { |
2860 | 0 | MEM(slog = talloc_zero(treq, trunk_request_state_log_t)); |
2861 | 0 | talloc_set_destructor(slog, _state_log_entry_free); |
2862 | 0 | } |
2863 | |
|
2864 | 0 | slog->log_head = &treq->log; |
2865 | 0 | slog->from = treq->pub.state; |
2866 | 0 | slog->to = new; |
2867 | 0 | slog->function = function; |
2868 | 0 | slog->line = line; |
2869 | 0 | if (treq->pub.tconn) { |
2870 | 0 | slog->tconn = treq->pub.tconn; |
2871 | 0 | slog->tconn_id = treq->pub.tconn->pub.conn->id; |
2872 | 0 | slog->tconn_state = treq->pub.tconn->pub.state; |
2873 | 0 | } |
2874 | |
|
2875 | 0 | fr_dlist_insert_tail(&treq->log, slog); |
2876 | |
|
2877 | 0 | } |
2878 | | |
2879 | | void trunk_request_state_log(fr_log_t const *log, fr_log_type_t log_type, char const *file, int line, |
2880 | | trunk_request_t const *treq) |
2881 | 0 | { |
2882 | 0 | trunk_request_state_log_t *slog = NULL; |
2883 | |
|
2884 | 0 | int i; |
2885 | |
|
2886 | 0 | for (slog = fr_dlist_head(&treq->log), i = 0; |
2887 | 0 | slog; |
2888 | 0 | slog = fr_dlist_next(&treq->log, slog), i++) { |
2889 | 0 | fr_log(log, log_type, file, line, "[%u] %s:%i - in conn %"PRIu64" in state %s - %s -> %s", |
2890 | 0 | i, slog->function, slog->line, |
2891 | 0 | slog->tconn_id, |
2892 | 0 | slog->tconn ? fr_table_str_by_value(trunk_connection_states, |
2893 | 0 | slog->tconn_state, "<INVALID>") : "none", |
2894 | 0 | fr_table_str_by_value(trunk_request_states, slog->from, "<INVALID>"), |
2895 | 0 | fr_table_str_by_value(trunk_request_states, slog->to, "<INVALID>")); |
2896 | 0 | } |
2897 | 0 | } |
2898 | | #endif |
2899 | | |
2900 | | /** Return the count number of connections in the specified states |
2901 | | * |
2902 | | * @param[in] trunk to retrieve counts for. |
2903 | | * @param[in] conn_state One or more #trunk_connection_state_t states or'd together. |
2904 | | * @return The number of connections in the specified states. |
2905 | | */ |
2906 | | uint16_t trunk_connection_count_by_state(trunk_t *trunk, int conn_state) |
2907 | 0 | { |
2908 | 0 | uint16_t count = 0; |
2909 | |
|
2910 | 0 | if (conn_state & TRUNK_CONN_INIT) count += fr_dlist_num_elements(&trunk->init); |
2911 | 0 | if (conn_state & TRUNK_CONN_CONNECTING) count += fr_dlist_num_elements(&trunk->connecting); |
2912 | 0 | if (conn_state & TRUNK_CONN_ACTIVE) count += fr_minmax_heap_num_elements(trunk->active); |
2913 | 0 | if (conn_state & TRUNK_CONN_FULL) count += fr_dlist_num_elements(&trunk->full); |
2914 | 0 | if (conn_state & TRUNK_CONN_INACTIVE) count += fr_dlist_num_elements(&trunk->inactive); |
2915 | 0 | if (conn_state & TRUNK_CONN_INACTIVE_DRAINING) count += fr_dlist_num_elements(&trunk->inactive_draining); |
2916 | 0 | if (conn_state & TRUNK_CONN_CLOSED) count += fr_dlist_num_elements(&trunk->closed); |
2917 | 0 | if (conn_state & TRUNK_CONN_DRAINING) count += fr_dlist_num_elements(&trunk->draining); |
2918 | 0 | if (conn_state & TRUNK_CONN_DRAINING_TO_FREE) count += fr_dlist_num_elements(&trunk->draining_to_free); |
2919 | |
|
2920 | 0 | return count; |
2921 | 0 | } |
2922 | | |
2923 | | /** Return the count number of requests associated with a trunk connection |
2924 | | * |
2925 | | * @param[in] tconn to return request count for. |
2926 | | * @param[in] req_state One or more request states or'd together. |
2927 | | * |
2928 | | * @return The number of requests in the specified states, associated with a tconn. |
2929 | | */ |
2930 | | uint32_t trunk_request_count_by_connection(trunk_connection_t const *tconn, int req_state) |
2931 | 0 | { |
2932 | 0 | uint32_t count = 0; |
2933 | |
|
2934 | 0 | if (req_state & TRUNK_REQUEST_STATE_PENDING) count += fr_heap_num_elements(tconn->pending); |
2935 | 0 | if (req_state & TRUNK_REQUEST_STATE_PARTIAL) count += tconn->partial ? 1 : 0; |
2936 | 0 | if (req_state & TRUNK_REQUEST_STATE_SENT) count += fr_dlist_num_elements(&tconn->sent); |
2937 | 0 | if (req_state & TRUNK_REQUEST_STATE_REAPABLE) count += fr_dlist_num_elements(&tconn->reapable); |
2938 | 0 | if (req_state & TRUNK_REQUEST_STATE_CANCEL) count += fr_dlist_num_elements(&tconn->cancel); |
2939 | 0 | if (req_state & TRUNK_REQUEST_STATE_CANCEL_PARTIAL) count += tconn->cancel_partial ? 1 : 0; |
2940 | 0 | if (req_state & TRUNK_REQUEST_STATE_CANCEL_SENT) count += fr_dlist_num_elements(&tconn->cancel_sent); |
2941 | |
|
2942 | 0 | return count; |
2943 | 0 | } |
2944 | | |
2945 | | /** Automatically mark a connection as full |
2946 | | * |
2947 | | * @param[in] tconn to potentially mark as full. |
2948 | | */ |
2949 | | static inline void trunk_connection_auto_full(trunk_connection_t *tconn) |
2950 | 0 | { |
2951 | 0 | trunk_t *trunk = tconn->pub.trunk; |
2952 | 0 | uint32_t count; |
2953 | |
|
2954 | 0 | if (tconn->pub.state != TRUNK_CONN_ACTIVE) return; |
2955 | | |
2956 | | /* |
2957 | | * Enforces max_req_per_conn |
2958 | | */ |
2959 | 0 | if (trunk->conf.max_req_per_conn > 0) { |
2960 | 0 | count = trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL); |
2961 | 0 | if (count >= trunk->conf.max_req_per_conn) trunk_connection_enter_full(tconn); |
2962 | 0 | } |
2963 | 0 | } |
2964 | | |
2965 | | /** Return whether a trunk connection should currently be considered full |
2966 | | * |
2967 | | * @param[in] tconn to check. |
2968 | | * @return |
2969 | | * - true if the connection is full. |
2970 | | * - false if the connection is not full. |
2971 | | */ |
2972 | | static inline bool trunk_connection_is_full(trunk_connection_t *tconn) |
2973 | 0 | { |
2974 | 0 | trunk_t *trunk = tconn->pub.trunk; |
2975 | 0 | uint32_t count; |
2976 | | |
2977 | | /* |
2978 | | * Enforces max_req_per_conn |
2979 | | */ |
2980 | 0 | count = trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL); |
2981 | 0 | if ((trunk->conf.max_req_per_conn == 0) || (count < trunk->conf.max_req_per_conn)) return false; |
2982 | | |
2983 | 0 | return true; |
2984 | 0 | } |
2985 | | |
2986 | | /** Automatically mark a connection as active or reconnect it |
2987 | | * |
2988 | | * @param[in] tconn to potentially mark as active or reconnect. |
2989 | | */ |
2990 | | static inline void trunk_connection_auto_unfull(trunk_connection_t *tconn) |
2991 | 0 | { |
2992 | 0 | if (tconn->pub.state != TRUNK_CONN_FULL) return; |
2993 | | |
2994 | | /* |
2995 | | * Enforces max_req_per_conn |
2996 | | */ |
2997 | 0 | if (!trunk_connection_is_full(tconn)) trunk_connection_enter_active(tconn); |
2998 | 0 | } |
2999 | | |
3000 | | /** A connection is readable. Call the request_demux function to read pending requests |
3001 | | * |
3002 | | */ |
3003 | | static inline void trunk_connection_readable(trunk_connection_t *tconn) |
3004 | 0 | { |
3005 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3006 | |
|
3007 | 0 | DO_REQUEST_DEMUX(tconn); |
3008 | 0 | } |
3009 | | |
3010 | | /** A connection is writable. Call the request_mux function to write pending requests |
3011 | | * |
3012 | | */ |
3013 | | static inline void trunk_connection_writable(trunk_connection_t *tconn) |
3014 | 0 | { |
3015 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3016 | | |
3017 | | /* |
3018 | | * Call the cancel_sent function (if we have one) |
3019 | | * to inform a backend datastore we no longer |
3020 | | * care about the result |
3021 | | */ |
3022 | 0 | if (trunk->funcs.request_cancel_mux && trunk_request_count_by_connection(tconn, |
3023 | 0 | TRUNK_REQUEST_STATE_CANCEL | |
3024 | 0 | TRUNK_REQUEST_STATE_CANCEL_PARTIAL)) { |
3025 | 0 | DO_REQUEST_CANCEL_MUX(tconn); |
3026 | 0 | } |
3027 | 0 | if (!trunk_request_count_by_connection(tconn, |
3028 | 0 | TRUNK_REQUEST_STATE_PENDING | |
3029 | 0 | TRUNK_REQUEST_STATE_PARTIAL)) return; |
3030 | 0 | DO_REQUEST_MUX(tconn); |
3031 | 0 | } |
3032 | | |
3033 | | /** Update the registrations for I/O events we're interested in |
3034 | | * |
3035 | | */ |
3036 | | static void trunk_connection_event_update(trunk_connection_t *tconn) |
3037 | 0 | { |
3038 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3039 | 0 | trunk_connection_event_t events = TRUNK_CONN_EVENT_NONE; |
3040 | |
|
3041 | 0 | switch (tconn->pub.state) { |
3042 | | /* |
3043 | | * We only register I/O events if the trunk connection is |
3044 | | * in one of these states. |
3045 | | * |
3046 | | * For the other states the trunk shouldn't be processing |
3047 | | * requests. |
3048 | | */ |
3049 | 0 | case TRUNK_CONN_ACTIVE: |
3050 | 0 | case TRUNK_CONN_FULL: |
3051 | 0 | case TRUNK_CONN_INACTIVE: |
3052 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: |
3053 | 0 | case TRUNK_CONN_DRAINING: |
3054 | 0 | case TRUNK_CONN_DRAINING_TO_FREE: |
3055 | | /* |
3056 | | * If the connection is always writable, |
3057 | | * then we don't care about write events. |
3058 | | */ |
3059 | 0 | if (!trunk->conf.always_writable && |
3060 | 0 | trunk_request_count_by_connection(tconn, |
3061 | 0 | TRUNK_REQUEST_STATE_PARTIAL | |
3062 | 0 | TRUNK_REQUEST_STATE_PENDING | |
3063 | 0 | (trunk->funcs.request_cancel_mux ? |
3064 | 0 | TRUNK_REQUEST_STATE_CANCEL | |
3065 | 0 | TRUNK_REQUEST_STATE_CANCEL_PARTIAL : 0)) > 0) { |
3066 | 0 | events |= TRUNK_CONN_EVENT_WRITE; |
3067 | 0 | } |
3068 | |
|
3069 | 0 | if (trunk_request_count_by_connection(tconn, |
3070 | 0 | TRUNK_REQUEST_STATE_SENT | |
3071 | 0 | (trunk->funcs.request_cancel_mux ? |
3072 | 0 | TRUNK_REQUEST_STATE_CANCEL_SENT : 0)) > 0) { |
3073 | 0 | events |= TRUNK_CONN_EVENT_READ; |
3074 | 0 | } |
3075 | 0 | break; |
3076 | | |
3077 | 0 | default: |
3078 | 0 | break; |
3079 | 0 | } |
3080 | | |
3081 | 0 | if (tconn->events != events) { |
3082 | | /* |
3083 | | * There may be a fatal error which results |
3084 | | * in the connection being freed. |
3085 | | * |
3086 | | * Stop that from happening until after |
3087 | | * we're done using it. |
3088 | | */ |
3089 | 0 | connection_signals_pause(tconn->pub.conn); |
3090 | 0 | DO_CONNECTION_NOTIFY(tconn, events); |
3091 | 0 | tconn->events = events; |
3092 | 0 | connection_signals_resume(tconn->pub.conn); |
3093 | 0 | } |
3094 | 0 | } |
3095 | | |
3096 | | /** Remove a trunk connection from whichever list it's currently in |
3097 | | * |
3098 | | * @param[in] tconn to remove. |
3099 | | */ |
3100 | | static void trunk_connection_remove(trunk_connection_t *tconn) |
3101 | 0 | { |
3102 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3103 | |
|
3104 | 0 | switch (tconn->pub.state) { |
3105 | 0 | case TRUNK_CONN_ACTIVE: |
3106 | 0 | { |
3107 | 0 | int ret; |
3108 | |
|
3109 | 0 | ret = fr_minmax_heap_extract(trunk->active, tconn); |
3110 | 0 | if (!fr_cond_assert_msg(ret == 0, "Failed extracting conn from active heap: %s", fr_strerror())) return; |
3111 | 0 | } |
3112 | 0 | return; |
3113 | | |
3114 | 0 | case TRUNK_CONN_INIT: |
3115 | 0 | fr_dlist_remove(&trunk->init, tconn); |
3116 | 0 | break; |
3117 | | |
3118 | 0 | case TRUNK_CONN_CONNECTING: |
3119 | 0 | fr_dlist_remove(&trunk->connecting, tconn); |
3120 | 0 | return; |
3121 | | |
3122 | 0 | case TRUNK_CONN_CLOSED: |
3123 | 0 | fr_dlist_remove(&trunk->closed, tconn); |
3124 | 0 | return; |
3125 | | |
3126 | 0 | case TRUNK_CONN_FULL: |
3127 | 0 | trunk_list_full_remove(trunk, tconn); |
3128 | 0 | return; |
3129 | | |
3130 | 0 | case TRUNK_CONN_INACTIVE: |
3131 | 0 | trunk_list_inactive_remove(trunk, tconn); |
3132 | 0 | return; |
3133 | | |
3134 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: |
3135 | 0 | trunk_list_inactive_draining_remove(trunk, tconn); |
3136 | 0 | return; |
3137 | | |
3138 | 0 | case TRUNK_CONN_DRAINING: |
3139 | 0 | trunk_list_draining_remove(trunk, tconn); |
3140 | 0 | return; |
3141 | | |
3142 | 0 | case TRUNK_CONN_DRAINING_TO_FREE: |
3143 | 0 | fr_dlist_remove(&trunk->draining_to_free, tconn); |
3144 | 0 | return; |
3145 | | |
3146 | 0 | case TRUNK_CONN_HALTED: |
3147 | 0 | return; |
3148 | 0 | } |
3149 | 0 | } |
3150 | | |
3151 | | /** Transition a connection to the full state |
3152 | | * |
3153 | | * Called whenever a trunk connection is at the maximum number of requests. |
3154 | | * Removes the connection from the connected heap, and places it in the full list. |
3155 | | */ |
3156 | | static void trunk_connection_enter_full(trunk_connection_t *tconn) |
3157 | 0 | { |
3158 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3159 | |
|
3160 | 0 | switch (tconn->pub.state) { |
3161 | 0 | case TRUNK_CONN_ACTIVE: |
3162 | 0 | trunk_connection_remove(tconn); |
3163 | 0 | break; |
3164 | | |
3165 | 0 | default: |
3166 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_FULL); |
3167 | 0 | } |
3168 | | |
3169 | 0 | trunk_list_full_add(trunk, tconn); |
3170 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_FULL, DEBUG2); |
3171 | 0 | } |
3172 | | |
3173 | | /** Transition a connection to the inactive state |
3174 | | * |
3175 | | * Called whenever the API client wants to stop new requests being enqueued |
3176 | | * on a trunk connection. |
3177 | | */ |
3178 | | static void trunk_connection_enter_inactive(trunk_connection_t *tconn) |
3179 | 0 | { |
3180 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3181 | |
|
3182 | 0 | switch (tconn->pub.state) { |
3183 | 0 | case TRUNK_CONN_ACTIVE: |
3184 | 0 | case TRUNK_CONN_FULL: |
3185 | 0 | trunk_connection_remove(tconn); |
3186 | 0 | break; |
3187 | | |
3188 | 0 | default: |
3189 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_INACTIVE); |
3190 | 0 | } |
3191 | | |
3192 | 0 | trunk_list_inactive_add(trunk, tconn); |
3193 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_INACTIVE, DEBUG2); |
3194 | 0 | } |
3195 | | |
3196 | | /** Transition a connection to the inactive-draining state |
3197 | | * |
3198 | | * Called whenever the trunk manager wants to drain an inactive connection |
3199 | | * of its requests. |
3200 | | */ |
3201 | | static void trunk_connection_enter_inactive_draining(trunk_connection_t *tconn) |
3202 | 0 | { |
3203 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3204 | |
|
3205 | 0 | switch (tconn->pub.state) { |
3206 | 0 | case TRUNK_CONN_INACTIVE: |
3207 | 0 | case TRUNK_CONN_DRAINING: |
3208 | 0 | trunk_connection_remove(tconn); |
3209 | 0 | break; |
3210 | | |
3211 | 0 | default: |
3212 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_INACTIVE_DRAINING); |
3213 | 0 | } |
3214 | | |
3215 | 0 | trunk_list_inactive_draining_add(trunk, tconn); |
3216 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_INACTIVE_DRAINING, INFO); |
3217 | | |
3218 | | /* |
3219 | | * Immediately re-enqueue all pending |
3220 | | * requests, so the connection is drained |
3221 | | * quicker. |
3222 | | */ |
3223 | 0 | trunk_connection_requests_requeue_priv(tconn, TRUNK_REQUEST_STATE_PENDING, 0, false); |
3224 | 0 | } |
3225 | | |
3226 | | /** Transition a connection to the draining state |
3227 | | * |
3228 | | * Removes the connection from the active heap so it won't be assigned any new |
3229 | | * connections. |
3230 | | */ |
3231 | | static void trunk_connection_enter_draining(trunk_connection_t *tconn) |
3232 | 0 | { |
3233 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3234 | |
|
3235 | 0 | switch (tconn->pub.state) { |
3236 | 0 | case TRUNK_CONN_ACTIVE: |
3237 | 0 | case TRUNK_CONN_FULL: |
3238 | 0 | case TRUNK_CONN_INACTIVE: |
3239 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: |
3240 | 0 | trunk_connection_remove(tconn); |
3241 | 0 | break; |
3242 | | |
3243 | 0 | default: |
3244 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_DRAINING); |
3245 | 0 | } |
3246 | | |
3247 | 0 | trunk_list_draining_add(trunk, tconn); |
3248 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_DRAINING, INFO); |
3249 | | |
3250 | | /* |
3251 | | * Immediately re-enqueue all pending |
3252 | | * requests, so the connection is drained |
3253 | | * quicker. |
3254 | | */ |
3255 | 0 | trunk_connection_requests_requeue_priv(tconn, TRUNK_REQUEST_STATE_PENDING, 0, false); |
3256 | 0 | } |
3257 | | |
3258 | | /** Transition a connection to the draining-to-reconnect state |
3259 | | * |
3260 | | * Removes the connection from the active heap so it won't be assigned any new |
3261 | | * connections. |
3262 | | */ |
3263 | | static void trunk_connection_enter_draining_to_free(trunk_connection_t *tconn) |
3264 | 0 | { |
3265 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3266 | |
|
3267 | 0 | FR_TIMER_DISARM(tconn->lifetime_ev); |
3268 | |
|
3269 | 0 | switch (tconn->pub.state) { |
3270 | 0 | case TRUNK_CONN_ACTIVE: |
3271 | 0 | case TRUNK_CONN_FULL: |
3272 | 0 | case TRUNK_CONN_INACTIVE: |
3273 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: |
3274 | 0 | case TRUNK_CONN_DRAINING: |
3275 | 0 | trunk_connection_remove(tconn); |
3276 | 0 | break; |
3277 | | |
3278 | 0 | default: |
3279 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_DRAINING_TO_FREE); |
3280 | 0 | } |
3281 | | |
3282 | 0 | fr_dlist_insert_head(&trunk->draining_to_free, tconn); |
3283 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_DRAINING_TO_FREE, INFO); |
3284 | | |
3285 | | /* |
3286 | | * Immediately re-enqueue all pending |
3287 | | * requests, so the connection is drained |
3288 | | * quicker. |
3289 | | */ |
3290 | 0 | trunk_connection_requests_requeue_priv(tconn, TRUNK_REQUEST_STATE_PENDING, 0, false); |
3291 | 0 | } |
3292 | | |
3293 | | |
3294 | | /** Transition a connection back to the active state |
3295 | | * |
3296 | | * This should only be called on a connection which is in the full state, |
3297 | | * inactive state, draining state or connecting state. |
3298 | | */ |
3299 | | static void trunk_connection_enter_active(trunk_connection_t *tconn) |
3300 | | { |
3301 | | trunk_t *trunk = tconn->pub.trunk; |
3302 | | int ret; |
3303 | | |
3304 | | switch (tconn->pub.state) { |
3305 | | case TRUNK_CONN_FULL: |
3306 | | case TRUNK_CONN_INACTIVE: |
3307 | | case TRUNK_CONN_INACTIVE_DRAINING: |
3308 | | case TRUNK_CONN_DRAINING: |
3309 | | trunk_connection_remove(tconn); |
3310 | | break; |
3311 | | |
3312 | | case TRUNK_CONN_INIT: |
3313 | | case TRUNK_CONN_CONNECTING: |
3314 | | trunk_connection_remove(tconn); |
3315 | | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_PENDING)); |
3316 | | break; |
3317 | | |
3318 | | default: |
3319 | | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_ACTIVE); |
3320 | | } |
3321 | | |
3322 | | ret = fr_minmax_heap_insert(trunk->active, tconn); /* re-insert into the active heap*/ |
3323 | | if (!fr_cond_assert_msg(ret == 0, "Failed inserting connection into active heap: %s", fr_strerror())) { |
3324 | | trunk_connection_enter_inactive_draining(tconn); |
3325 | | return; |
3326 | | } |
3327 | | |
3328 | | CONN_STATE_TRANSITION(TRUNK_CONN_ACTIVE, DEBUG2); |
3329 | | |
3330 | | /* |
3331 | | * Reorder the connections |
3332 | | */ |
3333 | | CONN_REORDER(tconn); |
3334 | | |
3335 | | /* |
3336 | | * Rebalance requests |
3337 | | */ |
3338 | | trunk_rebalance(trunk); |
3339 | | |
3340 | | /* |
3341 | | * We place requests into the backlog |
3342 | | * because there were no connections |
3343 | | * available to handle them. |
3344 | | * |
3345 | | * If a connection has become active |
3346 | | * chances are those backlogged requests |
3347 | | * can now be enqueued, so try and do |
3348 | | * that now. |
3349 | | * |
3350 | | * If there's requests sitting in the |
3351 | | * backlog indefinitely, it's because |
3352 | | * they were inserted there erroneously |
3353 | | * when there were active connections |
3354 | | * which could have handled them. |
3355 | | */ |
3356 | | trunk_backlog_drain(trunk); |
3357 | | } |
3358 | | |
3359 | | /** Connection transitioned to the init state |
3360 | | * |
3361 | | * Reflect the connection state change in the lists we use to track connections. |
3362 | | * |
3363 | | * @note This function is only called from the connection API as a watcher. |
3364 | | * |
3365 | | * @param[in] conn The connection which changes state. |
3366 | | * @param[in] prev The connection is was in. |
3367 | | * @param[in] state The connection is now in. |
3368 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3369 | | */ |
3370 | | static void _trunk_connection_on_init(UNUSED connection_t *conn, |
3371 | | UNUSED connection_state_t prev, |
3372 | | UNUSED connection_state_t state, |
3373 | | void *uctx) |
3374 | 0 | { |
3375 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3376 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3377 | |
|
3378 | 0 | switch (tconn->pub.state) { |
3379 | 0 | case TRUNK_CONN_HALTED: |
3380 | 0 | break; |
3381 | | |
3382 | 0 | case TRUNK_CONN_CLOSED: |
3383 | 0 | trunk_connection_remove(tconn); |
3384 | 0 | break; |
3385 | | |
3386 | 0 | default: |
3387 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_INIT); |
3388 | 0 | } |
3389 | | |
3390 | 0 | fr_dlist_insert_head(&trunk->init, tconn); |
3391 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_INIT, DEBUG2); |
3392 | 0 | } |
3393 | | |
3394 | | /** Connection transitioned to the connecting state |
3395 | | * |
3396 | | * Reflect the connection state change in the lists we use to track connections. |
3397 | | * |
3398 | | * @note This function is only called from the connection API as a watcher. |
3399 | | * |
3400 | | * @param[in] conn The connection which changes state. |
3401 | | * @param[in] prev The connection is was in. |
3402 | | * @param[in] state The connection is now in. |
3403 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3404 | | */ |
3405 | | static void _trunk_connection_on_connecting(UNUSED connection_t *conn, |
3406 | | UNUSED connection_state_t prev, |
3407 | | UNUSED connection_state_t state, |
3408 | | void *uctx) |
3409 | 0 | { |
3410 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3411 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3412 | |
|
3413 | 0 | switch (tconn->pub.state) { |
3414 | 0 | case TRUNK_CONN_INIT: |
3415 | 0 | case TRUNK_CONN_CLOSED: |
3416 | 0 | trunk_connection_remove(tconn); |
3417 | 0 | break; |
3418 | | |
3419 | 0 | default: |
3420 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_CONNECTING); |
3421 | 0 | } |
3422 | | |
3423 | | /* |
3424 | | * If a connection just entered the |
3425 | | * connecting state, it should have |
3426 | | * no requests associated with it. |
3427 | | */ |
3428 | 0 | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0); |
3429 | |
|
3430 | 0 | fr_dlist_insert_head(&trunk->connecting, tconn); /* MUST remain a head insertion for reconnect logic */ |
3431 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_CONNECTING, INFO); |
3432 | 0 | } |
3433 | | |
3434 | | /** Connection transitioned to the shutdown state |
3435 | | * |
3436 | | * If we're not already in the draining-to-free state, transition there now. |
3437 | | * |
3438 | | * The idea is that if something signalled the connection to shutdown, we need |
3439 | | * to reflect that by dequeuing any pending requests, not accepting new ones, |
3440 | | * and waiting for the existing requests to complete. |
3441 | | * |
3442 | | * @note This function is only called from the connection API as a watcher. |
3443 | | * |
3444 | | * @param[in] conn The connection which changes state. |
3445 | | * @param[in] prev The connection is was in. |
3446 | | * @param[in] state The connection is now in. |
3447 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3448 | | */ |
3449 | | static void _trunk_connection_on_shutdown(UNUSED connection_t *conn, |
3450 | | UNUSED connection_state_t prev, |
3451 | | UNUSED connection_state_t state, |
3452 | | void *uctx) |
3453 | | { |
3454 | | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3455 | | |
3456 | | switch (tconn->pub.state) { |
3457 | | case TRUNK_CONN_DRAINING_TO_FREE: |
3458 | | /* |
3459 | | * Shutdown from draining-to-free means no outstanding requests. |
3460 | | * Now signal to halt. |
3461 | | */ |
3462 | | connection_signal_halt(conn); |
3463 | | return; |
3464 | | |
3465 | | case TRUNK_CONN_ACTIVE: /* Transition to draining-to-free */ |
3466 | | case TRUNK_CONN_FULL: |
3467 | | case TRUNK_CONN_INACTIVE: |
3468 | | case TRUNK_CONN_INACTIVE_DRAINING: |
3469 | | case TRUNK_CONN_DRAINING: |
3470 | | break; |
3471 | | |
3472 | | case TRUNK_CONN_INIT: |
3473 | | case TRUNK_CONN_CONNECTING: |
3474 | | case TRUNK_CONN_CLOSED: |
3475 | | case TRUNK_CONN_HALTED: |
3476 | | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_DRAINING_TO_FREE); |
3477 | | } |
3478 | | |
3479 | | trunk_connection_enter_draining_to_free(tconn); |
3480 | | } |
3481 | | |
3482 | | /** Trigger a reconnection of the trunk connection |
3483 | | * |
3484 | | * @param[in] tl timer list the timer was inserted into. |
3485 | | * @param[in] now Current time. |
3486 | | * @param[in] uctx The tconn. |
3487 | | */ |
3488 | | static void _trunk_connection_lifetime_expire(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx) |
3489 | 0 | { |
3490 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3491 | |
|
3492 | 0 | trunk_connection_enter_draining_to_free(tconn); |
3493 | 0 | } |
3494 | | |
3495 | | /** Connection transitioned to the connected state |
3496 | | * |
3497 | | * Reflect the connection state change in the lists we use to track connections. |
3498 | | * |
3499 | | * @note This function is only called from the connection API as a watcher. |
3500 | | * |
3501 | | * @param[in] conn The connection which changes state. |
3502 | | * @param[in] prev The connection is was in. |
3503 | | * @param[in] state The connection is now in. |
3504 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3505 | | */ |
3506 | | static void _trunk_connection_on_connected(UNUSED connection_t *conn, |
3507 | | UNUSED connection_state_t prev, |
3508 | | UNUSED connection_state_t state, |
3509 | | void *uctx) |
3510 | 0 | { |
3511 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3512 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3513 | | |
3514 | | /* |
3515 | | * If a connection was just connected, it should only |
3516 | | * have a pending list of requests. This state is found |
3517 | | * in the rlm_radius module, which starts a new trunk, |
3518 | | * and then immediately enqueues a request onto it. The |
3519 | | * alternative for rlm_radius is to keep it's own queue |
3520 | | * of pending requests before the trunk is fully |
3521 | | * initialized. And then enqueue them onto the trunk |
3522 | | * when the trunk is connected. |
3523 | | * |
3524 | | * It's instead easier (and makes more sense) to allow |
3525 | | * the trunk to accept packets into its queue. If there |
3526 | | * are no connections within a period of time, then the |
3527 | | * requests will retry, or will time out. |
3528 | | */ |
3529 | 0 | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_PENDING)); |
3530 | | |
3531 | | /* |
3532 | | * Set here, as the active state can |
3533 | | * be transitioned to from full and |
3534 | | * draining too. |
3535 | | */ |
3536 | 0 | trunk->pub.last_connected = fr_time(); |
3537 | | |
3538 | | /* |
3539 | | * Set last_write_success so that idle timeout checks will run |
3540 | | * from when the connection has connected if they fire before |
3541 | | * any requests are written, rather than from server start time. |
3542 | | */ |
3543 | 0 | tconn->pub.last_write_success = fr_time(); |
3544 | | |
3545 | | /* |
3546 | | * Insert a timer to reconnect the |
3547 | | * connection periodically. |
3548 | | */ |
3549 | 0 | if (fr_time_delta_ispos(trunk->conf.lifetime)) { |
3550 | 0 | if (fr_timer_in(tconn, trunk->el->tl, &tconn->lifetime_ev, |
3551 | 0 | trunk->conf.lifetime, false, _trunk_connection_lifetime_expire, tconn) < 0) { |
3552 | 0 | PERROR("Failed inserting connection reconnection timer event, halting connection"); |
3553 | 0 | connection_signal_shutdown(tconn->pub.conn); |
3554 | 0 | return; |
3555 | 0 | } |
3556 | 0 | } |
3557 | | |
3558 | 0 | trunk_connection_enter_active(tconn); |
3559 | 0 | } |
3560 | | |
3561 | | /** Connection failed after it was connected |
3562 | | * |
3563 | | * Reflect the connection state change in the lists we use to track connections. |
3564 | | * |
3565 | | * @note This function is only called from the connection API as a watcher. |
3566 | | * |
3567 | | * @param[in] conn The connection which changes state. |
3568 | | * @param[in] prev The connection is was in. |
3569 | | * @param[in] state The connection is now in. |
3570 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3571 | | */ |
3572 | | static void _trunk_connection_on_closed(UNUSED connection_t *conn, |
3573 | | UNUSED connection_state_t prev, |
3574 | | UNUSED connection_state_t state, |
3575 | | void *uctx) |
3576 | 0 | { |
3577 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3578 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3579 | 0 | bool need_requeue = false; |
3580 | |
|
3581 | 0 | switch (tconn->pub.state) { |
3582 | 0 | case TRUNK_CONN_ACTIVE: |
3583 | 0 | case TRUNK_CONN_FULL: |
3584 | 0 | case TRUNK_CONN_INACTIVE: |
3585 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: |
3586 | 0 | case TRUNK_CONN_DRAINING: |
3587 | 0 | case TRUNK_CONN_DRAINING_TO_FREE: |
3588 | 0 | need_requeue = true; |
3589 | 0 | trunk_connection_remove(tconn); |
3590 | 0 | break; |
3591 | | |
3592 | 0 | case TRUNK_CONN_INIT: /* Initialisation failed */ |
3593 | 0 | case TRUNK_CONN_CONNECTING: |
3594 | 0 | trunk_connection_remove(tconn); |
3595 | 0 | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0); |
3596 | 0 | break; |
3597 | | |
3598 | 0 | case TRUNK_CONN_CLOSED: |
3599 | 0 | case TRUNK_CONN_HALTED: /* Can't move backwards? */ |
3600 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_CLOSED); |
3601 | 0 | } |
3602 | | |
3603 | 0 | fr_dlist_insert_head(&trunk->closed, tconn); /* MUST remain a head insertion for reconnect logic */ |
3604 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_CLOSED, INFO); |
3605 | | |
3606 | | /* |
3607 | | * Now *AFTER* the connection has been |
3608 | | * removed from the active, pool |
3609 | | * re-enqueue the requests. |
3610 | | */ |
3611 | 0 | if (need_requeue) trunk_connection_requests_requeue_priv(tconn, TRUNK_REQUEST_STATE_ALL, 0, true); |
3612 | | |
3613 | | /* |
3614 | | * There should be no requests left on this |
3615 | | * connection. They should have all been |
3616 | | * moved off or failed. |
3617 | | */ |
3618 | 0 | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0); |
3619 | | |
3620 | | /* |
3621 | | * Clear statistics and flags |
3622 | | */ |
3623 | 0 | tconn->sent_count = 0; |
3624 | | |
3625 | | /* |
3626 | | * Remove the reconnect event |
3627 | | */ |
3628 | 0 | if (fr_time_delta_ispos(trunk->conf.lifetime)) FR_TIMER_DELETE(&tconn->lifetime_ev); |
3629 | | |
3630 | | /* |
3631 | | * Remove the I/O events |
3632 | | */ |
3633 | 0 | trunk_connection_event_update(tconn); |
3634 | 0 | } |
3635 | | |
3636 | | /** Connection failed |
3637 | | * |
3638 | | * @param[in] conn The connection which changes state. |
3639 | | * @param[in] prev The connection is was in. |
3640 | | * @param[in] state The connection is now in. |
3641 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3642 | | */ |
3643 | | static void _trunk_connection_on_failed(connection_t *conn, |
3644 | | connection_state_t prev, |
3645 | | connection_state_t state, |
3646 | | void *uctx) |
3647 | 0 | { |
3648 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3649 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3650 | | |
3651 | | /* |
3652 | | * Need to set this first as it |
3653 | | * determines whether requests are |
3654 | | * re-queued or fail outright. |
3655 | | */ |
3656 | 0 | trunk->pub.last_failed = fr_time(); |
3657 | | |
3658 | | /* |
3659 | | * Failed in the init state, transition the |
3660 | | * connection to closed, else we get an |
3661 | | * INIT -> INIT transition which triggers |
3662 | | * an assert. |
3663 | | */ |
3664 | 0 | if (prev == CONNECTION_STATE_INIT) _trunk_connection_on_closed(conn, prev, state, uctx); |
3665 | | |
3666 | | /* |
3667 | | * See what the state of the trunk is |
3668 | | * if there are no connections that could |
3669 | | * potentially accept requests in the near |
3670 | | * future, then fail all the requests in the |
3671 | | * trunk backlog. |
3672 | | */ |
3673 | 0 | if ((prev == CONNECTION_STATE_CONNECTED) && |
3674 | 0 | (trunk_connection_count_by_state(trunk, |
3675 | 0 | (TRUNK_CONN_ACTIVE | |
3676 | 0 | TRUNK_CONN_FULL | |
3677 | 0 | TRUNK_CONN_DRAINING)) == 0)) trunk_backlog_drain(trunk); |
3678 | 0 | } |
3679 | | |
3680 | | /** Connection transitioned to the halted state |
3681 | | * |
3682 | | * Remove the connection remove all lists, as it's likely about to be freed. |
3683 | | * |
3684 | | * Setting the trunk back to the init state ensures that if the code is ever |
3685 | | * refactored and #connection_signal_reconnect is used after a connection |
3686 | | * is halted, then everything is maintained in a valid state. |
3687 | | * |
3688 | | * @note This function is only called from the connection API as a watcher. |
3689 | | * |
3690 | | * @param[in] conn The connection which changes state. |
3691 | | * @param[in] prev The connection is was in. |
3692 | | * @param[in] state The connection is now in. |
3693 | | * @param[in] uctx The trunk_connection_t wrapping the connection. |
3694 | | */ |
3695 | | static void _trunk_connection_on_halted(UNUSED connection_t *conn, |
3696 | | UNUSED connection_state_t prev, |
3697 | | UNUSED connection_state_t state, |
3698 | | void *uctx) |
3699 | 0 | { |
3700 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
3701 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3702 | |
|
3703 | 0 | switch (tconn->pub.state) { |
3704 | 0 | case TRUNK_CONN_INIT: |
3705 | 0 | case TRUNK_CONN_CLOSED: |
3706 | 0 | trunk_connection_remove(tconn); |
3707 | 0 | break; |
3708 | | |
3709 | 0 | default: |
3710 | 0 | CONN_BAD_STATE_TRANSITION(TRUNK_CONN_HALTED); |
3711 | 0 | } |
3712 | | |
3713 | | /* |
3714 | | * It began life in the halted state, |
3715 | | * and will end life in the halted state. |
3716 | | */ |
3717 | 0 | CONN_STATE_TRANSITION(TRUNK_CONN_HALTED, DEBUG2); |
3718 | | |
3719 | | /* |
3720 | | * There should be no requests left on this |
3721 | | * connection. They should have all been |
3722 | | * moved off or failed. |
3723 | | */ |
3724 | 0 | fr_assert(trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0); |
3725 | | |
3726 | | /* |
3727 | | * And free the connection... |
3728 | | */ |
3729 | 0 | if (trunk->in_handler) { |
3730 | | /* |
3731 | | * ...later. |
3732 | | */ |
3733 | 0 | fr_dlist_insert_tail(&trunk->to_free, tconn); |
3734 | 0 | return; |
3735 | 0 | } |
3736 | 0 | talloc_free(tconn); |
3737 | 0 | } |
3738 | | |
3739 | | /** Free a connection |
3740 | | * |
3741 | | * Enforces orderly free order of children of the tconn |
3742 | | */ |
3743 | | static int _trunk_connection_free(trunk_connection_t *tconn) |
3744 | 0 | { |
3745 | 0 | fr_assert(tconn->pub.state == TRUNK_CONN_HALTED); |
3746 | 0 | fr_assert(!fr_dlist_entry_in_list(&tconn->entry)); /* Should not be in a list */ |
3747 | | |
3748 | | /* |
3749 | | * Loop over all the requests we gathered |
3750 | | * and transition them to the failed state, |
3751 | | * freeing them. |
3752 | | * |
3753 | | * Usually, requests will be re-queued when |
3754 | | * a connection enters the closed state, |
3755 | | * but in this case because the whole trunk |
3756 | | * is being freed, we don't bother, and |
3757 | | * just signal to the API client that the |
3758 | | * requests failed. |
3759 | | */ |
3760 | 0 | if (tconn->pub.trunk->freeing) { |
3761 | 0 | fr_dlist_head_t to_fail; |
3762 | 0 | trunk_request_t *treq = NULL; |
3763 | |
|
3764 | 0 | fr_dlist_talloc_init(&to_fail, trunk_request_t, entry); |
3765 | | |
3766 | | /* |
3767 | | * Remove requests from this connection |
3768 | | */ |
3769 | 0 | trunk_connection_requests_dequeue(&to_fail, tconn, TRUNK_REQUEST_STATE_ALL, 0); |
3770 | 0 | while ((treq = fr_dlist_next(&to_fail, treq))) { |
3771 | 0 | trunk_request_t *prev; |
3772 | |
|
3773 | 0 | prev = fr_dlist_remove(&to_fail, treq); |
3774 | 0 | trunk_request_enter_failed(treq); |
3775 | 0 | treq = prev; |
3776 | 0 | } |
3777 | 0 | } |
3778 | | |
3779 | | /* |
3780 | | * Ensure we're not signalled by the connection |
3781 | | * as it processes its backlog of state changes, |
3782 | | * as we are about to be freed. |
3783 | | */ |
3784 | 0 | connection_del_watch_pre(tconn->pub.conn, CONNECTION_STATE_INIT, _trunk_connection_on_init); |
3785 | 0 | connection_del_watch_post(tconn->pub.conn, CONNECTION_STATE_CONNECTING, _trunk_connection_on_connecting); |
3786 | 0 | connection_del_watch_post(tconn->pub.conn, CONNECTION_STATE_CONNECTED, _trunk_connection_on_connected); |
3787 | 0 | connection_del_watch_pre(tconn->pub.conn, CONNECTION_STATE_CLOSED, _trunk_connection_on_closed); |
3788 | 0 | connection_del_watch_post(tconn->pub.conn, CONNECTION_STATE_SHUTDOWN, _trunk_connection_on_shutdown); |
3789 | 0 | connection_del_watch_pre(tconn->pub.conn, CONNECTION_STATE_FAILED, _trunk_connection_on_failed); |
3790 | 0 | connection_del_watch_post(tconn->pub.conn, CONNECTION_STATE_HALTED, _trunk_connection_on_halted); |
3791 | | |
3792 | | /* |
3793 | | * This may return -1, indicating the free was deferred |
3794 | | * this is fine. It just means the conn will be freed |
3795 | | * after all the handlers have exited. |
3796 | | */ |
3797 | 0 | (void)talloc_free(tconn->pub.conn); |
3798 | 0 | tconn->pub.conn = NULL; |
3799 | |
|
3800 | 0 | return 0; |
3801 | 0 | } |
3802 | | |
3803 | | /** Attempt to spawn a new connection |
3804 | | * |
3805 | | * Calls the API client's alloc() callback to create a new connection_t, |
3806 | | * then inserts the connection into the 'connecting' list. |
3807 | | * |
3808 | | * @param[in] trunk to spawn connection in. |
3809 | | * @param[in] now The current time. |
3810 | | */ |
3811 | | static int trunk_connection_spawn(trunk_t *trunk, fr_time_t now) |
3812 | 0 | { |
3813 | 0 | trunk_connection_t *tconn; |
3814 | | |
3815 | | |
3816 | | /* |
3817 | | * Call the API client's callback to create |
3818 | | * a new connection_t. |
3819 | | */ |
3820 | 0 | MEM(tconn = talloc_zero(trunk, trunk_connection_t)); |
3821 | 0 | tconn->pub.trunk = trunk; |
3822 | 0 | tconn->pub.state = TRUNK_CONN_HALTED; /* All connections start in the halted state */ |
3823 | | |
3824 | | /* |
3825 | | * Allocate a new connection_t or fail. |
3826 | | */ |
3827 | 0 | DO_CONNECTION_ALLOC(tconn); |
3828 | | |
3829 | 0 | MEM(tconn->pending = fr_heap_talloc_alloc(tconn, _trunk_request_prioritise, trunk_request_t, heap_id, 0)); |
3830 | 0 | fr_dlist_talloc_init(&tconn->sent, trunk_request_t, entry); |
3831 | 0 | fr_dlist_talloc_init(&tconn->reapable, trunk_request_t, entry); |
3832 | 0 | fr_dlist_talloc_init(&tconn->cancel, trunk_request_t, entry); |
3833 | 0 | fr_dlist_talloc_init(&tconn->cancel_sent, trunk_request_t, entry); |
3834 | | |
3835 | | /* |
3836 | | * OK, we have the connection, now setup watch |
3837 | | * points so we know when it changes state. |
3838 | | * |
3839 | | * This lets us automatically move the tconn |
3840 | | * between the different lists in the trunk |
3841 | | * with minimum extra code. |
3842 | | */ |
3843 | 0 | connection_add_watch_pre(tconn->pub.conn, CONNECTION_STATE_INIT, |
3844 | 0 | _trunk_connection_on_init, false, tconn); /* Before init() has been called */ |
3845 | |
|
3846 | 0 | connection_add_watch_post(tconn->pub.conn, CONNECTION_STATE_CONNECTING, |
3847 | 0 | _trunk_connection_on_connecting, false, tconn); /* After init() has been called */ |
3848 | |
|
3849 | 0 | connection_add_watch_post(tconn->pub.conn, CONNECTION_STATE_CONNECTED, |
3850 | 0 | _trunk_connection_on_connected, false, tconn); /* After open() has been called */ |
3851 | |
|
3852 | 0 | connection_add_watch_pre(tconn->pub.conn, CONNECTION_STATE_CLOSED, |
3853 | 0 | _trunk_connection_on_closed, false, tconn); /* Before close() has been called */ |
3854 | |
|
3855 | 0 | connection_add_watch_pre(tconn->pub.conn, CONNECTION_STATE_FAILED, |
3856 | 0 | _trunk_connection_on_failed, false, tconn); /* Before failed() has been called */ |
3857 | |
|
3858 | 0 | connection_add_watch_post(tconn->pub.conn, CONNECTION_STATE_SHUTDOWN, |
3859 | 0 | _trunk_connection_on_shutdown, false, tconn); /* After shutdown() has been called */ |
3860 | |
|
3861 | 0 | connection_add_watch_post(tconn->pub.conn, CONNECTION_STATE_HALTED, |
3862 | 0 | _trunk_connection_on_halted, false, tconn); /* About to be freed */ |
3863 | |
|
3864 | 0 | talloc_set_destructor(tconn, _trunk_connection_free); |
3865 | |
|
3866 | 0 | connection_signal_init(tconn->pub.conn); /* annnnd GO! */ |
3867 | |
|
3868 | 0 | trunk->pub.last_open = now; |
3869 | |
|
3870 | 0 | return 0; |
3871 | 0 | } |
3872 | | |
3873 | | /** Pop a cancellation request off a connection's cancellation queue |
3874 | | * |
3875 | | * The request we return is advanced by the request moving out of the |
3876 | | * cancel state and into the cancel_sent or cancel_complete state. |
3877 | | * |
3878 | | * One of these signalling functions must be called after the request |
3879 | | * has been popped: |
3880 | | * |
3881 | | * - #trunk_request_signal_cancel_sent |
3882 | | * The remote datastore has been informed, but we need to wait for acknowledgement. |
3883 | | * The #trunk_request_demux_t callback must handle the acks calling |
3884 | | * #trunk_request_signal_cancel_complete when an ack is received. |
3885 | | * |
3886 | | * - #trunk_request_signal_cancel_complete |
3887 | | * The request was cancelled and we don't need to wait, clean it up immediately. |
3888 | | * |
3889 | | * @param[out] treq_out to process |
3890 | | * @param[in] tconn Connection to drain cancellation request from. |
3891 | | * @return |
3892 | | * - 1 if no more requests. |
3893 | | * - 0 if a new request was written to treq_out. |
3894 | | * - -1 if the connection was previously freed. Caller *MUST NOT* touch any |
3895 | | * memory or requests associated with the connection. |
3896 | | * - -2 if called outside of the cancel muxer. |
3897 | | */ |
3898 | | int trunk_connection_pop_cancellation(trunk_request_t **treq_out, trunk_connection_t *tconn) |
3899 | 0 | { |
3900 | 0 | if (unlikely(tconn->pub.state == TRUNK_CONN_HALTED)) return -1; |
3901 | | |
3902 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_CANCEL_MUX(tconn->pub.trunk), |
3903 | 0 | "%s can only be called from within request_cancel_mux handler", |
3904 | 0 | __FUNCTION__)) return -2; |
3905 | | |
3906 | 0 | *treq_out = tconn->cancel_partial ? tconn->cancel_partial : fr_dlist_head(&tconn->cancel); |
3907 | 0 | if (!*treq_out) return 1; |
3908 | | |
3909 | 0 | return 0; |
3910 | 0 | } |
3911 | | |
3912 | | /** Pop a request off a connection's pending queue |
3913 | | * |
3914 | | * The request we return is advanced by the request moving out of the partial or |
3915 | | * pending states, when the mux function signals us. |
3916 | | * |
3917 | | * If the same request is returned again and again, it means the muxer isn't actually |
3918 | | * doing anything with the request we returned, and it's and error in the muxer code. |
3919 | | * |
3920 | | * One of these signalling functions must be used after the request has been popped: |
3921 | | * |
3922 | | * - #trunk_request_signal_complete |
3923 | | * The request was completed. Either we got a synchronous response, or we knew the |
3924 | | * response without contacting an external server (cache). |
3925 | | * |
3926 | | * - #trunk_request_signal_fail |
3927 | | * Failed muxing the request due to a permanent issue, i.e. an invalid request. |
3928 | | * |
3929 | | * - #trunk_request_signal_partial |
3930 | | * Wrote part of a request. This request will be returned on the next call to this |
3931 | | * function so that the request_mux function can finish writing it. Only useful |
3932 | | * for stream type connections. Datagram type connections cannot have partial |
3933 | | * writes. |
3934 | | * |
3935 | | * - #trunk_request_signal_sent Successfully sent a request. |
3936 | | * |
3937 | | * @param[out] treq_out to process |
3938 | | * @param[in] tconn to pop a request from. |
3939 | | * @return |
3940 | | * - 1 if no more requests. |
3941 | | * - 0 if a new request was written to treq_out. |
3942 | | * - -1 if the connection was previously freed. Caller *MUST NOT* touch any |
3943 | | * memory or requests associated with the connection. |
3944 | | * - -2 if called outside of the muxer. |
3945 | | */ |
3946 | | int trunk_connection_pop_request(trunk_request_t **treq_out, trunk_connection_t *tconn) |
3947 | 0 | { |
3948 | 0 | if (unlikely(tconn->pub.state == TRUNK_CONN_HALTED)) return -1; |
3949 | | |
3950 | 0 | if (!fr_cond_assert_msg(IN_REQUEST_MUX(tconn->pub.trunk), |
3951 | 0 | "%s can only be called from within request_mux handler", |
3952 | 0 | __FUNCTION__)) return -2; |
3953 | | |
3954 | 0 | *treq_out = tconn->partial ? tconn->partial : fr_heap_peek(tconn->pending); |
3955 | 0 | if (!*treq_out) return 1; |
3956 | | |
3957 | 0 | return 0; |
3958 | 0 | } |
3959 | | |
3960 | | /** Signal that a trunk connection is writable |
3961 | | * |
3962 | | * Should be called from the 'write' I/O handler to signal that requests can be enqueued. |
3963 | | * |
3964 | | * @param[in] tconn to signal. |
3965 | | */ |
3966 | | void trunk_connection_signal_writable(trunk_connection_t *tconn) |
3967 | 0 | { |
3968 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3969 | |
|
3970 | 0 | if (!fr_cond_assert_msg(!IN_HANDLER(tconn->pub.trunk), |
3971 | 0 | "%s cannot be called within a handler", __FUNCTION__)) return; |
3972 | | |
3973 | 0 | DEBUG3("[%" PRIu64 "] Signalled writable", tconn->pub.conn->id); |
3974 | |
|
3975 | 0 | trunk_connection_writable(tconn); |
3976 | 0 | } |
3977 | | |
3978 | | /** Signal that a trunk connection is readable |
3979 | | * |
3980 | | * Should be called from the 'read' I/O handler to signal that requests should be dequeued. |
3981 | | * |
3982 | | * @param[in] tconn to signal. |
3983 | | */ |
3984 | | void trunk_connection_signal_readable(trunk_connection_t *tconn) |
3985 | 0 | { |
3986 | 0 | trunk_t *trunk = tconn->pub.trunk; |
3987 | |
|
3988 | 0 | if (!fr_cond_assert_msg(!IN_HANDLER(tconn->pub.trunk), |
3989 | 0 | "%s cannot be called within a handler", __FUNCTION__)) return; |
3990 | | |
3991 | 0 | DEBUG3("[%" PRIu64 "] Signalled readable", tconn->pub.conn->id); |
3992 | |
|
3993 | 0 | trunk_connection_readable(tconn); |
3994 | 0 | } |
3995 | | |
3996 | | /** Signal a trunk connection cannot accept more requests |
3997 | | * |
3998 | | * @param[in] tconn to signal. |
3999 | | */ |
4000 | | void trunk_connection_signal_inactive(trunk_connection_t *tconn) |
4001 | 0 | { |
4002 | | /* Can be called anywhere */ |
4003 | |
|
4004 | 0 | switch (tconn->pub.state) { |
4005 | 0 | case TRUNK_CONN_ACTIVE: |
4006 | 0 | case TRUNK_CONN_FULL: |
4007 | 0 | trunk_connection_enter_inactive(tconn); |
4008 | 0 | break; |
4009 | | |
4010 | 0 | case TRUNK_CONN_DRAINING: |
4011 | 0 | trunk_connection_enter_inactive_draining(tconn); |
4012 | 0 | break; |
4013 | | |
4014 | 0 | default: |
4015 | 0 | return; |
4016 | 0 | } |
4017 | 0 | } |
4018 | | |
4019 | | /** Signal a trunk connection is no longer full |
4020 | | * |
4021 | | * @param[in] tconn to signal. |
4022 | | */ |
4023 | | void trunk_connection_signal_active(trunk_connection_t *tconn) |
4024 | 0 | { |
4025 | 0 | switch (tconn->pub.state) { |
4026 | 0 | case TRUNK_CONN_FULL: |
4027 | 0 | trunk_connection_auto_unfull(tconn); /* Mark as active if it should be active */ |
4028 | 0 | break; |
4029 | | |
4030 | 0 | case TRUNK_CONN_INACTIVE: |
4031 | | /* |
4032 | | * Do the appropriate state transition based on |
4033 | | * how many requests the trunk connection is |
4034 | | * currently servicing. |
4035 | | */ |
4036 | 0 | if (trunk_connection_is_full(tconn)) { |
4037 | 0 | trunk_connection_enter_full(tconn); |
4038 | 0 | break; |
4039 | 0 | } |
4040 | 0 | trunk_connection_enter_active(tconn); |
4041 | 0 | break; |
4042 | | |
4043 | | /* |
4044 | | * Unsetting the active flag just moves |
4045 | | * the connection back to the normal |
4046 | | * draining state. |
4047 | | */ |
4048 | 0 | case TRUNK_CONN_INACTIVE_DRAINING: /* Only an external signal can trigger this transition */ |
4049 | 0 | trunk_connection_enter_draining(tconn); |
4050 | 0 | break; |
4051 | | |
4052 | 0 | default: |
4053 | 0 | return; |
4054 | 0 | } |
4055 | 0 | } |
4056 | | |
4057 | | /** Signal a trunk connection is no longer viable |
4058 | | * |
4059 | | * @param[in] tconn to signal. |
4060 | | * @param[in] reason the connection is being reconnected. |
4061 | | */ |
4062 | | void trunk_connection_signal_reconnect(trunk_connection_t *tconn, connection_reason_t reason) |
4063 | 0 | { |
4064 | 0 | connection_signal_reconnect(tconn->pub.conn, reason); |
4065 | 0 | } |
4066 | | |
4067 | | /** Standard I/O read function |
4068 | | * |
4069 | | * Underlying FD in now readable, so call the trunk to read any pending requests |
4070 | | * from this connection. |
4071 | | * |
4072 | | * @param[in] el The event list signalling. |
4073 | | * @param[in] fd that's now readable. |
4074 | | * @param[in] flags describing the read event. |
4075 | | * @param[in] uctx The trunk connection handle (tconn). |
4076 | | */ |
4077 | | void trunk_connection_callback_readable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx) |
4078 | 0 | { |
4079 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
4080 | |
|
4081 | 0 | trunk_connection_signal_readable(tconn); |
4082 | 0 | } |
4083 | | |
4084 | | /** Standard I/O write function |
4085 | | * |
4086 | | * Underlying FD is now writable, so call the trunk to write any pending requests |
4087 | | * to this connection. |
4088 | | * |
4089 | | * @param[in] el The event list signalling. |
4090 | | * @param[in] fd that's now writable. |
4091 | | * @param[in] flags describing the write event. |
4092 | | * @param[in] uctx The trunk connection handle (tcon). |
4093 | | */ |
4094 | | void trunk_connection_callback_writable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx) |
4095 | 0 | { |
4096 | 0 | trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t); |
4097 | |
|
4098 | 0 | trunk_connection_signal_writable(tconn); |
4099 | 0 | } |
4100 | | |
4101 | | |
4102 | | /** Returns true if the trunk connection is in one of the specified states |
4103 | | * |
4104 | | * @param[in] tconn To check state for. |
4105 | | * @param[in] state to check |
4106 | | * @return |
4107 | | * - True if trunk connection is in a particular state. |
4108 | | * - False if trunk connection is not in a particular state. |
4109 | | */ |
4110 | | bool trunk_connection_in_state(trunk_connection_t *tconn, int state) |
4111 | 0 | { |
4112 | 0 | return (bool)(tconn->pub.state & state); |
4113 | 0 | } |
4114 | | |
4115 | | /** Close connections in a particular connection list if they have no requests associated with them |
4116 | | * |
4117 | | * @param[in] trunk containing connections we want to close. |
4118 | | * @param[in] head of list of connections to examine. |
4119 | | */ |
4120 | | static void trunk_connection_close_if_empty(trunk_t *trunk, fr_dlist_head_t *head) |
4121 | 0 | { |
4122 | 0 | trunk_connection_t *tconn = NULL; |
4123 | |
|
4124 | 0 | while ((tconn = fr_dlist_next(head, tconn))) { |
4125 | 0 | trunk_connection_t *prev; |
4126 | |
|
4127 | 0 | if (trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) != 0) continue; |
4128 | | |
4129 | 0 | prev = fr_dlist_prev(head, tconn); |
4130 | |
|
4131 | 0 | DEBUG3("Closing %s connection with no requests", |
4132 | 0 | fr_table_str_by_value(trunk_connection_states, tconn->pub.state, "<INVALID>")); |
4133 | | /* |
4134 | | * Close the connection as gracefully |
4135 | | * as possible by signalling it should |
4136 | | * shutdown. |
4137 | | * |
4138 | | * The connection, should, if serviced |
4139 | | * correctly by the underlying library, |
4140 | | * automatically transition to halted after |
4141 | | * all pending reads/writes are |
4142 | | * complete at which point we'll be informed |
4143 | | * and free our tconn wrapper. |
4144 | | */ |
4145 | 0 | connection_signal_shutdown(tconn->pub.conn); |
4146 | 0 | tconn = prev; |
4147 | 0 | } |
4148 | 0 | } |
4149 | | |
4150 | | /** Rebalance connections across active trunk members when a new connection becomes active |
4151 | | * |
4152 | | * We don't have any visibility into the connection prioritisation algorithm |
4153 | | * it's essentially a black box. |
4154 | | * |
4155 | | * We can however determine when the correct level of requests per connection |
4156 | | * has been reached, by dequeuing and requeing requests up until the point |
4157 | | * where the connection that just had a request dequeued, receives the same |
4158 | | * request back. |
4159 | | * |
4160 | | * @param[in] trunk The trunk to rebalance. |
4161 | | */ |
4162 | | static void trunk_rebalance(trunk_t *trunk) |
4163 | 0 | { |
4164 | 0 | trunk_connection_t *head; |
4165 | |
|
4166 | 0 | head = fr_minmax_heap_min_peek(trunk->active); |
4167 | | |
4168 | | /* |
4169 | | * Only rebalance if the top and bottom of |
4170 | | * the heap are not equal. |
4171 | | */ |
4172 | 0 | if (trunk->funcs.connection_prioritise(fr_minmax_heap_max_peek(trunk->active), head) == 0) return; |
4173 | | |
4174 | 0 | DEBUG3("Rebalancing requests"); |
4175 | | |
4176 | | /* |
4177 | | * Keep requeuing requests from the connection |
4178 | | * at the bottom of the heap until the |
4179 | | * connection at the top is shifted from that |
4180 | | * position. |
4181 | | */ |
4182 | 0 | while ((fr_minmax_heap_min_peek(trunk->active) == head) && |
4183 | 0 | trunk_connection_requests_requeue_priv(fr_minmax_heap_max_peek(trunk->active), |
4184 | 0 | TRUNK_REQUEST_STATE_PENDING, 1, false)); |
4185 | 0 | } |
4186 | | |
4187 | | /** Recalculate the trunk's aggregate state from its connection counts |
4188 | | * |
4189 | | * Derives the global #trunk_state_t from the number of connections in each |
4190 | | * connection state, and fires any registered state-change watchers (via |
4191 | | * #TRUNK_STATE_TRANSITION) if the aggregate state has changed. |
4192 | | * |
4193 | | * @param[in] trunk to update. |
4194 | | */ |
4195 | | static void trunk_state_update(trunk_t *trunk) |
4196 | 0 | { |
4197 | 0 | trunk_state_t new_state; |
4198 | | |
4199 | | /* |
4200 | | * Don't churn the state or fire watchers while the trunk is |
4201 | | * being torn down. |
4202 | | */ |
4203 | 0 | if (trunk->freeing) return; |
4204 | | |
4205 | 0 | if (trunk_connection_count_by_state(trunk, TRUNK_CONN_ACTIVE)) { |
4206 | | /* |
4207 | | * One or more connections are active and operational. The trunk is ACTIVE. |
4208 | | */ |
4209 | 0 | new_state = TRUNK_STATE_ACTIVE; |
4210 | |
|
4211 | 0 | } else if (trunk_connection_count_by_state(trunk, TRUNK_CONN_INIT | TRUNK_CONN_CONNECTING)) { |
4212 | | /* |
4213 | | * Connections are being opened, but none are usable yet. |
4214 | | * |
4215 | | * This is checked before FULL. If a connection is CONNECTING, then the trunk is by |
4216 | | * definition not full. |
4217 | | */ |
4218 | 0 | new_state = TRUNK_STATE_PENDING; |
4219 | |
|
4220 | 0 | } else if (trunk->conf.max && |
4221 | 0 | (trunk_connection_count_by_state(trunk, TRUNK_CONN_FULL) == trunk->conf.max)) { |
4222 | | /* |
4223 | | * No active or connecting connections, and every one of the maximum permitted |
4224 | | * connections is connected and full. The backend is reachable, but the trunk has no |
4225 | | * spare capacity, and can accept no more traffic. |
4226 | | */ |
4227 | 0 | new_state = TRUNK_STATE_FULL; |
4228 | |
|
4229 | 0 | } else if (trunk_connection_count_by_state(trunk, TRUNK_CONN_CLOSED)) { |
4230 | | /* |
4231 | | * Connections exist, but they have all failed and are |
4232 | | * closed / in reconnect backoff. The backend is |
4233 | | * currently unreachable. |
4234 | | */ |
4235 | 0 | new_state = TRUNK_STATE_FAILED; |
4236 | |
|
4237 | 0 | } else { |
4238 | 0 | new_state = TRUNK_STATE_IDLE; |
4239 | 0 | } |
4240 | |
|
4241 | 0 | if (new_state == trunk->pub.state) return; |
4242 | | |
4243 | | /* |
4244 | | * This can be reached from within a state-change watcher. A watcher may enqueue a request or |
4245 | | * reconnect a connection, which changes a connection's state and calls |
4246 | | * trunk_requests_per_connection() -> trunk_state_update(). |
4247 | | * |
4248 | | * Nested watcher calls are not allowed (see trunk_watch_call()). If we're already inside one, |
4249 | | * leave the state unchanged and let the next non-nested update, or the periodic trunk_manage(), |
4250 | | * reconcile it. |
4251 | | */ |
4252 | 0 | if (trunk->next_watcher != NULL) return; |
4253 | | |
4254 | 0 | TRUNK_STATE_TRANSITION(new_state); |
4255 | 0 | } |
4256 | | |
4257 | | /** Implements the algorithm we use to manage requests per connection levels |
4258 | | * |
4259 | | * This is executed periodically using a timer event, and opens/closes |
4260 | | * connections. |
4261 | | * |
4262 | | * The aim is to try and keep the request per connection level in a sweet spot, |
4263 | | * where there's enough outstanding work for the connection/pipelining to work |
4264 | | * efficiently, but not so much so that we encounter increased latency. |
4265 | | * |
4266 | | * In the request enqueue and dequeue functions we record every time the |
4267 | | * average number of requests per connection goes above the target count |
4268 | | * and record every time the average number of requests per connection goes |
4269 | | * below the target count. |
4270 | | * |
4271 | | * This may sound expensive, but in all cases we're just summing counters. |
4272 | | * CPU time required does not increase with additional requests, only with |
4273 | | * large numbers of connections. |
4274 | | * |
4275 | | * If we do encounter scaling issues, we can always maintain the counters |
4276 | | * as aggregates as an optimisation later. |
4277 | | * |
4278 | | * If when the management function runs, the trunk was above the target |
4279 | | * most recently, we: |
4280 | | * - Return if we've been in this state for a shorter period than 'open_delay'. |
4281 | | * - Return if we're at max. |
4282 | | * - Return if opening a new connection will take us below the load target. |
4283 | | * - Return if we last opened a connection within 'open_delay'. |
4284 | | * - Otherwise we attempt to open a new connection. |
4285 | | * |
4286 | | * If the trunk we below the target most recently, we: |
4287 | | * - Return if we've been in this state for a shorter period than 'close_delay'. |
4288 | | * - Return if we're at min. |
4289 | | * - Return if we have no connections. |
4290 | | * - Close a connection if min is 0, and we have no outstanding |
4291 | | * requests. Then return. |
4292 | | * - Return if closing a new connection will take us above the load target. |
4293 | | * - Return if we last closed a connection within 'closed_delay'. |
4294 | | * - Otherwise we move a connection to draining state. |
4295 | | */ |
4296 | | static void trunk_manage(trunk_t *trunk, fr_time_t now) |
4297 | 0 | { |
4298 | 0 | trunk_connection_t *tconn = NULL; |
4299 | 0 | trunk_request_t *treq; |
4300 | 0 | uint32_t average = 0; |
4301 | 0 | uint32_t req_count; |
4302 | 0 | uint16_t conn_count; |
4303 | |
|
4304 | 0 | DEBUG4("Managing trunk"); |
4305 | | |
4306 | | /* |
4307 | | * Cleanup requests in our request cache which |
4308 | | * have been reapable for too long. |
4309 | | */ |
4310 | 0 | while ((treq = trunk_list_free_requests_peek(trunk)) && |
4311 | 0 | fr_time_lteq(fr_time_add(treq->last_freed, trunk->conf.req_cleanup_delay), now)) talloc_free(treq); |
4312 | | |
4313 | | /* |
4314 | | * If we have idle connections, then close them, maintaining "min" connections. |
4315 | | */ |
4316 | 0 | if (fr_time_delta_ispos(trunk->conf.idle_timeout) && |
4317 | 0 | (fr_minmax_heap_num_elements(trunk->active) > trunk->conf.min)) { |
4318 | 0 | fr_minmax_heap_iter_t iter; |
4319 | 0 | fr_time_t idle_cutoff = fr_time_sub(now, trunk->conf.idle_timeout); |
4320 | |
|
4321 | 0 | for (tconn = fr_minmax_heap_iter_init(trunk->active, &iter); |
4322 | 0 | tconn; |
4323 | 0 | tconn = fr_minmax_heap_iter_next(trunk->active, &iter)) { |
4324 | | /* |
4325 | | * The connection has outstanding requests without replies, don't do anything. |
4326 | | */ |
4327 | 0 | if (fr_heap_num_elements(tconn->pending) > 0) continue; |
4328 | | |
4329 | | /* |
4330 | | * The connection was last active after the idle cutoff time, don't do anything. |
4331 | | */ |
4332 | 0 | if (fr_time_gt(tconn->pub.last_write_success, idle_cutoff)) continue; |
4333 | | |
4334 | | /* |
4335 | | * This connection has been inactive since before the idle timeout. Drain it, |
4336 | | * and free it. |
4337 | | * |
4338 | | * This also extracts the connection from the minmax heap, which invalidates the |
4339 | | * iterator, so we stop iterating over it. |
4340 | | */ |
4341 | 0 | trunk_connection_enter_draining_to_free(tconn); |
4342 | 0 | break; |
4343 | 0 | } |
4344 | 0 | } |
4345 | | |
4346 | | /* |
4347 | | * Free any connections which have drained |
4348 | | * and we didn't reactivate during the last |
4349 | | * round of management. |
4350 | | */ |
4351 | 0 | trunk_connection_close_if_empty(trunk, &trunk->inactive_draining); |
4352 | 0 | trunk_connection_close_if_empty(trunk, &trunk->draining); |
4353 | 0 | trunk_connection_close_if_empty(trunk, &trunk->draining_to_free); |
4354 | | |
4355 | | /* |
4356 | | * Process deferred connection freeing |
4357 | | */ |
4358 | 0 | if (!trunk->in_handler) fr_dlist_talloc_free(&trunk->to_free); |
4359 | | |
4360 | | /* |
4361 | | * Update the state of the trunk |
4362 | | */ |
4363 | 0 | trunk_state_update(trunk); |
4364 | | |
4365 | | /* |
4366 | | * A trunk can be signalled to not proactively |
4367 | | * manage connections if a destination is known |
4368 | | * to be unreachable, and doing so would result |
4369 | | * in spurious connections still being opened. |
4370 | | * |
4371 | | * We still run other connection management |
4372 | | * functions and just short circuit the function |
4373 | | * here. |
4374 | | */ |
4375 | 0 | if (!trunk->managing_connections) return; |
4376 | | |
4377 | | /* |
4378 | | * We're above the target requests per connection |
4379 | | * spawn more connections! |
4380 | | */ |
4381 | 0 | if (fr_time_gteq(trunk->pub.last_above_target, trunk->pub.last_below_target)) { |
4382 | | /* |
4383 | | * If connecting is provided, check we |
4384 | | * wouldn't have too many connections in |
4385 | | * the connecting state. |
4386 | | * |
4387 | | * This is a throttle in the case of transitory |
4388 | | * load spikes, or a backend becoming |
4389 | | * unavailable. |
4390 | | */ |
4391 | 0 | if ((trunk->conf.connecting > 0) && |
4392 | 0 | (trunk_connection_count_by_state(trunk, TRUNK_CONN_CONNECTING) >= |
4393 | 0 | trunk->conf.connecting)) { |
4394 | 0 | DEBUG4("Not opening connection - Too many (%u) connections in the connecting state", |
4395 | 0 | trunk->conf.connecting); |
4396 | 0 | return; |
4397 | 0 | } |
4398 | | |
4399 | 0 | trunk_requests_per_connection(&conn_count, &req_count, trunk, now, true); |
4400 | | |
4401 | | /* |
4402 | | * Only apply hysteresis if we have at least |
4403 | | * one available connection. |
4404 | | */ |
4405 | 0 | if (conn_count && fr_time_gt(fr_time_add(trunk->pub.last_above_target, trunk->conf.open_delay), now)) { |
4406 | 0 | DEBUG4("Not opening connection - Need to be above target for %pVs. It's been %pVs", |
4407 | 0 | fr_box_time_delta(trunk->conf.open_delay), |
4408 | 0 | fr_box_time_delta(fr_time_sub(now, trunk->pub.last_above_target))); |
4409 | 0 | return; /* too soon */ |
4410 | 0 | } |
4411 | | |
4412 | | /* |
4413 | | * We don't consider 'draining' connections |
4414 | | * in the max calculation, as if we do |
4415 | | * determine that we need to spawn a new |
4416 | | * request, then we'd move all 'draining' |
4417 | | * connections to active before spawning |
4418 | | * any new connections. |
4419 | | */ |
4420 | 0 | if ((trunk->conf.max > 0) && (conn_count >= trunk->conf.max)) { |
4421 | 0 | DEBUG4("Not opening connection - Have %u connections, need %u or below", |
4422 | 0 | conn_count, trunk->conf.max); |
4423 | 0 | return; |
4424 | 0 | } |
4425 | | |
4426 | | /* |
4427 | | * We consider requests pending on all connections |
4428 | | * and the trunk's backlog as that's the current count |
4429 | | * load. |
4430 | | */ |
4431 | 0 | if (!req_count) { |
4432 | 0 | DEBUG4("Not opening connection - No outstanding requests"); |
4433 | 0 | return; |
4434 | 0 | } |
4435 | | |
4436 | | /* |
4437 | | * Do the n+1 check, i.e. if we open one connection |
4438 | | * will that take us below our target threshold. |
4439 | | */ |
4440 | 0 | if (conn_count > 0) { |
4441 | 0 | average = ROUND_UP_DIV(req_count, (conn_count + 1)); |
4442 | 0 | if (average < trunk->conf.target_req_per_conn) { |
4443 | 0 | DEBUG4("Not opening connection - Would leave us below our target requests " |
4444 | 0 | "per connection (now %u, after open %u)", |
4445 | 0 | ROUND_UP_DIV(req_count, conn_count), average); |
4446 | 0 | return; |
4447 | 0 | } |
4448 | 0 | } else { |
4449 | 0 | (void)trunk_connection_spawn(trunk, now); |
4450 | 0 | return; |
4451 | 0 | } |
4452 | | |
4453 | | /* |
4454 | | * If we've got a connection in the draining list |
4455 | | * move it back into the active list if we've |
4456 | | * been requested to add a connection back in. |
4457 | | */ |
4458 | 0 | tconn = fr_dlist_head(&trunk->draining); |
4459 | 0 | if (tconn) { |
4460 | 0 | if (trunk_connection_is_full(tconn)) { |
4461 | 0 | trunk_connection_enter_full(tconn); |
4462 | 0 | } else { |
4463 | 0 | trunk_connection_enter_active(tconn); |
4464 | 0 | } |
4465 | 0 | return; |
4466 | 0 | } |
4467 | | |
4468 | | /* |
4469 | | * Implement delay if there's no connections that |
4470 | | * could be immediately re-activated. |
4471 | | */ |
4472 | 0 | if (fr_time_gt(fr_time_add(trunk->pub.last_open, trunk->conf.open_delay), now)) { |
4473 | 0 | DEBUG4("Not opening connection - Need to wait %pVs before opening another connection. " |
4474 | 0 | "It's been %pVs", |
4475 | 0 | fr_box_time_delta(trunk->conf.open_delay), |
4476 | 0 | fr_box_time_delta(fr_time_sub(now, trunk->pub.last_open))); |
4477 | 0 | return; |
4478 | 0 | } |
4479 | | |
4480 | 0 | DEBUG4("Opening connection - Above target requests per connection (now %u, target %u)", |
4481 | 0 | ROUND_UP_DIV(req_count, conn_count), trunk->conf.target_req_per_conn); |
4482 | | /* last_open set by trunk_connection_spawn */ |
4483 | 0 | (void)trunk_connection_spawn(trunk, now); |
4484 | 0 | } |
4485 | | |
4486 | | /* |
4487 | | * We're below the target requests per connection. |
4488 | | * Free some connections... |
4489 | | */ |
4490 | 0 | else if (fr_time_gt(trunk->pub.last_below_target, trunk->pub.last_above_target)) { |
4491 | 0 | if (fr_time_gt(fr_time_add(trunk->pub.last_below_target, trunk->conf.close_delay), now)) { |
4492 | 0 | DEBUG4("Not closing connection - Need to be below target for %pVs. It's been %pVs", |
4493 | 0 | fr_box_time_delta(trunk->conf.close_delay), |
4494 | 0 | fr_box_time_delta(fr_time_sub(now, trunk->pub.last_below_target))); |
4495 | 0 | return; /* too soon */ |
4496 | 0 | } |
4497 | | |
4498 | 0 | trunk_requests_per_connection(&conn_count, &req_count, trunk, now, true); |
4499 | |
|
4500 | 0 | if (!conn_count) { |
4501 | 0 | DEBUG4("Not closing connection - No connections to close!"); |
4502 | 0 | return; |
4503 | 0 | } |
4504 | | |
4505 | 0 | if ((trunk->conf.min > 0) && ((conn_count - 1) < trunk->conf.min)) { |
4506 | 0 | DEBUG4("Not closing connection - Have %u connections, need %u or above", |
4507 | 0 | conn_count, trunk->conf.min); |
4508 | 0 | return; |
4509 | 0 | } |
4510 | | |
4511 | 0 | if (!req_count) { |
4512 | 0 | DEBUG4("Closing connection - No outstanding requests"); |
4513 | 0 | goto close; |
4514 | 0 | } |
4515 | | |
4516 | | /* |
4517 | | * The minimum number of connections must be set |
4518 | | * to zero for this to work. |
4519 | | * min == 0, no requests, close all the connections. |
4520 | | * This is useful for backup databases, when |
4521 | | * maintaining the connection would lead to lots of |
4522 | | * log file churn. |
4523 | | */ |
4524 | 0 | if (conn_count == 1) { |
4525 | 0 | DEBUG4("Not closing connection - Would leave connections " |
4526 | 0 | "and there are still %u outstanding requests", req_count); |
4527 | 0 | return; |
4528 | 0 | } |
4529 | | |
4530 | | /* |
4531 | | * Do the n-1 check, i.e. if we close one connection |
4532 | | * will that take us above our target threshold. |
4533 | | */ |
4534 | 0 | average = ROUND_UP_DIV(req_count, (conn_count - 1)); |
4535 | 0 | if (average > trunk->conf.target_req_per_conn) { |
4536 | 0 | DEBUG4("Not closing connection - Would leave us above our target requests per connection " |
4537 | 0 | "(now %u, after close %u)", ROUND_UP_DIV(req_count, conn_count), average); |
4538 | 0 | return; |
4539 | 0 | } |
4540 | | |
4541 | 0 | DEBUG4("Closing connection - Below target requests per connection (now %u, target %u)", |
4542 | 0 | ROUND_UP_DIV(req_count, conn_count), trunk->conf.target_req_per_conn); |
4543 | |
|
4544 | 0 | close: |
4545 | 0 | if (fr_time_gt(fr_time_add(trunk->pub.last_closed, trunk->conf.close_delay), now)) { |
4546 | 0 | DEBUG4("Not closing connection - Need to wait %pVs before closing another connection. " |
4547 | 0 | "It's been %pVs", |
4548 | 0 | fr_box_time_delta(trunk->conf.close_delay), |
4549 | 0 | fr_box_time_delta(fr_time_sub(now, trunk->pub.last_closed))); |
4550 | 0 | return; |
4551 | 0 | } |
4552 | | |
4553 | | /* |
4554 | | * If the last event on the trunk was a connection failure and |
4555 | | * there is only one connection, this may well be a reconnect |
4556 | | * attempt after a failure - and needs to persist otherwise |
4557 | | * the last event will be a failure and no new connection will |
4558 | | * be made, leading to no new requests being enqueued. |
4559 | | */ |
4560 | 0 | if (fr_time_gt(trunk->pub.last_failed, fr_time_wrap(0)) && |
4561 | 0 | fr_time_lt(trunk->pub.last_connected, trunk->pub.last_failed) && (conn_count == 1)) { |
4562 | 0 | DEBUG4("Not closing remaining connection - last event was a failure"); |
4563 | 0 | return; |
4564 | 0 | } |
4565 | | |
4566 | | /* |
4567 | | * Inactive connections get counted in the |
4568 | | * set of viable connections, but are likely |
4569 | | * to be congested or dead, so we drain |
4570 | | * (and possibly eventually free) those first. |
4571 | | */ |
4572 | 0 | if ((tconn = trunk_list_inactive_peek(trunk))) { |
4573 | | /* |
4574 | | * If the connection has no requests associated |
4575 | | * with it then immediately free. |
4576 | | */ |
4577 | 0 | if (trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0) { |
4578 | 0 | connection_signal_halt(tconn->pub.conn); /* Also frees the tconn */ |
4579 | 0 | } else { |
4580 | 0 | trunk_connection_enter_inactive_draining(tconn); |
4581 | 0 | } |
4582 | | /* |
4583 | | * It is possible to have too may connecting |
4584 | | * connections when the connections are |
4585 | | * taking a while to open and the number |
4586 | | * of requests decreases. |
4587 | | */ |
4588 | 0 | } else if ((tconn = fr_dlist_tail(&trunk->connecting))) { |
4589 | 0 | connection_signal_halt(tconn->pub.conn); /* Also frees the tconn */ |
4590 | | |
4591 | | /* |
4592 | | * Finally if there are no "connecting" |
4593 | | * connections to close, and no "inactive" |
4594 | | * connections, start draining "active" |
4595 | | * connections. |
4596 | | */ |
4597 | 0 | } else if ((tconn = fr_minmax_heap_max_peek(trunk->active))) { |
4598 | | /* |
4599 | | * If the connection has no requests associated |
4600 | | * with it then immediately free. |
4601 | | */ |
4602 | 0 | if (trunk_request_count_by_connection(tconn, TRUNK_REQUEST_STATE_ALL) == 0) { |
4603 | 0 | connection_signal_halt(tconn->pub.conn); /* Also frees the tconn */ |
4604 | 0 | } else { |
4605 | 0 | trunk_connection_enter_draining(tconn); |
4606 | 0 | } |
4607 | 0 | } |
4608 | |
|
4609 | 0 | trunk->pub.last_closed = now; |
4610 | |
|
4611 | 0 | return; |
4612 | 0 | } |
4613 | 0 | } |
4614 | | |
4615 | | /** Event to periodically call the connection management function |
4616 | | * |
4617 | | * @param[in] tl this event belongs to. |
4618 | | * @param[in] now current time. |
4619 | | * @param[in] uctx The trunk. |
4620 | | */ |
4621 | | static void _trunk_timer(fr_timer_list_t *tl, fr_time_t now, void *uctx) |
4622 | 0 | { |
4623 | 0 | trunk_t *trunk = talloc_get_type_abort(uctx, trunk_t); |
4624 | |
|
4625 | 0 | trunk_manage(trunk, now); |
4626 | |
|
4627 | 0 | if (fr_time_delta_ispos(trunk->conf.manage_interval)) { |
4628 | 0 | if (fr_timer_in(trunk, tl, &trunk->manage_ev, trunk->conf.manage_interval, |
4629 | 0 | false, _trunk_timer, trunk) < 0) { |
4630 | 0 | PERROR("Failed inserting trunk management event"); |
4631 | | /* Not much we can do, hopefully the trunk will be freed soon */ |
4632 | 0 | } |
4633 | 0 | } |
4634 | 0 | } |
4635 | | |
4636 | | /** Return a count of requests on a connection in a specific state |
4637 | | * |
4638 | | * @param[in] trunk to retrieve counts for. |
4639 | | * @param[in] conn_state One or more connection states or'd together. |
4640 | | * @param[in] req_state One or more request states or'd together. |
4641 | | * @return The number of requests in a particular state, on connection in a particular state. |
4642 | | */ |
4643 | | uint64_t trunk_request_count_by_state(trunk_t *trunk, int conn_state, int req_state) |
4644 | 0 | { |
4645 | 0 | uint64_t count = 0; |
4646 | 0 | trunk_connection_t *tconn = NULL; |
4647 | 0 | fr_minmax_heap_iter_t iter; |
4648 | |
|
4649 | 0 | #define COUNT_BY_STATE(_state, _list) \ |
4650 | 0 | do { \ |
4651 | 0 | if (conn_state & (_state)) { \ |
4652 | 0 | tconn = NULL; \ |
4653 | 0 | while ((tconn = fr_dlist_next(&trunk->_list, tconn))) { \ |
4654 | 0 | count += trunk_request_count_by_connection(tconn, req_state); \ |
4655 | 0 | } \ |
4656 | 0 | } \ |
4657 | 0 | } while (0) |
4658 | |
|
4659 | 0 | if (conn_state & TRUNK_CONN_ACTIVE) { |
4660 | 0 | for (tconn = fr_minmax_heap_iter_init(trunk->active, &iter); |
4661 | 0 | tconn; |
4662 | 0 | tconn = fr_minmax_heap_iter_next(trunk->active, &iter)) { |
4663 | 0 | count += trunk_request_count_by_connection(tconn, req_state); |
4664 | 0 | } |
4665 | 0 | } |
4666 | |
|
4667 | 0 | COUNT_BY_STATE(TRUNK_CONN_FULL, full); |
4668 | 0 | COUNT_BY_STATE(TRUNK_CONN_INACTIVE, inactive); |
4669 | 0 | COUNT_BY_STATE(TRUNK_CONN_INACTIVE_DRAINING, inactive_draining); |
4670 | 0 | COUNT_BY_STATE(TRUNK_CONN_DRAINING, draining); |
4671 | 0 | COUNT_BY_STATE(TRUNK_CONN_DRAINING_TO_FREE, draining_to_free); |
4672 | |
|
4673 | 0 | if (req_state & TRUNK_REQUEST_STATE_BACKLOG) count += fr_heap_num_elements(trunk->backlog); |
4674 | |
|
4675 | 0 | return count; |
4676 | 0 | } |
4677 | | |
4678 | | /** Update timestamps for when we last had a transition from above target to below target or vice versa |
4679 | | * |
4680 | | * Should be called on every time a connection or request is allocated or freed. |
4681 | | * |
4682 | | * @param[out] conn_count_out How many connections we considered. |
4683 | | * @param[out] req_count_out How many requests we considered. |
4684 | | * @param[in] trunk to operate on. |
4685 | | * @param[in] now The current time. |
4686 | | * @param[in] verify if true (and this is a debug build), then assert if req_per_conn |
4687 | | * has changed. |
4688 | | * @return |
4689 | | * - 0 if the average couldn't be calculated (no requests or no connections). |
4690 | | * - The average number of requests per connection. |
4691 | | */ |
4692 | | static uint64_t trunk_requests_per_connection(uint16_t *conn_count_out, uint32_t *req_count_out, |
4693 | | trunk_t *trunk, fr_time_t now, |
4694 | | NDEBUG_UNUSED bool verify) |
4695 | 0 | { |
4696 | 0 | uint32_t req_count = 0; |
4697 | 0 | uint16_t conn_count = 0; |
4698 | 0 | uint64_t req_per_conn = 0; |
4699 | |
|
4700 | 0 | fr_assert(fr_time_gt(now, fr_time_wrap(0))); |
4701 | | |
4702 | | /* |
4703 | | * Recompute the trunk's aggregate state (and fire any state |
4704 | | * watchers) now that a connection's state may have changed. |
4705 | | * This is the authoritative, prompt trigger for the trunk |
4706 | | * entering / leaving states such as ACTIVE, FULL and FAILED. |
4707 | | * trunk_state_update() no-ops if the trunk is being freed. |
4708 | | */ |
4709 | 0 | trunk_state_update(trunk); |
4710 | | |
4711 | | /* |
4712 | | * No need to update these as the trunk is being freed |
4713 | | */ |
4714 | 0 | if (trunk->freeing) goto done; |
4715 | | |
4716 | | /* |
4717 | | * Count all connections except draining and draining to free. |
4718 | | * |
4719 | | * Omitting these connection states artificially raises the |
4720 | | * request to connection ratio, so that we can preemptively spawn |
4721 | | * new connections. |
4722 | | * |
4723 | | * In the case of TRUNK_CONN_DRAINING | TRUNK_CONN_INACTIVE_DRAINING |
4724 | | * the trunk management code has enough hysteresis to not |
4725 | | * immediately reactivate the connection. |
4726 | | * |
4727 | | * In the case of TRUNK_CONN_DRAINING_TO_FREE the trunk |
4728 | | * management code should spawn a new connection to takes its place. |
4729 | | * |
4730 | | * Connections placed in the DRAINING_TO_FREE state are being |
4731 | | * closed preemptively to deal with bugs on the server we're |
4732 | | * talking to, or misconfigured firewalls which are trashing |
4733 | | * TCP/UDP connection states. |
4734 | | */ |
4735 | 0 | conn_count = trunk_connection_count_by_state(trunk, TRUNK_CONN_ALL ^ |
4736 | 0 | (TRUNK_CONN_DRAINING | |
4737 | 0 | TRUNK_CONN_INACTIVE_DRAINING | |
4738 | 0 | TRUNK_CONN_DRAINING_TO_FREE)); |
4739 | | |
4740 | | /* |
4741 | | * Requests on all connections |
4742 | | */ |
4743 | 0 | req_count = trunk_request_count_by_state(trunk, |
4744 | 0 | TRUNK_CONN_ALL ^ |
4745 | 0 | TRUNK_CONN_DRAINING_TO_FREE, TRUNK_REQUEST_STATE_ALL); |
4746 | | |
4747 | | /* |
4748 | | * No connections, but we do have requests |
4749 | | */ |
4750 | 0 | if (conn_count == 0) { |
4751 | 0 | if ((req_count > 0) && (trunk->conf.target_req_per_conn > 0)) goto above_target; |
4752 | 0 | goto done; |
4753 | 0 | } |
4754 | | |
4755 | 0 | if (req_count == 0) { |
4756 | 0 | if (trunk->conf.target_req_per_conn > 0) goto below_target; |
4757 | 0 | goto done; |
4758 | 0 | } |
4759 | | |
4760 | | /* |
4761 | | * Calculate the req_per_conn |
4762 | | */ |
4763 | 0 | req_per_conn = ROUND_UP_DIV(req_count, conn_count); |
4764 | 0 | if (req_per_conn > trunk->conf.target_req_per_conn) { |
4765 | 0 | above_target: |
4766 | | /* |
4767 | | * Edge - Below target to above target (too many requests per conn - spawn more) |
4768 | | * |
4769 | | * The equality check is correct here as both values start at 0. |
4770 | | */ |
4771 | 0 | if (fr_time_lteq(trunk->pub.last_above_target, trunk->pub.last_below_target)) trunk->pub.last_above_target = now; |
4772 | 0 | } else if (req_per_conn < trunk->conf.target_req_per_conn) { |
4773 | 0 | below_target: |
4774 | | /* |
4775 | | * Edge - Above target to below target (too few requests per conn - close some) |
4776 | | * |
4777 | | * The equality check is correct here as both values start at 0. |
4778 | | */ |
4779 | 0 | if (fr_time_lteq(trunk->pub.last_below_target, trunk->pub.last_above_target)) trunk->pub.last_below_target = now; |
4780 | 0 | } |
4781 | |
|
4782 | 0 | done: |
4783 | 0 | if (conn_count_out) *conn_count_out = conn_count; |
4784 | 0 | if (req_count_out) *req_count_out = req_count; |
4785 | | |
4786 | | /* |
4787 | | * Check we haven't missed a call to trunk_requests_per_connection |
4788 | | */ |
4789 | 0 | fr_assert(!verify || (trunk->last_req_per_conn == 0) || (req_per_conn == trunk->last_req_per_conn)); |
4790 | |
|
4791 | 0 | trunk->last_req_per_conn = req_per_conn; |
4792 | |
|
4793 | 0 | return req_per_conn; |
4794 | 0 | } |
4795 | | |
4796 | | /** Drain the backlog of as many requests as possible |
4797 | | * |
4798 | | * @param[in] trunk To drain backlog requests for. |
4799 | | */ |
4800 | | static void trunk_backlog_drain(trunk_t *trunk) |
4801 | 0 | { |
4802 | 0 | trunk_request_t *treq; |
4803 | |
|
4804 | 0 | if (fr_heap_num_elements(trunk->backlog) == 0) return; |
4805 | | |
4806 | | /* |
4807 | | * If it's always writable, this isn't |
4808 | | * really a noteworthy event. |
4809 | | */ |
4810 | 0 | if (!trunk->conf.always_writable) DEBUG3("Draining backlog of requests"); |
4811 | | |
4812 | | /* |
4813 | | * Do *NOT* add an artificial limit |
4814 | | * here. We rely on all available |
4815 | | * connections entering the full |
4816 | | * state and transitioning back to |
4817 | | * active in order to drain the |
4818 | | * backlog. |
4819 | | */ |
4820 | 0 | while ((treq = fr_heap_peek(trunk->backlog))) { |
4821 | 0 | switch (trunk_request_enqueue_existing(treq)) { |
4822 | 0 | case TRUNK_ENQUEUE_OK: |
4823 | 0 | continue; |
4824 | | |
4825 | | /* |
4826 | | * Signal to stop |
4827 | | */ |
4828 | 0 | case TRUNK_ENQUEUE_IN_BACKLOG: |
4829 | 0 | break; |
4830 | | |
4831 | | /* |
4832 | | * Failed enqueueing the request, |
4833 | | * have it enter the failed state |
4834 | | * which will free it and |
4835 | | * re-enliven the yielded request. |
4836 | | */ |
4837 | 0 | case TRUNK_ENQUEUE_DST_UNAVAILABLE: |
4838 | 0 | case TRUNK_ENQUEUE_FAIL: |
4839 | 0 | trunk_request_enter_failed(treq); |
4840 | 0 | continue; |
4841 | | |
4842 | 0 | case TRUNK_ENQUEUE_NO_CAPACITY: |
4843 | 0 | fr_assert(fr_minmax_heap_num_elements(trunk->active) == 0); |
4844 | 0 | return; |
4845 | 0 | } |
4846 | 0 | } |
4847 | 0 | } |
4848 | | |
4849 | | /** Force the trunk to re-establish its connections |
4850 | | * |
4851 | | * @param[in] trunk to signal. |
4852 | | * @param[in] states One or more states or'd together. |
4853 | | * @param[in] reason Why the connections are being signalled to reconnect. |
4854 | | */ |
4855 | | void trunk_reconnect(trunk_t *trunk, int states, connection_reason_t reason) |
4856 | 0 | { |
4857 | |
|
4858 | 0 | #define RECONNECT_BY_STATE(_state, _list) \ |
4859 | 0 | do { \ |
4860 | 0 | if (states & (_state)) { \ |
4861 | 0 | size_t i; \ |
4862 | 0 | for (i = fr_dlist_num_elements(&trunk->_list); i > 0; i--) { \ |
4863 | 0 | connection_signal_reconnect(((trunk_connection_t *)fr_dlist_tail(&trunk->_list))->pub.conn, reason); \ |
4864 | 0 | } \ |
4865 | 0 | } \ |
4866 | 0 | } while (0) |
4867 | | |
4868 | | /* |
4869 | | * Connections in the 'connecting' state |
4870 | | * may re-enter that state, so we need to |
4871 | | * be careful not to enter an infinite |
4872 | | * loop, as we iterate over the list |
4873 | | * again and again. |
4874 | | */ |
4875 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_CONNECTING, connecting); |
4876 | |
|
4877 | 0 | if (states & TRUNK_CONN_ACTIVE) { |
4878 | 0 | trunk_connection_t *tconn; |
4879 | 0 | while ((tconn = fr_minmax_heap_min_peek(trunk->active))) connection_signal_reconnect(tconn->pub.conn, reason); |
4880 | 0 | } |
4881 | |
|
4882 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_INIT, init); |
4883 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_FULL, full); |
4884 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_INACTIVE, inactive); |
4885 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_INACTIVE_DRAINING, inactive_draining); |
4886 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_CLOSED, closed); |
4887 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_DRAINING, draining); |
4888 | 0 | RECONNECT_BY_STATE(TRUNK_CONN_DRAINING_TO_FREE, draining_to_free); |
4889 | 0 | } |
4890 | | |
4891 | | /** Start the trunk running |
4892 | | * |
4893 | | */ |
4894 | | int trunk_start(trunk_t *trunk) |
4895 | 0 | { |
4896 | 0 | uint16_t i; |
4897 | |
|
4898 | 0 | if (unlikely(trunk->started)) return 0; |
4899 | | |
4900 | | /* |
4901 | | * Spawn the initial set of connections |
4902 | | */ |
4903 | 0 | for (i = 0; i < trunk->conf.start; i++) { |
4904 | 0 | DEBUG("[%i] Starting initial connection", i); |
4905 | 0 | if (trunk_connection_spawn(trunk, fr_time()) != 0) return -1; |
4906 | 0 | } |
4907 | | |
4908 | | /* |
4909 | | * If the idle timeout is set, AND there's no management interval, OR the management interval is |
4910 | | * less than the idle timeout, update the management interval. |
4911 | | */ |
4912 | 0 | if (fr_time_delta_ispos(trunk->conf.idle_timeout) && |
4913 | 0 | (!fr_time_delta_ispos(trunk->conf.manage_interval) || |
4914 | 0 | fr_time_delta_gt(trunk->conf.manage_interval, trunk->conf.idle_timeout))) { |
4915 | 0 | trunk->conf.manage_interval = trunk->conf.idle_timeout; |
4916 | 0 | } |
4917 | |
|
4918 | 0 | if (fr_time_delta_ispos(trunk->conf.manage_interval)) { |
4919 | | /* |
4920 | | * Insert the event timer to manage |
4921 | | * the interval between managing connections. |
4922 | | */ |
4923 | 0 | if (fr_timer_in(trunk, trunk->el->tl, &trunk->manage_ev, trunk->conf.manage_interval, |
4924 | 0 | false, _trunk_timer, trunk) < 0) { |
4925 | 0 | PERROR("Failed inserting trunk management event"); |
4926 | 0 | return -1; |
4927 | 0 | } |
4928 | 0 | } |
4929 | 0 | trunk->started = true; |
4930 | 0 | trunk->managing_connections = true; |
4931 | |
|
4932 | 0 | return 0; |
4933 | 0 | } |
4934 | | |
4935 | | /** Allow the trunk to open and close connections in response to load |
4936 | | * |
4937 | | */ |
4938 | | void trunk_connection_manage_start(trunk_t *trunk) |
4939 | 0 | { |
4940 | 0 | if (!trunk->started || trunk->managing_connections) return; |
4941 | | |
4942 | 0 | DEBUG3("Connection management enabled"); |
4943 | 0 | trunk->managing_connections = true; |
4944 | 0 | } |
4945 | | |
4946 | | /** Stop the trunk from opening and closing connections in response to load |
4947 | | * |
4948 | | */ |
4949 | | void trunk_connection_manage_stop(trunk_t *trunk) |
4950 | 0 | { |
4951 | 0 | if (!trunk->started || !trunk->managing_connections) return; |
4952 | | |
4953 | 0 | DEBUG3("Connection management disabled"); |
4954 | 0 | trunk->managing_connections = false; |
4955 | 0 | } |
4956 | | |
4957 | | /** Schedule a trunk management event for the next time the event loop is executed |
4958 | | */ |
4959 | | int trunk_connection_manage_schedule(trunk_t *trunk) |
4960 | 0 | { |
4961 | 0 | if (!trunk->started || !trunk->managing_connections) return 0; |
4962 | | |
4963 | 0 | if (fr_timer_in(trunk, trunk->el->tl, &trunk->manage_ev, fr_time_delta_wrap(0), |
4964 | 0 | false, _trunk_timer, trunk) < 0) { |
4965 | 0 | PERROR("Failed inserting trunk management event"); |
4966 | 0 | return -1; |
4967 | 0 | } |
4968 | | |
4969 | 0 | return 0; |
4970 | 0 | } |
4971 | | |
4972 | | /** Order connections by queue depth |
4973 | | * |
4974 | | */ |
4975 | | static int8_t _trunk_connection_order_by_shortest_queue(void const *one, void const *two) |
4976 | 0 | { |
4977 | 0 | trunk_connection_t const *a = talloc_get_type_abort_const(one, trunk_connection_t); |
4978 | 0 | trunk_connection_t const *b = talloc_get_type_abort_const(two, trunk_connection_t); |
4979 | |
|
4980 | 0 | uint32_t a_count = trunk_request_count_by_connection(a, TRUNK_REQUEST_STATE_ALL); |
4981 | 0 | uint32_t b_count = trunk_request_count_by_connection(b, TRUNK_REQUEST_STATE_ALL); |
4982 | | |
4983 | | /* |
4984 | | * Add a fudge factor of 1 to reduce spurious rebalancing |
4985 | | */ |
4986 | 0 | return ((a_count > b_count) && ((a_count - b_count) > 1)) - ((b_count > a_count) && ((b_count - a_count) > 1)); |
4987 | 0 | } |
4988 | | |
4989 | | /** Free a trunk, gracefully closing all connections. |
4990 | | * |
4991 | | */ |
4992 | | static int _trunk_free(trunk_t *trunk) |
4993 | 0 | { |
4994 | 0 | trunk_connection_t *tconn; |
4995 | 0 | trunk_request_t *treq; |
4996 | 0 | trunk_watch_entry_t *watch; |
4997 | 0 | size_t i; |
4998 | |
|
4999 | 0 | DEBUG4("Trunk free %p", trunk); |
5000 | |
|
5001 | 0 | trunk->freeing = true; /* Prevent re-enqueuing */ |
5002 | | |
5003 | | /* |
5004 | | * We really don't want this firing after |
5005 | | * we've freed everything. |
5006 | | */ |
5007 | 0 | FR_TIMER_DELETE_RETURN(&trunk->manage_ev); |
5008 | | |
5009 | | /* |
5010 | | * Now free the connections in each of the lists. |
5011 | | * |
5012 | | * Each time a connection is freed it removes itself from the list |
5013 | | * its in, which means the head should keep advancing automatically. |
5014 | | */ |
5015 | 0 | while ((tconn = fr_minmax_heap_min_peek(trunk->active))) connection_signal_halt(tconn->pub.conn); |
5016 | 0 | while ((tconn = fr_dlist_head(&trunk->init))) connection_signal_halt(tconn->pub.conn); |
5017 | 0 | while ((tconn = fr_dlist_head(&trunk->connecting))) connection_signal_halt(tconn->pub.conn); |
5018 | 0 | while ((tconn = fr_dlist_head(&trunk->full))) connection_signal_halt(tconn->pub.conn); |
5019 | 0 | while ((tconn = fr_dlist_head(&trunk->inactive))) connection_signal_halt(tconn->pub.conn); |
5020 | 0 | while ((tconn = fr_dlist_head(&trunk->inactive_draining))) connection_signal_halt(tconn->pub.conn); |
5021 | 0 | while ((tconn = fr_dlist_head(&trunk->closed))) connection_signal_halt(tconn->pub.conn); |
5022 | 0 | while ((tconn = fr_dlist_head(&trunk->draining))) connection_signal_halt(tconn->pub.conn); |
5023 | 0 | while ((tconn = fr_dlist_head(&trunk->draining_to_free))) connection_signal_halt(tconn->pub.conn); |
5024 | | |
5025 | | /* |
5026 | | * Process any deferred connection frees |
5027 | | */ |
5028 | 0 | fr_dlist_talloc_free(&trunk->to_free); |
5029 | | |
5030 | | /* |
5031 | | * Free any requests left in the backlog |
5032 | | */ |
5033 | 0 | while ((treq = fr_heap_peek(trunk->backlog))) trunk_request_enter_failed(treq); |
5034 | | |
5035 | | /* |
5036 | | * Free any requests in our request cache |
5037 | | */ |
5038 | 0 | while ((treq = trunk_list_free_requests_peek(trunk))) talloc_free(treq); |
5039 | | |
5040 | | /* |
5041 | | * Free any entries in the watch lists |
5042 | | */ |
5043 | 0 | for (i = 0; i < NUM_ELEMENTS(trunk->watch); i++) { |
5044 | 0 | while ((watch = fr_dlist_pop_head(&trunk->watch[i]))) talloc_free(watch); |
5045 | 0 | } |
5046 | |
|
5047 | 0 | return 0; |
5048 | 0 | } |
5049 | | |
5050 | | /** Allocate a new collection of connections |
5051 | | * |
5052 | | * This function should be called first to allocate a new trunk connection. |
5053 | | * |
5054 | | * After the trunk has been allocated, #trunk_request_alloc and |
5055 | | * #trunk_request_enqueue should be used to allocate memory for trunk |
5056 | | * requests, and pass a preq (protocol request) to the trunk for |
5057 | | * processing. |
5058 | | * |
5059 | | * The trunk will then asynchronously process the request, writing the result |
5060 | | * to a specified rctx. See #trunk_request_enqueue for more details. |
5061 | | * |
5062 | | * @note Trunks may not be shared between multiple threads under any circumstances. |
5063 | | * |
5064 | | * @param[in] ctx To use for any memory allocations. Must be thread local. |
5065 | | * @param[in] el to use for I/O and timer events. |
5066 | | * @param[in] funcs Callback functions. |
5067 | | * @param[in] conf Common user configurable parameters. |
5068 | | * @param[in] log_prefix To prepend to global messages. |
5069 | | * @param[in] uctx User data to pass to the alloc function. |
5070 | | * @param[in] delay_start If true, then we will not spawn any connections |
5071 | | * until the first request is enqueued. |
5072 | | * @param[in] trigger_args Pairs to pass to trigger requests, if triggers are enabled. |
5073 | | * @return |
5074 | | * - New trunk handle on success. |
5075 | | * - NULL on error. |
5076 | | */ |
5077 | | trunk_t *trunk_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, |
5078 | | trunk_io_funcs_t const *funcs, trunk_conf_t const *conf, |
5079 | | char const *log_prefix, void const *uctx, bool delay_start, fr_pair_list_t *trigger_args) |
5080 | 0 | { |
5081 | 0 | trunk_t *trunk; |
5082 | 0 | size_t i; |
5083 | | |
5084 | | /* |
5085 | | * Check we have the functions we need |
5086 | | */ |
5087 | 0 | if (!fr_cond_assert(funcs->connection_alloc)) return NULL; |
5088 | | |
5089 | 0 | MEM(trunk = talloc_zero(ctx, trunk_t)); |
5090 | 0 | trunk->el = el; |
5091 | 0 | trunk->log_prefix = talloc_strdup(trunk, log_prefix); |
5092 | 0 | trunk->trigger_args = trigger_args; |
5093 | |
|
5094 | 0 | memcpy(&trunk->funcs, funcs, sizeof(trunk->funcs)); |
5095 | 0 | if (!trunk->funcs.connection_prioritise) { |
5096 | 0 | trunk->funcs.connection_prioritise = _trunk_connection_order_by_shortest_queue; |
5097 | 0 | } |
5098 | 0 | if (!trunk->funcs.request_prioritise) trunk->funcs.request_prioritise = fr_pointer_cmp; |
5099 | |
|
5100 | 0 | memcpy(&trunk->conf, conf, sizeof(trunk->conf)); |
5101 | |
|
5102 | 0 | memcpy(&trunk->uctx, &uctx, sizeof(trunk->uctx)); |
5103 | 0 | talloc_set_destructor(trunk, _trunk_free); |
5104 | | |
5105 | | /* |
5106 | | * Free request list... |
5107 | | */ |
5108 | 0 | fr_dlist_talloc_init(&trunk->free_requests, trunk_request_t, entry); |
5109 | | |
5110 | | /* |
5111 | | * Request backlog queue |
5112 | | */ |
5113 | 0 | MEM(trunk->backlog = fr_heap_talloc_alloc(trunk, _trunk_request_prioritise, |
5114 | 0 | trunk_request_t, heap_id, 0)); |
5115 | | |
5116 | | /* |
5117 | | * Connection queues and trees |
5118 | | */ |
5119 | 0 | MEM(trunk->active = fr_minmax_heap_talloc_alloc(trunk, trunk->funcs.connection_prioritise, |
5120 | 0 | trunk_connection_t, heap_id, 0)); |
5121 | 0 | fr_dlist_talloc_init(&trunk->init, trunk_connection_t, entry); |
5122 | 0 | fr_dlist_talloc_init(&trunk->connecting, trunk_connection_t, entry); |
5123 | 0 | fr_dlist_talloc_init(&trunk->full, trunk_connection_t, entry); |
5124 | 0 | fr_dlist_talloc_init(&trunk->inactive, trunk_connection_t, entry); |
5125 | 0 | fr_dlist_talloc_init(&trunk->inactive_draining, trunk_connection_t, entry); |
5126 | 0 | fr_dlist_talloc_init(&trunk->closed, trunk_connection_t, entry); |
5127 | 0 | fr_dlist_talloc_init(&trunk->draining, trunk_connection_t, entry); |
5128 | 0 | fr_dlist_talloc_init(&trunk->draining_to_free, trunk_connection_t, entry); |
5129 | 0 | fr_dlist_talloc_init(&trunk->to_free, trunk_connection_t, entry); |
5130 | | |
5131 | | /* |
5132 | | * Watch lists |
5133 | | */ |
5134 | 0 | for (i = 0; i < NUM_ELEMENTS(trunk->watch); i++) { |
5135 | 0 | fr_dlist_talloc_init(&trunk->watch[i], trunk_watch_entry_t, entry); |
5136 | 0 | } |
5137 | |
|
5138 | 0 | DEBUG4("Trunk allocated %p", trunk); |
5139 | |
|
5140 | 0 | if (!delay_start) { |
5141 | 0 | if (trunk_start(trunk) < 0) { |
5142 | 0 | talloc_free(trunk); |
5143 | 0 | return NULL; |
5144 | 0 | } |
5145 | 0 | } |
5146 | | |
5147 | 0 | return trunk; |
5148 | 0 | } |
5149 | | |
5150 | | /** Check for a module trigger section when parsing the `triggers` option. |
5151 | | * |
5152 | | */ |
5153 | | int trunk_trigger_cf_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule) |
5154 | 0 | { |
5155 | 0 | trunk_conf_t *conf = parent; |
5156 | 0 | CONF_SECTION *cs = cf_item_to_section(cf_parent(ci)); |
5157 | |
|
5158 | 0 | if (cf_pair_parse_value(ctx, out, parent, ci, rule)< 0) return -1; |
5159 | | |
5160 | | /* |
5161 | | * If the parent section of the `triggers` option contains a trigger |
5162 | | * section then store it as the module CONF SECTION for the appropriate |
5163 | | * trigger group. |
5164 | | */ |
5165 | 0 | if (cf_section_find(cs, "trigger", NULL)) { |
5166 | 0 | if (strcmp(cf_section_name(cs), "request") == 0) { |
5167 | 0 | conf->req_trigger_cs = cs; |
5168 | 0 | } else { |
5169 | 0 | conf->conn_trigger_cs = cs; |
5170 | 0 | } |
5171 | 0 | } |
5172 | |
|
5173 | 0 | return 0; |
5174 | 0 | } |
5175 | | |
5176 | | #ifndef TALLOC_GET_TYPE_ABORT_NOOP |
5177 | | /** Verify a trunk |
5178 | | * |
5179 | | * A trunk has some number of connections, which each have some number of requests. The connections and |
5180 | | * requests are in differing kinds of containers depending on their state and how they are used, and may |
5181 | | * have fields that can only be validated by comparison with a parent. We had planned on passing a "context" |
5182 | | * down with the ancestral values, but that breaks the foo_verify() API. Each foo_verify() will only verify the |
5183 | | * foo's children. |
5184 | | */ |
5185 | | void trunk_verify(char const *file, int line, trunk_t *trunk) |
5186 | 0 | { |
5187 | 0 | fr_fatal_assert_msg(trunk, "CONSISTENCY CHECK FAILED %s[%i]: trunk_t pointer was NULL", file, line); |
5188 | 0 | (void) talloc_get_type_abort(trunk, trunk_t); |
5189 | |
|
5190 | 0 | for (size_t i = 0; i < NUM_ELEMENTS(trunk->watch); i++) { |
5191 | 0 | _fr_dlist_verify(file, line, &trunk->watch[i]); |
5192 | 0 | } |
5193 | |
|
5194 | 0 | #define IO_FUNC_VERIFY(_func) \ |
5195 | 0 | fr_fatal_assert_msg(trunk->funcs._func, "CONSISTENCY_CHECK_FAILED %s[%i}: " #_func " was NULL", file, line) |
5196 | | |
5197 | | /* |
5198 | | * Only a few of the function pointers *must* be non-NULL.. |
5199 | | */ |
5200 | 0 | IO_FUNC_VERIFY(connection_alloc); |
5201 | 0 | IO_FUNC_VERIFY(connection_prioritise); |
5202 | 0 | IO_FUNC_VERIFY(request_prioritise); |
5203 | |
|
5204 | 0 | #define TRUNK_TCONN_CHECKS(_tconn, _state) \ |
5205 | 0 | do { \ |
5206 | 0 | fr_fatal_assert_msg(trunk == _tconn->pub.trunk, \ |
5207 | 0 | "CONSISTENCY_CHECK_FAILED %s[%i}: connection-trunk mismatch", file, line); \ |
5208 | 0 | fr_fatal_assert_msg(_state == _tconn->pub.state, \ |
5209 | 0 | "CONSISTENCY_CHECK_FAILED %s[%i}: connection-state mismatch", file, line); \ |
5210 | 0 | } while (0) |
5211 | |
|
5212 | 0 | #define TCONN_DLIST_VERIFY(_dlist, _state) \ |
5213 | 0 | do { \ |
5214 | 0 | _fr_dlist_verify(file, line, &(trunk->_dlist)); \ |
5215 | 0 | fr_dlist_foreach(&(trunk->_dlist), trunk_connection_t, tconn) { \ |
5216 | 0 | trunk_connection_verify(file, line, tconn); \ |
5217 | 0 | TRUNK_TCONN_CHECKS(tconn, _state); \ |
5218 | 0 | } \ |
5219 | 0 | } while (0) |
5220 | |
|
5221 | 0 | #define TCONN_MINMAX_HEAP_VERIFY(_heap, _state) \ |
5222 | 0 | do {\ |
5223 | 0 | fr_minmax_heap_verify(file, line, trunk->_heap); \ |
5224 | 0 | fr_minmax_heap_foreach(trunk->_heap, trunk_connection_t, tconn) { \ |
5225 | 0 | trunk_connection_verify(file, line, tconn); \ |
5226 | 0 | TRUNK_TCONN_CHECKS(tconn, _state); \ |
5227 | 0 | }} \ |
5228 | 0 | } while (0) |
5229 | |
|
5230 | 0 | fr_dlist_verify(&(trunk->free_requests)); |
5231 | 0 | FR_HEAP_VERIFY(trunk->backlog); |
5232 | |
|
5233 | 0 | TCONN_DLIST_VERIFY(init, TRUNK_CONN_INIT); |
5234 | 0 | TCONN_DLIST_VERIFY(connecting, TRUNK_CONN_CONNECTING); |
5235 | 0 | TCONN_MINMAX_HEAP_VERIFY(active, TRUNK_CONN_ACTIVE); |
5236 | 0 | TCONN_DLIST_VERIFY(full, TRUNK_CONN_FULL); |
5237 | 0 | TCONN_DLIST_VERIFY(inactive, TRUNK_CONN_INACTIVE); |
5238 | 0 | TCONN_DLIST_VERIFY(inactive_draining, TRUNK_CONN_INACTIVE_DRAINING); |
5239 | | /* TCONN_DLIST_VERIFY(failed, ???); */ |
5240 | 0 | TCONN_DLIST_VERIFY(closed, TRUNK_CONN_CLOSED); |
5241 | 0 | TCONN_DLIST_VERIFY(draining, TRUNK_CONN_DRAINING); |
5242 | 0 | TCONN_DLIST_VERIFY(draining_to_free, TRUNK_CONN_DRAINING_TO_FREE); |
5243 | 0 | TCONN_DLIST_VERIFY(to_free, TRUNK_CONN_HALTED); |
5244 | 0 | } |
5245 | | |
5246 | | void trunk_connection_verify(char const *file, int line, trunk_connection_t *tconn) |
5247 | 0 | { |
5248 | 0 | fr_fatal_assert_msg(tconn, "CONSISTENCY CHECK FAILED %s[%i]: trunk_connection_t pointer was NULL", file, line); |
5249 | 0 | (void) talloc_get_type_abort(tconn, trunk_connection_t); |
5250 | |
|
5251 | 0 | (void) talloc_get_type_abort(tconn->pub.trunk, trunk_t); |
5252 | | |
5253 | | /* |
5254 | | * shouldn't be both in heap and on list--but it doesn't look like moves |
5255 | | * to active heap wipe the dlist pointers. |
5256 | | */ |
5257 | |
|
5258 | 0 | #define TCONN_TREQ_CHECKS(_treq, _state) \ |
5259 | 0 | do { \ |
5260 | 0 | fr_fatal_assert_msg(tconn == _treq->pub.tconn, \ |
5261 | 0 | "CONSISTENCY_CHECK_FAILED %s[%i}: trunk request-tconn mismatch", file, line); \ |
5262 | 0 | fr_fatal_assert_msg(tconn->pub.trunk == _treq->pub.trunk, \ |
5263 | 0 | "CONSISTENCY_CHECK_FAILED %s[%i}: trunk request-trunk mismatch", file, line); \ |
5264 | 0 | fr_fatal_assert_msg(_state == _treq->pub.state, \ |
5265 | 0 | "CONSISTENCY_CHECK_FAILED %s[%i}: trunk request-state mismatch", file, line); \ |
5266 | 0 | } while (0) |
5267 | |
|
5268 | 0 | #define TREQ_DLIST_VERIFY(_dlist, _state) \ |
5269 | 0 | do { \ |
5270 | 0 | _fr_dlist_verify(file, line, &(tconn->_dlist)); \ |
5271 | 0 | fr_dlist_foreach(&(tconn->_dlist), trunk_request_t, treq) { \ |
5272 | 0 | trunk_request_verify(file, line, treq); \ |
5273 | 0 | TCONN_TREQ_CHECKS(treq, _state); \ |
5274 | 0 | } \ |
5275 | 0 | } while (0) |
5276 | |
|
5277 | 0 | #define TREQ_HEAP_VERIFY(_heap, _state) \ |
5278 | 0 | do { \ |
5279 | 0 | fr_heap_iter_t _iter; \ |
5280 | 0 | fr_heap_verify(file, line, tconn->_heap); \ |
5281 | 0 | for (trunk_request_t *treq = fr_heap_iter_init(tconn->_heap, &_iter); \ |
5282 | 0 | treq; \ |
5283 | 0 | treq = fr_heap_iter_next(tconn->_heap, &_iter)) { \ |
5284 | 0 | trunk_request_verify(file, line, treq); \ |
5285 | 0 | TCONN_TREQ_CHECKS(treq, _state); \ |
5286 | 0 | } \ |
5287 | 0 | } while (0) |
5288 | |
|
5289 | 0 | #define TREQ_OPTION_VERIFY(_option, _state) \ |
5290 | 0 | do { \ |
5291 | 0 | if (tconn->_option) { \ |
5292 | 0 | trunk_request_verify(file, line, tconn->_option); \ |
5293 | 0 | TCONN_TREQ_CHECKS(tconn->_option, _state); \ |
5294 | 0 | } \ |
5295 | 0 | } while (0) |
5296 | | |
5297 | | /* verify associated requests */ |
5298 | 0 | TREQ_HEAP_VERIFY(pending, TRUNK_REQUEST_STATE_PENDING); |
5299 | 0 | TREQ_DLIST_VERIFY(sent, TRUNK_REQUEST_STATE_SENT); |
5300 | 0 | TREQ_DLIST_VERIFY(cancel, TRUNK_REQUEST_STATE_CANCEL); |
5301 | 0 | TREQ_DLIST_VERIFY(cancel_sent, TRUNK_REQUEST_STATE_CANCEL_SENT); |
5302 | 0 | TREQ_OPTION_VERIFY(partial, TRUNK_REQUEST_STATE_PARTIAL); |
5303 | 0 | TREQ_OPTION_VERIFY(cancel_partial, TRUNK_REQUEST_STATE_CANCEL_PARTIAL); |
5304 | 0 | } |
5305 | | |
5306 | | void trunk_request_verify(char const *file, int line, trunk_request_t *treq) |
5307 | 0 | { |
5308 | 0 | fr_fatal_assert_msg(treq, "CONSISTENCY CHECK FAILED %s[%i]: trunk_request_t pointer was NULL", file, line); |
5309 | 0 | (void) talloc_get_type_abort(treq, trunk_request_t); |
5310 | |
|
5311 | 0 | #ifdef WITH_VERIFY_PTR |
5312 | 0 | if (treq->pub.request) request_verify(file, line, treq->pub.request); |
5313 | 0 | #endif |
5314 | 0 | } |
5315 | | |
5316 | | |
5317 | | bool trunk_search(trunk_t *trunk, void *ptr) |
5318 | 0 | { |
5319 | 0 | #define TCONN_DLIST_SEARCH(_dlist) \ |
5320 | 0 | do { \ |
5321 | 0 | fr_dlist_foreach(&(trunk->_dlist), trunk_connection_t, tconn) { \ |
5322 | 0 | if (ptr == tconn) { \ |
5323 | 0 | fr_fprintf(stderr, "trunk_search: tconn %p on " #_dlist "\n", ptr); \ |
5324 | 0 | return true; \ |
5325 | 0 | } \ |
5326 | 0 | if (trunk_connection_search(tconn, ptr)) { \ |
5327 | 0 | fr_fprintf(stderr, " in tconn %p on " #_dlist "\n", tconn); \ |
5328 | 0 | return true; \ |
5329 | 0 | } \ |
5330 | 0 | } \ |
5331 | 0 | } while (0) |
5332 | |
|
5333 | 0 | #define TCONN_MINMAX_HEAP_SEARCH(_heap) \ |
5334 | 0 | do { \ |
5335 | 0 | fr_minmax_heap_foreach(trunk->_heap, trunk_connection_t, tconn) { \ |
5336 | 0 | if (ptr == tconn) { \ |
5337 | 0 | fr_fprintf(stderr, "trunk_search: tconn %p on " #_heap "\n", ptr); \ |
5338 | 0 | return true; \ |
5339 | 0 | } \ |
5340 | 0 | if (trunk_connection_search(tconn, ptr)) { \ |
5341 | 0 | fr_fprintf(stderr, " on tconn %p on " #_heap "\n", tconn); \ |
5342 | 0 | return true; \ |
5343 | 0 | } \ |
5344 | 0 | }}\ |
5345 | 0 | } while (0) |
5346 | |
|
5347 | 0 | TCONN_DLIST_SEARCH(init); |
5348 | 0 | TCONN_DLIST_SEARCH(connecting); |
5349 | 0 | TCONN_MINMAX_HEAP_SEARCH(active); |
5350 | 0 | TCONN_DLIST_SEARCH(full); |
5351 | 0 | TCONN_DLIST_SEARCH(inactive); |
5352 | 0 | TCONN_DLIST_SEARCH(inactive_draining); |
5353 | 0 | TCONN_DLIST_SEARCH(failed); |
5354 | 0 | TCONN_DLIST_SEARCH(closed); |
5355 | 0 | TCONN_DLIST_SEARCH(draining); |
5356 | 0 | TCONN_DLIST_SEARCH(draining_to_free); |
5357 | 0 | TCONN_DLIST_SEARCH(to_free); |
5358 | | |
5359 | 0 | return false; |
5360 | 0 | } |
5361 | | |
5362 | | bool trunk_connection_search(trunk_connection_t *tconn, void *ptr) |
5363 | 0 | { |
5364 | 0 | #define TREQ_DLIST_SEARCH(_dlist) \ |
5365 | 0 | do { \ |
5366 | 0 | fr_dlist_foreach(&(tconn->_dlist), trunk_request_t, treq) { \ |
5367 | 0 | if (ptr == treq) { \ |
5368 | 0 | fr_fprintf(stderr, "trunk_search: treq %p on " #_dlist "\n", ptr); \ |
5369 | 0 | return true; \ |
5370 | 0 | } \ |
5371 | 0 | if (trunk_request_search(treq, ptr)) { \ |
5372 | 0 | fr_fprintf(stderr, "trunk_search: preq %p found on " #_dlist, ptr); \ |
5373 | 0 | return true; \ |
5374 | 0 | } \ |
5375 | 0 | } \ |
5376 | 0 | } while (0) |
5377 | |
|
5378 | 0 | #define TREQ_HEAP_SEARCH(_heap) \ |
5379 | 0 | do { \ |
5380 | 0 | fr_heap_iter_t _iter; \ |
5381 | 0 | for (trunk_request_t *treq = fr_heap_iter_init(tconn->_heap, &_iter); \ |
5382 | 0 | treq; \ |
5383 | 0 | treq = fr_heap_iter_next(tconn->_heap, &_iter)) { \ |
5384 | 0 | if (ptr == treq) { \ |
5385 | 0 | fr_fprintf(stderr, "trunk_search: treq %p in " #_heap "\n", ptr); \ |
5386 | 0 | return true; \ |
5387 | 0 | } \ |
5388 | 0 | if (trunk_request_search(treq, ptr)) { \ |
5389 | 0 | fr_fprintf(stderr, "trunk_search: preq %p found in " #_heap, ptr); \ |
5390 | 0 | return true; \ |
5391 | 0 | } \ |
5392 | 0 | } \ |
5393 | 0 | } while (0) |
5394 | |
|
5395 | 0 | #define TREQ_OPTION_SEARCH(_option) \ |
5396 | 0 | do { \ |
5397 | 0 | if (tconn->_option) { \ |
5398 | 0 | if (ptr == tconn->_option) { \ |
5399 | 0 | fr_fprintf(stderr, "trunk_search: treq %p is " #_option "\n", ptr); \ |
5400 | 0 | return true; \ |
5401 | 0 | } \ |
5402 | 0 | if (trunk_request_search(tconn->_option, ptr)) { \ |
5403 | 0 | fr_fprintf(stderr, "trunk_search: preq %p found in " #_option, ptr); \ |
5404 | 0 | return true; \ |
5405 | 0 | } \ |
5406 | 0 | } \ |
5407 | 0 | } while (0) |
5408 | | |
5409 | | /* search associated requests */ |
5410 | 0 | TREQ_HEAP_SEARCH(pending); |
5411 | 0 | TREQ_DLIST_SEARCH(sent); |
5412 | 0 | TREQ_DLIST_SEARCH(cancel); |
5413 | 0 | TREQ_DLIST_SEARCH(cancel_sent); |
5414 | 0 | TREQ_OPTION_SEARCH(partial); |
5415 | 0 | TREQ_OPTION_SEARCH(cancel_partial); |
5416 | | |
5417 | 0 | return false; |
5418 | 0 | } |
5419 | | |
5420 | | bool trunk_request_search(trunk_request_t *treq, void *ptr) |
5421 | 0 | { |
5422 | 0 | return treq->pub.preq == ptr; |
5423 | 0 | } |
5424 | | #endif |