Skip to content

Commit 2a1570a

Browse files
committed
fix DurabilityWorker drop order bug
1 parent fe7d98b commit 2a1570a

1 file changed

Lines changed: 54 additions & 18 deletions

File tree

crates/core/src/db/durability.rs

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::db::persistence::Durability;
2-
use futures::{channel::mpsc, StreamExt};
32
use spacetimedb_commitlog::payload::{
43
txdata::{Mutations, Ops},
54
Txdata,
@@ -9,11 +8,15 @@ use spacetimedb_datastore::{execution_context::ReducerContext, traits::TxData};
98
use spacetimedb_durability::DurableOffset;
109
use spacetimedb_primitives::TableId;
1110
use std::sync::Arc;
12-
13-
/// A request to persist a transaction.
14-
pub struct DurabilityRequest {
15-
reducer_context: Option<ReducerContext>,
16-
tx_data: Arc<TxData>,
11+
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
12+
13+
/// A request to persist a transaction or to terminate the actor.
14+
pub enum DurabilityRequest {
15+
Work {
16+
reducer_context: Option<ReducerContext>,
17+
tx_data: Arc<TxData>,
18+
},
19+
Close,
1720
}
1821

1922
/// Represents a handle to a background task that persists transactions
@@ -23,14 +26,31 @@ pub struct DurabilityRequest {
2326
/// before sending over to the `Durability` layer.
2427
#[derive(Clone)]
2528
pub struct DurabilityWorker {
26-
request_tx: mpsc::UnboundedSender<DurabilityRequest>,
29+
request_tx: UnboundedSender<DurabilityRequest>,
2730
durability: Arc<Durability>,
2831
}
2932

33+
/// Those who run seem to have all the fun... 🎶
34+
const HUNG_UP: &str = "durability actor hung up / panicked";
35+
36+
impl Drop for DurabilityWorker {
37+
fn drop(&mut self) {
38+
// Try to close the actor.
39+
// If the actor paniced, or a clone of `self` was `Drop`ped,
40+
// This an return `Err(_)`,
41+
// in which case we need only drop `self.durability`.
42+
if let Ok(_) = self.request_tx.send(DurabilityRequest::Close) {
43+
// Wait until the actor's `Arc<Durability>` has been dropped.
44+
// After that, we drop `self.durability` as normal.
45+
futures::executor::block_on(self.request_tx.closed());
46+
}
47+
}
48+
}
49+
3050
impl DurabilityWorker {
3151
/// Create a new [`DurabilityWorker`] using the given `durability` policy.
3252
pub fn new(durability: Arc<Durability>) -> Self {
33-
let (request_tx, request_rx) = mpsc::unbounded();
53+
let (request_tx, request_rx) = unbounded_channel();
3454

3555
let actor = DurabilityWorkerActor {
3656
request_rx,
@@ -54,14 +74,15 @@ impl DurabilityWorker {
5474
/// and sends the work to an actor that collects data and calls `durability.append_tx`.
5575
///
5676
/// Panics if the durability worker has closed the receive end of its queue(s),
57-
/// which is likely due to it having panicked.
77+
/// which is likely due to it having panicked
78+
/// or because `DurabilityWorker` and thus `RelationalDB` was cloned.
5879
pub fn request_durability(&self, reducer_context: Option<ReducerContext>, tx_data: &Arc<TxData>) {
5980
self.request_tx
60-
.unbounded_send(DurabilityRequest {
81+
.send(DurabilityRequest::Work {
6182
reducer_context,
6283
tx_data: tx_data.clone(),
6384
})
64-
.expect("durability worker panicked");
85+
.expect(HUNG_UP);
6586
}
6687

6788
/// Get the [`DurableOffset`] of this database.
@@ -71,19 +92,34 @@ impl DurabilityWorker {
7192
}
7293

7394
pub struct DurabilityWorkerActor {
74-
request_rx: mpsc::UnboundedReceiver<DurabilityRequest>,
95+
request_rx: UnboundedReceiver<DurabilityRequest>,
7596
durability: Arc<Durability>,
7697
}
7798

7899
impl DurabilityWorkerActor {
79100
/// Processes requests to do durability.
80101
async fn run(mut self) {
81-
while let Some(DurabilityRequest {
82-
reducer_context,
83-
tx_data,
84-
}) = self.request_rx.next().await
85-
{
86-
Self::do_durability(&*self.durability, reducer_context, &tx_data);
102+
while let Some(req) = self.request_rx.recv().await {
103+
match req {
104+
DurabilityRequest::Work {
105+
reducer_context,
106+
tx_data,
107+
} => Self::do_durability(&*self.durability, reducer_context, &tx_data),
108+
109+
// Terminate the actor
110+
// and make sure we drop `self.durability`
111+
// before we drop `self.request_tx`.
112+
//
113+
// After a `Close`,
114+
// there should be no more `Work` incoming or buffered,
115+
// as `Close` hangs up the receiver end of the channel,
116+
// so nothing can be sent to it.
117+
DurabilityRequest::Close => {
118+
drop(self.durability);
119+
drop(self.request_rx);
120+
return;
121+
}
122+
}
87123
}
88124
}
89125

0 commit comments

Comments
 (0)