11use crate :: db:: persistence:: Durability ;
2- use futures:: { channel:: mpsc, StreamExt } ;
32use spacetimedb_commitlog:: payload:: {
43 txdata:: { Mutations , Ops } ,
54 Txdata ,
@@ -9,11 +8,15 @@ use spacetimedb_datastore::{execution_context::ReducerContext, traits::TxData};
98use spacetimedb_durability:: DurableOffset ;
109use spacetimedb_primitives:: TableId ;
1110use 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 ) ]
2528pub 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+
3050impl 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
7394pub struct DurabilityWorkerActor {
74- request_rx : mpsc :: UnboundedReceiver < DurabilityRequest > ,
95+ request_rx : UnboundedReceiver < DurabilityRequest > ,
7596 durability : Arc < Durability > ,
7697}
7798
7899impl 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