@@ -17,9 +17,7 @@ use sled_storage::manager::NestedDatasetConfig;
1717use sled_storage:: manager:: NestedDatasetListOptions ;
1818use sled_storage:: manager:: NestedDatasetLocation ;
1919use slog:: Logger ;
20- use slog:: warn;
2120use std:: sync:: Arc ;
22- use std:: sync:: Mutex ;
2321use std:: sync:: OnceLock ;
2422use tokio:: sync:: watch;
2523
@@ -59,6 +57,16 @@ pub enum TimeSyncConfig {
5957 Skip ,
6058}
6159
60+ #[ derive( Debug ) ]
61+ pub struct ConfigReconcilerHandleSpawnToken {
62+ key_requester : StorageKeyRequester ,
63+ time_sync_config : TimeSyncConfig ,
64+ reconciler_result_tx : watch:: Sender < ReconcilerResult > ,
65+ currently_managed_zpools_tx : watch:: Sender < Arc < CurrentlyManagedZpools > > ,
66+ ledger_task_log : Logger ,
67+ reconciler_task_log : Logger ,
68+ }
69+
6270#[ derive( Debug ) ]
6371pub struct ConfigReconcilerHandle {
6472 raw_disks_tx : RawDisksSender ,
@@ -67,15 +75,8 @@ pub struct ConfigReconcilerHandle {
6775 reconciler_result_rx : watch:: Receiver < ReconcilerResult > ,
6876 currently_managed_zpools_rx : CurrentlyManagedZpoolsReceiver ,
6977
70- // `None` until `spawn_reconciliation_task()` is called.
78+ // Empty until `spawn_reconciliation_task()` is called.
7179 ledger_task : OnceLock < LedgerTaskHandle > ,
72-
73- // We have a two-phase initialization: we get some of our dependencies when
74- // `Self::new()` is called and the rest when
75- // `Self::spawn_reconciliation_task()` is called. We hold the dependencies
76- // that are available in `new` but not needed until
77- // `spawn_reconciliation_task` in this field.
78- reconciler_task_dependencies : Mutex < Option < ReconcilerTaskDependencies > > ,
7980}
8081
8182impl ConfigReconcilerHandle {
@@ -93,7 +94,7 @@ impl ConfigReconcilerHandle {
9394 key_requester : StorageKeyRequester ,
9495 time_sync_config : TimeSyncConfig ,
9596 base_log : & Logger ,
96- ) -> Self {
97+ ) -> ( Self , ConfigReconcilerHandleSpawnToken ) {
9798 let mount_config = Arc :: new ( mount_config) ;
9899
99100 // Spawn the task that monitors our internal disks (M.2s).
@@ -111,14 +112,27 @@ impl ConfigReconcilerHandle {
111112 base_log,
112113 ) ;
113114
114- // Stash the dependencies the reconciler task will need in
115- // `spawn_reconciliation_task()`.
116115 let ( reconciler_result_tx, reconciler_result_rx) =
117116 watch:: channel ( ReconcilerResult :: default ( ) ) ;
118117 let ( currently_managed_zpools_tx, currently_managed_zpools_rx) =
119118 watch:: channel ( Arc :: default ( ) ) ;
120- let reconciler_task_dependencies =
121- Mutex :: new ( Some ( ReconcilerTaskDependencies {
119+
120+ (
121+ Self {
122+ raw_disks_tx,
123+ internal_disks_rx,
124+ dataset_task,
125+ ledger_task : OnceLock :: new ( ) ,
126+ reconciler_result_rx,
127+ currently_managed_zpools_rx :
128+ CurrentlyManagedZpoolsReceiver :: new (
129+ currently_managed_zpools_rx,
130+ ) ,
131+ } ,
132+ // Stash the dependencies the reconciler task will need in
133+ // `spawn_reconciliation_task()` inside this token that the caller
134+ // has to hold until it has the other outside dependencies ready.
135+ ConfigReconcilerHandleSpawnToken {
122136 key_requester,
123137 time_sync_config,
124138 reconciler_result_tx,
@@ -127,25 +141,19 @@ impl ConfigReconcilerHandle {
127141 . new ( slog:: o!( "component" => "SledConfigLedgerTask" ) ) ,
128142 reconciler_task_log : base_log
129143 . new ( slog:: o!( "component" => "ConfigReconcilerTask" ) ) ,
130- } ) ) ;
131-
132- Self {
133- raw_disks_tx,
134- internal_disks_rx,
135- dataset_task,
136- ledger_task : OnceLock :: new ( ) ,
137- reconciler_result_rx,
138- currently_managed_zpools_rx : CurrentlyManagedZpoolsReceiver :: new (
139- currently_managed_zpools_rx,
140- ) ,
141- reconciler_task_dependencies,
142- }
144+ } ,
145+ )
143146 }
144147
145148 /// Spawn the primary config reconciliation task.
146149 ///
147- /// This method can effectively only be called once; any subsequent calls
148- /// will log a warning and do nothing.
150+ /// This method can effectively only be called once, because the caller must
151+ /// supply the `token` returned by `new()` when this handle was created.
152+ ///
153+ /// # Panics
154+ ///
155+ /// Panics if called multiple times, which is statically impossible outside
156+ /// shenanigans to get a second `ConfigReconcilerHandleSpawnToken`.
149157 pub fn spawn_reconciliation_task <
150158 T : SledAgentFacilities ,
151159 U : SledAgentArtifactStore ,
@@ -154,26 +162,16 @@ impl ConfigReconcilerHandle {
154162 underlay_vnic : EtherstubVnic ,
155163 sled_agent_facilities : T ,
156164 sled_agent_artifact_store : U ,
157- log : & Logger ,
165+ token : ConfigReconcilerHandleSpawnToken ,
158166 ) {
159- let ReconcilerTaskDependencies {
167+ let ConfigReconcilerHandleSpawnToken {
160168 key_requester,
161169 time_sync_config,
162170 reconciler_result_tx,
163171 currently_managed_zpools_tx,
164172 ledger_task_log,
165173 reconciler_task_log,
166- } = match self . reconciler_task_dependencies . lock ( ) . unwrap ( ) . take ( ) {
167- Some ( dependencies) => dependencies,
168- None => {
169- warn ! (
170- log,
171- "spawn_reconciliation_task() called multiple times \
172- (ignored after first call)"
173- ) ;
174- return ;
175- }
176- } ;
174+ } = token;
177175
178176 // Spawn the task that manages our config ledger.
179177 let ( ledger_task, current_config_rx) =
@@ -184,10 +182,13 @@ impl ConfigReconcilerHandle {
184182 ) ;
185183 match self . ledger_task . set ( ledger_task) {
186184 Ok ( ( ) ) => ( ) ,
187- // We know via the `lock().take()` above that this only executes
188- // once, so `ledger_task` is always set here.
185+ // We can only be called with the `token` we returned in `new()` and
186+ // we document that we panic if called multiple times via some
187+ // multi-token shenanigans.
189188 Err ( _) => {
190- unreachable ! ( "spawn_reconciliation_task() only executes once" )
189+ panic ! (
190+ "spawn_reconciliation_task() called with multiple tokens"
191+ )
191192 }
192193 }
193194
0 commit comments