Coverage Report

Created: 2026-09-28 07:44

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