Coverage Report

Created: 2026-07-29 18:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-util/src/store_trait.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::borrow::{Borrow, BorrowMut};
16
use core::convert::Into;
17
use core::fmt::{self, Debug, Display};
18
use core::future;
19
use core::hash::{Hash, Hasher};
20
use core::ops::{Bound, RangeBounds};
21
use core::pin::Pin;
22
use core::ptr::addr_eq;
23
use core::time::Duration;
24
use std::borrow::Cow;
25
use std::collections::hash_map::DefaultHasher as StdHasher;
26
use std::ffi::OsString;
27
use std::sync::{Arc, OnceLock};
28
29
use async_trait::async_trait;
30
use bytes::{Bytes, BytesMut};
31
use futures::{Future, FutureExt, Stream, join, try_join};
32
use nativelink_error::{Code, Error, ResultExt, error_if, make_err};
33
use nativelink_metric::MetricsComponent;
34
use rand::rngs::StdRng;
35
use rand::{RngCore, SeedableRng};
36
use serde::{Deserialize, Serialize};
37
use tokio::io::{AsyncReadExt, AsyncSeekExt};
38
use tracing::warn;
39
40
use crate::buf_channel::{DropCloserReadHalf, DropCloserWriteHalf, make_buf_channel_pair};
41
use crate::common::DigestInfo;
42
use crate::digest_hasher::{DigestHasher, DigestHasherFunc, default_digest_hasher_func};
43
use crate::fs;
44
use crate::health_utils::{HealthRegistryBuilder, HealthStatus, HealthStatusIndicator};
45
46
static DEFAULT_DIGEST_SIZE_HEALTH_CHECK: OnceLock<usize> = OnceLock::new();
47
/// Default digest size for health check data. Any change in this value
48
/// changes the default contract. `GlobalConfig` should be updated to reflect
49
/// changes in this value.
50
pub const DEFAULT_DIGEST_SIZE_HEALTH_CHECK_CFG: usize = 1024 * 1024;
51
52
// Get the default digest size for health check data, if value is unset a system wide default is used.
53
1
pub fn default_digest_size_health_check() -> usize {
54
1
    *DEFAULT_DIGEST_SIZE_HEALTH_CHECK.get_or_init(|| DEFAULT_DIGEST_SIZE_HEALTH_CHECK_CFG)
55
1
}
56
57
/// Set the default digest size for health check data, this should be called once.
58
0
pub fn set_default_digest_size_health_check(size: usize) -> Result<(), Error> {
59
0
    DEFAULT_DIGEST_SIZE_HEALTH_CHECK.set(size).map_err(|_| {
60
0
        make_err!(
61
0
            Code::Internal,
62
            "set_default_digest_size_health_check already set"
63
        )
64
0
    })
65
0
}
66
67
#[derive(
68
    Debug,
69
    PartialEq,
70
    Eq,
71
    Copy,
72
    Clone,
73
    Serialize,
74
    Deserialize,
75
0
    wincode::SchemaWrite,
76
0
    wincode::SchemaRead,
77
)]
78
pub enum UploadSizeInfo {
79
    /// When the data transfer amount is known to be exact size, this enum should be used.
80
    /// The receiver store can use this to better optimize the way the data is sent or stored.
81
    ExactSize(u64),
82
83
    /// When the data transfer amount is not known to be exact, the caller should use this enum
84
    /// to provide the maximum size that could possibly be sent. This will bypass the exact size
85
    /// checks, but still provide useful information to the underlying store about the data being
86
    /// sent that it can then use to optimize the upload process.
87
    MaxSize(u64),
88
}
89
90
/// Utility to send all the data to the store from a file.
91
// Note: This is not inlined because some code may want to bypass any underlying
92
// optimizations that may be present in the inner store.
93
11
pub async fn slow_update_store_with_file<S: StoreDriver + ?Sized>(
94
11
    store: Pin<&S>,
95
11
    digest: impl Into<StoreKey<'_>>,
96
11
    file: &mut fs::FileSlot,
97
11
    upload_size: UploadSizeInfo,
98
11
) -> Result<u64, Error> {
99
11
    file.rewind()
100
11
        .await
101
11
        .err_tip(|| "Failed to rewind in upload_file_to_store")
?0
;
102
11
    let (mut tx, rx) = make_buf_channel_pair();
103
104
11
    let update_fut = store
105
11
        .update(digest.into(), rx, upload_size)
106
11
        .map(|r| r.err_tip(|| "Could not upload data to store in upload_file_to_store"));
107
11
    let read_data_fut = async move {
108
        loop {
109
85
            let mut buf = BytesMut::with_capacity(fs::DEFAULT_READ_BUFF_SIZE);
110
85
            let read = file
111
85
                .read_buf(&mut buf)
112
85
                .await
113
85
                .err_tip(|| "Failed to read in upload_file_to_store")
?0
;
114
85
            if read == 0 {
115
11
                break;
116
74
            }
117
74
            tx.send(buf.freeze())
118
74
                .await
119
74
                .err_tip(|| "Failed to send in upload_file_to_store")
?0
;
120
        }
121
11
        tx.send_eof()
122
11
            .err_tip(|| "Could not send EOF to store in upload_file_to_store")
123
11
    };
124
11
    tokio::pin!(read_data_fut);
125
11
    let (update_res, read_res) = tokio::join!(update_fut, read_data_fut);
126
11
    read_res.merge(update_res)
127
11
}
128
129
/// Optimizations that stores may want to expose to the callers.
130
/// This is useful for specific cases when the store can optimize the processing
131
/// of the data being processed.
132
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
133
pub enum StoreOptimizations {
134
    /// The store can optimize the upload process when it knows the data is coming from a file.
135
    FileUpdates,
136
137
    /// If the store will ignore the data uploads.
138
    NoopUpdates,
139
140
    /// If the store will never serve downloads.
141
    NoopDownloads,
142
143
    /// If the store will determine whether a key has associated data once a read has been
144
    /// attempted instead of calling `.has()` first.
145
    LazyExistenceOnSync,
146
147
    /// The store provides an optimized `update_oneshot` implementation that bypasses
148
    /// channel overhead for direct Bytes writes. Stores with this optimization can
149
    /// accept complete data directly without going through the MPSC channel.
150
    SubscribesToUpdateOneshot,
151
}
152
153
/// A wrapper struct for [`StoreKey`] to work around
154
/// lifetime limitations in `HashMap::get()` as described in
155
/// <https://github.com/rust-lang/rust/issues/80389>
156
///
157
/// As such this is a wrapper type that is stored in the
158
/// maps using the workaround as described in
159
/// <https://blinsay.com/blog/compound-keys/>
160
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
161
#[repr(transparent)]
162
pub struct StoreKeyBorrow(StoreKey<'static>);
163
164
impl From<StoreKey<'static>> for StoreKeyBorrow {
165
9.47k
    fn from(key: StoreKey<'static>) -> Self {
166
9.47k
        Self(key)
167
9.47k
    }
168
}
169
170
impl From<StoreKeyBorrow> for StoreKey<'static> {
171
0
    fn from(key_borrow: StoreKeyBorrow) -> Self {
172
0
        key_borrow.0
173
0
    }
174
}
175
176
impl<'a> Borrow<StoreKey<'a>> for StoreKeyBorrow {
177
11.9k
    fn borrow(&self) -> &StoreKey<'a> {
178
11.9k
        &self.0
179
11.9k
    }
180
}
181
182
impl<'a> Borrow<StoreKey<'a>> for &StoreKeyBorrow {
183
0
    fn borrow(&self) -> &StoreKey<'a> {
184
0
        &self.0
185
0
    }
186
}
187
188
/// Holds something that can be converted into a key the
189
/// store API can understand. Generally this is a digest
190
/// but it can also be a string if the caller wishes to
191
/// store the data directly and reference it by a string
192
/// directly.
193
#[derive(Debug, Eq)]
194
pub enum StoreKey<'a> {
195
    /// A string key.
196
    Str(Cow<'a, str>),
197
198
    /// A key that is a digest.
199
    Digest(DigestInfo),
200
}
201
202
impl<'a> StoreKey<'a> {
203
    /// Creates a new store key from a string.
204
12
    pub const fn new_str(s: &'a str) -> Self {
205
12
        StoreKey::Str(Cow::Borrowed(s))
206
12
    }
207
208
    /// Returns a shallow clone of the key.
209
    /// This is extremely cheap and should be used when clone
210
    /// is needed but the key is not going to be modified.
211
    #[must_use]
212
    #[allow(
213
        clippy::missing_const_for_fn,
214
        reason = "False positive on stable, but not on nightly"
215
    )]
216
31.9k
    pub fn borrow(&'a self) -> Self {
217
83
        match self {
218
28
            StoreKey::Str(Cow::Owned(s)) => StoreKey::Str(Cow::Borrowed(s)),
219
55
            StoreKey::Str(Cow::Borrowed(s)) => StoreKey::Str(Cow::Borrowed(s)),
220
31.9k
            StoreKey::Digest(d) => StoreKey::Digest(*d),
221
        }
222
31.9k
    }
223
224
    /// Converts the key into an owned version. This is useful
225
    /// when the caller needs an owned version of the key.
226
27.5k
    pub fn into_owned(self) -> StoreKey<'static> {
227
81
        match self {
228
7
            StoreKey::Str(Cow::Owned(s)) => StoreKey::Str(Cow::Owned(s)),
229
74
            StoreKey::Str(Cow::Borrowed(s)) => StoreKey::Str(Cow::Owned(s.to_owned())),
230
27.4k
            StoreKey::Digest(d) => StoreKey::Digest(d),
231
        }
232
27.5k
    }
233
234
    /// Converts the key into a digest. This is useful when the caller
235
    /// must have a digest key. If the data is not a digest, it may
236
    /// hash the underlying key and return a digest of the hash of the key
237
1.94k
    pub fn into_digest(self) -> DigestInfo {
238
1.94k
        match self {
239
1.93k
            StoreKey::Digest(digest) => digest,
240
2
            StoreKey::Str(s) => {
241
2
                let mut hasher = DigestHasherFunc::Blake3.hasher();
242
2
                hasher.update(s.as_bytes());
243
2
                hasher.finalize_digest()
244
            }
245
        }
246
1.94k
    }
247
248
    /// Returns the key as a string. If the key is a digest, it will
249
    /// return a string representation of the digest. If the key is a string,
250
    /// it will return the string itself.
251
707
    pub fn as_str(&'a self) -> Cow<'a, str> {
252
544
        match self {
253
541
            StoreKey::Str(Cow::Owned(s)) => Cow::Borrowed(s),
254
3
            StoreKey::Str(Cow::Borrowed(s)) => Cow::Borrowed(s),
255
163
            StoreKey::Digest(d) => Cow::Owned(format!("{d}")),
256
        }
257
707
    }
258
}
259
260
impl Clone for StoreKey<'static> {
261
15.3k
    fn clone(&self) -> Self {
262
15.3k
        match self {
263
27
            StoreKey::Str(s) => StoreKey::Str(s.clone()),
264
15.3k
            StoreKey::Digest(d) => StoreKey::Digest(*d),
265
        }
266
15.3k
    }
267
}
268
269
impl PartialOrd for StoreKey<'_> {
270
40
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
271
40
        Some(self.cmp(other))
272
40
    }
273
}
274
275
impl Ord for StoreKey<'_> {
276
57
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
277
57
        match (self, other) {
278
57
            (StoreKey::Str(a), StoreKey::Str(b)) => a.cmp(b),
279
0
            (StoreKey::Digest(a), StoreKey::Digest(b)) => a.cmp(b),
280
0
            (StoreKey::Str(_), StoreKey::Digest(_)) => core::cmp::Ordering::Less,
281
0
            (StoreKey::Digest(_), StoreKey::Str(_)) => core::cmp::Ordering::Greater,
282
        }
283
57
    }
284
}
285
286
impl PartialEq for StoreKey<'_> {
287
15.1k
    fn eq(&self, other: &Self) -> bool {
288
15.1k
        match (self, other) {
289
57
            (StoreKey::Str(a), StoreKey::Str(b)) => a == b,
290
15.0k
            (StoreKey::Digest(a), StoreKey::Digest(b)) => a == b,
291
0
            _ => false,
292
        }
293
15.1k
    }
294
}
295
296
impl Hash for StoreKey<'_> {
297
50.1k
    fn hash<H: Hasher>(&self, state: &mut H) {
298
        /// Salts the hash with the enum value that represents
299
        /// the type of the key.
300
        #[repr(u8)]
301
        enum HashId {
302
            Str = 0,
303
            Digest = 1,
304
        }
305
50.1k
        match self {
306
44
            StoreKey::Str(s) => {
307
44
                (HashId::Str as u8).hash(state);
308
44
                s.hash(state);
309
44
            }
310
50.1k
            StoreKey::Digest(d) => {
311
50.1k
                (HashId::Digest as u8).hash(state);
312
50.1k
                d.hash(state);
313
50.1k
            }
314
        }
315
50.1k
    }
316
}
317
318
impl<'a> From<&'a str> for StoreKey<'a> {
319
1
    fn from(s: &'a str) -> Self {
320
1
        StoreKey::Str(Cow::Borrowed(s))
321
1
    }
322
}
323
324
impl From<String> for StoreKey<'static> {
325
0
    fn from(s: String) -> Self {
326
0
        StoreKey::Str(Cow::Owned(s))
327
0
    }
328
}
329
330
impl From<DigestInfo> for StoreKey<'_> {
331
15.4k
    fn from(d: DigestInfo) -> Self {
332
15.4k
        StoreKey::Digest(d)
333
15.4k
    }
334
}
335
336
impl From<&DigestInfo> for StoreKey<'_> {
337
2.55k
    fn from(d: &DigestInfo) -> Self {
338
2.55k
        StoreKey::Digest(*d)
339
2.55k
    }
340
}
341
342
// mostly for use with tracing::Value
343
impl Display for StoreKey<'_> {
344
56
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345
56
        match self {
346
32
            StoreKey::Str(s) => {
347
32
                write!(f, "{s}")
348
            }
349
24
            StoreKey::Digest(d) => {
350
24
                write!(f, "Digest: {d}")
351
            }
352
        }
353
56
    }
354
}
355
356
#[derive(Clone, MetricsComponent)]
357
#[repr(transparent)]
358
pub struct Store {
359
    #[metric]
360
    inner: Arc<dyn StoreDriver>,
361
}
362
363
impl Debug for Store {
364
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365
0
        f.debug_struct("Store").finish_non_exhaustive()
366
0
    }
367
}
368
369
impl Store {
370
530
    pub fn new(inner: Arc<dyn StoreDriver>) -> Self {
371
530
        Self { inner }
372
530
    }
373
374
    /// Returns the immediate inner store driver.
375
    /// Note: This does not recursively try to resolve underlying store drivers
376
    /// like `.inner_store()` does.
377
    #[inline]
378
10
    pub fn into_inner(self) -> Arc<dyn StoreDriver> {
379
10
        self.inner
380
10
    }
381
382
    /// Gets the underlying store for the given digest.
383
    /// A caller might want to use this to obtain a reference to the "real" underlying store
384
    /// (if applicable) and check if it implements some special traits that allow optimizations.
385
    /// Note: If the store performs complex operations on the data, it should return itself.
386
    #[inline]
387
3.13k
    pub fn inner_store<'a, K: Into<StoreKey<'a>>>(&self, digest: Option<K>) -> &dyn StoreDriver {
388
3.13k
        self.inner.inner_store(digest.map(Into::into))
389
3.13k
    }
390
391
    /// Tries to cast the underlying store to the given type.
392
    #[inline]
393
166
    pub fn downcast_ref<U: StoreDriver>(&self, maybe_digest: Option<StoreKey<'_>>) -> Option<&U> {
394
166
        self.inner.inner_store(maybe_digest).as_any().downcast_ref()
395
166
    }
396
397
    /// Register health checks used to monitor the store.
398
    #[inline]
399
0
    pub fn register_health(&self, registry: &mut HealthRegistryBuilder) {
400
0
        self.inner.clone().register_health(registry);
401
0
    }
402
403
    #[inline]
404
11
    pub fn register_remove_callback(&self, callback: RemoveCallback) -> Result<(), Error> {
405
11
        self.inner.clone().register_remove_callback(callback)
406
11
    }
407
}
408
409
impl StoreLike for Store {
410
    #[inline]
411
19.3k
    fn as_store_driver(&self) -> &'_ dyn StoreDriver {
412
19.3k
        self.inner.as_ref()
413
19.3k
    }
414
415
12
    fn as_pin(&self) -> Pin<&Self> {
416
12
        Pin::new(self)
417
12
    }
418
}
419
420
impl<T> StoreLike for T
421
where
422
    T: StoreDriver + Sized,
423
{
424
    #[inline]
425
10.5k
    fn as_store_driver(&self) -> &'_ dyn StoreDriver {
426
10.5k
        self
427
10.5k
    }
428
429
120
    fn as_pin(&self) -> Pin<&Self> {
430
120
        Pin::new(self)
431
120
    }
432
}
433
434
pub trait StoreLike: Send + Sync + Sized + Unpin + 'static {
435
    /// Returns the immediate inner store driver.
436
    fn as_store_driver(&self) -> &'_ dyn StoreDriver;
437
438
    /// Utility function to return a pinned reference to self.
439
    fn as_pin(&self) -> Pin<&Self>;
440
441
    /// Utility function to return a pinned reference to the store driver.
442
    #[inline]
443
29.8k
    fn as_store_driver_pin(&self) -> Pin<&'_ dyn StoreDriver> {
444
29.8k
        Pin::new(self.as_store_driver())
445
29.8k
    }
446
447
    /// Look up a digest in the store and return None if it does not exist in
448
    /// the store, or Some(size) if it does.
449
    /// Note: On an AC store the size will be incorrect and should not be used!
450
    #[inline]
451
3.68k
    fn has<'a>(
452
3.68k
        &'a self,
453
3.68k
        digest: impl Into<StoreKey<'a>>,
454
3.68k
    ) -> impl Future<Output = Result<Option<u64>, Error>> + 'a {
455
3.68k
        self.as_store_driver_pin().has(digest.into())
456
3.68k
    }
457
458
    /// Look up a list of digests in the store and return a result for each in
459
    /// the same order as input.  The result will either be None if it does not
460
    /// exist in the store, or Some(size) if it does.
461
    /// Note: On an AC store the size will be incorrect and should not be used!
462
    #[inline]
463
30
    fn has_many<'a>(
464
30
        &'a self,
465
30
        digests: &'a [StoreKey<'a>],
466
30
    ) -> impl Future<Output = Result<Vec<Option<u64>>, Error>> + Send + 'a {
467
30
        if digests.is_empty() {
468
3
            return future::ready(Ok(vec![])).boxed();
469
27
        }
470
27
        self.as_store_driver_pin().has_many(digests)
471
30
    }
472
473
    /// The implementation of the above has and `has_many` functions.  See their
474
    /// documentation for details.
475
    #[inline]
476
79
    fn has_with_results<'a>(
477
79
        &'a self,
478
79
        digests: &'a [StoreKey<'a>],
479
79
        results: &'a mut [Option<u64>],
480
79
    ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
481
79
        if digests.is_empty() {
482
3
            return future::ready(Ok(())).boxed();
483
76
        }
484
76
        self.as_store_driver_pin()
485
76
            .has_with_results(digests, results)
486
79
    }
487
488
    /// List all the keys in the store that are within the given range.
489
    /// `handler` is called for each key in the range. If `handler` returns
490
    /// false, the listing is stopped.
491
    ///
492
    /// The number of keys passed through the handler is the return value.
493
    #[inline]
494
15
    fn list<'a, 'b>(
495
15
        &'a self,
496
15
        range: impl RangeBounds<StoreKey<'b>> + Send + 'b,
497
15
        mut handler: impl for<'c> FnMut(&'c StoreKey) -> bool + Send + Sync + 'a,
498
15
    ) -> impl Future<Output = Result<u64, Error>> + Send + 'a
499
15
    where
500
15
        'b: 'a,
501
    {
502
        // Note: We use a manual async move, so the future can own the `range` and `handler`,
503
        // otherwise we'd require the caller to pass them in by reference making more borrow
504
        // checker noise.
505
15
        async move {
506
15
            self.as_store_driver_pin()
507
15
                .list(
508
15
                    (
509
15
                        range.start_bound().map(StoreKey::borrow),
510
15
                        range.end_bound().map(StoreKey::borrow),
511
15
                    ),
512
15
                    &mut handler,
513
15
                )
514
15
                .await
515
15
        }
516
15
    }
517
518
    /// Sends the data to the store.
519
    #[inline]
520
6.71k
    fn update<'a>(
521
6.71k
        &'a self,
522
6.71k
        digest: impl Into<StoreKey<'a>>,
523
6.71k
        reader: DropCloserReadHalf,
524
6.71k
        upload_size: UploadSizeInfo,
525
6.71k
    ) -> impl Future<Output = Result<u64, Error>> + Send + 'a {
526
6.71k
        self.as_store_driver_pin()
527
6.71k
            .update(digest.into(), reader, upload_size)
528
6.71k
    }
529
530
    /// Any optimizations the store might want to expose to the callers.
531
    /// By default, no optimizations are exposed.
532
    #[inline]
533
32
    fn optimized_for(&self, optimization: StoreOptimizations) -> bool {
534
32
        self.as_store_driver_pin().optimized_for(optimization)
535
32
    }
536
537
    /// Specialized version of `.update()` which takes a `FileSlot`.
538
    /// This is useful if the underlying store can optimize the upload process
539
    /// when it knows the data is coming from a file.
540
    #[inline]
541
14
    fn update_with_whole_file<'a>(
542
14
        &'a self,
543
14
        digest: impl Into<StoreKey<'a>>,
544
14
        path: OsString,
545
14
        file: fs::FileSlot,
546
14
        upload_size: UploadSizeInfo,
547
14
    ) -> impl Future<Output = Result<(u64, Option<fs::FileSlot>), Error>> + Send + 'a {
548
14
        self.as_store_driver_pin()
549
14
            .update_with_whole_file(digest.into(), path, file, upload_size)
550
14
    }
551
552
    /// Utility to send all the data to the store when you have all the bytes.
553
    #[inline]
554
7.67k
    fn update_oneshot<'a>(
555
7.67k
        &'a self,
556
7.67k
        digest: impl Into<StoreKey<'a>>,
557
7.67k
        data: Bytes,
558
7.67k
    ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
559
7.67k
        self.as_store_driver_pin()
560
7.67k
            .update_oneshot(digest.into(), data)
561
7.67k
    }
562
563
    /// Retrieves part of the data from the store and writes it to the given writer.
564
    #[inline]
565
1.53k
    fn get_part<'a>(
566
1.53k
        &'a self,
567
1.53k
        digest: impl Into<StoreKey<'a>>,
568
1.53k
        mut writer: impl BorrowMut<DropCloserWriteHalf> + Send + 'a,
569
1.53k
        offset: u64,
570
1.53k
        length: Option<u64>,
571
1.53k
    ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
572
1.53k
        let key = digest.into();
573
        // Note: We need to capture `writer` just in case the caller
574
        // expects the drop() method to be called on it when the future
575
        // is done due to the complex interaction between the DropCloserWriteHalf
576
        // and the DropCloserReadHalf during drop().
577
1.53k
        async move {
578
1.53k
            self.as_store_driver_pin()
579
1.53k
                .get_part(key, writer.borrow_mut(), offset, length)
580
1.53k
                .await
581
1.53k
        }
582
1.53k
    }
583
584
    /// Utility that works the same as `.get_part()`, but writes all the data.
585
    #[inline]
586
1.38k
    fn get<'a>(
587
1.38k
        &'a self,
588
1.38k
        key: impl Into<StoreKey<'a>>,
589
1.38k
        writer: DropCloserWriteHalf,
590
1.38k
    ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
591
1.38k
        self.as_store_driver_pin().get(key.into(), writer)
592
1.38k
    }
593
594
    /// Utility that will return all the bytes at once instead of in a streaming manner.
595
    #[inline]
596
5.99k
    fn get_part_unchunked<'a>(
597
5.99k
        &'a self,
598
5.99k
        key: impl Into<StoreKey<'a>>,
599
5.99k
        offset: u64,
600
5.99k
        length: Option<u64>,
601
5.99k
    ) -> impl Future<Output = Result<Bytes, Error>> + Send + 'a {
602
5.99k
        self.as_store_driver_pin()
603
5.99k
            .get_part_unchunked(key.into(), offset, length)
604
5.99k
    }
605
606
    /// Default implementation of the health check. Some stores may want to override this
607
    /// in situations where the default implementation is not sufficient.
608
    #[inline]
609
1
    fn check_health(
610
1
        &self,
611
1
        namespace: Cow<'static, str>,
612
1
    ) -> impl Future<Output = HealthStatus> + Send {
613
1
        self.as_store_driver_pin().check_health(namespace)
614
1
    }
615
}
616
617
pub type RemoveCallback = Arc<dyn RemoveItemCallback>;
618
619
#[async_trait]
620
pub trait StoreDriver:
621
    Sync + Send + Unpin + MetricsComponent + HealthStatusIndicator + 'static
622
{
623
    // Do "all the stores are setup" init e.g. if we need access to the store manager
624
    // for ref stores
625
    async fn post_init(self: Arc<Self>) -> Result<(), Error>;
626
627
    /// See: [`StoreLike::has`] for details.
628
    #[inline]
629
3.71k
    async fn has(self: Pin<&Self>, key: StoreKey<'_>) -> Result<Option<u64>, Error> {
630
        let mut result = [None];
631
        self.has_with_results(&[key], &mut result).await?;
632
        Ok(result[0])
633
3.71k
    }
634
635
    /// See: [`StoreLike::has_many`] for details.
636
    #[inline]
637
    async fn has_many(
638
        self: Pin<&Self>,
639
        digests: &[StoreKey<'_>],
640
27
    ) -> Result<Vec<Option<u64>>, Error> {
641
        let mut results = vec![None; digests.len()];
642
        self.has_with_results(digests, &mut results).await?;
643
        Ok(results)
644
27
    }
645
646
    /// See: [`StoreLike::has_with_results`] for details.
647
    async fn has_with_results(
648
        self: Pin<&Self>,
649
        digests: &[StoreKey<'_>],
650
        results: &mut [Option<u64>],
651
    ) -> Result<(), Error>;
652
653
    /// See: [`StoreLike::list`] for details.
654
    async fn list(
655
        self: Pin<&Self>,
656
        _range: (Bound<StoreKey<'_>>, Bound<StoreKey<'_>>),
657
        _handler: &mut (dyn for<'a> FnMut(&'a StoreKey) -> bool + Send + Sync + '_),
658
0
    ) -> Result<u64, Error> {
659
        // TODO(palfrey) We should force all stores to implement this function instead of
660
        // providing a default implementation.
661
        Err(make_err!(
662
            Code::Unimplemented,
663
            "Store::list() not implemented for this store"
664
        ))
665
0
    }
666
667
    /// See: [`StoreLike::update`] for details.
668
    async fn update(
669
        self: Pin<&Self>,
670
        key: StoreKey<'_>,
671
        reader: DropCloserReadHalf,
672
        upload_size: UploadSizeInfo,
673
    ) -> Result<u64, Error>;
674
675
    /// See: [`StoreLike::optimized_for`] for details.
676
1.10k
    fn optimized_for(&self, _optimization: StoreOptimizations) -> bool {
677
1.10k
        false
678
1.10k
    }
679
680
    /// See: [`StoreLike::update_with_whole_file`] for details.
681
    async fn update_with_whole_file(
682
        self: Pin<&Self>,
683
        key: StoreKey<'_>,
684
        path: OsString,
685
        mut file: fs::FileSlot,
686
        upload_size: UploadSizeInfo,
687
1
    ) -> Result<(u64, Option<fs::FileSlot>), Error> {
688
        let inner_store = self.inner_store(Some(key.borrow()));
689
        if inner_store.optimized_for(StoreOptimizations::FileUpdates) {
690
            error_if!(
691
                addr_eq(inner_store, &raw const *self),
692
                "Store::inner_store() returned self when optimization present"
693
            );
694
            return Pin::new(inner_store)
695
                .update_with_whole_file(key, path, file, upload_size)
696
                .await;
697
        }
698
        let size = slow_update_store_with_file(self, key, &mut file, upload_size).await?;
699
        Ok((size, Some(file)))
700
1
    }
701
702
    /// See: [`StoreLike::update_oneshot`] for details.
703
5.21k
    async fn update_oneshot(self: Pin<&Self>, key: StoreKey<'_>, data: Bytes) -> Result<(), Error> {
704
        // TODO(palfrey) This is extremely inefficient, since we have exactly
705
        // what we need here. Maybe we could instead make a version of the stream
706
        // that can take objects already fully in memory instead?
707
        let (mut tx, rx) = make_buf_channel_pair();
708
709
        let data_len =
710
            u64::try_from(data.len()).err_tip(|| "Could not convert data.len() to u64")?;
711
5.21k
        let send_fut = async move {
712
            // Only send if we are not EOF.
713
5.21k
            if !data.is_empty() {
714
5.16k
                tx.send(data)
715
5.16k
                    .await
716
5.16k
                    .err_tip(|| "Failed to write data in update_oneshot")
?0
;
717
53
            }
718
5.21k
            tx.send_eof()
719
5.21k
                .err_tip(|| "Failed to write EOF in update_oneshot")
?0
;
720
5.21k
            Ok(())
721
5.21k
        };
722
        try_join!(
723
            send_fut,
724
            self.update(key, rx, UploadSizeInfo::ExactSize(data_len))
725
        )?;
726
        Ok(())
727
5.21k
    }
728
729
    /// See: [`StoreLike::get_part`] for details.
730
    async fn get_part(
731
        self: Pin<&Self>,
732
        key: StoreKey<'_>,
733
        writer: &mut DropCloserWriteHalf,
734
        offset: u64,
735
        length: Option<u64>,
736
    ) -> Result<(), Error>;
737
738
    /// See: [`StoreLike::get`] for details.
739
    #[inline]
740
    async fn get(
741
        self: Pin<&Self>,
742
        key: StoreKey<'_>,
743
        mut writer: DropCloserWriteHalf,
744
1.38k
    ) -> Result<(), Error> {
745
        self.get_part(key, &mut writer, 0, None).await
746
1.38k
    }
747
748
    /// See: [`StoreLike::get_part_unchunked`] for details.
749
    async fn get_part_unchunked(
750
        self: Pin<&Self>,
751
        key: StoreKey<'_>,
752
        offset: u64,
753
        length: Option<u64>,
754
6.35k
    ) -> Result<Bytes, Error> {
755
        let length_usize = length
756
2.54k
            .map(|v| usize::try_from(v).err_tip(|| "Could not convert length to usize"))
757
            .transpose()?;
758
759
        // TODO(palfrey) This is extremely inefficient, since we have exactly
760
        // what we need here. Maybe we could instead make a version of the stream
761
        // that can take objects already fully in memory instead?
762
        let (mut tx, mut rx) = make_buf_channel_pair();
763
764
        let (data_res, get_part_res) = join!(
765
            rx.consume(length_usize),
766
            // We use a closure here to ensure that the `tx` is dropped when the
767
            // future is done.
768
6.35k
            async move { self.get_part(key, &mut tx, offset, length).await 
}6.35k
,
769
        );
770
        get_part_res
771
            .err_tip(|| "Failed to get_part in get_part_unchunked")
772
            .merge(data_res.err_tip(|| "Failed to read stream to completion in get_part_unchunked"))
773
6.35k
    }
774
775
    /// See: [`StoreLike::check_health`] for details.
776
1
    async fn check_health(self: Pin<&Self>, namespace: Cow<'static, str>) -> HealthStatus {
777
        let digest_data_size = default_digest_size_health_check();
778
        let mut digest_data = vec![0u8; digest_data_size];
779
780
        let mut namespace_hasher = StdHasher::new();
781
        namespace.hash(&mut namespace_hasher);
782
        self.get_name().hash(&mut namespace_hasher);
783
        let hash_seed = namespace_hasher.finish();
784
785
        // Fill the digest data with random data based on a stable
786
        // hash of the namespace and store name. Intention is to
787
        // have randomly filled data that is unique per store and
788
        // does not change between health checks. This is to ensure
789
        // we are not adding more data to store on each health check.
790
        let mut rng: StdRng = StdRng::seed_from_u64(hash_seed);
791
        rng.fill_bytes(&mut digest_data);
792
793
        let mut digest_hasher = default_digest_hasher_func().hasher();
794
        digest_hasher.update(&digest_data);
795
        let digest_data_len = digest_data.len() as u64;
796
        let digest_info = StoreKey::from(digest_hasher.finalize_digest());
797
798
        let digest_bytes = Bytes::copy_from_slice(&digest_data);
799
800
        if let Err(e) = self
801
            .update_oneshot(digest_info.borrow(), digest_bytes.clone())
802
            .await
803
        {
804
            warn!(?e, "check_health Store.update_oneshot() failed");
805
            return HealthStatus::new_failed(
806
                self.get_ref(),
807
                format!("Store.update_oneshot() failed: {e}").into(),
808
            );
809
        }
810
811
        match self.has(digest_info.borrow()).await {
812
            Ok(Some(s)) => {
813
                if s != digest_data_len {
814
                    return HealthStatus::new_failed(
815
                        self.get_ref(),
816
                        format!("Store.has() size mismatch {s} != {digest_data_len}").into(),
817
                    );
818
                }
819
            }
820
            Ok(None) => {
821
                return HealthStatus::new_failed(
822
                    self.get_ref(),
823
                    "Store.has() size not found".into(),
824
                );
825
            }
826
            Err(e) => {
827
                return HealthStatus::new_failed(
828
                    self.get_ref(),
829
                    format!("Store.has() failed: {e}").into(),
830
                );
831
            }
832
        }
833
834
        match self
835
            .get_part_unchunked(digest_info, 0, Some(digest_data_len))
836
            .await
837
        {
838
            Ok(b) => {
839
                if b != digest_bytes {
840
                    return HealthStatus::new_failed(
841
                        self.get_ref(),
842
                        "Store.get_part_unchunked() data mismatch".into(),
843
                    );
844
                }
845
            }
846
            Err(e) => {
847
                return HealthStatus::new_failed(
848
                    self.get_ref(),
849
                    format!("Store.get_part_unchunked() failed: {e}").into(),
850
                );
851
            }
852
        }
853
854
        HealthStatus::new_ok(self.get_ref(), "Successfully store health check".into())
855
1
    }
856
857
    /// See: [`Store::inner_store`] for details.
858
    fn inner_store(&self, _digest: Option<StoreKey<'_>>) -> &dyn StoreDriver;
859
860
    /// Returns an Any variation of whatever Self is.
861
    fn as_any(&self) -> &(dyn core::any::Any + Sync + Send + 'static);
862
    fn as_any_arc(self: Arc<Self>) -> Arc<dyn core::any::Any + Sync + Send + 'static>;
863
864
    // Register health checks used to monitor the store.
865
0
    fn register_health(self: Arc<Self>, _registry: &mut HealthRegistryBuilder) {}
866
867
    fn register_remove_callback(self: Arc<Self>, callback: RemoveCallback) -> Result<(), Error>;
868
}
869
870
// Callback to be called when a store deletes an item. This is used so
871
// compound stores can remove items from their internal state when their
872
// underlying stores remove items e.g. caches
873
pub trait RemoveItemCallback: Debug + Send + Sync {
874
    fn callback<'a>(
875
        &'a self,
876
        store_key: StoreKey<'a>,
877
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
878
}
879
880
/// The instructions on how to decode a value from a Bytes & version into
881
/// the underlying type.
882
pub trait SchedulerStoreDecodeTo {
883
    type DecodeOutput;
884
    fn decode(version: i64, data: Bytes) -> Result<Self::DecodeOutput, Error>;
885
}
886
887
pub trait SchedulerSubscription: Send + Sync {
888
    fn changed(&mut self) -> impl Future<Output = Result<(), Error>> + Send;
889
}
890
891
pub trait SchedulerSubscriptionManager: Send + Sync {
892
    type Subscription: SchedulerSubscription;
893
894
    fn subscribe<K>(&self, key: K) -> Result<Self::Subscription, Error>
895
    where
896
        K: SchedulerStoreKeyProvider;
897
898
    fn is_reliable() -> bool;
899
}
900
901
/// The API surface for a scheduler store.
902
pub trait SchedulerStore: Send + Sync + 'static {
903
    type SubscriptionManager: SchedulerSubscriptionManager;
904
905
    /// Returns the subscription manager for the scheduler store.
906
    fn subscription_manager(
907
        &self,
908
    ) -> impl Future<Output = Result<Arc<Self::SubscriptionManager>, Error>> + Send;
909
910
    /// Updates or inserts an entry into the underlying store.
911
    /// Metadata about the key is attached to the compile-time type.
912
    /// If `StoreKeyProvider::Versioned` is `TrueValue`, the data will not
913
    /// be updated if the current version in the database does not match
914
    /// the version in the passed in data.
915
    /// No guarantees are made about when `Version` is `FalseValue`.
916
    /// Indexes are guaranteed to be updated atomically with the data.
917
    fn update_data<T>(
918
        &self,
919
        data: T,
920
        expiry: Option<Duration>,
921
    ) -> impl Future<Output = Result<Option<i64>, Error>> + Send
922
    where
923
        T: SchedulerStoreDataProvider
924
            + SchedulerStoreKeyProvider
925
            + SchedulerCurrentVersionProvider
926
            + Send;
927
928
    /// Searches for all keys in the store that match the given index prefix.
929
    fn search_by_index_prefix<K>(
930
        &self,
931
        index: K,
932
    ) -> impl Future<
933
        Output = Result<
934
            impl Stream<Item = Result<<K as SchedulerStoreDecodeTo>::DecodeOutput, Error>> + Send,
935
            Error,
936
        >,
937
    > + Send
938
    where
939
        K: SchedulerIndexProvider + SchedulerStoreDecodeTo + Send,
940
        <K as SchedulerStoreDecodeTo>::DecodeOutput: Send;
941
942
    /// Returns data for the provided key with the given version if
943
    /// `StoreKeyProvider::Versioned` is `TrueValue`.
944
    fn get_and_decode<K>(
945
        &self,
946
        key: K,
947
    ) -> impl Future<Output = Result<Option<<K as SchedulerStoreDecodeTo>::DecodeOutput>, Error>> + Send
948
    where
949
        K: SchedulerStoreKeyProvider + SchedulerStoreDecodeTo + Send;
950
}
951
952
/// A type that is used to let the scheduler store know what
953
/// index is being requested.
954
pub trait SchedulerIndexProvider {
955
    /// Only keys inserted with this prefix will be indexed.
956
    const KEY_PREFIX: &'static str;
957
958
    /// The name of the index.
959
    const INDEX_NAME: &'static str;
960
961
    /// The sort key for the index (if any).
962
    const MAYBE_SORT_KEY: Option<&'static str> = None;
963
964
    /// If the data is versioned.
965
    type Versioned: BoolValue;
966
967
    /// The value of the index.
968
    fn index_value(&self) -> Cow<'_, str>;
969
}
970
971
/// Provides a key to lookup data in the store.
972
pub trait SchedulerStoreKeyProvider {
973
    /// If the data is versioned.
974
    type Versioned: BoolValue;
975
976
    /// Returns the key for the data.
977
    fn get_key(&self) -> StoreKey<'static>;
978
}
979
980
/// Provides data to be stored in the scheduler store.
981
pub trait SchedulerStoreDataProvider {
982
    /// Converts the data into bytes to be stored in the store.
983
    fn try_into_bytes(self) -> Result<Bytes, Error>;
984
985
    /// Returns the indexes for the data if any.
986
4
    fn get_indexes(&self) -> Result<Vec<(&'static str, Bytes)>, Error> {
987
4
        Ok(Vec::new())
988
4
    }
989
}
990
991
/// Provides the current version of the data in the store.
992
pub trait SchedulerCurrentVersionProvider {
993
    /// Returns the current version of the data in the store.
994
    fn current_version(&self) -> i64;
995
}
996
997
/// Default implementation for when we are not providing a version
998
/// for the data.
999
impl<T> SchedulerCurrentVersionProvider for T
1000
where
1001
    T: SchedulerStoreKeyProvider<Versioned = FalseValue>,
1002
{
1003
0
    fn current_version(&self) -> i64 {
1004
0
        0
1005
0
    }
1006
}
1007
1008
/// Compile time types for booleans.
1009
pub trait BoolValue {
1010
    const VALUE: bool;
1011
}
1012
/// Compile time check if something is false.
1013
pub trait IsFalse {}
1014
/// Compile time check if something is true.
1015
pub trait IsTrue {}
1016
1017
/// Compile time true value.
1018
#[derive(Debug, Clone, Copy)]
1019
pub struct TrueValue;
1020
impl BoolValue for TrueValue {
1021
    const VALUE: bool = true;
1022
}
1023
impl IsTrue for TrueValue {}
1024
1025
/// Compile time false value.
1026
#[derive(Debug, Clone, Copy)]
1027
pub struct FalseValue;
1028
impl BoolValue for FalseValue {
1029
    const VALUE: bool = false;
1030
}
1031
impl IsFalse for FalseValue {}