Skip to content

Commit 1ca247c

Browse files
committed
Fix: electrum server graceful shutdown doesn't work
1 parent fd35014 commit 1ca247c

File tree

7 files changed

+218
-62
lines changed

7 files changed

+218
-62
lines changed

src/bin/electrs.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ fn run_server(config: Arc<Config>) -> Result<()> {
105105
loop {
106106
if let Err(err) = signal.wait(Duration::from_secs(5), true) {
107107
info!("stopping server: {}", err);
108+
109+
electrs::util::spawn_thread("shutdown-thread-checker", || {
110+
let mut counter = 40;
111+
let interval_ms = 500;
112+
113+
while counter > 0 {
114+
electrs::util::with_spawned_threads(|threads| {
115+
debug!("Threads during shutdown: {:?}", threads);
116+
});
117+
std::thread::sleep(std::time::Duration::from_millis(interval_ms));
118+
counter -= 1;
119+
}
120+
});
121+
108122
rest_server.stop();
109123
// the electrum server is stopped when dropped
110124
break;
@@ -133,4 +147,7 @@ fn main() {
133147
error!("server failed: {}", e.display_chain());
134148
process::exit(1);
135149
}
150+
electrs::util::with_spawned_threads(|threads| {
151+
debug!("Threads before closing: {:?}", threads);
152+
});
136153
}

src/electrum/server.rs

Lines changed: 124 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::collections::HashMap;
22
use std::io::{BufRead, BufReader, Write};
33
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
4-
use std::sync::mpsc::{Sender, SyncSender, TrySendError};
4+
use std::sync::atomic::AtomicBool;
5+
use std::sync::mpsc::{Receiver, Sender};
56
use std::sync::{Arc, Mutex};
67
use std::thread;
78

@@ -100,6 +101,7 @@ struct Connection {
100101
chan: SyncChannel<Message>,
101102
stats: Arc<Stats>,
102103
txs_limit: usize,
104+
die_please: Option<Receiver<()>>,
103105
#[cfg(feature = "electrum-discovery")]
104106
discovery: Option<Arc<DiscoveryManager>>,
105107
}
@@ -111,6 +113,7 @@ impl Connection {
111113
addr: SocketAddr,
112114
stats: Arc<Stats>,
113115
txs_limit: usize,
116+
die_please: Receiver<()>,
114117
#[cfg(feature = "electrum-discovery")] discovery: Option<Arc<DiscoveryManager>>,
115118
) -> Connection {
116119
Connection {
@@ -122,6 +125,7 @@ impl Connection {
122125
chan: SyncChannel::new(10),
123126
stats,
124127
txs_limit,
128+
die_please: Some(die_please),
125129
#[cfg(feature = "electrum-discovery")]
126130
discovery,
127131
}
@@ -501,40 +505,52 @@ impl Connection {
501505
Ok(())
502506
}
503507

504-
fn handle_replies(&mut self) -> Result<()> {
508+
fn handle_replies(&mut self, shutdown: crossbeam_channel::Receiver<()>) -> Result<()> {
505509
let empty_params = json!([]);
506510
loop {
507-
let msg = self.chan.receiver().recv().chain_err(|| "channel closed")?;
508-
trace!("RPC {:?}", msg);
509-
match msg {
510-
Message::Request(line) => {
511-
let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?;
512-
let reply = match (
513-
cmd.get("method"),
514-
cmd.get("params").unwrap_or_else(|| &empty_params),
515-
cmd.get("id"),
516-
) {
517-
(
518-
Some(&Value::String(ref method)),
519-
&Value::Array(ref params),
520-
Some(ref id),
521-
) => self.handle_command(method, params, id)?,
522-
_ => bail!("invalid command: {}", cmd),
523-
};
524-
self.send_values(&[reply])?
511+
crossbeam_channel::select! {
512+
recv(self.chan.receiver()) -> msg => {
513+
let msg = msg.chain_err(|| "channel closed")?;
514+
trace!("RPC {:?}", msg);
515+
match msg {
516+
Message::Request(line) => {
517+
let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?;
518+
let reply = match (
519+
cmd.get("method"),
520+
cmd.get("params").unwrap_or(&empty_params),
521+
cmd.get("id"),
522+
) {
523+
(Some(Value::String(method)), Value::Array(params), Some(id)) => {
524+
self.handle_command(method, params, id)?
525+
}
526+
_ => bail!("invalid command: {}", cmd),
527+
};
528+
self.send_values(&[reply])?
529+
}
530+
Message::PeriodicUpdate => {
531+
let values = self
532+
.update_subscriptions()
533+
.chain_err(|| "failed to update subscriptions")?;
534+
self.send_values(&values)?
535+
}
536+
Message::Done => {
537+
self.chan.close();
538+
return Ok(());
539+
}
540+
}
525541
}
526-
Message::PeriodicUpdate => {
527-
let values = self
528-
.update_subscriptions()
529-
.chain_err(|| "failed to update subscriptions")?;
530-
self.send_values(&values)?
542+
recv(shutdown) -> _ => {
543+
self.chan.close();
544+
return Ok(());
531545
}
532-
Message::Done => return Ok(()),
533546
}
534547
}
535548
}
536549

537-
fn handle_requests(mut reader: BufReader<TcpStream>, tx: SyncSender<Message>) -> Result<()> {
550+
fn handle_requests(
551+
mut reader: BufReader<TcpStream>,
552+
tx: crossbeam_channel::Sender<Message>,
553+
) -> Result<()> {
538554
loop {
539555
let mut line = Vec::<u8>::new();
540556
reader
@@ -566,8 +582,24 @@ impl Connection {
566582
self.stats.clients.inc();
567583
let reader = BufReader::new(self.stream.try_clone().expect("failed to clone TcpStream"));
568584
let tx = self.chan.sender();
585+
586+
let die_please = self.die_please.take().unwrap();
587+
let (reply_killer, reply_receiver) = crossbeam_channel::unbounded();
588+
589+
// We create a clone of the stream and put it in an Arc
590+
// This will drop at the end of the function.
591+
let arc_stream = Arc::new(self.stream.try_clone().expect("failed to clone TcpStream"));
592+
// We don't want to keep the stream alive until SIGINT
593+
// It should drop (close) no matter what.
594+
let maybe_stream = Arc::downgrade(&arc_stream);
595+
spawn_thread("properly-die", move || {
596+
let _ = die_please.recv();
597+
let _ = maybe_stream.upgrade().map(|s| s.shutdown(Shutdown::Both));
598+
let _ = reply_killer.send(());
599+
});
600+
569601
let child = spawn_thread("reader", || Connection::handle_requests(reader, tx));
570-
if let Err(e) = self.handle_replies() {
602+
if let Err(e) = self.handle_replies(reply_receiver) {
571603
error!(
572604
"[{}] connection handling failed: {}",
573605
self.addr,
@@ -580,6 +612,8 @@ impl Connection {
580612
.sub(self.status_hashes.len() as i64);
581613

582614
debug!("[{}] shutting down connection", self.addr);
615+
// Drop the Arc so that the stream properly closes.
616+
drop(arc_stream);
583617
let _ = self.stream.shutdown(Shutdown::Both);
584618
if let Err(err) = child.join().expect("receiver panicked") {
585619
error!("[{}] receiver failed: {}", self.addr, err);
@@ -633,30 +667,38 @@ struct Stats {
633667
impl RPC {
634668
fn start_notifier(
635669
notification: Channel<Notification>,
636-
senders: Arc<Mutex<Vec<SyncSender<Message>>>>,
670+
senders: Arc<Mutex<Vec<crossbeam_channel::Sender<Message>>>>,
637671
acceptor: Sender<Option<(TcpStream, SocketAddr)>>,
672+
acceptor_shutdown: Sender<()>,
638673
) {
639674
spawn_thread("notification", move || {
640675
for msg in notification.receiver().iter() {
641676
let mut senders = senders.lock().unwrap();
642677
match msg {
643678
Notification::Periodic => {
644679
for sender in senders.split_off(0) {
645-
if let Err(TrySendError::Disconnected(_)) =
680+
if let Err(crossbeam_channel::TrySendError::Disconnected(_)) =
646681
sender.try_send(Message::PeriodicUpdate)
647682
{
648683
continue;
649684
}
650685
senders.push(sender);
651686
}
652687
}
653-
Notification::Exit => acceptor.send(None).unwrap(), // mark acceptor as done
688+
Notification::Exit => {
689+
acceptor_shutdown.send(()).unwrap(); // Stop the acceptor itself
690+
acceptor.send(None).unwrap(); // mark acceptor as done
691+
break;
692+
}
654693
}
655694
}
656695
});
657696
}
658697

659-
fn start_acceptor(addr: SocketAddr) -> Channel<Option<(TcpStream, SocketAddr)>> {
698+
fn start_acceptor(
699+
addr: SocketAddr,
700+
shutdown_channel: Channel<()>,
701+
) -> Channel<Option<(TcpStream, SocketAddr)>> {
660702
let chan = Channel::unbounded();
661703
let acceptor = chan.sender();
662704
spawn_thread("acceptor", move || {
@@ -666,10 +708,29 @@ impl RPC {
666708
.set_nonblocking(false)
667709
.expect("cannot set nonblocking to false");
668710
let listener = TcpListener::from(socket);
711+
let local_addr = listener.local_addr().unwrap();
712+
let shutdown_bool = Arc::new(AtomicBool::new(false));
713+
714+
{
715+
let shutdown_bool = Arc::clone(&shutdown_bool);
716+
crate::util::spawn_thread("shutdown-acceptor", move || {
717+
// Block until shutdown is sent.
718+
let _ = shutdown_channel.receiver().recv();
719+
// Store the bool so after the next accept it will break the loop
720+
shutdown_bool.store(true, std::sync::atomic::Ordering::Release);
721+
// Connect to the socket to cause it to unblock
722+
let _ = TcpStream::connect(local_addr);
723+
});
724+
}
669725

670726
info!("Electrum RPC server running on {}", addr);
671727
loop {
672728
let (stream, addr) = listener.accept().expect("accept failed");
729+
730+
if shutdown_bool.load(std::sync::atomic::Ordering::Acquire) {
731+
break;
732+
}
733+
673734
stream
674735
.set_nonblocking(false)
675736
.expect("failed to set connection as blocking");
@@ -726,10 +787,18 @@ impl RPC {
726787
RPC {
727788
notification: notification.sender(),
728789
server: Some(spawn_thread("rpc", move || {
729-
let senders = Arc::new(Mutex::new(Vec::<SyncSender<Message>>::new()));
730-
731-
let acceptor = RPC::start_acceptor(rpc_addr);
732-
RPC::start_notifier(notification, senders.clone(), acceptor.sender());
790+
let senders =
791+
Arc::new(Mutex::new(Vec::<crossbeam_channel::Sender<Message>>::new()));
792+
793+
let acceptor_shutdown = Channel::unbounded();
794+
let acceptor_shutdown_sender = acceptor_shutdown.sender();
795+
let acceptor = RPC::start_acceptor(rpc_addr, acceptor_shutdown);
796+
RPC::start_notifier(
797+
notification,
798+
senders.clone(),
799+
acceptor.sender(),
800+
acceptor_shutdown_sender,
801+
);
733802

734803
let mut threads = HashMap::new();
735804
let (garbage_sender, garbage_receiver) = crossbeam_channel::unbounded();
@@ -740,6 +809,11 @@ impl RPC {
740809
let senders = Arc::clone(&senders);
741810
let stats = Arc::clone(&stats);
742811
let garbage_sender = garbage_sender.clone();
812+
813+
// Kill the peers properly
814+
let (killer, peace_receiver) = std::sync::mpsc::channel();
815+
let killer_clone = killer.clone();
816+
743817
#[cfg(feature = "electrum-discovery")]
744818
let discovery = discovery.clone();
745819

@@ -751,34 +825,41 @@ impl RPC {
751825
addr,
752826
stats,
753827
txs_limit,
828+
peace_receiver,
754829
#[cfg(feature = "electrum-discovery")]
755830
discovery,
756831
);
757832
senders.lock().unwrap().push(conn.chan.sender());
758833
conn.run();
759834
info!("[{}] disconnected peer", addr);
835+
let _ = killer_clone.send(());
760836
let _ = garbage_sender.send(std::thread::current().id());
761837
});
762838

763839
trace!("[{}] spawned {:?}", addr, spawned.thread().id());
764-
threads.insert(spawned.thread().id(), spawned);
840+
threads.insert(spawned.thread().id(), (spawned, killer));
765841
while let Ok(id) = garbage_receiver.try_recv() {
766-
if let Some(thread) = threads.remove(&id) {
842+
if let Some((thread, killer)) = threads.remove(&id) {
767843
trace!("[{}] joining {:?}", addr, id);
844+
let _ = killer.send(());
768845
if let Err(error) = thread.join() {
769846
error!("failed to join {:?}: {:?}", id, error);
770847
}
771848
}
772849
}
773850
}
851+
// Drop these
852+
drop(acceptor);
853+
drop(garbage_receiver);
774854

775855
trace!("closing {} RPC connections", senders.lock().unwrap().len());
776856
for sender in senders.lock().unwrap().iter() {
777-
let _ = sender.send(Message::Done);
857+
let _ = sender.try_send(Message::Done);
778858
}
779859

780-
for (id, thread) in threads {
860+
for (id, (thread, killer)) in threads {
781861
trace!("joining {:?}", id);
862+
let _ = killer.send(());
782863
if let Err(error) = thread.join() {
783864
error!("failed to join {:?}: {:?}", id, error);
784865
}
@@ -802,5 +883,8 @@ impl Drop for RPC {
802883
handle.join().unwrap();
803884
}
804885
trace!("RPC server is stopped");
886+
crate::util::with_spawned_threads(|threads| {
887+
trace!("Threads after dropping RPC: {:?}", threads);
888+
});
805889
}
806890
}

src/elements/registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ impl AssetRegistry {
102102
}
103103

104104
pub fn spawn_sync(asset_db: Arc<RwLock<AssetRegistry>>) -> thread::JoinHandle<()> {
105-
thread::spawn(move || loop {
105+
crate::util::spawn_thread("asset-registry", move || loop {
106106
if let Err(e) = asset_db.write().unwrap().fs_sync() {
107107
error!("registry fs_sync failed: {:?}", e);
108108
}

src/new_index/fetch.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use std::collections::HashMap;
99
use std::fs;
1010
use std::io::Cursor;
1111
use std::path::PathBuf;
12-
use std::sync::mpsc::Receiver;
1312
use std::thread;
1413

1514
use crate::chain::{Block, BlockHash};
@@ -44,12 +43,12 @@ pub struct BlockEntry {
4443
type SizedBlock = (Block, u32);
4544

4645
pub struct Fetcher<T> {
47-
receiver: Receiver<T>,
46+
receiver: crossbeam_channel::Receiver<T>,
4847
thread: thread::JoinHandle<()>,
4948
}
5049

5150
impl<T> Fetcher<T> {
52-
fn from(receiver: Receiver<T>, thread: thread::JoinHandle<()>) -> Self {
51+
fn from(receiver: crossbeam_channel::Receiver<T>, thread: thread::JoinHandle<()>) -> Self {
5352
Fetcher { receiver, thread }
5453
}
5554

src/rest.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ pub fn start(config: Arc<Config>, query: Arc<Query>) -> Handle {
594594

595595
Handle {
596596
tx,
597-
thread: thread::spawn(move || {
597+
thread: crate::util::spawn_thread("rest-server", move || {
598598
run_server(config, query, rx);
599599
}),
600600
}

src/signal.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crossbeam_channel as channel;
22
use crossbeam_channel::RecvTimeoutError;
3-
use std::thread;
43
use std::time::{Duration, Instant};
54

65
use signal_hook::consts::{SIGINT, SIGTERM, SIGUSR1};
@@ -16,7 +15,7 @@ fn notify(signals: &[i32]) -> channel::Receiver<i32> {
1615
let (s, r) = channel::bounded(1);
1716
let mut signals =
1817
signal_hook::iterator::Signals::new(signals).expect("failed to register signal hook");
19-
thread::spawn(move || {
18+
crate::util::spawn_thread("signal-notifier", move || {
2019
for signal in signals.forever() {
2120
s.send(signal)
2221
.unwrap_or_else(|_| panic!("failed to send signal {}", signal));

0 commit comments

Comments
 (0)