@@ -7,23 +7,38 @@ use async_channel::{bounded, unbounded};
7
7
use config:: PoolConfig ;
8
8
use error:: PoolError ;
9
9
use mining_pool:: { get_coinbase_output, Pool } ;
10
+ use std:: sync:: { Arc , Mutex } ;
10
11
use template_receiver:: TemplateRx ;
11
12
use tokio:: select;
12
13
use tracing:: { error, info, warn} ;
13
14
14
15
#[ derive( Debug , Clone ) ]
15
16
pub struct PoolSv2 {
16
17
config : PoolConfig ,
18
+ status_tx : Arc < Mutex < Option < async_channel:: Sender < status:: Status > > > > ,
17
19
}
18
20
19
21
impl PoolSv2 {
20
22
pub fn new ( config : PoolConfig ) -> PoolSv2 {
21
- PoolSv2 { config }
23
+ PoolSv2 {
24
+ config,
25
+ status_tx : Arc :: new ( Mutex :: new ( None ) ) ,
26
+ }
22
27
}
23
28
24
29
pub async fn start ( & self ) -> Result < ( ) , PoolError > {
25
30
let config = self . config . clone ( ) ;
26
31
let ( status_tx, status_rx) = unbounded ( ) ;
32
+
33
+ if let Ok ( mut s_tx) = self . status_tx . lock ( ) {
34
+ * s_tx = Some ( status_tx. clone ( ) ) ;
35
+ } else {
36
+ error ! ( "Failed to access Pool status lock" ) ;
37
+ return Err ( PoolError :: Custom (
38
+ "Failed to access Pool status lock" . to_string ( ) ,
39
+ ) ) ;
40
+ }
41
+ let ( send_stop_signal, recv_stop_signal) = tokio:: sync:: watch:: channel ( ( ) ) ;
27
42
let ( s_new_t, r_new_t) = bounded ( 10 ) ;
28
43
let ( s_prev_hash, r_prev_hash) = bounded ( 10 ) ;
29
44
let ( s_solution, r_solution) = bounded ( 10 ) ;
@@ -60,8 +75,9 @@ impl PoolSv2 {
60
75
s_message_recv_signal,
61
76
status:: Sender :: DownstreamListener ( status_tx) ,
62
77
config. shares_per_minute ( ) ,
63
- ) ;
64
-
78
+ recv_stop_signal,
79
+ )
80
+ . await ?;
65
81
// Start the error handling loop
66
82
// See `./status.rs` and `utils/error_handling` for information on how this operates
67
83
tokio:: spawn ( async move {
@@ -84,16 +100,23 @@ impl PoolSv2 {
84
100
let task_status: status:: Status = task_status. unwrap ( ) ;
85
101
86
102
match task_status. state {
103
+ status:: State :: Shutdown => {
104
+ info ! ( "Received shutdown signal" ) ;
105
+ let _ = send_stop_signal. send ( ( ) ) ;
106
+ break ;
107
+ }
87
108
// Should only be sent by the downstream listener
88
109
status:: State :: DownstreamShutdown ( err) => {
89
110
error ! (
90
111
"SHUTDOWN from Downstream: {}\n Try to restart the downstream listener" ,
91
112
err
92
113
) ;
114
+ let _ = send_stop_signal. send ( ( ) ) ;
93
115
break ;
94
116
}
95
117
status:: State :: TemplateProviderShutdown ( err) => {
96
118
error ! ( "SHUTDOWN from Upstream: {}\n Try to reconnecting or connecting to a new upstream" , err) ;
119
+ let _ = send_stop_signal. send ( ( ) ) ;
97
120
break ;
98
121
}
99
122
status:: State :: Healthy ( msg) => {
@@ -105,6 +128,7 @@ impl PoolSv2 {
105
128
. safe_lock ( |p| p. remove_downstream ( downstream_id) )
106
129
. is_err ( )
107
130
{
131
+ let _ = send_stop_signal. send ( ( ) ) ;
108
132
break ;
109
133
}
110
134
}
@@ -113,6 +137,31 @@ impl PoolSv2 {
113
137
} ) ;
114
138
Ok ( ( ) )
115
139
}
140
+
141
+ pub fn shutdown ( & self ) {
142
+ info ! ( "Attempting to shutdown pool" ) ;
143
+ if let Ok ( status_tx) = & self . status_tx . lock ( ) {
144
+ if let Some ( status_tx) = status_tx. as_ref ( ) . cloned ( ) {
145
+ info ! ( "Pool is running, sending shutdown signal" ) ;
146
+ tokio:: spawn ( async move {
147
+ if let Err ( e) = status_tx
148
+ . send ( status:: Status {
149
+ state : status:: State :: Shutdown ,
150
+ } )
151
+ . await
152
+ {
153
+ error ! ( "Failed to send shutdown signal to status loop: {:?}" , e) ;
154
+ } else {
155
+ info ! ( "Sent shutdown signal to Pool" ) ;
156
+ }
157
+ } ) ;
158
+ } else {
159
+ info ! ( "Pool is not running." ) ;
160
+ }
161
+ } else {
162
+ error ! ( "Failed to access Pool status lock" ) ;
163
+ }
164
+ }
116
165
}
117
166
118
167
#[ cfg( test) ]
@@ -148,4 +197,33 @@ mod tests {
148
197
let result = pool. start ( ) . await ;
149
198
assert ! ( result. is_err( ) ) ;
150
199
}
200
+
201
+ #[ tokio:: test]
202
+ async fn shutdown_pool ( ) {
203
+ let config_path = "config-examples/pool-config-hosted-tp-example.toml" ;
204
+ let config: PoolConfig = match Config :: builder ( )
205
+ . add_source ( File :: new ( config_path, FileFormat :: Toml ) )
206
+ . build ( )
207
+ {
208
+ Ok ( settings) => match settings. try_deserialize :: < PoolConfig > ( ) {
209
+ Ok ( c) => c,
210
+ Err ( e) => {
211
+ error ! ( "Failed to deserialize config: {}" , e) ;
212
+ return ;
213
+ }
214
+ } ,
215
+ Err ( e) => {
216
+ error ! ( "Failed to build config: {}" , e) ;
217
+ return ;
218
+ }
219
+ } ;
220
+ let pool_0 = PoolSv2 :: new ( config. clone ( ) ) ;
221
+ let pool_1 = PoolSv2 :: new ( config) ;
222
+ assert ! ( pool_0. start( ) . await . is_ok( ) ) ;
223
+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 500 ) ) . await ;
224
+ assert ! ( pool_1. start( ) . await . is_err( ) ) ;
225
+ pool_0. shutdown ( ) ;
226
+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_secs ( 1 ) ) . await ;
227
+ assert ! ( pool_1. start( ) . await . is_ok( ) ) ;
228
+ }
151
229
}
0 commit comments