5
5
use {
6
6
crate :: store:: Store ,
7
7
anyhow:: Result ,
8
+ futures:: future:: join_all,
9
+ std:: sync:: atomic:: AtomicBool ,
8
10
structopt:: StructOpt ,
11
+ tokio:: spawn,
9
12
} ;
10
13
11
14
mod api;
@@ -15,6 +18,14 @@ mod macros;
15
18
mod network;
16
19
mod store;
17
20
21
+ // A static exit flag to indicate to running threads that we're shutting down. This is used to
22
+ // gracefully shutdown the application.
23
+ //
24
+ // NOTE: A more idiomatic approach would be to use a tokio::sync::broadcast channel, and to send a
25
+ // shutdown signal to all running tasks. However, this is a bit more complicated to implement and
26
+ // we don't rely on global state for anything else.
27
+ pub ( crate ) static SHOULD_EXIT : AtomicBool = AtomicBool :: new ( false ) ;
28
+
18
29
/// Initialize the Application. This can be invoked either by real main, or by the Geyser plugin.
19
30
async fn init ( ) -> Result < ( ) > {
20
31
log:: info!( "Initializing Hermes..." ) ;
@@ -29,35 +40,46 @@ async fn init() -> Result<()> {
29
40
let ( update_tx, update_rx) = tokio:: sync:: mpsc:: channel ( 1000 ) ;
30
41
31
42
// Initialize a cache store with a 1000 element circular buffer.
32
- let store = Store :: new ( update_tx, 1000 ) ;
43
+ let store = Store :: new ( update_tx. clone ( ) , 1000 ) ;
44
+
45
+ // Listen for Ctrl+C so we can set the exit flag and wait for a graceful shutdown. We
46
+ // also send off any notifications needed to close off any waiting tasks.
47
+ spawn ( async move {
48
+ tokio:: signal:: ctrl_c ( ) . await . unwrap ( ) ;
49
+ log:: info!( "Shut down signal received, waiting for tasks..." ) ;
50
+ SHOULD_EXIT . store ( true , std:: sync:: atomic:: Ordering :: Release ) ;
51
+ let _ = update_tx. send ( ( ) ) . await ;
52
+ } ) ;
33
53
34
- network:: p2p:: spawn ( opts. clone ( ) , store. clone ( ) ) . await ?;
35
- network:: pythnet:: spawn ( opts. clone ( ) , store. clone ( ) ) . await ?;
36
- api:: run ( opts. clone ( ) , store. clone ( ) , update_rx) . await ?;
54
+ // Spawn all worker tasks, and wait for all to complete (which will happen if a shutdown
55
+ // signal has been observed).
56
+ let tasks = join_all ( [
57
+ Box :: pin ( spawn ( network:: p2p:: spawn ( opts. clone ( ) , store. clone ( ) ) ) ) ,
58
+ Box :: pin ( spawn ( network:: pythnet:: spawn ( opts. clone ( ) , store. clone ( ) ) ) ) ,
59
+ Box :: pin ( spawn ( api:: run ( opts. clone ( ) , store. clone ( ) , update_rx) ) ) ,
60
+ ] )
61
+ . await ;
62
+
63
+ for task in tasks {
64
+ task??;
65
+ }
37
66
}
38
67
}
39
68
40
69
Ok ( ( ) )
41
70
}
42
71
43
72
#[ tokio:: main]
44
- async fn main ( ) -> Result < ! > {
73
+ async fn main ( ) -> Result < ( ) > {
45
74
env_logger:: init ( ) ;
46
75
47
- tokio:: spawn ( async move {
48
- // Launch the application. If it fails, print the full backtrace and exit. RUST_BACKTRACE
49
- // should be set to 1 for this otherwise it will only print the top-level error.
50
- if let Err ( result) = init ( ) . await {
51
- eprintln ! ( "{}" , result. backtrace( ) ) ;
52
- for cause in result. chain ( ) {
53
- eprintln ! ( "{cause}" ) ;
54
- }
55
- std:: process:: exit ( 1 ) ;
56
- }
57
- } ) ;
76
+ // Launch the application. If it fails, print the full backtrace and exit. RUST_BACKTRACE
77
+ // should be set to 1 for this otherwise it will only print the top-level error.
78
+ if let Err ( result) = init ( ) . await {
79
+ eprintln ! ( "{}" , result. backtrace( ) ) ;
80
+ result. chain ( ) . for_each ( |cause| eprintln ! ( "{cause}" ) ) ;
81
+ std:: process:: exit ( 1 ) ;
82
+ }
58
83
59
- // TODO: Setup a Ctrl-C handler that waits. We use process::exit(0) for now but we should have
60
- // a graceful shutdown with an AtomicBool or similar before production.
61
- tokio:: signal:: ctrl_c ( ) . await ?;
62
- std:: process:: exit ( 0 ) ;
84
+ Ok ( ( ) )
63
85
}
0 commit comments