Problem: intentional bridge shutdowns

It is impossible to tell whether the bridge
is being shut down intentionally or because of
an error. This is particularly important
for supervising the process, both in development
and production.

Solution: handle SIGINT and SIGTERM as a special case
and designate a separate status code (3) for intentional
shutdowns.

Also, include an example supervisor for development
mode (examples/suprevisor). Simply prepend it before
the invocation of bridge to supervise it.
This commit is contained in:
Yurii Rashkovskii 2018-03-15 21:10:53 +07:00
parent 103af3e3fa
commit 38e1cac265
No known key found for this signature in database
GPG Key ID: 1D60D7CFD80845FF
7 changed files with 78 additions and 5 deletions

30
Cargo.lock generated
View File

@ -44,6 +44,11 @@ dependencies = [
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
@ -78,6 +83,7 @@ name = "bridge-cli"
version = "0.1.0"
dependencies = [
"bridge 0.1.0",
"ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
@ -116,6 +122,16 @@ name = "crunchy"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ctrlc"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "difference"
version = "1.0.0"
@ -459,6 +475,17 @@ dependencies = [
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.12"
@ -980,12 +1007,14 @@ dependencies = [
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
"checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9"
"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
"checksum ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "653abc99aa905f693d89df4797fadc08085baee379db92be9f2496cefe8a6f2c"
"checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8"
"checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
@ -1022,6 +1051,7 @@ dependencies = [
"checksum mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1731a873077147b626d89cc6c2a0db6288d607496c5d10c0cfcf3adc697ec673"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09"
"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10"

View File

@ -7,6 +7,9 @@ use error::{Error, ResultExt, ErrorKind};
use config::Config;
use contracts::{home, foreign};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
pub struct App<T> where T: Transport {
pub config: Config,
pub database_path: PathBuf,
@ -14,6 +17,7 @@ pub struct App<T> where T: Transport {
pub home_bridge: home::HomeBridge,
pub foreign_bridge: foreign::ForeignBridge,
pub timer: Timer,
pub running: Arc<AtomicBool>
}
pub struct Connections<T> where T: Transport {
@ -50,7 +54,7 @@ impl<T: Transport> Connections<T> {
}
impl App<Ipc> {
pub fn new_ipc<P: AsRef<Path>>(config: Config, database_path: P, handle: &Handle) -> Result<Self, Error> {
pub fn new_ipc<P: AsRef<Path>>(config: Config, database_path: P, handle: &Handle, running: Arc<AtomicBool>) -> Result<Self, Error> {
let connections = Connections::new_ipc(handle, &config.home.ipc, &config.foreign.ipc)?;
let result = App {
config,
@ -59,6 +63,7 @@ impl App<Ipc> {
home_bridge: home::HomeBridge::default(),
foreign_bridge: foreign::ForeignBridge::default(),
timer: Timer::default(),
running,
};
Ok(result)
}
@ -73,6 +78,7 @@ impl<T: Transport> App<T> {
home_bridge: home::HomeBridge::default(),
foreign_bridge: foreign::ForeignBridge::default(),
timer: self.timer.clone(),
running: self.running.clone(),
}
}
}

View File

@ -10,7 +10,7 @@ use futures::{Stream, Poll, Async};
use web3::Transport;
use app::App;
use database::Database;
use error::{Error, Result};
use error::{Error, ErrorKind, Result};
pub use self::deploy::{Deploy, Deployed, create_deploy};
pub use self::deposit_relay::{DepositRelay, create_deposit_relay};
@ -82,6 +82,7 @@ pub fn create_bridge_backed_by<T: Transport + Clone, F: BridgeBackend>(app: Arc<
withdraw_confirm: create_withdraw_confirm(app.clone(), init),
state: BridgeStatus::Wait,
backend,
running: app.running.clone(),
}
}
@ -91,8 +92,11 @@ pub struct Bridge<T: Transport, F> {
withdraw_confirm: WithdrawConfirm<T>,
state: BridgeStatus,
backend: F,
running: Arc<AtomicBool>,
}
use std::sync::atomic::{AtomicBool, Ordering};
impl<T: Transport, F: BridgeBackend> Stream for Bridge<T, F> {
type Item = ();
type Error = Error;
@ -101,6 +105,9 @@ impl<T: Transport, F: BridgeBackend> Stream for Bridge<T, F> {
loop {
let next_state = match self.state {
BridgeStatus::Wait => {
if !self.running.load(Ordering::SeqCst) {
return Err(ErrorKind::ShutdownRequested.into())
}
let d_relay = try_bridge!(self.deposit_relay.poll()).map(BridgeChecked::DepositRelay);
let w_relay = try_bridge!(self.withdraw_relay.poll()).map(BridgeChecked::WithdrawRelay);
let w_confirm = try_bridge!(self.withdraw_confirm.poll()).map(BridgeChecked::WithdrawConfirm);

View File

@ -19,6 +19,7 @@ error_chain! {
}
errors {
ShutdownRequested
// api timeout
Timeout(request: &'static str) {
description("Request timeout"),

View File

@ -16,3 +16,4 @@ docopt = "0.8.1"
log = "0.3"
env_logger = "0.4"
futures = "0.1.14"
ctrlc = { version = "3.1", features = ["termination"] }

View File

@ -8,6 +8,7 @@ extern crate tokio_core;
extern crate log;
extern crate env_logger;
extern crate bridge;
extern crate ctrlc;
use std::{env, fs, io};
use std::sync::Arc;
@ -24,6 +25,7 @@ use bridge::web3;
const ERR_UNKNOWN: i32 = 1;
const ERR_IO_ERROR: i32 = 2;
const ERR_SHUTDOWN_REQUESTED: i32 = 3;
const ERR_CANNOT_CONNECT: i32 = 10;
const ERR_CONNECTION_LOST: i32 = 11;
const ERR_BRIDGE_CRASH: i32 = 11;
@ -75,9 +77,19 @@ pub struct Args {
arg_database: PathBuf,
}
use std::sync::atomic::{AtomicBool, Ordering};
fn main() {
let _ = env_logger::init();
let result = execute(env::args());
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}).expect("Error setting Ctrl-C handler");
let result = execute(env::args(), running);
match result {
Ok(s) => println!("{}", s),
@ -94,7 +106,7 @@ fn print_err(err: Error) {
println!("{}", message);
}
fn execute<S, I>(command: I) -> Result<String, UserFacingError> where I: IntoIterator<Item=S>, S: AsRef<str> {
fn execute<S, I>(command: I, running: Arc<AtomicBool>) -> Result<String, UserFacingError> where I: IntoIterator<Item=S>, S: AsRef<str> {
info!(target: "bridge", "Parsing cli arguments");
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(command).deserialize()).map_err(|e| e.to_string())?;
@ -106,7 +118,7 @@ fn execute<S, I>(command: I) -> Result<String, UserFacingError> where I: IntoIte
let mut event_loop = Core::new().unwrap();
info!(target: "bridge", "Establishing ipc connection");
let app = match App::new_ipc(config.clone(), &args.arg_database, &event_loop.handle()) {
let app = match App::new_ipc(config.clone(), &args.arg_database, &event_loop.handle(), running) {
Ok(app) => app,
Err(e) => {
warn!("Can't establish an IPC connection: {:?}", e);
@ -144,6 +156,10 @@ fn execute<S, I>(command: I) -> Result<String, UserFacingError> where I: IntoIte
return Err((ERR_IO_ERROR, e.into()).into());
}
},
Err(e @ Error(ErrorKind::ShutdownRequested, _)) => {
info!("Shutdown requested, terminating");
return Err((ERR_SHUTDOWN_REQUESTED, e.into()).into());
}
Err(e) => {
warn!("Bridge crashed with {}", e);
return Err((ERR_BRIDGE_CRASH, e).into());

12
examples/supervisor Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
while true; do
"$@"
if [ $? == 3 ]; then
# shutdown requested by user
echo "Shutting down"
exit 0
fi
echo "Restarting after 1 second"
sleep 1
done