1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
//! a rust library providing an RCU (read-copy-update) algorithm specifically made for async rust with tokio.
//!
//! this provides a lock-free and wait-free way to update a shared piece of state while it is concurrently being read and updated by
//! other tasks.
//!
//! the core primitive provided by this crate is [`synchronize_rcu`], which works just like the `synchronize_rcu` function in the
//! linux kernel - it waits for an rcu grace period, which allows writers to track when exactly they can reclaim swapped out data.
//!
//! the low level [`synchronize_rcu`] primitive can be used to build a bunch of higher level abstractions.
//! the simplest abstraction - a single pointer to shared data - is implemented in this crate by the [`RcuPtr`] type.
//!
//! # performance
//!
//! NOTE: this section specifically refers to [`RcuPtr`], the main high level abstraction provided by this crate, but will probably also
//! apply to most other abstractions which can be implemented using the rcu primitive.
//!
//! this crate is speicifcally useful for read-mostly data, as it makes readers extremely fast at the cost of making the writers slower.
//! when a reader reads the rcu protected data, the read operation is basically just a single load of an atomic pointer.
//! (plus a write to a non-shared thread local variable, which is only used to track misuse of the rcu primitive, and is negligible in
//! terms of performance).
//! no memory writes to shared data are performed, as opposed to spinlocks and mutexes which requires such memory writes, and sometimes
//! even syscalls, just to access the underlying data.
//!
//! specifically, for read-mostly data, the cache line containing the pointer can be shared between all readers, and the read operation
//! basically becomes a single load from the cpu cache (plus the aforementioned negligible thread-local write).
//! compared to spinlocks and mutexes which usually require exclusive ownership over the cacheline due to writes and other atomic
//! operations, this is much faster and provides much better reader latency.
//!
//! also note that the time spent on a read operation is very predictable and static. other users of the data, such as concurrent readers
//! and even writers, do not affect the time it takes for a reader to read the data. a read is always the same amount of operations.
//! specifically a writer can slightly delay this load due to invalidating the cacheline containing the rcu protected pointer when writing
//! to it, but this is mostly negligible.
//! this can be very important in latency-critical applications which require a high-performance fast path with predictable latency.
//!
//! also see [benchmnarks](#benchmarks).
//!
//! # quick start
//!
//! ```rust
//! use tokio_rcu::{rcu_block_on, rcu_ptr::RcuPtr};
//!
//! fn main() {
//! rcu_block_on(async move {
//! let numbers = RcuPtr::new(Box::new(vec![1, 2, 3, 4]));
//!
//! // rcu protected values can be accessed using the `with` function.
//! numbers.with(|numbers| {
//! assert!(numbers.contains(&3));
//! assert!(!numbers.contains(&5));
//! });
//!
//! // the rcu protected value can be modified while readers are using it.
//! // and, the old allocation is returned.
//! let new_numbers = Box::new(vec![5, 6, 7, 8]);
//! let _old_numbers: Box<Vec<i32>> = numbers.swap(new_numbers).await;
//!
//! numbers.with(|numbers| {
//! assert!(numbers.contains(&6));
//! assert!(!numbers.contains(&10));
//! });
//! })
//! }
//! ```
//!
//! # realistic use case
//!
//! for a more realistic use case, see `examples/basic.rs`.
//!
//! # how does it work?
//!
//! when a writer swaps out an old data pointer with a new data pointer containing updated data, he must then know when the previous
//! data pointer can be freed.
//!
//! to free the data, the writer must first wait for all potential readers, who have already read the pointer and are now using it, to
//! finish using that pointer, to avoid a UAF (use-after-free) situation.
//!
//! this problem can be solved in many ways, but rcu usually solves it by defining a state called a "quiescent state", such that when
//! a specific execution context (which can be a cpu core, or an OS thread) reaches that quiescent state, it is guaranteed to not hold
//! any rcu protected pointer.
//!
//! in this specific crate, the execution contexts are tokio threads, and the quiescent state was chosen to be tokio's
//! [`on_after_task_poll`] hook.
//!
//! this works because this crate limits the usage of rcu protected pointers in a way that prevents them from being held across await
//! points.
//! so, when the runtime reaches the [`on_after_task_poll`] hook, it is guaranteed that no future is currently being executed on
//! the current thread, and since rcu protected pointers can't be held across await points, it is basically guaranteed that the current
//! thread is not holding any rcu protected pointers.
//!
//! waiting an rcu grace period thus means first ensuring that all of our previous memory writes (e.g. rcu pointer swaps) are visible to
//! all other threads, and then just waiting for each other thread to pass at least once through a quiescent state.
//! after such a grace period, it is guaranteed that any swapped out pointers are no longer used by any of the threads, so their memory
//! can be reclaimed.
//!
//! # enabling rcu support
//!
//! to use the rcu primitives, you must use an rcu enabled tokio runtime.
//!
//! the easiest way to do this is to use the [`rcu_block_on`] function which creates a tokio runtime with rcu support enabled, and then
//! runs the provided future inside that runtime using tokio's [`block_on`](tokio::runtime::Runtime::block_on).
//!
//! if you wish to manually configure your runtime, you can use the more low-level [`enable_rcu`](TokioRuntimeBuilderExt::enable_rcu) and
//! [`rcu_block_on`](TokioRuntimeExt::rcu_block_on) functions.
//!
//! # performance overhead
//!
//! enabling rcu for a tokio runtime does introduce a little bit of overhead.
//!
//! specifically, this crate uses tokio hooks (e.g. [`on_after_task_poll`]) to track quiescent states of tokio's worker threads.
//!
//! but, this crate performs a lot of efforts to make this overhead as small as possible, especially in hooks like [`on_after_task_poll`]
//! which are called very often.
//!
//! specifically, the current implementation of the [`on_after_task_poll`] hook is basically just a couple of atomic loads and stores,
//! and is unnoticeable in terms of performance.
//!
//! # other async runtimes
//!
//! this crate could quite easily be ported to work with other runtimes other than tokio. i chose tokio because it is the most popular
//! runtime, and because it already provides hooks which allow me to track quiescent states quite easily.
//!
//! # stability
//!
//! this crate currently requires using the `tokio_unstable` configuration of tokio. this is required since the [`on_after_task_poll`]
//! hook is currently unstable, and is needed to make this crate work.
//!
//! # benchmarks
//!
//! to run the benchmarks, use:
//! ```bash
//! cargo bench
//! ```
//!
//! the benchmarks mostly compare this crate with the `arc_swap` crate, which solves the same problem in a different way and provides
//! a very similar interface.
//!
//! here are the results of running the benchmarks on my 20-core `12th Gen Intel(R) Core(TM) i7-12700` cpu:
//! ```text
//! Timer precision: 40 ns
//! comparison fastest │ slowest │ median │ mean │ samples │ iters
//! ├─ arc_swap_read_only │ │ │ │ │
//! │ ├─ 1 21.68 ms │ 32.75 ms │ 22.53 ms │ 23.69 ms │ 100 │ 100
//! │ ├─ 8 23.73 ms │ 49.89 ms │ 29.11 ms │ 30.15 ms │ 100 │ 100
//! │ ├─ 16 39.49 ms │ 44.63 ms │ 43.61 ms │ 42.87 ms │ 100 │ 100
//! │ ├─ 32 65.75 ms │ 81.64 ms │ 68.5 ms │ 69.41 ms │ 100 │ 100
//! │ ╰─ 64 129.5 ms │ 137.9 ms │ 132.6 ms │ 132.5 ms │ 100 │ 100
//! ├─ rcu_ptr_read_only │ │ │ │ │
//! │ ├─ 1 7.322 ms │ 28.86 ms │ 11.49 ms │ 13.23 ms │ 100 │ 100
//! │ ├─ 8 8.782 ms │ 15.87 ms │ 9.6 ms │ 10.24 ms │ 100 │ 100
//! │ ├─ 16 10.04 ms │ 16.07 ms │ 10.48 ms │ 10.59 ms │ 100 │ 100
//! │ ├─ 32 16.04 ms │ 20.69 ms │ 16.69 ms │ 17.5 ms │ 100 │ 100
//! │ ╰─ 64 31.18 ms │ 38.09 ms │ 32.9 ms │ 33.63 ms │ 100 │ 100
//! ├─ arc_swap_read_while_writing │ │ │ │ │
//! │ ├─ 1 reader tasks, 1 writer tasks 24.7 ms │ 33.48 ms │ 26.03 ms │ 26.49 ms │ 100 │ 100
//! │ ├─ 8 reader tasks, 1 writer tasks 30.04 ms │ 43.33 ms │ 33.75 ms │ 34.59 ms │ 100 │ 100
//! │ ├─ 8 reader tasks, 2 writer tasks 34.77 ms │ 50.08 ms │ 38.08 ms │ 38.96 ms │ 100 │ 100
//! │ ├─ 16 reader tasks, 1 writer tasks 48.31 ms │ 59.53 ms │ 54.27 ms │ 54 ms │ 100 │ 100
//! │ ├─ 16 reader tasks, 2 writer tasks 54.81 ms │ 74.05 ms │ 65.95 ms │ 64.88 ms │ 100 │ 100
//! │ ├─ 32 reader tasks, 1 writer tasks 67.09 ms │ 87.9 ms │ 69.57 ms │ 71.65 ms │ 100 │ 100
//! │ ├─ 32 reader tasks, 2 writer tasks 68.71 ms │ 85.48 ms │ 73.15 ms │ 74.21 ms │ 100 │ 100
//! │ ├─ 64 reader tasks, 1 writer tasks 130.2 ms │ 140.5 ms │ 134.1 ms │ 134 ms │ 100 │ 100
//! │ ╰─ 64 reader tasks, 2 writer tasks 131 ms │ 145.5 ms │ 136 ms │ 136 ms │ 100 │ 100
//! ├─ rcu_ptr_read_while_writing │ │ │ │ │
//! │ ├─ 1 reader tasks, 1 writer tasks 7.907 ms │ 12.98 ms │ 8.422 ms │ 8.755 ms │ 100 │ 100
//! │ ├─ 8 reader tasks, 1 writer tasks 9.129 ms │ 17.74 ms │ 10 ms │ 10.26 ms │ 100 │ 100
//! │ ├─ 8 reader tasks, 2 writer tasks 9.387 ms │ 12.91 ms │ 10.03 ms │ 10.23 ms │ 100 │ 100
//! │ ├─ 16 reader tasks, 1 writer tasks 11 ms │ 14.48 ms │ 11.16 ms │ 11.31 ms │ 100 │ 100
//! │ ├─ 16 reader tasks, 2 writer tasks 11.21 ms │ 13.17 ms │ 11.37 ms │ 11.5 ms │ 100 │ 100
//! │ ├─ 32 reader tasks, 1 writer tasks 17.22 ms │ 22.5 ms │ 17.78 ms │ 18.6 ms │ 100 │ 100
//! │ ├─ 32 reader tasks, 2 writer tasks 17.19 ms │ 22.42 ms │ 18.16 ms │ 18.85 ms │ 100 │ 100
//! │ ├─ 64 reader tasks, 1 writer tasks 33.22 ms │ 38.55 ms │ 34.12 ms │ 35.01 ms │ 100 │ 100
//! │ ╰─ 64 reader tasks, 2 writer tasks 33.38 ms │ 38.62 ms │ 34.79 ms │ 35.45 ms │ 100 │ 100
//! ├─ arc_swap_write_while_reading │ │ │ │ │
//! │ ├─ 1 reader tasks, 1 writer tasks 455.3 µs │ 852.4 µs │ 517.6 µs │ 565.2 µs │ 100 │ 100
//! │ ├─ 1 reader tasks, 8 writer tasks 558.8 µs │ 3.202 ms │ 814.3 µs │ 900.5 µs │ 100 │ 100
//! │ ├─ 1 reader tasks, 16 writer tasks 746.1 µs │ 2.984 ms │ 1.567 ms │ 1.584 ms │ 100 │ 100
//! │ ├─ 1 reader tasks, 32 writer tasks 1.304 ms │ 6.793 ms │ 3.76 ms │ 3.352 ms │ 100 │ 100
//! │ ├─ 1 reader tasks, 64 writer tasks 1.634 ms │ 10.09 ms │ 4.119 ms │ 4.971 ms │ 100 │ 100
//! │ ├─ 2 reader tasks, 2 writer tasks 468.9 µs │ 917.3 µs │ 617.7 µs │ 637.6 µs │ 100 │ 100
//! │ ├─ 4 reader tasks, 8 writer tasks 742.1 µs │ 3.01 ms │ 1.525 ms │ 1.48 ms │ 100 │ 100
//! │ ├─ 8 reader tasks, 8 writer tasks 970.5 µs │ 3.635 ms │ 1.915 ms │ 1.986 ms │ 100 │ 100
//! │ ├─ 16 reader tasks, 16 writer tasks 1.903 ms │ 5.748 ms │ 2.21 ms │ 2.576 ms │ 100 │ 100
//! │ ├─ 32 reader tasks, 32 writer tasks 4.504 ms │ 10.46 ms │ 6.464 ms │ 6.322 ms │ 100 │ 100
//! │ ╰─ 64 reader tasks, 64 writer tasks 10.1 ms │ 18.84 ms │ 12.01 ms │ 12.27 ms │ 100 │ 100
//! ╰─ rcu_ptr_write_while_reading │ │ │ │ │
//! ├─ 1 reader tasks, 1 writer tasks 818.2 µs │ 3.266 ms │ 1.275 ms │ 1.458 ms │ 100 │ 100
//! ├─ 1 reader tasks, 8 writer tasks 2.283 ms │ 11.61 ms │ 2.724 ms │ 3.751 ms │ 100 │ 100
//! ├─ 1 reader tasks, 16 writer tasks 2.801 ms │ 7.454 ms │ 2.966 ms │ 3.294 ms │ 100 │ 100
//! ├─ 1 reader tasks, 32 writer tasks 3.042 ms │ 199 ms │ 3.792 ms │ 34.18 ms │ 100 │ 100
//! ├─ 1 reader tasks, 64 writer tasks 3.328 ms │ 199.2 ms │ 6.99 ms │ 53.64 ms │ 100 │ 100
//! ├─ 2 reader tasks, 2 writer tasks 1.769 ms │ 3.773 ms │ 2.059 ms │ 2.097 ms │ 100 │ 100
//! ├─ 4 reader tasks, 8 writer tasks 2.543 ms │ 11.66 ms │ 3.055 ms │ 3.992 ms │ 100 │ 100
//! ├─ 8 reader tasks, 8 writer tasks 3.249 ms │ 6.313 ms │ 3.452 ms │ 3.606 ms │ 100 │ 100
//! ├─ 16 reader tasks, 16 writer tasks 5.776 ms │ 10.99 ms │ 6.268 ms │ 6.458 ms │ 100 │ 100
//! ├─ 32 reader tasks, 32 writer tasks 10.09 ms │ 16.99 ms │ 12.66 ms │ 12.29 ms │ 100 │ 100
//! ╰─ 64 reader tasks, 64 writer tasks 15.13 ms │ 67.13 ms │ 18.01 ms │ 18.84 ms │ 100 │ 100
//! ```
//!
//! as you can see, `tokio_rcu`'s reads are faster than `arc_swap`'s reads (about 3x faster on average), while `tokio_rcu`'s writes
//! are slower than `arc_swap`'s writes (about 3x times slower on average). for a read-heavy situation, this is ideal.
//!
//! furthermore, note that when using `arc_swap`, the time it takes for a single read operation seems to scale with the number of
//! concurrent readers (see the results of the `arc_swap_read_only` and `arc_swap_read_while_writing` benchmarks), while `tokio_rcu`'s
//! read operation takes roughly the same amount of time regardless of the number of concurrent readers, up until the point where there
//! are more readers than cpu cores (more than 20 reader tasks), at which point the readers start sharing cpu cores and competing for
//! their runtime, which obviously takes its toll on the performance.
//!
//! also note that this constant time for the read operation holds even when writers are concurrently modifying the data - the time
//! spent on a single read operation remains roughly the same (see `rcu_ptr_read_while_writing`), unlike `arc_swap` (see
//! `arc_swap_read_while_writing`).
//!
//! moreover, while `tokio_rcu`'s writes are slower, it is mostly because the writers are sleeping while waiting for other threads to
//! pass through a quiescent state, so they are not slower in the sense that they perform more cpu-bound work, only the total time it
//! takes for a swap operation to complete after fully awaiting it. in practice the writes may actually spend less cpu time than
//! `arc_swap`'s write.
//!
//! # testing
//!
//! to run the tests, use:
//! ```bash
//! cargo all-features nextest run --release
//! cargo all-features test --doc --release
//! ```
//!
//! (NOTE: this requires installing `cargo-all-features` and `cargo-nextest`)
//!
//! `cargo-nextest` is used since it allows running each test as a separate process, which is important for testing this crate, since
//! this crate heavily relies on thread local variables and generally assumes that only a single tokio runtime is used per process.
//! furthermore, bugs in the rcu primitives can cause UAFs which may crash the process. if the process crashes when using `cargo test`,
//! all tests stop running and no diagnostics are reported. with `cargo-nextest`, such failures are gracefully reported as test failures.
//!
//! sadly, `cargo-nextest` currently does not support running doctests, so we must run them separately. note that `cargo test --doc`
//! already runs each test in its own process, so luckily for us, we don't need `cargo-nextest` for process isolation in this case.
//!
//! furthermore, `cargo-all-features` is used to also test the crate under the `small_epoch_id` feature flag, which is for testing mode
//! only, and allows testing some internal edge cases of this crate which are extremely hard to reach in the default configuration.
//!
//! it is also recommended to run the tests in release mode since it increases the probability of being able to find race conditions and
//! other hard to catch edge cases.
//!
//! # platform support
//!
//! currently, this crate only works on linux and windows.
//!
//! the limitation stems from the membarrier operation, which is currently only implemented for linux (using the membarrier syscall),
//! and windows (using FlushProcessWriteBuffers).
//!
//! more platforms can be added in the future if needed, and given that they have a way to emulate the behaviour of membarrier.
//!
//! # license
//!
//! This project is licensed under the MIT license.
//!
//! [`on_after_task_poll`]: tokio::runtime::Builder::on_after_task_poll
//! [`RcuPtr`]: rcu_ptr::RcuPtr
use ;
use crate::;
use RuntimeFlavor;
/// a notification which is notified when threads update their last seen epoch id or change their status in any other meaningful
/// way (e.g. become non-busy). used by waiters to wait for notifications in a blocking manner while waiting for threads to see
/// their new epoch id, instead of constantly busy polling all threads.
static THREAD_EPOCH_UPDATED_NOTIFY: Notify = new;
/// a lock used to synchronize the reset operation.
/// a reset operation is performed when the epoch id overflows, in order to reset the epoch id back to its minimum value.
///
/// when some thread increments the epoch id and causes it to exceed its max threshold, this thread begins a reset operation.
/// for resetting the epoch id, the thread must reset the global epoch id back to its initial value, then wait for all threads to
/// see this new state while blocking any further increments of the epoch id until all threads see the reset value.
///
/// in order to prevent the further increments of the epoch id during the reset operation, this lock is used.
/// all incrementors of the epoch id lock it for reading before incrementing, and during the reset operation, the leader of the reset (the
/// first one to increment the epoch id past its max threshold) locks this lock for writing, thus preventing any new incrementors from
/// incrementing the epoch id.
///
/// this also ensures that we don't start performing a reset operation while some incrementor thread is still waiting for threads to see
/// his incremented epoch id. if we were to start the reset while we was waiting, we would get stuck until the next overflow of the epoch
/// id.
static EPOCH_ID_RESET_SYNC_LOCK: RwLock = const_new;
/// when a thread increments the epoch id past its max threshold, this thread begins a reset operation.
/// while that thread was incrementing the epoch id, another thread may have also been incrementing the epoch id, and also saw that it
/// reached its max threshold. so, that thread also begins the reset operation.
///
/// in practice, the reset is actually only performed by a single thread - the leader, and all other threads that entered reset just wait
/// for him to finish resetting.
///
/// so, this notification used by the leader of a reset operation to notify all other threads that have also entered reset that the reset
/// operation is done.
static RESET_FINISHED_NOTIFICATION: Notify = new;
/// wait for an RCU grace period.
///
/// this function first performs a membarrier to synchronize all previous writes performed by the current thread with all other
/// threads in the process.
///
/// after performing the membarrier, this function waits for every thread that was active during the membarrier operation to pass
/// through a quiescent state or to became unactive.
///
/// a quiescent state of a thread is defined as a state where the thread is not executing any user-defined task, and is instead executing
/// code inside tokio's task scheduling logic.
pub async
/// wait for all other threads in the process other than the current thread to see some epoch id as implemented in the given predicate
/// which processes the last seen epoch id of each thread.
///
/// this function does not take into account new threads just starting, nor new threads just existing the busy state.
async
/// "see" a new epoch id in the current thread.
/// this fetches the current epoch id with a proper memory ordering - a release memory ordering, which provides the required
/// guaranteed, for example it guarantees that once we see an updated epoch id, we see the swap of the rcu protected pointer
/// as happened before that store to the epoch id.
/// extension methods for tokio's runtime builder.
/// extension methods for tokio's runtime.
/// runs the provided future inside a new multi-threaded tokio runtime with all features enabled and with rcu support.
/// a wrapper around the root future of a tokio `block_on` call.
///
/// this is required since tokio's hooks only apply to tokio's worker threads, but not to the main thread which initially calls `block_on`.
///
/// but, we need the main thread to also perform the book-keeping needed by the rcu primitive, in order for it to be able use the rcu
/// primitives and to interact with the other threads using the rcu primitives.
///
/// so, we wrap the main future passed to `block_on` in a custom wrapper which emulates the call to the different worker hooks.
/// this lets the main thread participate in the book-keeping like any other worker thread.