Coverage Report

Created: 2026-10-28 00:07

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