The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
retry.c
Go to the documentation of this file.
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: 2c6fd448fd318d077c4377ea07704e44875f2999 $
19 * @file lib/bio/retry.c
20 * @brief Binary IO abstractions for retrying packets.
21 *
22 * The retry BIO provides a mechanism for the application to send one packet, and then delegate
23 * retransmissions to the retry bio.
24 *
25 * This BIO will monitor writes, and run callbacks when a packet is sent, received, and released. The
26 * application should cache the request and response until the release callback has been run. The BIO will
27 * call the application on retries, or when the retransmissions have stopped.
28 *
29 * The retry BIO also deals with partially written packets. The BIO takes responsibility for not writing
30 * partial packets, which means that requests can be rleeased even if the data has been partially written.
31 * The application can also cancel an ongoing retryt entrty at any time.
32 *
33 * If something blocks IO, the application should call the blocked / resume functions for this BIO to inform
34 * it of IO changes. Otherwise, the only time this BIO blocks is when it runs out of retransmission slots.
35 *
36 * There are provisions for application-layer watchdogs, where the application can reserve a retry entry. It
37 * can then call the fr_bio_retry_rewrite() function instead of fr_bio_write() to write the watchdog packet.
38 * Any retransmission timers for the application-layer watchdog must be handled by the application. The BIO
39 * will not retry reserved watchdog requests.
40 *
41 * In general, the next BIO after this one should be the memory bio, so that this bio receives only complete
42 * packets.
43 *
44 * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
45 */
46
47#include <freeradius-devel/bio/bio_priv.h>
48#include <freeradius-devel/bio/null.h>
49#include <freeradius-devel/bio/buf.h>
50#include <freeradius-devel/util/rb.h>
51
52#define _BIO_RETRY_PRIVATE
53#include <freeradius-devel/bio/retry.h>
54
55typedef struct fr_bio_retry_list_s fr_bio_retry_list_t;
57
58/*
59 * Define type-safe wrappers for head and entry definitions.
60 */
61FR_DLIST_TYPES(fr_bio_retry_list)
62
64 void *uctx;
66 fr_bio_retry_rewrite_t rewrite; //!< per-packet rewrite callback
67 void *rewrite_ctx; //!< context specifically for rewriting this packet
68
69 fr_retry_t retry; //!< retry timers and counters
70
71 union {
72 fr_rb_node_t next_retry_node; //!< for retries
73 FR_DLIST_ENTRY(fr_bio_retry_list) entry; //!< for the free list
74 };
75 fr_rb_node_t expiry_node; //!< for expiries
76
77 fr_bio_retry_t *my; //!< so we can get to it from the event timer callback
78
79 uint8_t const *buffer; //!< cached copy of the packet to send
80 size_t size; //!< size of the cached packet
81
82 bool cancelled; //!< was this item cancelled?
83 bool reserved; //!< for application-layer watchdog
84};
85
86FR_DLIST_FUNCS(fr_bio_retry_list, fr_bio_retry_entry_t, entry)
87
90
91 fr_timer_list_t *next_tl; //!< when packets are retried next
92 fr_timer_list_t *expiry_tl; //!< when packets expire, so that we expire packets when the socket is blocked.
93
95
97
98 bool all_used; //!< blocked due to no free entries
99
100 /*
101 * Cache a partial write when IO is blocked. Partial
102 * packets are left in the timer tree so that they can be expired.
103 */
104 fr_bio_retry_entry_t *partial; //!< for partial writes
105
106 fr_bio_retry_sent_t sent; //!< callback for when we successfully sent a packet
107 fr_bio_retry_rewrite_t rewrite; //!< optional callback which can change a packet on retry
108 fr_bio_retry_response_t response; //!< callback to see if we got a valid response
109 fr_bio_retry_release_t release; //!< callback to release a request / response pair
110
111 fr_bio_buf_t buffer; //!< to store partial packets
112
113 FR_DLIST_HEAD(fr_bio_retry_list) free; //!< free lists are better than memory fragmentation
114};
115
116static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size);
118
119/** Release an entry back to the free list.
120 *
121 */
123{
124 item->cancelled = true;
125
126 /*
127 * Remove the item from all timer lists before calling the application "release" function.
128 *
129 * reserved items (e.g. application-layer watchdogs like Status-Server) are run by the
130 * application, and aren't inserted into any tree.
131 */
132 if (!item->reserved) {
133 (void) fr_timer_uctx_remove(my->next_tl, item);
135 }
136
137 /*
138 * Tell the caller that we've released it before doing anything else. That way we can safely
139 * modify anything we want.
140 */
141 my->release((fr_bio_t *) my, item, reason);
142
143 /*
144 * We've partially written this item. Don't bother changing it's position in any of the lists,
145 * as it's in progress.
146 */
147 if (my->partial == item) return;
148
149 /*
150 * This item is reserved. The application has cached a pointer to it, so it never gets returned
151 * to the free list.
152 */
153 if (item->reserved) return;
154
155 item->packet_ctx = NULL;
156
157 fr_bio_retry_list_insert_head(&my->free, item);
158
159 /*
160 * If all entries are in use, then freeing an item lets the application write another entry.
161 *
162 * The item has to be on the free list before the resume callback runs, because the application
163 * may write a new packet from inside the resume callback.
164 *
165 * If the IO is also blocked, then fr_bio_retry_release() does not call the resume callback.
166 * fr_bio_retry_write_resume() resumes writes once the socket becomes writable.
167 */
168 if (my->all_used) {
169 fr_assert(fr_bio_retry_list_num_elements(&my->free) == 1);
170
171 my->all_used = false;
172
173 if (!my->info.write_blocked && my->cb.write_resume) (void) my->cb.write_resume(&my->bio);
174 }
175}
176
177/** Writes are blocked.
178 *
179 */
181{
182 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
183
184 if (my->info.write_blocked) {
185 return 1;
186 }
187
188 /*
189 * Disarm the retry timer, and enable the expiry timer.
190 *
191 * i.e. we won't retry packets, but we will expire them when their timer runs out.
192 *
193 * Arming the expiry timer runs any expiry events which are already due, and
194 * fr_bio_retry_expiry_timer() requires that writes are marked as blocked. We therefore set the
195 * flag before arming the expiry timer.
196 */
197 my->info.write_blocked = true;
198
199 if (fr_timer_list_disarm(my->next_tl) < 0) return fr_bio_error(GENERIC);
200
201 if (fr_timer_list_arm(my->expiry_tl) < 0) return fr_bio_error(GENERIC);
202
203 return 1;
204}
205
206
207/** Write one item.
208 *
209 * @return
210 * - <0 on error. When the next bio returns an error other than IO_WOULD_BLOCK,
211 * fr_bio_retry_rewrite() has already released the item.
212 * - 0 for "can't write any more"
213 * - 1 for "wrote a packet"
214 */
216{
217 ssize_t rcode;
218 fr_retry_state_t state;
219
220 fr_assert(!my->partial);
221 fr_assert(!item->reserved);
222
223 /*
224 * Are we there yet?
225 *
226 * Release it, indicating whether or not we successfully got a reply.
227 */
228 state = fr_retry_next(&item->retry, now);
229 if (state != FR_RETRY_CONTINUE) {
231 return 1;
232 }
233
234 /*
235 * Track when we last sent a NEW packet. Also track when we first sent a packet after becoming
236 * writeable again.
237 */
238 if ((item->retry.count == 1) && fr_time_lt(my->info.last_sent, now)) {
239 my->info.last_sent = now;
240
241 if (fr_time_lteq(my->info.first_sent, my->info.last_idle)) my->info.first_sent = now;
242 }
243
244 fr_assert(fr_time_gt(item->retry.next, now));
245
246 /*
247 * We rewrote the "next" timer. Remove the item from the timer tree, which doesn't call the cmp
248 * function and therefore doesn't care that the time has changed. Then re-insert it, which does
249 * call the cmp function.
250 */
251 (void) fr_timer_uctx_remove(my->next_tl, item);
252 (void) fr_timer_uctx_insert(my->next_tl, item);
253
254 /*
255 * Write out the packet.
256 *
257 * The rewrite callback (item->rewrite or my->rewrite) is either fr_bio_retry_rewrite(), or a
258 * callback which calls fr_bio_retry_rewrite(), or a callback which releases the item itself. When
259 * the next bio returns an error other than IO_WOULD_BLOCK, the item has therefore already been
260 * released, and we return the error without releasing the item. The application controls the
261 * bio chain, so we do not call fr_bio_shutdown() here. The next bio decides whether the error is
262 * fatal.
263 */
264 if (item->rewrite) {
265 rcode = item->rewrite(&my->bio, item, item->buffer, item->size);
266 } else {
267 rcode = my->rewrite(&my->bio, item, item->buffer, item->size);
268 }
269 if (rcode < 0) return rcode;
270
271 /*
272 * We did not write the whole packet, so we are blocked.
273 *
274 * On a partial write, fr_bio_retry_rewrite() has already saved the rest of the packet and has
275 * blocked writes. On a zero-length write, there is nothing to save. In both cases, we must not
276 * save the rest of the packet a second time.
277 */
278 if ((size_t) rcode < item->size) {
279 fr_assert((rcode == 0) || (my->partial == item));
280 return 0;
281 }
282
283 return 1;
284}
285
286/** Resume writes.
287 *
288 * On resume, we try to flush any pending packets which should have been sent.
289 */
291{
292 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
293
294 if (!my->info.write_blocked) return 1;
295
296 my->info.write_blocked = false;
297
298 /*
299 * Disarm the expiry list, and rearm the next retry list.
300 *
301 * Rearming the next retry list will cause all pending events to be run. Which means calling the
302 * write routine for each item. If the write ends up blocking, it will disarm the next retry
303 * timer, re-arm the expiry timer, and then set the write_blocked flag.
304 */
306 (void) fr_timer_list_arm(my->next_tl);
307
308 return !my->info.write_blocked; /* return 0 for "can't resume" and 1 for "can resume" */
309}
310
311
312/** There's a partial packet written. Write all of that one first, before writing another packet.
313 *
314 * The packet can either be cancelled, or IO blocked. In either case, we must write the full packet before
315 * going on to the next one, OR retrying another packet.
316 */
317static ssize_t fr_bio_retry_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
318{
319 size_t used;
320 ssize_t rcode;
321 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
322 fr_bio_t *next;
324
325 fr_assert(my->partial != NULL);
327
329 fr_assert(used > 0);
330
331 /*
332 * There must be a next bio.
333 */
334 next = fr_bio_next(&my->bio);
335 fr_assert(next != NULL);
336
337 rcode = next->write(next, NULL, my->buffer.read, used);
338 if (rcode <= 0) return rcode;
339
340 my->buffer.read += rcode;
341
342 /*
343 * Still data in the buffer. We can't send more packets until we finished writing this one.
344 */
345 if (fr_bio_buf_used(&my->buffer) > 0) return 0;
346
347 /*
348 * We're done. Reset the buffer and clean up our cached partial packet.
349 */
351 my->partial = NULL;
352
353 /*
354 * The item was cancelled, which means it's no longer in the timer tree.
355 *
356 * If it's not cancelled, then we leave it in the tree, and run its timers s normal.
357 */
358 if (item->cancelled) {
359 item->packet_ctx = NULL;
360
361 fr_bio_retry_list_insert_head(&my->free, item);
362 }
363
364 /*
365 * Update the write function to allow writes before calling the resume function. The resume
366 * function may flush a partial write.
367 */
368 my->bio.write = fr_bio_retry_write;
369
370 rcode = fr_bio_retry_write_resume(&my->bio);
371 if (rcode <= 0) return rcode;
372
373 /*
374 * Try to write the packet which we were given.
375 */
376 return fr_bio_retry_write(bio, packet_ctx, buffer, size);
377}
378
379/** Save a partial packet when the write becomes blocked.
380 */
382{
383 fr_assert(!my->partial);
384 fr_assert(rcode > 0);
385 fr_assert((size_t) rcode < item->size);
386
387 /*
388 * (re)-alloc the buffer for partial writes.
389 */
390 if (!my->buffer.start ||
391 (item->size > fr_bio_buf_size(&my->buffer))) {
392 if (fr_bio_buf_alloc(my, &my->buffer, item->size)) {
393 /*
394 * We failed to save the item, and the only thing that we can do is release it. This
395 * lets the application stop tracking it, along with the timers.
396 */
398 return fr_bio_error(OOM);
399 }
400 }
401
402 fr_assert(fr_bio_buf_used(&my->buffer) == 0);
403 fr_assert(my->buffer.read == my->buffer.start);
404
405 fr_bio_buf_write(&my->buffer, item->buffer + rcode, item->size - rcode);
406
407 my->partial = item;
408
409 /*
410 * If the "next" BIO blocked, then the call to fr_bio_write_blocked() will have already called
411 * this function.
412 */
413 if (fr_bio_retry_write_blocked(&my->bio) < 0) return fr_bio_error(GENERIC);
414
415 my->bio.write = fr_bio_retry_write_partial;
416
417 /*
418 * We leave the entry in the timer tree so that the expiry timer will get hit.
419 *
420 * And then return the size of the partial data we wrote.
421 */
422 return rcode;
423}
424
425
426/** Resend a packet.
427 *
428 * This function should be called by the rewrite() callback, after (possibly) re-encoding the packet.
429 *
430 * If the next bio returns an error other than IO_WOULD_BLOCK, fr_bio_retry_rewrite() releases the item
431 * with FR_BIO_RETRY_WRITE_ERROR before returning the error. A rewrite() callback which does not call
432 * fr_bio_retry_rewrite() must release the item itself when the next bio returns an error, because
433 * fr_bio_retry_write_item() does not release the item.
434 *
435 * @param bio the binary IO handler
436 * @param item the retry context from #fr_bio_retry_sent_t
437 * @param buffer raw data for the packet. May be NULL, in which case the previous packet is retried
438 * @param size size of the raw data
439 * @return
440 * - <0 on error
441 * - 0 for "wrote no data"
442 * - >0 for "wrote data".
443 */
445{
446 ssize_t rcode;
447 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
448 fr_bio_t *next;
449
450 /*
451 * The caller may (accidentally or intentionally) call this function when there's a partial
452 * packet. The intention for rewrite() is that it is only called from timers, and those only run
453 * when the socket isn't blocked. But the caller might not pay attention to those issues.
454 */
455 if (my->partial) return 0;
456
457 /*
458 * There must be a next bio.
459 */
460 next = fr_bio_next(&my->bio);
461 fr_assert(next != NULL);
462
463 /*
464 * The caller should pass NULL for "use the previous packet".
465 */
466 if (buffer) {
467 item->buffer = buffer;
468 item->size = size;
469 }
470
471 /*
472 * Write out the packet, if everything is OK, return.
473 *
474 * Note that we don't update any timers if the write succeeded. That is handled by the caller.
475 */
476 rcode = next->write(next, item->packet_ctx, item->buffer, item->size);
477 if ((size_t) rcode == item->size) return rcode;
478
479 /*
480 * Can't write anything, be sad.
481 */
482 if (rcode == 0) return 0;
483
484 /*
485 * The next bio returned an error. We return IO_WOULD_BLOCK without releasing the item. For any
486 * other error, we release the item with FR_BIO_RETRY_WRITE_ERROR. fr_bio_retry_release() removes
487 * an unreserved item from the timer lists, and moves the unreserved item to the free list.
488 *
489 * The next bio decides whether the error is fatal, so we return the error.
490 */
491 if (rcode < 0) {
492 if (rcode == fr_bio_error(IO_WOULD_BLOCK)) return rcode;
493
495 return rcode;
496 }
497
498 /*
499 * We had previously written the packet, so save the re-sent one, too.
500 */
501 return fr_bio_retry_save_write(my, item, rcode);
502}
503
504/** Run an expiry timer event.
505 *
506 */
508{
509 fr_bio_retry_entry_t *item = uctx; /* an element of a talloc array, so it has no talloc header */
510 fr_bio_retry_t *my = talloc_get_type_abort(item->my, fr_bio_retry_t);
511
512 /*
513 * We only expire entries if writing is blocked.
514 */
516
517 /*
518 * An item is DONE if it received a reply, then waited for another reply, and then the socket
519 * became blocked.
520 */
522}
523
524/** Run a timer event. Usually to write out another packet.
525 *
526 */
528{
529 fr_bio_retry_entry_t *item = uctx; /* an element of a talloc array, so it has no talloc header */
530 fr_bio_retry_t *my = talloc_get_type_abort(item->my, fr_bio_retry_t);
531
532 fr_assert(my->partial == NULL);
534
535 /*
536 * Retry one item.
537 *
538 * A timer has no caller to return an error to. On error, the rewrite function has already
539 * released the item with FR_BIO_RETRY_WRITE_ERROR, and the release callback tells the
540 * application about the failure. The next bio decides whether the error is fatal. For
541 * a fatal error, the next bio has already shut the chain down, so fr_bio_retry_next_timer()
542 * has nothing more to do.
543 */
544 (void) fr_bio_retry_write_item(my, item, now);
545}
546
547/** Write a request, and see if we have a reply.
548 *
549 */
550static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
551{
552 ssize_t rcode;
554 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
555 fr_bio_t *next;
556
557 fr_assert(!my->partial);
558
559 /*
560 * There must be a next bio.
561 */
562 next = fr_bio_next(&my->bio);
563 fr_assert(next != NULL);
564
565 /*
566 * The caller is trying to flush partial data. But we don't have any partial data, so just call
567 * the next bio to flush it.
568 */
569 if (!buffer) {
570 return next->write(next, packet_ctx, NULL, size);
571 }
572
573 /*
574 * Catch the corner case where the max number of saved packets is exceeded.
575 */
576 if (fr_bio_retry_list_num_elements(&my->free) == 0) {
577 /*
578 * Grab the first item which can be expired.
579 */
581
582 /*
583 * If the item has no replies, we can't cancel it. Otherwise, try to cancel it, which
584 * will give us a free slot. If we can't cancel it, tell the application that we're
585 * blocked.
586 *
587 * Note that we do NOT call fr_bio_retry_write_blocked(), as that assumes the IO is
588 * blocked, and will stop all of the timers. Instead, the IO is fine, but we have no way
589 * to send more packets.
590 */
591 if (!item || !item->retry.replies || (fr_bio_retry_entry_cancel(bio, item) < 0)) {
592 /*
593 * write_blocked means "the IO is blocked", and the timers depend on that
594 * meaning. fr_bio_retry_next_timer() asserts that my->info.write_blocked is
595 * clear. fr_bio_retry_write_blocked() does not switch from the retry timers to
596 * the expiry timers if my->info.write_blocked is already set. Running out of
597 * entries then only sets all_used, and leaves write_blocked clear.
598 *
599 * When an entry is released, then all_used is reset, and if we don't have
600 * write_blocked, then we can resume.
601 */
602 my->all_used = true;
603
604 /*
605 * Previous BIOs are blocked, but we still try to write retries.
606 */
607 rcode = fr_bio_write_blocked(bio);
608 if (rcode < 0) return rcode;
609
610 return fr_bio_error(IO_WOULD_BLOCK);
611 }
612
613 /*
614 * We now have a free item, so we can use it.
615 */
616 fr_assert(fr_bio_retry_list_num_elements(&my->free) > 0);
617 }
618
619 /*
620 * Write out the packet. If there's an error, OR we wrote nothing, return.
621 *
622 * Note that we don't mark the socket as blocked if the next bio didn't write anything. We want
623 * the caller to know that the write didn't succeed, and the caller takes care of managing the
624 * current packet. So there's no need for us to do that.
625 */
626 rcode = next->write(next, packet_ctx, buffer, size);
627 if (rcode <= 0) return rcode;
628
629 /*
630 * Initialize the retry timers after writing the packet.
631 */
632 item = fr_bio_retry_list_pop_head(&my->free);
633 fr_assert(item != NULL);
634
635 fr_assert(item->my == my);
637 .my = my,
638 .retry.start = fr_time(),
639 .packet_ctx = packet_ctx,
640 .buffer = buffer,
641 .size = size,
642 };
643
644 /*
645 * Always initialize the retry timer. That way the sent() callback doesn't have to call
646 * fr_time().
647 *
648 * The application can call fr_bio_retry_entry_init() to re-initialize it, but that's fine.
649 */
650 fr_retry_init(&item->retry, item->retry.start, &my->retry_config);
651
652 /*
653 * Tell the application that we've saved the packet. The "item" pointer allows the application
654 * to cancel this packet if necessary.
655 */
656 my->sent(bio, packet_ctx, buffer, size, item);
657
658 /*
659 * This should never fail.
660 */
661 (void) fr_timer_uctx_insert(my->next_tl, item);
663
664 /*
665 * We only wrote part of the packet, remember to write the rest of it.
666 */
667 if ((size_t) rcode < size) {
668 return fr_bio_retry_save_write(my, item, rcode);
669 }
670
671 return size;
672}
673
674static ssize_t fr_bio_retry_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
675{
676 ssize_t rcode;
678 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
679 fr_bio_t *next;
680
681 /*
682 * There must be a next bio.
683 */
684 next = fr_bio_next(&my->bio);
685 fr_assert(next != NULL);
686
687 /*
688 * Read the packet. If error or nothing, return immediately.
689 */
690 rcode = next->read(next, packet_ctx, buffer, size);
691 if (rcode <= 0) return rcode;
692
693 /*
694 * Not a valid response to a request, OR a duplicate response to a request: don't return it to
695 * the caller.
696 *
697 * But if it is a duplicate response, update the counters and do cleanups as necessary.
698 */
699 item = NULL;
700 if (!my->response(bio, &item, packet_ctx, buffer, size)) {
701 if (!item) return 0;
702
703 item->retry.replies++;
704
705 /*
706 * We have enough replies. Release it.
707 */
708 if ((item->retry.replies >= item->retry.count) || !fr_time_delta_ispos(my->retry_config.mrd)) {
710 }
711
712 return 0;
713 }
714
715 fr_assert(item != NULL);
716 fr_assert(item->retry.replies == 0);
717 fr_assert(item != my->partial);
718
719 /*
720 * Track when the "most recently sent" packet has a reply. This metric is better than most
721 * others for judging the liveliness of the destination.
722 */
723 if (fr_time_lt(my->info.mrs_time, item->retry.start)) my->info.mrs_time = item->retry.start;
724
725 /*
726 * We have a new reply, remember when that happened. Note that we don't update this timer for
727 * duplicate replies, but perhaps we should?
728 */
729 my->info.last_reply = fr_time();
730
731 /*
732 * We have a new reply. If we've received all of the replies (i.e. one), OR we don't have a
733 * maximum lifetime for this request, then release it immediately.
734 */
735 item->retry.replies++;
736
737 /*
738 * We don't retry application-layer watchdog packets. And we don't run timers for them. The
739 * application is responsible for managing those timers itself.
740 */
741 if (item->reserved) return rcode;
742
743 /*
744 * There are no more packets to send, so this connection is idle.
745 *
746 * Note that partial packets aren't tracked in the timer tree. We can't do retransmits until the
747 * socket is writable.
748 */
750
751 /*
752 * We have enough replies. Release it.
753 */
754 if ((item->retry.replies >= item->retry.count) || !fr_time_delta_ispos(my->retry_config.mrd)) {
756 return rcode;
757 }
758
759 /*
760 * There are more replies pending. Wait passively for more replies, and clean up the item
761 * when the timer has expired.
762 */
763 item->retry.next = fr_time_add_time_delta(item->retry.start, my->retry_config.mrd);
764
765 (void) fr_timer_uctx_remove(my->next_tl, item);
766 (void) fr_timer_uctx_insert(my->next_tl, item);
767
768 return rcode;
769}
770
771/*
772 * Order the retries by what we have to do next.
773 *
774 * Note that "retry.next" here is capped at "retry.end". So if we need to expire an entry, it will
775 * happen at the "next" retry.
776 */
777static fr_cmp_ret_t _next_retry_cmp(void const *one, void const *two)
778{
779 fr_bio_retry_entry_t const *a = one;
780 fr_bio_retry_entry_t const *b = two;
781
782 fr_assert(a->buffer);
783 fr_assert(b->buffer);
784
785 return fr_time_cmp(a->retry.next, b->retry.next);
786}
787
788/*
789 * Order entries by when they expire, when we're not retrying.
790 *
791 * i.e. the socket is blocked, so all retries are paused.
792 */
793static fr_cmp_ret_t _expiry_cmp(void const *one, void const *two)
794{
795 fr_bio_retry_entry_t const *a = one;
796 fr_bio_retry_entry_t const *b = two;
797
798 fr_assert(a->buffer);
799 fr_assert(b->buffer);
800
801 return fr_time_cmp(a->retry.end, b->retry.end);
802}
803
804/** Cancel one item.
805 *
806 * If "item" is NULL, the last entry in the timer tree is cancelled.
807 *
808 * @param bio the binary IO handler
809 * @param item the retry context from #fr_bio_retry_sent_t
810 * @return
811 * - <0 error
812 * - 0 - didn't cancel
813 * - 1 - did cancel
814 */
816{
817 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
818
819 /*
820 * No item passed, try to cancel the first one to expire.
821 */
822 if (!item) {
824 if (!item) return 0;
825
826 /*
827 * This item hasn't had a response, we can't cancel it.
828 */
829 if (!item->retry.replies) return 0;
830 }
831
832 /*
833 * If the caller has cached a previously finished item, then that's a fatal error.
834 */
835 fr_assert(item->buffer != NULL);
836
838
839 return 1;
840}
841
842/** Set a per-packet retry config
843 *
844 * This function should be called from the #fr_bio_retry_sent_t callback to set a unique retry timer for this
845 * packet. If no retry configuration is set, then the main one from the alloc() function is used.
846 */
848{
849 fr_assert(item->buffer != NULL);
850
851 if (item->retry.config) return 0;
852
854
855 fr_retry_init(&item->retry, item->retry.start, cfg);
856
857 return 0;
858}
859
860/** Allow the callbacks / application to know when things are being retried.
861 *
862 * This is not initialized util _after_ fr_bio_retry_entry_start() has been called.
863 */
865{
866 fr_assert(item->buffer != NULL);
867
868 if (!item->retry.config) return NULL;
869
870 return &item->retry;
871}
872
873/** Orderly shutdown.
874 *
875 */
877{
878 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
880
883
884 /*
885 * Cancel all outgoing packets. Don't bother updating the tree or the free list, as all of the
886 * entries will be deleted when the memory is freed.
887 */
888 while ((item = fr_timer_uctx_peek(my->next_tl)) != NULL) {
889 (void) fr_timer_uctx_remove(my->next_tl, item);
891 }
892
893 return 0;
894}
895
896/** Allocate a #fr_bio_retry_t
897 *
898 */
899fr_bio_t *fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved,
904 fr_bio_retry_config_t const *cfg,
905 fr_bio_t *next)
906{
907 size_t i;
908 fr_bio_retry_t *my;
910
911 fr_assert(cfg->el);
912
913 /*
914 * Limit to reasonable values.
915 */
916 if (!max_saved) return NULL;
917 if (max_saved > 65536) return NULL;
918
919 my = talloc_zero(ctx, fr_bio_retry_t);
920 if (!my) return NULL;
921
922 /*
923 * Allocate everything up front, to get better locality of reference, less memory fragmentation,
924 * and better reuse of data structures.
925 */
926 items = talloc_array(my, fr_bio_retry_entry_t, max_saved);
927 if (!items) {
928 error:
929 talloc_free(my);
930 return NULL;
931 }
932
933 /*
934 * Insert the entries into the free list in order.
935 */
936 fr_bio_retry_list_init(&my->free);
937 for (i = 0; i < max_saved; i++) {
938 items[i].my = my;
939 fr_bio_retry_list_insert_tail(&my->free, &items[i]);
940 }
941
943 offsetof(fr_bio_retry_entry_t, next_retry_node),
944 offsetof(fr_bio_retry_entry_t, retry.next));
945 if (!my->next_tl) goto error;
946
948 offsetof(fr_bio_retry_entry_t, expiry_node),
949 offsetof(fr_bio_retry_entry_t, retry.end));
950 if (!my->expiry_tl) goto error;
951
952 /*
953 * The expiry list is run only when writes are blocked. We cannot have both lists active at the
954 * same time.
955 */
957
958 my->sent = sent;
959 if (!rewrite) {
961 } else {
962 my->rewrite = rewrite;
963 }
964 my->response = response;
965 my->release = release;
966
967 my->info.last_idle = fr_time();
968 my->info.el = cfg->el;
969 my->info.cfg = cfg;
970
971 my->retry_config = cfg->retry_config;
972
973 my->bio.write = fr_bio_retry_write;
974 my->bio.read = fr_bio_retry_read;
975
976 my->priv_cb.write_blocked = fr_bio_retry_write_blocked;
977 my->priv_cb.write_resume = fr_bio_retry_write_resume;
978 my->priv_cb.shutdown = fr_bio_retry_shutdown;
979
980 fr_bio_chain(&my->bio, next);
981
982 talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
983 return (fr_bio_t *) my;
984}
985
987{
988 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
989
990 return &my->info;
991}
992
994{
995 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
996 uint64_t num;
997
999
1000 if (!my->partial) return num;
1001
1002 /*
1003 * Only count partially written items if they haven't been cancelled.
1004 */
1005 return num + !my->partial->cancelled;
1006}
1007
1008/** Reserve an entry for later use with fr_bio_retry_rewrite()
1009 *
1010 * So that application-layer watchdogs can bypass the normal write / retry routines.
1011 */
1013{
1014 fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
1016
1017 item = fr_bio_retry_list_pop_head(&my->free);
1018 if (!item) return NULL;
1019
1020 fr_assert(item->my == my);
1022 .my = my,
1023 .reserved = true,
1024 };
1025
1026 return item;
1027}
static int const char char buffer[256]
Definition acutest.h:635
fr_bio_write_t _CONST write
write to the underlying bio
Definition base.h:117
fr_bio_read_t _CONST read
read from the underlying bio
Definition base.h:116
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:131
#define fr_bio_error(_x)
Definition base.h:200
static ssize_t fr_bio_retry_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Definition retry.c:674
fr_bio_retry_release_t release
callback to release a request / response pair
Definition retry.c:109
uint64_t fr_bio_retry_outstanding(fr_bio_t *bio)
Definition retry.c:993
static fr_cmp_ret_t _next_retry_cmp(void const *one, void const *two)
Definition retry.c:777
int fr_bio_retry_entry_init(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item, fr_retry_config_t const *cfg)
Set a per-packet retry config.
Definition retry.c:847
fr_bio_t * fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_retry_sent_t sent, fr_bio_retry_response_t response, fr_bio_retry_rewrite_t rewrite, fr_bio_retry_release_t release, fr_bio_retry_config_t const *cfg, fr_bio_t *next)
Allocate a fr_bio_retry_t.
Definition retry.c:899
static ssize_t fr_bio_retry_save_write(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, ssize_t rcode)
Save a partial packet when the write becomes blocked.
Definition retry.c:381
fr_bio_buf_t buffer
to store partial packets
Definition retry.c:111
static void fr_bio_retry_release(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_bio_retry_release_reason_t reason)
Release an entry back to the free list.
Definition retry.c:122
fr_timer_list_t * next_tl
when packets are retried next
Definition retry.c:91
static void fr_bio_retry_next_timer(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx)
Run a timer event.
Definition retry.c:527
int fr_bio_retry_entry_cancel(fr_bio_t *bio, fr_bio_retry_entry_t *item)
Cancel one item.
Definition retry.c:815
static fr_cmp_ret_t _expiry_cmp(void const *one, void const *two)
Definition retry.c:793
fr_timer_list_t * expiry_tl
when packets expire, so that we expire packets when the socket is blocked.
Definition retry.c:92
struct fr_bio_retry_list_s fr_bio_retry_list_t
Definition retry.c:55
ssize_t fr_bio_retry_rewrite(fr_bio_t *bio, fr_bio_retry_entry_t *item, const void *buffer, size_t size)
Resend a packet.
Definition retry.c:444
static int fr_bio_retry_write_item(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_time_t now)
Write one item.
Definition retry.c:215
static int fr_bio_retry_shutdown(fr_bio_t *bio)
Orderly shutdown.
Definition retry.c:876
fr_retry_config_t retry_config
Definition retry.c:96
static void fr_bio_retry_expiry_timer(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Run an expiry timer event.
Definition retry.c:507
fr_bio_retry_entry_t * partial
for partial writes
Definition retry.c:104
const fr_retry_t * fr_bio_retry_entry_info(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item)
Allow the callbacks / application to know when things are being retried.
Definition retry.c:864
fr_bio_retry_sent_t sent
callback for when we successfully sent a packet
Definition retry.c:106
fr_bio_retry_response_t response
callback to see if we got a valid response
Definition retry.c:108
static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write a request, and see if we have a reply.
Definition retry.c:550
bool all_used
blocked due to no free entries
Definition retry.c:98
static int fr_bio_retry_write_blocked(fr_bio_t *bio)
Writes are blocked.
Definition retry.c:180
static ssize_t fr_bio_retry_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
There's a partial packet written.
Definition retry.c:317
fr_bio_retry_info_t info
Definition retry.c:94
fr_bio_retry_rewrite_t rewrite
optional callback which can change a packet on retry
Definition retry.c:107
fr_bio_retry_entry_t * fr_bio_retry_item_reserve(fr_bio_t *bio)
Reserve an entry for later use with fr_bio_retry_rewrite()
Definition retry.c:1012
fr_bio_retry_info_t const * fr_bio_retry_info(fr_bio_t *bio)
Definition retry.c:986
static int fr_bio_retry_write_resume(fr_bio_t *bio)
Resume writes.
Definition retry.c:290
ssize_t(* fr_bio_retry_rewrite_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, const void *buffer, size_t size)
Definition retry.h:69
fr_time_t last_sent
last time we sent a packet.
Definition retry.h:56
fr_event_list_t * el
event list
Definition retry.h:51
void * rewrite_ctx
context specifically for rewriting this packet
Definition retry.c:67
fr_retry_config_t retry_config
base retry config
Definition retry.h:47
void(* fr_bio_retry_release_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, fr_bio_retry_release_reason_t reason)
Callback on release the packet (timeout or have all replies)
Definition retry.h:137
fr_retry_t retry
retry timers and counters
Definition retry.c:69
fr_bio_retry_release_reason_t
Definition retry.h:82
@ FR_BIO_RETRY_WRITE_ERROR
Definition retry.h:86
@ FR_BIO_RETRY_CANCELLED
Definition retry.h:85
@ FR_BIO_RETRY_DONE
Definition retry.h:83
@ FR_BIO_RETRY_NO_REPLY
Definition retry.h:84
fr_bio_retry_config_t const * cfg
so we know what was asked
Definition retry.h:61
struct fr_bio_retry_entry_s fr_bio_retry_entry_t
Definition retry.h:64
uint8_t const * buffer
cached copy of the packet to send
Definition retry.c:79
size_t size
size of the cached packet
Definition retry.c:80
bool reserved
for application-layer watchdog
Definition retry.c:83
void(* fr_bio_retry_sent_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size, fr_bio_retry_entry_t *retry_ctx)
Callback for when a packet is sent.
Definition retry.h:101
fr_time_t first_sent
first time we sent a packet since going idle
Definition retry.h:55
bool(* fr_bio_retry_response_t)(fr_bio_t *bio, fr_bio_retry_entry_t **item_p, void *packet_ctx, const void *buffer, size_t size)
Callback on read to see if a packet is a response.
Definition retry.h:124
fr_event_list_t * el
event list
Definition retry.h:45
fr_time_t mrs_time
Most recent sent time which had a reply.
Definition retry.h:53
void * packet_ctx
packet_ctx from the write() call
Definition retry.c:65
fr_rb_node_t expiry_node
for expiries
Definition retry.c:75
bool cancelled
was this item cancelled?
Definition retry.c:82
fr_time_t last_reply
When we last received a reply.
Definition retry.h:54
fr_bio_retry_t * my
so we can get to it from the event timer callback
Definition retry.c:77
fr_bio_retry_rewrite_t rewrite
per-packet rewrite callback
Definition retry.c:66
void * uctx
user-writable context
Definition retry.c:64
bool write_blocked
are writes blocked?
Definition retry.h:59
fr_time_t last_idle
last time we had nothing to do
Definition retry.h:57
Definition retry.c:63
static int packet_ctx
the tests pass only the address of packet_ctx
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:86
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
Definition buf.c:117
ssize_t fr_bio_buf_write(fr_bio_buf_t *bio_buf, const void *buffer, size_t size)
Definition buf.c:84
uint8_t * start
start of the buffer
Definition buf.h:30
static size_t fr_bio_buf_used(fr_bio_buf_t const *bio_buf)
Definition buf.h:73
static void fr_bio_buf_reset(fr_bio_buf_t *bio_buf)
Definition buf.h:61
uint8_t * read
where in the buffer reads are taken from
Definition buf.h:33
static size_t fr_bio_buf_size(fr_bio_buf_t const *bio_buf)
Definition buf.h:151
#define UNUSED
Definition build.h:384
#define FR_DLIST_TYPES(_name)
Define type specific wrapper structs for dlists.
Definition dlist.h:1150
#define FR_DLIST_ENTRY(_name)
Expands to the type name used for the entry wrapper structure.
Definition dlist.h:1136
#define FR_DLIST_FUNCS(_name, _element_type, _element_entry)
Define type specific wrapper functions for dlists.
Definition dlist.h:1173
#define FR_DLIST_HEAD(_name)
Expands to the type name used for the head wrapper structure.
Definition dlist.h:1143
free(array)
talloc_free(hp)
int fr_bio_write_blocked(fr_bio_t *bio)
Internal BIO function to tell all BIOs that it's blocked.
Definition base.c:346
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
Definition base.c:35
#define fr_time()
Definition event.c:60
static void * item(fr_lst_t const *lst, fr_lst_index_t idx)
Definition lst.c:121
long int ssize_t
unsigned char uint8_t
static size_t used
fr_cmp_ret_t
Result of an ordering comparison.
Definition misc.h:50
#define fr_assert(_expr)
Definition rad_assert.h:37
static fr_time_t fr_time_add_time_delta(fr_time_t a, fr_time_delta_t b)
Definition time.h:173
static int64_t fr_time_delta_unwrap(fr_time_delta_t time)
Definition time.h:154
#define fr_time_lteq(_a, _b)
Definition time.h:240
#define fr_time_delta_ispos(_a)
Definition time.h:290
#define fr_time_gt(_a, _b)
Definition time.h:237
#define fr_time_lt(_a, _b)
Definition time.h:239
static int8_t fr_time_cmp(fr_time_t a, fr_time_t b)
Compare two fr_time_t values.
Definition time.h:916
"server local" time.
Definition time.h:69
int fr_timer_list_disarm(fr_timer_list_t *tl)
Disarm a timer list.
Definition timer.c:1100
uint64_t fr_timer_list_num_events(fr_timer_list_t *tl)
Return number of pending events.
Definition timer.c:1148
int fr_timer_uctx_insert(fr_timer_list_t *tl, void *uctx)
Insert a uctx into a shared timer, and update the timer.
Definition timer.c:1347
fr_timer_list_t * fr_timer_list_shared_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent, fr_cmp_t cmp, fr_timer_cb_t callback, size_t node_offset, size_t time_offset)
Allocate a new shared event timer list.
Definition timer.c:1311
int fr_timer_uctx_remove(fr_timer_list_t *tl, void *uctx)
Remove a uctx from a shared timer.
Definition timer.c:1370
void * fr_timer_uctx_peek(fr_timer_list_t *tl)
Definition timer.c:1380
int fr_timer_list_arm(fr_timer_list_t *tl)
Arm (or re-arm) a timer list.
Definition timer.c:1121
An event timer list.
Definition timer.c:49
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
Initialize a retransmission counter.
Definition retry.c:110
void fr_retry_init(fr_retry_t *r, fr_time_t now, fr_retry_config_t const *config)
Initialize a retransmission counter.
Definition retry.c:36
fr_time_delta_t irt
Initial transmission time.
Definition retry.h:33
fr_retry_state_t
Definition retry.h:45
@ FR_RETRY_CONTINUE
Definition retry.h:46
fr_time_t end
when we will end the retransmissions
Definition retry.h:54
fr_time_delta_t mrd
Maximum retransmission duration.
Definition retry.h:35
fr_time_t next
when the next timer should be set
Definition retry.h:55