Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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