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