/build/source/nativelink-scheduler/src/memory_awaited_action_db.rs
Line | Count | Source |
1 | | // Copyright 2024 Trace Machina, Inc. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use core::ops::{Bound, RangeBounds}; |
16 | | use core::time::Duration; |
17 | | use std::collections::hash_map::Entry; |
18 | | use std::collections::{BTreeMap, BTreeSet, HashMap}; |
19 | | use std::sync::Arc; |
20 | | |
21 | | use async_lock::Mutex; |
22 | | use futures::{FutureExt, Stream, stream}; |
23 | | use nativelink_config::stores::EvictionPolicy; |
24 | | use nativelink_error::{Code, Error, ResultExt, error_if, make_err}; |
25 | | use nativelink_metric::MetricsComponent; |
26 | | use nativelink_util::action_messages::{ |
27 | | ActionInfo, ActionStage, ActionUniqueKey, ActionUniqueQualifier, OperationId, |
28 | | }; |
29 | | use nativelink_util::evicting_map::{EvictingMap, LenEntry}; |
30 | | use nativelink_util::instant_wrapper::InstantWrapper; |
31 | | use nativelink_util::metrics::{ |
32 | | EXECUTION_METRICS, ExecutionResult, ExecutionStage, make_execution_attributes, |
33 | | }; |
34 | | use nativelink_util::spawn; |
35 | | use nativelink_util::task::JoinHandleDropGuard; |
36 | | use tokio::sync::{Notify, mpsc, watch}; |
37 | | use tracing::{debug, error}; |
38 | | |
39 | | use crate::awaited_action_db::{ |
40 | | AwaitedAction, AwaitedActionDb, AwaitedActionSubscriber, CLIENT_KEEPALIVE_DURATION, |
41 | | SortedAwaitedAction, SortedAwaitedActionState, |
42 | | }; |
43 | | |
44 | | /// Number of events to process per cycle. |
45 | | const MAX_ACTION_EVENTS_RX_PER_CYCLE: usize = 1024; |
46 | | |
47 | | /// Represents a client that is currently listening to an action. |
48 | | /// When the client is dropped, it will send the `AwaitedAction` to the |
49 | | /// `event_tx` if there are other cleanups needed. |
50 | | #[derive(Debug)] |
51 | | struct ClientAwaitedAction { |
52 | | /// The `OperationId` that the client is listening to. |
53 | | operation_id: OperationId, |
54 | | |
55 | | /// The sender to notify of this struct being dropped. |
56 | | event_tx: mpsc::UnboundedSender<ActionEvent>, |
57 | | } |
58 | | |
59 | | impl ClientAwaitedAction { |
60 | 11.5k | pub(crate) const fn new( |
61 | 11.5k | operation_id: OperationId, |
62 | 11.5k | event_tx: mpsc::UnboundedSender<ActionEvent>, |
63 | 11.5k | ) -> Self { |
64 | 11.5k | Self { |
65 | 11.5k | operation_id, |
66 | 11.5k | event_tx, |
67 | 11.5k | } |
68 | 11.5k | } |
69 | | |
70 | 3 | pub(crate) const fn operation_id(&self) -> &OperationId { |
71 | 3 | &self.operation_id |
72 | 3 | } |
73 | | } |
74 | | |
75 | | impl Drop for ClientAwaitedAction { |
76 | 11.5k | fn drop(&mut self) { |
77 | | // If we failed to send it means noone is listening. |
78 | 11.5k | drop(self.event_tx.send(ActionEvent::ClientDroppedOperation( |
79 | 11.5k | self.operation_id.clone(), |
80 | 11.5k | ))); |
81 | 11.5k | } |
82 | | } |
83 | | |
84 | | /// Trait to be able to use the `EvictingMap` with `ClientAwaitedAction`. |
85 | | /// Note: We only use `EvictingMap` for a time based eviction, which is |
86 | | /// why the implementation has fixed default values in it. |
87 | | impl LenEntry for ClientAwaitedAction { |
88 | | #[inline] |
89 | 23.1k | fn len(&self) -> u64 { |
90 | 23.1k | 0 |
91 | 23.1k | } |
92 | | |
93 | | #[inline] |
94 | 0 | fn is_empty(&self) -> bool { |
95 | 0 | true |
96 | 0 | } |
97 | | } |
98 | | |
99 | | /// Actions the `AwaitedActionsDb` needs to process. |
100 | | #[derive(Debug)] |
101 | | pub(crate) enum ActionEvent { |
102 | | /// A client has sent a keep alive message. |
103 | | ClientKeepAlive(OperationId), |
104 | | /// A client has dropped and pointed to `OperationId`. |
105 | | ClientDroppedOperation(OperationId), |
106 | | } |
107 | | |
108 | | /// Information required to track an individual client |
109 | | /// keep alive config and state. |
110 | | #[derive(Debug)] |
111 | | struct ClientInfo<I: InstantWrapper, NowFn: Fn() -> I> { |
112 | | /// The client operation id. |
113 | | client_operation_id: OperationId, |
114 | | /// The last time a keep alive was sent. |
115 | | last_keep_alive: I, |
116 | | /// The function to get the current time. |
117 | | now_fn: NowFn, |
118 | | /// The sender to notify of this struct had an event. |
119 | | event_tx: mpsc::UnboundedSender<ActionEvent>, |
120 | | } |
121 | | |
122 | | /// Subscriber that clients can be used to monitor when `AwaitedActions` change. |
123 | | #[derive(Debug)] |
124 | | pub struct MemoryAwaitedActionSubscriber<I: InstantWrapper, NowFn: Fn() -> I> { |
125 | | /// The receiver to listen for changes. |
126 | | awaited_action_rx: watch::Receiver<AwaitedAction>, |
127 | | /// If a client id is known this is the info needed to keep the client |
128 | | /// action alive. |
129 | | client_info: Option<ClientInfo<I, NowFn>>, |
130 | | } |
131 | | |
132 | | impl<I: InstantWrapper, NowFn: Fn() -> I> MemoryAwaitedActionSubscriber<I, NowFn> { |
133 | 6.22M | fn new(mut awaited_action_rx: watch::Receiver<AwaitedAction>) -> Self { |
134 | 6.22M | awaited_action_rx.mark_changed(); |
135 | 6.22M | Self { |
136 | 6.22M | awaited_action_rx, |
137 | 6.22M | client_info: None, |
138 | 6.22M | } |
139 | 6.22M | } |
140 | | |
141 | 11.5k | fn new_with_client( |
142 | 11.5k | mut awaited_action_rx: watch::Receiver<AwaitedAction>, |
143 | 11.5k | client_operation_id: OperationId, |
144 | 11.5k | event_tx: mpsc::UnboundedSender<ActionEvent>, |
145 | 11.5k | now_fn: NowFn, |
146 | 11.5k | ) -> Self |
147 | 11.5k | where |
148 | 11.5k | NowFn: Fn() -> I, |
149 | | { |
150 | 11.5k | awaited_action_rx.mark_changed(); |
151 | 11.5k | Self { |
152 | 11.5k | awaited_action_rx, |
153 | 11.5k | client_info: Some(ClientInfo { |
154 | 11.5k | client_operation_id, |
155 | 11.5k | last_keep_alive: I::from_secs(0), |
156 | 11.5k | now_fn, |
157 | 11.5k | event_tx, |
158 | 11.5k | }), |
159 | 11.5k | } |
160 | 11.5k | } |
161 | | } |
162 | | |
163 | | impl<I, NowFn> AwaitedActionSubscriber for MemoryAwaitedActionSubscriber<I, NowFn> |
164 | | where |
165 | | I: InstantWrapper, |
166 | | NowFn: Fn() -> I + Send + Sync + 'static, |
167 | | { |
168 | 33.8k | async fn changed(&mut self) -> Result<AwaitedAction, Error> { |
169 | 30.0k | let client_operation_id = { |
170 | 33.8k | let changed_fut = self.awaited_action_rx.changed().map(|r| {30.0k |
171 | 30.0k | r.map_err(|e| {0 |
172 | 0 | Error::from_std_err(Code::Internal, &e) |
173 | 0 | .append("Failed to wait for awaited action to change") |
174 | 0 | }) |
175 | 30.0k | }); |
176 | 33.8k | let Some(client_info) = self.client_info.as_mut() else { |
177 | 0 | changed_fut.await?; |
178 | 0 | return Ok(self.awaited_action_rx.borrow().clone()); |
179 | | }; |
180 | 33.8k | tokio::pin!(changed_fut); |
181 | | loop { |
182 | 34.7k | if client_info.last_keep_alive.elapsed() > CLIENT_KEEPALIVE_DURATION { |
183 | 12.1k | client_info.last_keep_alive = (client_info.now_fn)(); |
184 | 12.1k | // Failing to send just means our receiver dropped. |
185 | 12.1k | drop(client_info.event_tx.send(ActionEvent::ClientKeepAlive( |
186 | 12.1k | client_info.client_operation_id.clone(), |
187 | 12.1k | ))); |
188 | 22.5k | } |
189 | 34.7k | let sleep_fut = (client_info.now_fn)().sleep(CLIENT_KEEPALIVE_DURATION); |
190 | 34.7k | tokio::select! { |
191 | 34.7k | result30.0k = &mut changed_fut => { |
192 | 30.0k | result?0 ; |
193 | 30.0k | break; |
194 | | } |
195 | 34.7k | () = sleep_fut => { |
196 | 866 | // If we haven't received any updates for a while, we should |
197 | 866 | // let the database know that we are still listening to prevent |
198 | 866 | // the action from being dropped. |
199 | 866 | } |
200 | | } |
201 | | } |
202 | 30.0k | client_info.client_operation_id.clone() |
203 | | }; |
204 | | // At this stage we know that this event is a client request, so we need |
205 | | // to populate the client_operation_id. |
206 | 30.0k | let mut awaited_action = self.awaited_action_rx.borrow().clone(); |
207 | 30.0k | awaited_action.set_client_operation_id(client_operation_id); |
208 | 30.0k | Ok(awaited_action) |
209 | 30.0k | } |
210 | | |
211 | 18.6M | fn borrow(&self) -> impl Future<Output = Result<AwaitedAction, Error>> + Send { |
212 | 18.6M | let mut awaited_action = self.awaited_action_rx.borrow().clone(); |
213 | 18.6M | if let Some(client_info12.8k ) = self.client_info.as_ref() { |
214 | 12.8k | awaited_action.set_client_operation_id(client_info.client_operation_id.clone()); |
215 | 18.6M | } |
216 | 18.6M | std::future::ready(Ok(awaited_action)) |
217 | 18.6M | } |
218 | | } |
219 | | |
220 | | /// A struct that is used to keep the developer from trying to |
221 | | /// return early from a function. |
222 | | struct NoEarlyReturn; |
223 | | |
224 | | #[derive(Debug, Default, MetricsComponent)] |
225 | | struct SortedAwaitedActions { |
226 | | #[metric(group = "unknown")] |
227 | | unknown: BTreeSet<SortedAwaitedAction>, |
228 | | #[metric(group = "cache_check")] |
229 | | cache_check: BTreeSet<SortedAwaitedAction>, |
230 | | #[metric(group = "queued")] |
231 | | queued: BTreeSet<SortedAwaitedAction>, |
232 | | #[metric(group = "executing")] |
233 | | executing: BTreeSet<SortedAwaitedAction>, |
234 | | #[metric(group = "completed")] |
235 | | completed: BTreeSet<SortedAwaitedAction>, |
236 | | } |
237 | | |
238 | | impl SortedAwaitedActions { |
239 | 21.5k | const fn btree_for_state(&mut self, state: &ActionStage) -> &mut BTreeSet<SortedAwaitedAction> { |
240 | 21.5k | match state { |
241 | 0 | ActionStage::Unknown => &mut self.unknown, |
242 | 0 | ActionStage::CacheCheck => &mut self.cache_check, |
243 | 11.8k | ActionStage::Queued => &mut self.queued, |
244 | 9.70k | ActionStage::Executing => &mut self.executing, |
245 | 1 | ActionStage::Completed(_) | ActionStage::CompletedFromCache(_) => &mut self.completed, |
246 | | } |
247 | 21.5k | } |
248 | | |
249 | 33.1k | fn insert_sort_map_for_stage( |
250 | 33.1k | &mut self, |
251 | 33.1k | stage: &ActionStage, |
252 | 33.1k | sorted_awaited_action: &SortedAwaitedAction, |
253 | 33.1k | ) -> Result<(), Error> { |
254 | 33.1k | let newly_inserted = match stage { |
255 | 0 | ActionStage::Unknown => self.unknown.insert(sorted_awaited_action.clone()), |
256 | 0 | ActionStage::CacheCheck => self.cache_check.insert(sorted_awaited_action.clone()), |
257 | 12.1k | ActionStage::Queued => self.queued.insert(sorted_awaited_action.clone()), |
258 | 9.86k | ActionStage::Executing => self.executing.insert(sorted_awaited_action.clone()), |
259 | 11.1k | ActionStage::Completed(_) => self.completed.insert(sorted_awaited_action.clone()), |
260 | | ActionStage::CompletedFromCache(_) => { |
261 | 0 | self.completed.insert(sorted_awaited_action.clone()) |
262 | | } |
263 | | }; |
264 | 33.1k | if !newly_inserted { |
265 | 0 | return Err(make_err!( |
266 | 0 | Code::Internal, |
267 | 0 | "Tried to insert an action that was already in the sorted map. This should never happen. {:?} - {:?}", |
268 | 0 | stage, |
269 | 0 | sorted_awaited_action |
270 | 0 | )); |
271 | 33.1k | } |
272 | 33.1k | Ok(()) |
273 | 33.1k | } |
274 | | |
275 | 21.5k | fn process_state_changes( |
276 | 21.5k | &mut self, |
277 | 21.5k | old_awaited_action: &AwaitedAction, |
278 | 21.5k | new_awaited_action: &AwaitedAction, |
279 | 21.5k | ) -> Result<(), Error> { |
280 | 21.5k | let btree = self.btree_for_state(&old_awaited_action.state().stage); |
281 | 21.5k | let maybe_sorted_awaited_action = btree.take(&SortedAwaitedAction { |
282 | 21.5k | sort_key: old_awaited_action.sort_key(), |
283 | 21.5k | operation_id: new_awaited_action.operation_id().clone(), |
284 | 21.5k | }); |
285 | | |
286 | 21.5k | let Some(sorted_awaited_action) = maybe_sorted_awaited_action else { |
287 | 0 | return Err(make_err!( |
288 | 0 | Code::Internal, |
289 | 0 | "sorted_action_info_hash_keys and action_info_hash_key_to_awaited_action are out of sync - {} - {:?}", |
290 | 0 | new_awaited_action.operation_id(), |
291 | 0 | new_awaited_action, |
292 | 0 | )); |
293 | | }; |
294 | | |
295 | 21.5k | self.insert_sort_map_for_stage(&new_awaited_action.state().stage, &sorted_awaited_action) |
296 | 21.5k | .err_tip(|| "In AwaitedActionDb::update_awaited_action")?0 ; |
297 | 21.5k | Ok(()) |
298 | 21.5k | } |
299 | | } |
300 | | |
301 | | /// The database for storing the state of all actions. |
302 | | #[derive(Debug, MetricsComponent)] |
303 | | pub struct AwaitedActionDbImpl<I: InstantWrapper, NowFn: Fn() -> I> { |
304 | | /// A lookup table to lookup the state of an action by its client operation id. |
305 | | #[metric(group = "client_operation_ids")] |
306 | | client_operation_to_awaited_action: |
307 | | EvictingMap<OperationId, OperationId, Arc<ClientAwaitedAction>, I>, |
308 | | |
309 | | /// A lookup table to lookup the state of an action by its worker operation id. |
310 | | #[metric(group = "operation_ids")] |
311 | | operation_id_to_awaited_action: BTreeMap<OperationId, watch::Sender<AwaitedAction>>, |
312 | | |
313 | | /// A lookup table to lookup the state of an action by its unique qualifier. |
314 | | #[metric(group = "action_info_hash_key_to_awaited_action")] |
315 | | action_info_hash_key_to_awaited_action: HashMap<ActionUniqueKey, OperationId>, |
316 | | |
317 | | /// A sorted set of [`AwaitedAction`]s. A wrapper is used to perform sorting |
318 | | /// based on the [`AwaitedActionSortKey`] of the [`AwaitedAction`]. |
319 | | /// |
320 | | /// See [`AwaitedActionSortKey`] for more information on the ordering. |
321 | | #[metric(group = "sorted_action_infos")] |
322 | | sorted_action_info_hash_keys: SortedAwaitedActions, |
323 | | |
324 | | /// The number of connected clients for each operation id. |
325 | | #[metric(group = "connected_clients_for_operation_id")] |
326 | | connected_clients_for_operation_id: HashMap<OperationId, usize>, |
327 | | |
328 | | /// Where to send notifications about important events related to actions. |
329 | | action_event_tx: mpsc::UnboundedSender<ActionEvent>, |
330 | | |
331 | | /// The function to get the current time. |
332 | | now_fn: NowFn, |
333 | | |
334 | | /// Mints the id of each new operation. |
335 | | operation_id_fn: OperationIdFn, |
336 | | } |
337 | | |
338 | | /// Mints operation ids; a random UUID unless a test needs them repeatable. |
339 | | #[derive(Clone)] |
340 | | pub struct OperationIdFn(Arc<dyn Fn() -> OperationId + Send + Sync>); |
341 | | |
342 | | impl OperationIdFn { |
343 | 306 | pub fn new(f: impl Fn() -> OperationId + Send + Sync + 'static) -> Self { |
344 | 306 | Self(Arc::new(f)) |
345 | 306 | } |
346 | | } |
347 | | |
348 | | impl Default for OperationIdFn { |
349 | 189 | fn default() -> Self { |
350 | 189 | Self::new(OperationId::default) |
351 | 189 | } |
352 | | } |
353 | | |
354 | | impl core::fmt::Debug for OperationIdFn { |
355 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
356 | 0 | f.debug_struct("OperationIdFn").finish_non_exhaustive() |
357 | 0 | } |
358 | | } |
359 | | |
360 | | impl<I: InstantWrapper, NowFn: Fn() -> I + Clone + Send + Sync> AwaitedActionDbImpl<I, NowFn> { |
361 | 204 | async fn get_awaited_action_by_id( |
362 | 204 | &self, |
363 | 204 | client_operation_id: &OperationId, |
364 | 204 | ) -> Result<Option<MemoryAwaitedActionSubscriber<I, NowFn>>, Error> { |
365 | 204 | let maybe_client_awaited_action = self |
366 | 204 | .client_operation_to_awaited_action |
367 | 204 | .get(client_operation_id) |
368 | 204 | .await; |
369 | 204 | let Some(client_awaited_action3 ) = maybe_client_awaited_action else { |
370 | 201 | return Ok(None); |
371 | | }; |
372 | | |
373 | 3 | self.operation_id_to_awaited_action |
374 | 3 | .get(client_awaited_action.operation_id()) |
375 | 3 | .map(|tx| { |
376 | 3 | Some(MemoryAwaitedActionSubscriber::new_with_client( |
377 | 3 | tx.subscribe(), |
378 | 3 | client_operation_id.clone(), |
379 | 3 | self.action_event_tx.clone(), |
380 | 3 | self.now_fn.clone(), |
381 | 3 | )) |
382 | 3 | }) |
383 | 3 | .ok_or_else(|| {0 |
384 | 0 | make_err!( |
385 | 0 | Code::Internal, |
386 | | "Failed to get client operation id {client_operation_id:?}" |
387 | | ) |
388 | 0 | }) |
389 | 204 | } |
390 | | |
391 | | /// Processes action events that need to be handled by the database. |
392 | 11.2k | async fn handle_action_events( |
393 | 11.2k | &mut self, |
394 | 11.2k | action_events: impl IntoIterator<Item = ActionEvent>, |
395 | 11.2k | ) -> NoEarlyReturn { |
396 | 12.1k | for action in action_events11.2k { |
397 | 12.1k | debug!(?action, "Handling action"); |
398 | 12.1k | match action { |
399 | 3 | ActionEvent::ClientDroppedOperation(operation_id) => { |
400 | | // Cleanup operation_id_to_awaited_action. |
401 | 3 | let Some(tx) = self.operation_id_to_awaited_action.remove(&operation_id) else { |
402 | 0 | error!( |
403 | | %operation_id, |
404 | | "operation_id_to_awaited_action does not have operation_id" |
405 | | ); |
406 | 0 | continue; |
407 | | }; |
408 | | |
409 | 3 | let connected_clients = match self |
410 | 3 | .connected_clients_for_operation_id |
411 | 3 | .entry(operation_id.clone()) |
412 | | { |
413 | 3 | Entry::Occupied(entry) => { |
414 | 3 | let value = *entry.get(); |
415 | 3 | entry.remove(); |
416 | 3 | value - 1 |
417 | | } |
418 | | Entry::Vacant(_) => { |
419 | 0 | error!( |
420 | | %operation_id, |
421 | | "connected_clients_for_operation_id does not have operation_id" |
422 | | ); |
423 | 0 | 0 |
424 | | } |
425 | | }; |
426 | | |
427 | | // Note: It is rare to have more than one client listening |
428 | | // to the same action, so we assume that we are the last |
429 | | // client and insert it back into the map if we detect that |
430 | | // there are still clients listening (ie: the happy path |
431 | | // is operation.connected_clients == 0). |
432 | 3 | if connected_clients != 0 { |
433 | 1 | self.operation_id_to_awaited_action |
434 | 1 | .insert(operation_id.clone(), tx); |
435 | 1 | self.connected_clients_for_operation_id |
436 | 1 | .insert(operation_id, connected_clients); |
437 | 1 | continue; |
438 | 2 | } |
439 | 2 | debug!(%operation_id, "Clearing operation from state manager"); |
440 | 2 | let awaited_action = tx.borrow().clone(); |
441 | | // A removed operation has no later stage transition to |
442 | | // decrement its active count. This also covers clients |
443 | | // that disappear while an action is still executing. |
444 | 2 | let stage_attrs = vec![opentelemetry::KeyValue::new( |
445 | | nativelink_util::metrics::EXECUTION_STAGE, |
446 | 2 | ExecutionStage::from(&awaited_action.state().stage), |
447 | | )]; |
448 | 2 | EXECUTION_METRICS |
449 | 2 | .execution_active_count |
450 | 2 | .add(-1, &stage_attrs); |
451 | | // Cleanup action_info_hash_key_to_awaited_action if it was marked cached. |
452 | 2 | match &awaited_action.action_info().unique_qualifier { |
453 | 2 | ActionUniqueQualifier::Cacheable(action_key) => { |
454 | | // Once this operation finished, a newer operation for the |
455 | | // same action key may have claimed the entry; removing it |
456 | | // unconditionally here would orphan that operation's |
457 | | // deduplication entry. |
458 | 2 | let owned_by_this_operation = self |
459 | 2 | .action_info_hash_key_to_awaited_action |
460 | 2 | .get(action_key) |
461 | 2 | .is_some_and(|id| id == &operation_id); |
462 | 2 | if owned_by_this_operation { |
463 | 1 | self.action_info_hash_key_to_awaited_action |
464 | 1 | .remove(action_key); |
465 | 1 | } else if !awaited_action.state().stage.is_finished() { |
466 | 0 | error!( |
467 | | %operation_id, |
468 | | ?awaited_action, |
469 | | ?action_key, |
470 | | "action_info_hash_key_to_awaited_action and operation_id_to_awaited_action are out of sync", |
471 | | ); |
472 | 1 | } |
473 | | } |
474 | 0 | ActionUniqueQualifier::Uncacheable(_action_key) => { |
475 | 0 | // This Operation should not be in the hash_key map. |
476 | 0 | } |
477 | | } |
478 | | |
479 | | // Cleanup sorted_awaited_action. |
480 | 2 | let sort_key = awaited_action.sort_key(); |
481 | 2 | let sort_btree_for_state = self |
482 | 2 | .sorted_action_info_hash_keys |
483 | 2 | .btree_for_state(&awaited_action.state().stage); |
484 | | |
485 | 2 | let maybe_sorted_awaited_action = |
486 | 2 | sort_btree_for_state.take(&SortedAwaitedAction { |
487 | 2 | sort_key, |
488 | 2 | operation_id: operation_id.clone(), |
489 | 2 | }); |
490 | 2 | if maybe_sorted_awaited_action.is_none() { |
491 | 0 | error!( |
492 | | %operation_id, |
493 | | ?sort_key, |
494 | | "Expected maybe_sorted_awaited_action to have {sort_key:?}", |
495 | | ); |
496 | 2 | } |
497 | | } |
498 | 12.1k | ActionEvent::ClientKeepAlive(client_id) => { |
499 | 12.1k | if let Some(client_awaited_action) = self |
500 | 12.1k | .client_operation_to_awaited_action |
501 | 12.1k | .get(&client_id) |
502 | 12.1k | .await |
503 | | { |
504 | 12.1k | if let Some(awaited_action_sender) = self |
505 | 12.1k | .operation_id_to_awaited_action |
506 | 12.1k | .get(&client_awaited_action.operation_id) |
507 | | { |
508 | 12.1k | awaited_action_sender.send_if_modified(|awaited_action| { |
509 | 12.1k | awaited_action.update_client_keep_alive((self.now_fn)().now()); |
510 | 12.1k | false |
511 | 12.1k | }); |
512 | 0 | } |
513 | | } else { |
514 | 0 | error!( |
515 | | ?client_id, |
516 | | "client_operation_to_awaited_action does not have client_id", |
517 | | ); |
518 | | } |
519 | | } |
520 | | } |
521 | | } |
522 | 11.2k | NoEarlyReturn |
523 | 11.2k | } |
524 | | |
525 | 8 | fn get_awaited_actions_range( |
526 | 8 | &self, |
527 | 8 | start: Bound<&OperationId>, |
528 | 8 | end: Bound<&OperationId>, |
529 | 8 | ) -> impl Iterator<Item = (&'_ OperationId, MemoryAwaitedActionSubscriber<I, NowFn>)> |
530 | 8 | + use<'_, I, NowFn> { |
531 | 8 | self.operation_id_to_awaited_action |
532 | 8 | .range((start, end)) |
533 | 12 | .map8 (|(operation_id, tx)| { |
534 | 12 | ( |
535 | 12 | operation_id, |
536 | 12 | MemoryAwaitedActionSubscriber::<I, NowFn>::new(tx.subscribe()), |
537 | 12 | ) |
538 | 12 | }) |
539 | 8 | } |
540 | | |
541 | 6.22M | fn get_by_operation_id( |
542 | 6.22M | &self, |
543 | 6.22M | operation_id: &OperationId, |
544 | 6.22M | ) -> Option<MemoryAwaitedActionSubscriber<I, NowFn>> { |
545 | 6.22M | self.operation_id_to_awaited_action |
546 | 6.22M | .get(operation_id) |
547 | 6.22M | .map(|tx| MemoryAwaitedActionSubscriber::<I, NowFn>::new6.22M (tx6.22M .subscribe6.22M ())) |
548 | 6.22M | } |
549 | | |
550 | 31.4k | fn get_range_of_actions( |
551 | 31.4k | &self, |
552 | 31.4k | state: SortedAwaitedActionState, |
553 | 31.4k | range: impl RangeBounds<SortedAwaitedAction>, |
554 | 31.4k | ) -> impl DoubleEndedIterator< |
555 | 31.4k | Item = Result< |
556 | 31.4k | ( |
557 | 31.4k | &SortedAwaitedAction, |
558 | 31.4k | MemoryAwaitedActionSubscriber<I, NowFn>, |
559 | 31.4k | ), |
560 | 31.4k | Error, |
561 | 31.4k | >, |
562 | 31.4k | > { |
563 | 31.4k | let btree = match state { |
564 | 0 | SortedAwaitedActionState::CacheCheck => &self.sorted_action_info_hash_keys.cache_check, |
565 | 31.3k | SortedAwaitedActionState::Queued => &self.sorted_action_info_hash_keys.queued, |
566 | 124 | SortedAwaitedActionState::Executing => &self.sorted_action_info_hash_keys.executing, |
567 | 0 | SortedAwaitedActionState::Completed => &self.sorted_action_info_hash_keys.completed, |
568 | | }; |
569 | 6.19M | btree31.4k .range31.4k (range31.4k ).map31.4k (|sorted_awaited_action| { |
570 | 6.19M | let operation_id = &sorted_awaited_action.operation_id; |
571 | 6.19M | self.get_by_operation_id(operation_id) |
572 | 6.19M | .ok_or_else(|| {0 |
573 | 0 | make_err!( |
574 | 0 | Code::Internal, |
575 | | "Failed to get operation id {}", |
576 | | operation_id |
577 | | ) |
578 | 0 | }) |
579 | 6.19M | .map(|subscriber| (sorted_awaited_action, subscriber)) |
580 | 6.19M | }) |
581 | 31.4k | } |
582 | | |
583 | 21.5k | fn process_state_changes_for_hash_key_map( |
584 | 21.5k | action_info_hash_key_to_awaited_action: &mut HashMap<ActionUniqueKey, OperationId>, |
585 | 21.5k | new_awaited_action: &AwaitedAction, |
586 | 21.5k | ) { |
587 | | // Only process changes if the stage is not finished. |
588 | 21.5k | if !new_awaited_action.state().stage.is_finished() { |
589 | 10.4k | return; |
590 | 11.1k | } |
591 | 11.1k | match &new_awaited_action.action_info().unique_qualifier { |
592 | 10.8k | ActionUniqueQualifier::Cacheable(action_key) => { |
593 | 10.8k | match action_info_hash_key_to_awaited_action.get(action_key) { |
594 | 10.8k | Some(owning_operation_id) |
595 | 10.8k | if owning_operation_id == new_awaited_action.operation_id() => {} |
596 | 0 | Some(owning_operation_id) => { |
597 | | // The entry belongs to a newer operation for the same |
598 | | // action key; leave it in place. |
599 | 0 | error!( |
600 | | ?owning_operation_id, |
601 | | ?new_awaited_action, |
602 | | ?action_key, |
603 | | "action_info_hash_key_to_awaited_action and operation_id_to_awaited_action are out of sync", |
604 | | ); |
605 | 0 | return; |
606 | | } |
607 | | None => { |
608 | 0 | error!( |
609 | | ?new_awaited_action, |
610 | | ?action_key, |
611 | | "action_info_hash_key_to_awaited_action out of sync, it should have had the unique_key", |
612 | | ); |
613 | 0 | return; |
614 | | } |
615 | | } |
616 | 10.8k | action_info_hash_key_to_awaited_action.remove(action_key); |
617 | | } |
618 | 277 | ActionUniqueQualifier::Uncacheable(_action_key) => { |
619 | 277 | // If we are not cacheable, the action should not be in the |
620 | 277 | // hash_key map, so we don't need to process anything in |
621 | 277 | // action_info_hash_key_to_awaited_action. |
622 | 277 | } |
623 | | } |
624 | 21.5k | } |
625 | | |
626 | 21.5k | fn update_awaited_action( |
627 | 21.5k | &mut self, |
628 | 21.5k | mut new_awaited_action: AwaitedAction, |
629 | 21.5k | ) -> Result<(), Error> { |
630 | 21.5k | let tx = self |
631 | 21.5k | .operation_id_to_awaited_action |
632 | 21.5k | .get(new_awaited_action.operation_id()) |
633 | 21.5k | .ok_or_else(|| {0 |
634 | 0 | make_err!( |
635 | 0 | Code::Internal, |
636 | | "OperationId does not exist in map in AwaitedActionDb::update_awaited_action" |
637 | | ) |
638 | 0 | })?; |
639 | | { |
640 | | // Note: It's important to drop old_awaited_action before we call |
641 | | // send_replace or we will have a deadlock. |
642 | 21.5k | let old_awaited_action = tx.borrow(); |
643 | | |
644 | | // Do not process changes if the action version is not in sync with |
645 | | // what the sender based the update on. |
646 | 21.5k | if old_awaited_action.version() != new_awaited_action.version() { |
647 | 0 | return Err(make_err!( |
648 | 0 | // From: https://grpc.github.io/grpc/core/md_doc_statuscodes.html |
649 | 0 | // Use ABORTED if the client should retry at a higher level |
650 | 0 | // (e.g., when a client-specified test-and-set fails, |
651 | 0 | // indicating the client should restart a read-modify-write |
652 | 0 | // sequence) |
653 | 0 | Code::Aborted, |
654 | 0 | "{} Expected {} but got {} for operation_id {:?} - {:?}", |
655 | 0 | "Tried to update an awaited action with an incorrect version.", |
656 | 0 | old_awaited_action.version(), |
657 | 0 | new_awaited_action.version(), |
658 | 0 | old_awaited_action, |
659 | 0 | new_awaited_action, |
660 | 0 | )); |
661 | 21.5k | } |
662 | 21.5k | new_awaited_action.increment_version(); |
663 | | |
664 | 0 | error_if!( |
665 | 21.5k | old_awaited_action.action_info().unique_qualifier |
666 | 21.5k | != new_awaited_action.action_info().unique_qualifier, |
667 | | "Unique key changed for operation_id {:?} - {:?} - {:?}", |
668 | 0 | new_awaited_action.operation_id(), |
669 | 0 | old_awaited_action.action_info(), |
670 | 0 | new_awaited_action.action_info(), |
671 | | ); |
672 | 21.5k | let is_same_stage = old_awaited_action |
673 | 21.5k | .state() |
674 | 21.5k | .stage |
675 | 21.5k | .is_same_stage(&new_awaited_action.state().stage); |
676 | | |
677 | 21.5k | if !is_same_stage { |
678 | | // Record metrics for stage transitions |
679 | 21.5k | let metrics = &*EXECUTION_METRICS; |
680 | 21.5k | let old_stage = &old_awaited_action.state().stage; |
681 | 21.5k | let new_stage = &new_awaited_action.state().stage; |
682 | | |
683 | | // Track stage transitions |
684 | 21.5k | let base_attrs = make_execution_attributes( |
685 | 21.5k | "unknown", |
686 | 21.5k | None, |
687 | 21.5k | Some(old_awaited_action.action_info().priority), |
688 | | ); |
689 | 21.5k | metrics.execution_stage_transitions.add(1, &base_attrs); |
690 | | |
691 | | // Update active count for old stage |
692 | 21.5k | let old_stage_attrs = vec![opentelemetry::KeyValue::new( |
693 | | nativelink_util::metrics::EXECUTION_STAGE, |
694 | 21.5k | ExecutionStage::from(old_stage), |
695 | | )]; |
696 | 21.5k | metrics.execution_active_count.add(-1, &old_stage_attrs); |
697 | | |
698 | | // Update active count for new stage |
699 | 21.5k | let new_stage_attrs = vec![opentelemetry::KeyValue::new( |
700 | | nativelink_util::metrics::EXECUTION_STAGE, |
701 | 21.5k | ExecutionStage::from(new_stage), |
702 | | )]; |
703 | 21.5k | metrics.execution_active_count.add(1, &new_stage_attrs); |
704 | | |
705 | | // Record completion metrics with action digest for failure tracking |
706 | 21.5k | let action_digest = old_awaited_action.action_info().digest().to_string(); |
707 | 21.5k | if let ActionStage::Completed(action_result11.1k ) = new_stage { |
708 | 11.1k | let result_attrs = vec![ |
709 | 11.1k | opentelemetry::KeyValue::new( |
710 | | nativelink_util::metrics::EXECUTION_RESULT, |
711 | 11.1k | if action_result.exit_code == 0 { |
712 | 9.03k | ExecutionResult::Success |
713 | | } else { |
714 | 2.07k | ExecutionResult::Failure |
715 | | }, |
716 | | ), |
717 | 11.1k | opentelemetry::KeyValue::new( |
718 | | nativelink_util::metrics::EXECUTION_ACTION_DIGEST, |
719 | 11.1k | action_digest, |
720 | | ), |
721 | | ]; |
722 | 11.1k | metrics.execution_completed_count.add(1, &result_attrs); |
723 | 10.4k | } else if let ActionStage::CompletedFromCache(_) = new_stage { |
724 | 0 | let result_attrs = vec![ |
725 | 0 | opentelemetry::KeyValue::new( |
726 | 0 | nativelink_util::metrics::EXECUTION_RESULT, |
727 | 0 | ExecutionResult::CacheHit, |
728 | 0 | ), |
729 | 0 | opentelemetry::KeyValue::new( |
730 | 0 | nativelink_util::metrics::EXECUTION_ACTION_DIGEST, |
731 | 0 | action_digest, |
732 | 0 | ), |
733 | 0 | ]; |
734 | 0 | metrics.execution_completed_count.add(1, &result_attrs); |
735 | 10.4k | } |
736 | | |
737 | 21.5k | self.sorted_action_info_hash_keys |
738 | 21.5k | .process_state_changes(&old_awaited_action, &new_awaited_action)?0 ; |
739 | 21.5k | Self::process_state_changes_for_hash_key_map( |
740 | 21.5k | &mut self.action_info_hash_key_to_awaited_action, |
741 | 21.5k | &new_awaited_action, |
742 | | ); |
743 | 0 | } |
744 | | } |
745 | | |
746 | | // Notify all listeners of the new state and ignore if no one is listening. |
747 | | // Note: Do not use `.send()` as it will not update the state if all listeners |
748 | | // are dropped. |
749 | 21.5k | drop(tx.send_replace(new_awaited_action)); |
750 | | |
751 | 21.5k | Ok(()) |
752 | 21.5k | } |
753 | | |
754 | | /// Creates a new [`ClientAwaitedAction`] and a [`watch::Receiver`] to |
755 | | /// listen for changes. We don't do this in-line because it is important |
756 | | /// to ALWAYS construct a [`ClientAwaitedAction`] before inserting it into |
757 | | /// the map. Failing to do so may result in memory leaks. This is because |
758 | | /// [`ClientAwaitedAction`] implements a drop function that will trigger |
759 | | /// cleanup of the other maps on drop. |
760 | 11.5k | fn make_client_awaited_action( |
761 | 11.5k | &mut self, |
762 | 11.5k | operation_id: &OperationId, |
763 | 11.5k | awaited_action: AwaitedAction, |
764 | 11.5k | ) -> (Arc<ClientAwaitedAction>, watch::Receiver<AwaitedAction>) { |
765 | 11.5k | let (tx, rx) = watch::channel(awaited_action); |
766 | 11.5k | let client_awaited_action = Arc::new(ClientAwaitedAction::new( |
767 | 11.5k | operation_id.clone(), |
768 | 11.5k | self.action_event_tx.clone(), |
769 | | )); |
770 | 11.5k | self.operation_id_to_awaited_action |
771 | 11.5k | .insert(operation_id.clone(), tx); |
772 | 11.5k | self.connected_clients_for_operation_id |
773 | 11.5k | .insert(operation_id.clone(), 1); |
774 | 11.5k | (client_awaited_action, rx) |
775 | 11.5k | } |
776 | | |
777 | 11.5k | async fn add_action( |
778 | 11.5k | &mut self, |
779 | 11.5k | client_operation_id: OperationId, |
780 | 11.5k | action_info: Arc<ActionInfo>, |
781 | 11.5k | ) -> Result<MemoryAwaitedActionSubscriber<I, NowFn>, Error> { |
782 | | // Check to see if the action is already known and subscribe if it is. |
783 | 11.5k | let subscription_result = self |
784 | 11.5k | .try_subscribe( |
785 | 11.5k | &client_operation_id, |
786 | 11.5k | &action_info.unique_qualifier, |
787 | 11.5k | action_info.priority, |
788 | 11.5k | ) |
789 | 11.5k | .await |
790 | 11.5k | .err_tip(|| "In AwaitedActionDb::subscribe_or_add_action"); |
791 | 11.5k | match subscription_result { |
792 | 0 | Err(err) => return Err(err), |
793 | 8 | Ok(Some(subscription)) => return Ok(subscription), |
794 | 11.5k | Ok(None) => { /* Add item to queue. */ } |
795 | | } |
796 | | |
797 | 11.5k | let maybe_unique_key = match &action_info.unique_qualifier { |
798 | 11.2k | ActionUniqueQualifier::Cacheable(unique_key) => Some(unique_key.clone()), |
799 | 309 | ActionUniqueQualifier::Uncacheable(_unique_key) => None, |
800 | | }; |
801 | 11.5k | let operation_id = (self.operation_id_fn.0)(); |
802 | 11.5k | let awaited_action = AwaitedAction::new( |
803 | 11.5k | operation_id.clone(), |
804 | 11.5k | action_info.clone(), |
805 | 11.5k | (self.now_fn)().now(), |
806 | | ); |
807 | 11.5k | debug_assert!( |
808 | 0 | ActionStage::Queued == awaited_action.state().stage, |
809 | | "Expected action to be queued" |
810 | | ); |
811 | 11.5k | let sort_key = awaited_action.sort_key(); |
812 | | |
813 | 11.5k | let (client_awaited_action, rx) = |
814 | 11.5k | self.make_client_awaited_action(&operation_id.clone(), awaited_action); |
815 | | |
816 | 11.5k | debug!( |
817 | | %client_operation_id, |
818 | | %operation_id, |
819 | | ?client_awaited_action, |
820 | | "Adding action" |
821 | | ); |
822 | | |
823 | 11.5k | self.client_operation_to_awaited_action |
824 | 11.5k | .insert(client_operation_id.clone(), client_awaited_action) |
825 | 11.5k | .await; |
826 | | |
827 | | // Note: We only put items in the map that are cacheable. |
828 | 11.5k | if let Some(unique_key11.2k ) = maybe_unique_key { |
829 | 11.2k | let old_value = self |
830 | 11.2k | .action_info_hash_key_to_awaited_action |
831 | 11.2k | .insert(unique_key, operation_id.clone()); |
832 | 11.2k | if let Some(old_value0 ) = old_value { |
833 | 0 | error!( |
834 | | %operation_id, |
835 | | ?old_value, |
836 | | "action_info_hash_key_to_awaited_action already has unique_key" |
837 | | ); |
838 | 11.2k | } |
839 | 309 | } |
840 | | |
841 | | // Record metric for new action entering the queue |
842 | 11.5k | let metrics = &*EXECUTION_METRICS; |
843 | 11.5k | let _base_attrs = make_execution_attributes("unknown", None, Some(action_info.priority)); |
844 | 11.5k | let queued_attrs = vec![opentelemetry::KeyValue::new( |
845 | | nativelink_util::metrics::EXECUTION_STAGE, |
846 | 11.5k | ExecutionStage::Queued, |
847 | | )]; |
848 | 11.5k | metrics.execution_active_count.add(1, &queued_attrs); |
849 | | |
850 | 11.5k | self.sorted_action_info_hash_keys |
851 | 11.5k | .insert_sort_map_for_stage( |
852 | 11.5k | &ActionStage::Queued, |
853 | 11.5k | &SortedAwaitedAction { |
854 | 11.5k | sort_key, |
855 | 11.5k | operation_id, |
856 | 11.5k | }, |
857 | | ) |
858 | 11.5k | .err_tip(|| "In AwaitedActionDb::subscribe_or_add_action")?0 ; |
859 | | |
860 | 11.5k | Ok(MemoryAwaitedActionSubscriber::new_with_client( |
861 | 11.5k | rx, |
862 | 11.5k | client_operation_id, |
863 | 11.5k | self.action_event_tx.clone(), |
864 | 11.5k | self.now_fn.clone(), |
865 | 11.5k | )) |
866 | 11.5k | } |
867 | | |
868 | 11.5k | async fn try_subscribe( |
869 | 11.5k | &mut self, |
870 | 11.5k | client_operation_id: &OperationId, |
871 | 11.5k | unique_qualifier: &ActionUniqueQualifier, |
872 | 11.5k | // TODO(palfrey) To simplify the scheduler 2024 refactor, we |
873 | 11.5k | // removed the ability to upgrade priorities of actions. |
874 | 11.5k | // we should add priority upgrades back in. |
875 | 11.5k | _priority: i32, |
876 | 11.5k | ) -> Result<Option<MemoryAwaitedActionSubscriber<I, NowFn>>, Error> { |
877 | 11.5k | let unique_key11.2k = match unique_qualifier { |
878 | 11.2k | ActionUniqueQualifier::Cacheable(unique_key) => unique_key, |
879 | 309 | ActionUniqueQualifier::Uncacheable(_unique_key) => return Ok(None), |
880 | | }; |
881 | | |
882 | 11.2k | let Some(operation_id8 ) = self.action_info_hash_key_to_awaited_action.get(unique_key) else { |
883 | 11.2k | return Ok(None); // Not currently running. |
884 | | }; |
885 | | |
886 | 8 | let Some(tx) = self.operation_id_to_awaited_action.get(operation_id) else { |
887 | 0 | return Err(make_err!( |
888 | 0 | Code::Internal, |
889 | 0 | "operation_id_to_awaited_action and action_info_hash_key_to_awaited_action are out of sync for {unique_key:?} - {operation_id}" |
890 | 0 | )); |
891 | | }; |
892 | | |
893 | 0 | error_if!( |
894 | 8 | tx.borrow().state().stage.is_finished(), |
895 | | "Tried to subscribe to a completed action but it already finished. This should never happen. {:?}", |
896 | 0 | tx.borrow() |
897 | | ); |
898 | | |
899 | 8 | let maybe_connected_clients = self |
900 | 8 | .connected_clients_for_operation_id |
901 | 8 | .get_mut(operation_id); |
902 | 8 | let Some(connected_clients) = maybe_connected_clients else { |
903 | 0 | return Err(make_err!( |
904 | 0 | Code::Internal, |
905 | 0 | "connected_clients_for_operation_id and operation_id_to_awaited_action are out of sync for {unique_key:?} - {operation_id}" |
906 | 0 | )); |
907 | | }; |
908 | 8 | *connected_clients += 1; |
909 | | |
910 | | // Immediately mark the keep alive, we don't need to wake anyone |
911 | | // so we always fake that it was not actually changed. |
912 | | // Failing update the client could lead to the client connecting |
913 | | // then not updating the keep alive in time, resulting in the |
914 | | // operation timing out due to async behavior. |
915 | 8 | tx.send_if_modified(|awaited_action| { |
916 | 8 | awaited_action.update_client_keep_alive((self.now_fn)().now()); |
917 | 8 | false |
918 | 8 | }); |
919 | 8 | let subscription = tx.subscribe(); |
920 | | |
921 | 8 | self.client_operation_to_awaited_action |
922 | 8 | .insert( |
923 | 8 | client_operation_id.clone(), |
924 | 8 | Arc::new(ClientAwaitedAction::new( |
925 | 8 | operation_id.clone(), |
926 | 8 | self.action_event_tx.clone(), |
927 | 8 | )), |
928 | 8 | ) |
929 | 8 | .await; |
930 | | |
931 | 8 | Ok(Some(MemoryAwaitedActionSubscriber::new_with_client( |
932 | 8 | subscription, |
933 | 8 | client_operation_id.clone(), |
934 | 8 | self.action_event_tx.clone(), |
935 | 8 | self.now_fn.clone(), |
936 | 8 | ))) |
937 | 11.5k | } |
938 | | } |
939 | | |
940 | | #[derive(Debug, MetricsComponent)] |
941 | | pub struct MemoryAwaitedActionDb<I: InstantWrapper, NowFn: Fn() -> I> { |
942 | | #[metric] |
943 | | inner: Arc<Mutex<AwaitedActionDbImpl<I, NowFn>>>, |
944 | | tasks_change_notify: Arc<Notify>, |
945 | | _handle_awaited_action_events: JoinHandleDropGuard<()>, |
946 | | } |
947 | | |
948 | | impl<I: InstantWrapper, NowFn: Fn() -> I + Clone + Send + Sync + 'static> |
949 | | MemoryAwaitedActionDb<I, NowFn> |
950 | | { |
951 | 189 | pub fn new( |
952 | 189 | eviction_config: &EvictionPolicy, |
953 | 189 | tasks_change_notify: Arc<Notify>, |
954 | 189 | now_fn: NowFn, |
955 | 189 | ) -> Self { |
956 | 189 | Self::new_with_operation_id_fn( |
957 | 189 | eviction_config, |
958 | 189 | tasks_change_notify, |
959 | 189 | now_fn, |
960 | 189 | OperationIdFn::default(), |
961 | | ) |
962 | 189 | } |
963 | | |
964 | | /// Same as `new()`, with the source of new operation ids. |
965 | 306 | pub fn new_with_operation_id_fn( |
966 | 306 | eviction_config: &EvictionPolicy, |
967 | 306 | tasks_change_notify: Arc<Notify>, |
968 | 306 | now_fn: NowFn, |
969 | 306 | operation_id_fn: OperationIdFn, |
970 | 306 | ) -> Self { |
971 | 306 | let (action_event_tx, mut action_event_rx) = mpsc::unbounded_channel(); |
972 | 306 | let inner = Arc::new(Mutex::new(AwaitedActionDbImpl { |
973 | 306 | client_operation_to_awaited_action: EvictingMap::new(eviction_config, (now_fn)()), |
974 | 306 | operation_id_to_awaited_action: BTreeMap::new(), |
975 | 306 | action_info_hash_key_to_awaited_action: HashMap::new(), |
976 | 306 | sorted_action_info_hash_keys: SortedAwaitedActions::default(), |
977 | 306 | connected_clients_for_operation_id: HashMap::new(), |
978 | 306 | action_event_tx, |
979 | 306 | now_fn, |
980 | 306 | operation_id_fn, |
981 | 306 | })); |
982 | 306 | let weak_inner = Arc::downgrade(&inner); |
983 | | Self { |
984 | 306 | inner, |
985 | 306 | tasks_change_notify, |
986 | 306 | _handle_awaited_action_events: spawn!("handle_awaited_action_events", async move {260 |
987 | 260 | let mut dropped_operation_ids = Vec::with_capacity(MAX_ACTION_EVENTS_RX_PER_CYCLE); |
988 | | loop { |
989 | 11.4k | dropped_operation_ids.clear(); |
990 | 11.4k | action_event_rx |
991 | 11.4k | .recv_many(&mut dropped_operation_ids, MAX_ACTION_EVENTS_RX_PER_CYCLE) |
992 | 11.4k | .await; |
993 | 11.2k | let Some(inner) = weak_inner.upgrade() else { |
994 | 0 | return; // Nothing to cleanup, our struct is dropped. |
995 | | }; |
996 | 11.2k | let mut inner = inner.lock().await; |
997 | 11.2k | inner |
998 | 11.2k | .handle_action_events(dropped_operation_ids.drain(..)) |
999 | 11.2k | .await; |
1000 | | } |
1001 | 0 | }), |
1002 | | } |
1003 | 306 | } |
1004 | | } |
1005 | | |
1006 | | impl<I: InstantWrapper, NowFn: Fn() -> I + Clone + Send + Sync + 'static> AwaitedActionDb |
1007 | | for MemoryAwaitedActionDb<I, NowFn> |
1008 | | { |
1009 | | type Subscriber = MemoryAwaitedActionSubscriber<I, NowFn>; |
1010 | | |
1011 | 204 | async fn get_awaited_action_by_id( |
1012 | 204 | &self, |
1013 | 204 | client_operation_id: &OperationId, |
1014 | 204 | ) -> Result<Option<Self::Subscriber>, Error> { |
1015 | 204 | self.inner |
1016 | 204 | .lock() |
1017 | 204 | .await |
1018 | 204 | .get_awaited_action_by_id(client_operation_id) |
1019 | 204 | .await |
1020 | 204 | } |
1021 | | |
1022 | | /// The listing is read whole, under one hold of the lock taken here, |
1023 | | /// before the stream exists, and the stream then only hands out what |
1024 | | /// was read. The lock must not be taken inside the stream: a reader |
1025 | | /// that prefetches rows (the filter pass reads 64 at a time) would be |
1026 | | /// left with a lock wait parked inside the stream while it hands rows |
1027 | | /// on, and if the reader then writes, on the same task, it waits for |
1028 | | /// the same lock; the release goes to the parked wait, which nothing |
1029 | | /// polls, and the scheduler stops for good. |
1030 | 8 | async fn get_all_awaited_actions( |
1031 | 8 | &self, |
1032 | 8 | ) -> Result<impl Stream<Item = Result<Self::Subscriber, Error>> + Send, Error> { |
1033 | 8 | let listed: Vec<Result<Self::Subscriber, Error>> = { |
1034 | 8 | let inner = self.inner.lock().await; |
1035 | 8 | inner |
1036 | 8 | .get_awaited_actions_range(Bound::Unbounded, Bound::Unbounded) |
1037 | 12 | .map8 (|(_, item)| Ok(item)) |
1038 | 8 | .collect() |
1039 | | }; |
1040 | 8 | Ok(stream::iter(listed)) |
1041 | 8 | } |
1042 | | |
1043 | 25.6k | async fn get_by_operation_id( |
1044 | 25.6k | &self, |
1045 | 25.6k | operation_id: &OperationId, |
1046 | 25.6k | ) -> Result<Option<Self::Subscriber>, Error> { |
1047 | 25.6k | Ok(self.inner.lock().await.get_by_operation_id(operation_id)) |
1048 | 25.6k | } |
1049 | | |
1050 | | /// Read whole under one hold of the lock, like `get_all_awaited_actions`, |
1051 | | /// and for the same reason. |
1052 | 31.4k | async fn get_range_of_actions( |
1053 | 31.4k | &self, |
1054 | 31.4k | state: SortedAwaitedActionState, |
1055 | 31.4k | start: Bound<SortedAwaitedAction>, |
1056 | 31.4k | end: Bound<SortedAwaitedAction>, |
1057 | 31.4k | desc: bool, |
1058 | 31.4k | ) -> Result<impl Stream<Item = Result<Self::Subscriber, Error>> + Send, Error> { |
1059 | 31.4k | let listed: Vec<Result<Self::Subscriber, Error>> = { |
1060 | 31.4k | let inner = self.inner.lock().await; |
1061 | 31.4k | let iterator = inner |
1062 | 31.4k | .get_range_of_actions(state, (start.as_ref(), end.as_ref())) |
1063 | 6.19M | .map31.4k (|res| { |
1064 | 6.19M | res.map(|(_, item)| item) |
1065 | 6.19M | .err_tip(|| "In AwaitedActionDb::get_range_of_actions") |
1066 | 6.19M | }); |
1067 | | // TODO(palfrey) This should probably use the `.left()/right()` pattern, |
1068 | | // but that doesn't exist in the std or any libraries we use. |
1069 | 31.4k | if desc { |
1070 | 30.9k | iterator.rev().collect() |
1071 | | } else { |
1072 | 517 | iterator.collect() |
1073 | | } |
1074 | | }; |
1075 | 31.4k | Ok(stream::iter(listed)) |
1076 | 31.4k | } |
1077 | | |
1078 | 21.5k | async fn update_awaited_action(&self, new_awaited_action: AwaitedAction) -> Result<(), Error> { |
1079 | 21.5k | self.inner |
1080 | 21.5k | .lock() |
1081 | 21.5k | .await |
1082 | 21.5k | .update_awaited_action(new_awaited_action)?0 ; |
1083 | 21.5k | self.tasks_change_notify.notify_one(); |
1084 | 21.5k | Ok(()) |
1085 | 21.5k | } |
1086 | | |
1087 | 11.5k | async fn add_action( |
1088 | 11.5k | &self, |
1089 | 11.5k | client_operation_id: OperationId, |
1090 | 11.5k | action_info: Arc<ActionInfo>, |
1091 | 11.5k | _no_event_action_timeout: Duration, |
1092 | 11.5k | ) -> Result<Self::Subscriber, Error> { |
1093 | 11.5k | let subscriber = self |
1094 | 11.5k | .inner |
1095 | 11.5k | .lock() |
1096 | 11.5k | .await |
1097 | 11.5k | .add_action(client_operation_id, action_info) |
1098 | 11.5k | .await?0 ; |
1099 | 11.5k | self.tasks_change_notify.notify_one(); |
1100 | 11.5k | Ok(subscriber) |
1101 | 11.5k | } |
1102 | | } |