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
use SpindownRegistry;
use crateAppSpindownToken;
use Mutex;
use OnceLock;
use Duration;
// Global singleton spindown registry
static GLOBAL: = new;
// Spindown timeout (stored statically to allow customizing)
const DEFAULT_TIMEOUT_SECS: u64 = 2;
static TIMEOUT_SECS: = new;
/// A facade for interacting with the application’s global spindown registry.
///
/// Allows [registering](AppSpindown::register) arbitrary workloads and later
/// [waiting](AppSpindown::completed) for all registered workloads to signal
/// their graceful completion (within a configurable timeout).
///
/// ## Problem statement
///
/// Every application may choose to run asynchronous background tasks that hold
/// some kind of resource. An example here would be a pool of database connections
/// that is owned by a background task that lends access to the pool to any task
/// that requests it. However, when the application is shut down, all background
/// tasks are unceremoniously killed, which prevents proper clean-up, such as
/// closing the database connections.
///
/// ## Spindown
///
/// To solve the problem, this crate enables the following flow.
///
/// ### Main thread
///
/// The main thread:
///
/// - spawns background tasks,
/// - waits until the global [context](AppContext) is
/// [terminated](AppContext::terminated)(e.g., in a [`select`](tokio::select)
/// block, while also waiting for the main logic to finish),
/// - [waits](AppSpindown::completed) for all background tasks to signal
/// completion,
/// - returns from the `main` function.
///
/// ### Background tasks
///
/// Meanwhile, each spawned background task:
///
/// - [registers](AppSpindown::register) with the global spindown registry,
/// - also waits until the global [context](AppContext) is
/// [terminated](AppContext::terminated) (e.g., in a [`select`](tokio::select)
/// block, while also doing other work),
/// - performs clean-up procedures (e.g., closes connections, etc.),
/// - [punches out](AppSpindownToken::punch_out) the spindown token to signal
/// completion.
;