Skip to content

Commit 1ee8977

Browse files
authoredFeb 1, 2025··
Merge pull request #1360 from Shourya742/2025-01-20-change-shutdown-api-and-add-unit-test
Add jds-do-not-fail-on-jdc-shutdown integration test
2 parents 4637a82 + 1fe2395 commit 1ee8977

File tree

8 files changed

+155
-182
lines changed

8 files changed

+155
-182
lines changed
 

‎.github/workflows/mg.yaml

-10
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,6 @@ jobs:
4040
- name: Run jds-do-not-fail-on-wrong-tsdatasucc
4141
run: sh ./test/message-generator/test/jds-do-not-fail-on-wrong-tsdatasucc/jds-do-not-fail-on-wrong-tsdatasucc.sh
4242

43-
jds-do-not-panic-if-jdc-close-connection:
44-
runs-on: ubuntu-latest
45-
steps:
46-
- name: Checkout repository
47-
uses: actions/checkout@v4
48-
- name: Run jds-do-not-panic-if-jdc-close-connection
49-
run: sh ./test/message-generator/test/jds-do-not-panic-if-jdc-close-connection/jds-do-not-panic-if-jdc-close-connection.sh
50-
5143
jds-do-not-stackoverflow-when-no-token:
5244
runs-on: ubuntu-latest
5345
steps:
@@ -148,7 +140,6 @@ jobs:
148140
interop-proxy-with-multi-ups,
149141
interop-proxy-with-multi-ups-extended,
150142
jds-do-not-fail-on-wrong-tsdatasucc,
151-
jds-do-not-panic-if-jdc-close-connection,
152143
jds-do-not-stackoverflow-when-no-token,
153144
jds-receive-solution-while-processing-declared-job,
154145
pool-sri-test-1-standard,
@@ -167,7 +158,6 @@ jobs:
167158
[ "${{ needs.interop-proxy-with-multi-ups.result }}" != "success" ] ||
168159
[ "${{ needs.interop-proxy-with-multi-ups-extended.result }}" != "success" ] ||
169160
[ "${{ needs.jds-do-not-fail-on-wrong-tsdatasucc.result }}" != "success" ] ||
170-
[ "${{ needs.jds-do-not-panic-if-jdc-close-connection.result }}" != "success" ] ||
171161
[ "${{ needs.jds-do-not-stackoverflow-when-no-token.result }}" != "success" ] ||
172162
[ "${{ needs.jds-receive-solution-while-processing-declared-job.result }}" != "success" ] ||
173163
[ "${{ needs.pool-sri-test-1-standard.result }}" != "success" ] ||

‎roles/jd-client/src/lib/mod.rs

+67-6
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl JobDeclaratorClient {
7979
let task_collector = Arc::new(Mutex::new(vec![]));
8080

8181
tokio::spawn({
82-
let shutdown_signal = self.shutdown();
82+
let shutdown_signal = self.shutdown.clone();
8383
async move {
8484
if tokio::signal::ctrl_c().await.is_ok() {
8585
info!("Interrupt received");
@@ -93,17 +93,18 @@ impl JobDeclaratorClient {
9393
let task_collector = task_collector.clone();
9494
let tx_status = tx_status.clone();
9595
let proxy_config = proxy_config.clone();
96+
let root_handler;
9697
if let Some(upstream) = proxy_config.upstreams.get(upstream_index) {
9798
let tx_status = tx_status.clone();
9899
let task_collector = task_collector.clone();
99100
let upstream = upstream.clone();
100-
tokio::spawn(async move {
101+
root_handler = tokio::spawn(async move {
101102
Self::initialize_jd(proxy_config, tx_status, task_collector, upstream).await;
102103
});
103104
} else {
104105
let tx_status = tx_status.clone();
105106
let task_collector = task_collector.clone();
106-
tokio::spawn(async move {
107+
root_handler = tokio::spawn(async move {
107108
Self::initialize_jd_as_solo_miner(
108109
proxy_config,
109110
tx_status.clone(),
@@ -165,12 +166,28 @@ impl JobDeclaratorClient {
165166
}
166167
} else {
167168
info!("Received unknown task. Shutting down.");
169+
task_collector
170+
.safe_lock(|s| {
171+
for handle in s {
172+
handle.abort();
173+
}
174+
})
175+
.unwrap();
176+
root_handler.abort();
168177
break 'outer;
169178
}
170179
},
171180
_ = self.shutdown.notified().fuse() => {
172181
info!("Shutting down gracefully...");
173-
std::process::exit(0);
182+
task_collector
183+
.safe_lock(|s| {
184+
for handle in s {
185+
handle.abort();
186+
}
187+
})
188+
.unwrap();
189+
root_handler.abort();
190+
break 'outer;
174191
}
175192
};
176193
}
@@ -371,8 +388,14 @@ impl JobDeclaratorClient {
371388
.await;
372389
}
373390

374-
pub fn shutdown(&self) -> Arc<Notify> {
375-
self.shutdown.clone()
391+
/// Closes JDC role and any open connection associated with it.
392+
///
393+
/// Note that this method will result in a full exit of the running
394+
/// jd-client and any open connection most be re-initiated upon new
395+
/// start.
396+
#[allow(dead_code)]
397+
pub fn shutdown(&self) {
398+
self.shutdown.notify_one();
376399
}
377400
}
378401

@@ -409,3 +432,41 @@ impl PoolChangerTrigger {
409432
}
410433
}
411434
}
435+
436+
#[cfg(test)]
437+
mod tests {
438+
use ext_config::{Config, File, FileFormat};
439+
440+
use crate::*;
441+
442+
#[tokio::test]
443+
async fn test_shutdown() {
444+
let config_path = "config-examples/jdc-config-hosted-example.toml";
445+
let config: ProxyConfig = match Config::builder()
446+
.add_source(File::new(config_path, FileFormat::Toml))
447+
.build()
448+
{
449+
Ok(settings) => match settings.try_deserialize::<ProxyConfig>() {
450+
Ok(c) => c,
451+
Err(e) => {
452+
dbg!(&e);
453+
return;
454+
}
455+
},
456+
Err(e) => {
457+
dbg!(&e);
458+
return;
459+
}
460+
};
461+
let jdc = JobDeclaratorClient::new(config.clone());
462+
let cloned = jdc.clone();
463+
tokio::spawn(async move {
464+
cloned.start().await;
465+
});
466+
jdc.shutdown();
467+
let ip = config.downstream_address.clone();
468+
let port = config.downstream_port;
469+
let jdc_addr = format!("{}:{}", ip, port);
470+
assert!(std::net::TcpListener::bind(jdc_addr).is_ok());
471+
}
472+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use integration_tests_sv2::*;
2+
3+
use roles_logic_sv2::parsers::{CommonMessages, PoolMessages};
4+
5+
// This test verifies that the `jds` (Job Distributor Server) does not panic when the `jdc`
6+
// (Job Distributor Client) shuts down.
7+
//
8+
// The test follows these steps:
9+
// 1. Start a Template Provider (`tp`) and a Pool.
10+
// 2. Start the `jds` and the `jdc`, ensuring the `jdc` connects to the `jds`.
11+
// 3. Shut down the `jdc` and ensure the `jds` remains operational without panicking.
12+
// 4. Verify that the `jdc`'s address can be reused by asserting that a new `TcpListener` can bind
13+
// to the same address.
14+
// 5. Start a Sniffer as a proxy for observing messages exchanged between the `jds` and other
15+
// components.
16+
// 6. Reconnect a new `jdc` to the Sniffer and ensure the expected `SetupConnection` message is
17+
// received from the `jdc`.
18+
//
19+
// This ensures that the shutdown of the `jdc` is handled gracefully by the `jds`, and subsequent
20+
// connections or operations continue without issues.
21+
//
22+
// # Notes
23+
// - The test waits for a brief duration (`1 second`) after shutting down the `jdc` to allow for any
24+
// internal cleanup or reconnection attempts.
25+
#[tokio::test]
26+
async fn jds_should_not_panic_if_jdc_shutsdown() {
27+
let (_tp, tp_addr) = start_template_provider(None).await;
28+
let (_pool, pool_addr) = start_pool(Some(tp_addr)).await;
29+
let (_jds, jds_addr) = start_jds(tp_addr).await;
30+
let (jdc, jdc_addr) = start_jdc(pool_addr, tp_addr, jds_addr).await;
31+
jdc.shutdown();
32+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
33+
assert!(tokio::net::TcpListener::bind(jdc_addr).await.is_ok());
34+
let (sniffer, sniffer_addr) = start_sniffer("0".to_string(), jds_addr, false, None).await;
35+
let (_jdc, _jdc_addr) = start_jdc(pool_addr, tp_addr, sniffer_addr).await;
36+
assert_common_message!(sniffer.next_message_from_downstream(), SetupConnection);
37+
}

‎roles/translator/src/lib/mod.rs

+51-5
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl TranslatorSv2 {
7878
// Check all tasks if is_finished() is true, if so exit
7979

8080
tokio::spawn({
81-
let shutdown_signal = self.shutdown();
81+
let shutdown_signal = self.shutdown.clone();
8282
async move {
8383
if tokio::signal::ctrl_c().await.is_ok() {
8484
info!("Interrupt received");
@@ -94,7 +94,7 @@ impl TranslatorSv2 {
9494
match task_status_.state {
9595
State::DownstreamShutdown(err) | State::BridgeShutdown(err) | State::UpstreamShutdown(err) => {
9696
error!("SHUTDOWN from: {}", err);
97-
self.shutdown().notify_one();
97+
self.shutdown();
9898
}
9999
State::UpstreamTryReconnect(err) => {
100100
error!("Trying to reconnect the Upstream because of: {}", err);
@@ -126,16 +126,18 @@ impl TranslatorSv2 {
126126
}
127127
State::Healthy(msg) => {
128128
info!("HEALTHY message: {}", msg);
129-
self.shutdown().notify_one();
129+
self.shutdown();
130130
}
131131
}
132132
} else {
133133
info!("Channel closed");
134+
kill_tasks(task_collector.clone());
134135
break; // Channel closed
135136
}
136137
}
137138
_ = self.shutdown.notified() => {
138139
info!("Shutting down gracefully...");
140+
kill_tasks(task_collector.clone());
139141
break;
140142
}
141143
}
@@ -288,8 +290,13 @@ impl TranslatorSv2 {
288290
task_collector.safe_lock(|t| t.push((task.abort_handle(), "init task".to_string())));
289291
}
290292

291-
pub fn shutdown(&self) -> Arc<Notify> {
292-
self.shutdown.clone()
293+
/// Closes Translator role and any open connection associated with it.
294+
///
295+
/// Note that this method will result in a full exit of the running
296+
/// Translator and any open connection most be re-initiated upon new
297+
/// start.
298+
pub fn shutdown(&self) {
299+
self.shutdown.notify_one();
293300
}
294301
}
295302

@@ -301,3 +308,42 @@ fn kill_tasks(task_collector: Arc<Mutex<Vec<(AbortHandle, String)>>>) {
301308
}
302309
});
303310
}
311+
312+
#[cfg(test)]
313+
mod tests {
314+
use super::TranslatorSv2;
315+
use ext_config::{Config, File, FileFormat};
316+
317+
use crate::*;
318+
319+
#[tokio::test]
320+
async fn test_shutdown() {
321+
let config_path = "config-examples/tproxy-config-hosted-pool-example.toml";
322+
let config: ProxyConfig = match Config::builder()
323+
.add_source(File::new(config_path, FileFormat::Toml))
324+
.build()
325+
{
326+
Ok(settings) => match settings.try_deserialize::<ProxyConfig>() {
327+
Ok(c) => c,
328+
Err(e) => {
329+
dbg!(&e);
330+
return;
331+
}
332+
},
333+
Err(e) => {
334+
dbg!(&e);
335+
return;
336+
}
337+
};
338+
let translator = TranslatorSv2::new(config.clone());
339+
let cloned = translator.clone();
340+
tokio::spawn(async move {
341+
cloned.start().await;
342+
});
343+
translator.shutdown();
344+
let ip = config.downstream_address.clone();
345+
let port = config.downstream_port;
346+
let translator_addr = format!("{}:{}", ip, port);
347+
assert!(std::net::TcpListener::bind(translator_addr).is_ok());
348+
}
349+
}

‎test/config/jds-do-not-panic-if-jdc-close-connection/jds-config.toml

-20
This file was deleted.

‎test/message-generator/mock/job-declarator-mock-for-jds-do-not-panic-on-close-connection.json

-36
This file was deleted.

‎test/message-generator/test/jds-do-not-panic-if-jdc-close-connection/jds-do-not-panic-if-jdc-close-connection.json

-96
This file was deleted.

‎test/message-generator/test/jds-do-not-panic-if-jdc-close-connection/jds-do-not-panic-if-jdc-close-connection.sh

-9
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.