Skip to content

Commit

Permalink
feat(node_framework): Add a task to handle sigint (#1471)
Browse files Browse the repository at this point in the history
## What ❔

Adds a task that handles SIGINT (aka ctrl+c), propagating the signal to
the `ZkStackService` instead of shutting the node down immediately.

## Why ❔

- Graceful shutdown.
- Replicating the current behavior.

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zk fmt` and `zk lint`.
- [ ] Spellcheck has been run via `zk spellcheck`.
- [ ] Linkcheck has been run via `zk linkcheck`.
  • Loading branch information
popzxc committed Mar 22, 2024
1 parent e7a9d61 commit 2ba6527
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions checks-config/era.dic
Original file line number Diff line number Diff line change
Expand Up @@ -920,3 +920,4 @@ oneshot
p2p
StorageProcessor
StorageMarker
SIGINT
1 change: 1 addition & 0 deletions core/node/node_framework/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async-trait = "0.1"
futures = "0.3"
anyhow = "1"
tokio = { version = "1", features = ["rt"] }
ctrlc = { version = "3.1", features = ["termination"] }

[dev-dependencies]
zksync_env_config = { path = "../../lib/env_config" }
Expand Down
7 changes: 7 additions & 0 deletions core/node/node_framework/examples/main_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use zksync_node_framework::{
pools_layer::PoolsLayerBuilder,
proof_data_handler::ProofDataHandlerLayer,
query_eth_client::QueryEthClientLayer,
sigint::SigintHandlerLayer,
state_keeper::{
main_batch_executor::MainBatchExecutorLayer, mempool_io::MempoolIOLayer,
StateKeeperLayer,
Expand All @@ -62,6 +63,11 @@ impl MainNodeBuilder {
}
}

fn add_sigint_handler_layer(mut self) -> anyhow::Result<Self> {
self.node.add_layer(SigintHandlerLayer);
Ok(self)
}

fn add_pools_layer(mut self) -> anyhow::Result<Self> {
let config = PostgresConfig::from_env()?;
let pools_layer = PoolsLayerBuilder::empty(config)
Expand Down Expand Up @@ -316,6 +322,7 @@ fn main() -> anyhow::Result<()> {
.build();

MainNodeBuilder::new()
.add_sigint_handler_layer()?
.add_pools_layer()?
.add_query_eth_client_layer()?
.add_sequencer_l1_gas_layer()?
Expand Down
1 change: 1 addition & 0 deletions core/node/node_framework/src/implementations/layers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ pub mod pools_layer;
pub mod prometheus_exporter;
pub mod proof_data_handler;
pub mod query_eth_client;
pub mod sigint;
pub mod state_keeper;
pub mod web3_api;
60 changes: 60 additions & 0 deletions core/node/node_framework/src/implementations/layers/sigint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use tokio::sync::oneshot;

use crate::{
service::{ServiceContext, StopReceiver},
task::UnconstrainedTask,
wiring_layer::{WiringError, WiringLayer},
};

/// Layer that changes the handling of SIGINT signal, preventing an immediate shutdown.
/// Instead, it would propagate the signal to the rest of the node, allowing it to shut down gracefully.
#[derive(Debug)]
pub struct SigintHandlerLayer;

#[async_trait::async_trait]
impl WiringLayer for SigintHandlerLayer {
fn layer_name(&self) -> &'static str {
"sigint_handler_layer"
}

async fn wire(self: Box<Self>, mut node: ServiceContext<'_>) -> Result<(), WiringError> {
// SIGINT may happen at any time, so we must handle it as soon as it happens.
node.add_unconstrained_task(Box::new(SigintHandlerTask));
Ok(())
}
}

#[derive(Debug)]
struct SigintHandlerTask;

#[async_trait::async_trait]
impl UnconstrainedTask for SigintHandlerTask {
fn name(&self) -> &'static str {
"sigint_handler"
}

async fn run_unconstrained(
self: Box<Self>,
mut stop_receiver: StopReceiver,
) -> anyhow::Result<()> {
let (sigint_sender, sigint_receiver) = oneshot::channel();
let mut sigint_sender = Some(sigint_sender); // Has to be done this way since `set_handler` requires `FnMut`.
ctrlc::set_handler(move || {
if let Some(sigint_sender) = sigint_sender.take() {
sigint_sender.send(()).ok();
// ^ The send fails if `sigint_receiver` is dropped. We're OK with this,
// since at this point the node should be stopping anyway, or is not interested
// in listening to interrupt signals.
}
})
.expect("Error setting Ctrl+C handler");

// Wait for either SIGINT or stop signal.
tokio::select! {
_ = sigint_receiver => {},
_ = stop_receiver.0.changed() => {},
};

Ok(())
}
}

0 comments on commit 2ba6527

Please sign in to comment.