Skip to main content

ts_l3/
executor.rs

1// Copyright (c) 2026 LightSeek Foundation
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! The executor: bounded queues of storage jobs and the worker threads that
22//! run them against the store (`docs/design/async-l3-cache.md`).
23//!
24//! The scheduler plans each operation and holds its Host blocks until every
25//! cache owner proves the native I/O retired; the executor only runs what it
26//! is given, in a bounded window, and reports per-key facts. It never calls
27//! the scheduler, a collective or the GPU. A job that cannot run (a full
28//! queue, a backend held open after failures, shutdown) completes at once as
29//! a retired failure; a backend that cannot prove retirement disables new
30//! I/O on this rank until restart.
31
32use std::{
33    collections::{HashMap, VecDeque},
34    sync::{Arc, Condvar, Mutex, MutexGuard},
35    thread::JoinHandle,
36    time::{Duration, Instant},
37};
38
39use ts_metrics::{L3ExecutorSnapshot, L3OperationSnapshot};
40
41use crate::{
42    keys,
43    layout::{HostLayout, LayoutError, Range},
44    store::{HostBuffer, IoResult, Store},
45};
46
47/// The executor's limits: the scheduler's storage credits and the workers.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct ExecutorConfig {
50    /// Operations the scheduler keeps in flight (`StorageConfig`).
51    pub max_operations: usize,
52    /// Keys one operation names (`StorageConfig`).
53    pub max_keys_per_operation: usize,
54    /// Threads running existence queries.
55    pub query_workers: usize,
56    /// Threads running gets.
57    pub read_workers: usize,
58    /// Threads running puts.
59    pub write_workers: usize,
60    /// Failed backend calls that open the backend.
61    pub failure_threshold: u32,
62    /// How long an open backend refuses calls before one probe.
63    pub recovery_interval: Duration,
64}
65
66/// A monotonic clock, in seconds.
67pub type Clock = Arc<dyn Fn() -> f64 + Send + Sync>;
68
69/// The process's monotonic clock, in seconds since its first reading.
70pub fn monotonic_clock() -> Clock {
71    let start = Instant::now();
72    Arc::new(move || start.elapsed().as_secs_f64())
73}
74
75/// A content-addressed key of a scheduler storage operation (`CacheKey`).
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct StorageKey {
78    /// The cache group.
79    pub group: u32,
80    /// The prefix page's content hash.
81    pub content_hash: String,
82    /// The block's ordinal within its prefix page.
83    pub page_offset: i32,
84}
85
86/// A storage operation a plan starts (`StorageOperation`).
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum Operation {
89    /// Which keys the store holds.
90    Query {
91        /// Its id.
92        op_id: u64,
93        /// The keys.
94        keys: Vec<StorageKey>,
95    },
96    /// Fetch keys into Host blocks.
97    Get {
98        /// Its id.
99        op_id: u64,
100        /// The keys.
101        keys: Vec<StorageKey>,
102        /// Per key, the destination Host block.
103        host_blocks: Vec<i32>,
104    },
105    /// Store Host blocks under keys.
106    Put {
107        /// Its id.
108        op_id: u64,
109        /// The keys.
110        keys: Vec<StorageKey>,
111        /// Per key, the source Host block.
112        host_blocks: Vec<i32>,
113    },
114    /// Cancel an earlier operation; it gets no completion of its own.
115    Cancel {
116        /// The operation cancelled.
117        op_id: u64,
118    },
119}
120
121/// One operation this rank finished (`StorageCompletion`).
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct StorageCompletion {
124    /// The operation.
125    pub op_id: u64,
126    /// Per key: it succeeded here.
127    pub success: Vec<bool>,
128    /// The native I/O retired: nothing still touches its Host memory.
129    pub retired: bool,
130    /// Per key: the object is confirmed missing or invalid here.
131    pub object_failures: Vec<bool>,
132}
133
134/// The executor's queue pressure (`StoragePressure`).
135#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
136pub struct StoragePressure {
137    /// Operations queued.
138    pub queued_operations: i64,
139    /// Bytes queued.
140    pub queued_bytes: i64,
141    /// The oldest operation not yet finished, its age in microseconds.
142    pub oldest_operation_us: i64,
143}
144
145/// One poll of this rank's storage executor.
146#[derive(Debug, Clone, Default, PartialEq, Eq)]
147pub struct StoragePoll {
148    /// The storage clock, in microseconds; the replica takes rank 0's.
149    pub now_us: i64,
150    /// The operations finished since the last poll.
151    pub completions: Vec<StorageCompletion>,
152    /// The queues now.
153    pub pressure: StoragePressure,
154}
155
156/// A fault in what the executor was given.
157#[derive(Debug, thiserror::Error)]
158pub enum ExecutorError {
159    /// A key or block the Host layout cannot place.
160    #[error(transparent)]
161    Layout(#[from] LayoutError),
162    /// A descriptor no scheduler would plan.
163    #[error("invalid L3 job: {0}")]
164    Invalid(&'static str),
165    /// More operations in flight than the scheduler's credits.
166    #[error("the scheduler exceeded the L3 operation credits")]
167    Credits,
168    /// A namespace change while operations are in flight.
169    #[error("L3 operations must retire before the namespace changes")]
170    Busy,
171    /// Shutdown could not prove every native I/O retired.
172    #[error("L3 I/O retirement is unproven; the Host pool is retained; restart required")]
173    Unretired,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177enum Kind {
178    Query,
179    Get,
180    Put,
181}
182
183const KINDS: [Kind; 3] = [Kind::Query, Kind::Get, Kind::Put];
184
185impl Kind {
186    fn index(self) -> usize {
187        self as usize
188    }
189
190    fn name(self) -> &'static str {
191        match self {
192            Kind::Query => "query",
193            Kind::Get => "get",
194            Kind::Put => "put",
195        }
196    }
197}
198
199/// A job as the workers run it: object keys and, for a get or put, this
200/// rank's Host range per key (None where another rank owns the block).
201#[derive(Debug, Clone)]
202struct Job {
203    op_id: u64,
204    kind: Kind,
205    keys: Vec<String>,
206    ranges: Vec<Option<Range>>,
207}
208
209impl Job {
210    /// The positions with native work on this rank.
211    fn local(&self) -> Vec<usize> {
212        match self.kind {
213            Kind::Query => (0..self.keys.len()).collect(),
214            Kind::Get | Kind::Put => (0..self.keys.len())
215                .filter(|&i| self.ranges[i].is_some())
216                .collect(),
217        }
218    }
219
220    fn num_bytes(&self) -> usize {
221        self.ranges.iter().flatten().map(|range| range.size).sum()
222    }
223}
224
225#[derive(Debug)]
226struct JobState {
227    job: Arc<Job>,
228    submitted_at: f64,
229    num_bytes: usize,
230    started_at: Option<f64>,
231    cancelled: bool,
232    completed: bool,
233}
234
235#[derive(Debug, Default, Clone, Copy)]
236struct Counters {
237    accepted: u64,
238    backend_calls: u64,
239    completed: u64,
240    cancelled_queued: u64,
241    cancelled_running: u64,
242    rejected: u64,
243    unretired: u64,
244    success_keys: u64,
245    failed_keys: u64,
246    success_bytes: u64,
247    queue_seconds: f64,
248    backend_seconds: f64,
249}
250
251#[derive(Debug, Default)]
252struct State {
253    jobs: HashMap<u64, JobState>,
254    results: VecDeque<StorageCompletion>,
255    queues: [VecDeque<u64>; 3],
256    counters: [Counters; 3],
257    stopping: bool,
258    unsafe_io: bool,
259    last_submitted: u64,
260    /// Workers still running.
261    running_workers: usize,
262}
263
264/// A permission to call the backend, from one health epoch.
265#[derive(Debug, Clone, Copy)]
266struct HealthToken {
267    epoch: u64,
268    recovery: bool,
269}
270
271#[derive(Debug, Default)]
272struct HealthState {
273    epoch: u64,
274    failures: u32,
275    unavailable_until: Option<f64>,
276    probe_inflight: bool,
277}
278
279/// The backend's availability: after `threshold` failures it opens for the
280/// recovery interval, then admits one probe whose outcome closes it again
281/// or reopens it. Reports from an older epoch change nothing.
282struct Health {
283    threshold: u32,
284    interval: f64,
285    clock: Clock,
286    state: Mutex<HealthState>,
287}
288
289impl Health {
290    fn lock(&self) -> MutexGuard<'_, HealthState> {
291        self.state.lock().expect("L3 health lock")
292    }
293
294    fn begin(&self) -> Option<HealthToken> {
295        let mut state = self.lock();
296        match state.unavailable_until {
297            None => Some(HealthToken {
298                epoch: state.epoch,
299                recovery: false,
300            }),
301            Some(until) if (self.clock)() < until || state.probe_inflight => None,
302            Some(_) => {
303                state.probe_inflight = true;
304                Some(HealthToken {
305                    epoch: state.epoch,
306                    recovery: true,
307                })
308            }
309        }
310    }
311
312    fn failure(&self, token: HealthToken) {
313        let mut state = self.lock();
314        if token.epoch != state.epoch {
315            return;
316        }
317        state.failures += 1;
318        if token.recovery || state.failures >= self.threshold {
319            state.epoch += 1;
320            state.unavailable_until = Some((self.clock)() + self.interval);
321            state.probe_inflight = false;
322        }
323    }
324
325    fn success(&self, token: HealthToken) {
326        let mut state = self.lock();
327        if token.epoch != state.epoch {
328            return;
329        }
330        state.failures = 0;
331        if token.recovery {
332            state.epoch += 1;
333            state.unavailable_until = None;
334            state.probe_inflight = false;
335        }
336    }
337
338    /// `healthy`, `open` or `half_open`, and the epoch, without taking the
339    /// probe permission.
340    fn snapshot(&self) -> (&'static str, u64) {
341        let state = self.lock();
342        let name = match (state.unavailable_until, state.probe_inflight) {
343            (None, _) => "healthy",
344            (Some(_), true) => "half_open",
345            (Some(_), false) => "open",
346        };
347        (name, state.epoch)
348    }
349}
350
351/// The namespace objects are named in: the deployment, and whether a draft
352/// shares the weight versions.
353#[derive(Debug, Clone, PartialEq, Eq)]
354pub struct Namespace {
355    /// The deployment's identity (`storage_deployment_id`).
356    pub deployment_id: String,
357    /// The draft model's cache is stored too; its weights follow the
358    /// target's version.
359    pub has_draft: bool,
360}
361
362impl Namespace {
363    fn prefix(&self, weight_version: &str) -> String {
364        let draft = if self.has_draft { weight_version } else { "" };
365        keys::key_prefix(&self.deployment_id, weight_version, draft)
366    }
367}
368
369struct Shared {
370    store: Box<dyn Store>,
371    host: HostBuffer,
372    layout: HostLayout,
373    namespace: Namespace,
374    config: ExecutorConfig,
375    clock: Clock,
376    health: Health,
377    state: Mutex<State>,
378    /// Per kind: a job was queued, or shutdown began.
379    work: [Condvar; 3],
380    /// A worker exited.
381    exited: Condvar,
382    prefix: Mutex<String>,
383}
384
385impl Shared {
386    fn lock(&self) -> MutexGuard<'_, State> {
387        self.state.lock().expect("L3 executor lock")
388    }
389
390    /// Complete `op_id` with `result` (under the state lock).
391    fn finish(&self, state: &mut State, op_id: u64, result: IoResult) {
392        let now = (self.clock)();
393        let Some(job_state) = state.jobs.get_mut(&op_id) else {
394            return;
395        };
396        job_state.completed = true;
397        let job = Arc::clone(&job_state.job);
398        let queued_until_now = job_state
399            .started_at
400            .is_none()
401            .then_some(now - job_state.submitted_at);
402        let counters = &mut state.counters[job.kind.index()];
403        counters.completed += 1;
404        for position in job.local() {
405            if result.success[position] {
406                counters.success_keys += 1;
407                if let Some(range) = job.ranges.get(position).copied().flatten() {
408                    counters.success_bytes += range.size as u64;
409                }
410            } else {
411                counters.failed_keys += 1;
412            }
413        }
414        if let Some(queued) = queued_until_now {
415            counters.queue_seconds += queued;
416        }
417        if !result.retired {
418            counters.unretired += 1;
419            state.unsafe_io = true;
420        }
421        state.results.push_back(StorageCompletion {
422            op_id,
423            success: result.success,
424            retired: result.retired,
425            object_failures: result.object_failures,
426        });
427    }
428
429    /// A retired failure of every key: the job never ran.
430    fn rejected(keys: usize) -> IoResult {
431        IoResult {
432            success: vec![false; keys],
433            retired: true,
434            object_failures: vec![false; keys],
435        }
436    }
437
438    fn submit(&self, job: Job) -> Result<(), ExecutorError> {
439        if job.keys.is_empty() || job.keys.len() > self.config.max_keys_per_operation {
440            return Err(ExecutorError::Invalid("its key count"));
441        }
442        let mut state = self.lock();
443        if job.op_id <= state.last_submitted {
444            return Err(ExecutorError::Invalid("its operation id does not increase"));
445        }
446        state.last_submitted = job.op_id;
447        if state.jobs.len() >= self.config.max_operations {
448            // The scheduler bounds every accepted operation: exceeding it is
449            // a protocol fault, not a completion to drop silently.
450            return Err(ExecutorError::Credits);
451        }
452        let (op_id, kind, num_keys) = (job.op_id, job.kind, job.keys.len());
453        let num_bytes = job.num_bytes();
454        state.counters[kind.index()].accepted += 1;
455        state.jobs.insert(
456            op_id,
457            JobState {
458                job: Arc::new(job),
459                submitted_at: (self.clock)(),
460                num_bytes,
461                started_at: None,
462                cancelled: false,
463                completed: false,
464            },
465        );
466        let queue = &state.queues[kind.index()];
467        if state.stopping || state.unsafe_io || queue.len() >= self.config.max_operations {
468            state.counters[kind.index()].rejected += 1;
469            self.finish(&mut state, op_id, Self::rejected(num_keys));
470            return Ok(());
471        }
472        state.queues[kind.index()].push_back(op_id);
473        drop(state);
474        self.work[kind.index()].notify_one();
475        Ok(())
476    }
477
478    fn cancel(&self, op_id: u64) {
479        let mut state = self.lock();
480        let Some(job_state) = state.jobs.get_mut(&op_id) else {
481            return;
482        };
483        if job_state.completed || job_state.cancelled {
484            return;
485        }
486        job_state.cancelled = true;
487        let started = job_state.started_at.is_some();
488        let (kind, keys) = (job_state.job.kind, job_state.job.keys.len());
489        let counters = &mut state.counters[kind.index()];
490        if started {
491            counters.cancelled_running += 1;
492        } else {
493            counters.cancelled_queued += 1;
494            // Retire at once, even with every worker blocked in native I/O:
495            // the queue entry cannot start after this, and its worker skips
496            // it.
497            self.finish(&mut state, op_id, Self::rejected(keys));
498        }
499    }
500
501    /// One worker of `kind`'s queue, until shutdown drains it.
502    fn work(&self, kind: Kind) {
503        loop {
504            let mut state = self.lock();
505            let op_id = loop {
506                if let Some(op_id) = state.queues[kind.index()].pop_front() {
507                    break Some(op_id);
508                }
509                if state.stopping {
510                    break None;
511                }
512                state = self.work[kind.index()]
513                    .wait(state)
514                    .expect("L3 executor lock");
515            };
516            let Some(op_id) = op_id else {
517                state.running_workers -= 1;
518                drop(state);
519                self.exited.notify_all();
520                return;
521            };
522            let (stopping, unsafe_io) = (state.stopping, state.unsafe_io);
523            let Some(job_state) = state.jobs.get_mut(&op_id) else {
524                continue;
525            };
526            if job_state.completed {
527                continue;
528            }
529            let job = Arc::clone(&job_state.job);
530            if job_state.cancelled || stopping || unsafe_io {
531                state.counters[kind.index()].rejected += 1;
532                self.finish(&mut state, op_id, Self::rejected(job.keys.len()));
533                continue;
534            }
535            let now = (self.clock)();
536            let queued = now - job_state.submitted_at;
537            job_state.started_at = Some(now);
538            state.counters[kind.index()].queue_seconds += queued;
539            drop(state);
540            let result = self.run(&job);
541            let mut state = self.lock();
542            self.finish(&mut state, op_id, result);
543        }
544    }
545
546    /// Run `job` against the store, outside the state lock.
547    fn run(&self, job: &Job) -> IoResult {
548        let local = job.local();
549        let keys = job.keys.len();
550        if local.is_empty() {
551            // No native work on this owner: it neither probes the backend's
552            // health nor charges bytes it did not move.
553            return IoResult {
554                success: vec![true; keys],
555                retired: true,
556                object_failures: vec![false; keys],
557            };
558        }
559        let local_keys: Vec<String> = local.iter().map(|&i| job.keys[i].clone()).collect();
560        let local_ranges: Vec<Range> = match job.kind {
561            Kind::Query => Vec::new(),
562            Kind::Get | Kind::Put => local
563                .iter()
564                .map(|&i| job.ranges[i].expect("a local position has a range"))
565                .collect(),
566        };
567        let result = match self.health.begin() {
568            None => {
569                self.lock().counters[job.kind.index()].rejected += 1;
570                Self::rejected(local.len())
571            }
572            Some(token) => {
573                self.lock().counters[job.kind.index()].backend_calls += 1;
574                let started = (self.clock)();
575                let called = match job.kind {
576                    Kind::Query => self.store.batch_exists(&local_keys).map(|flags| IoResult {
577                        object_failures: vec![false; flags.len()],
578                        success: flags,
579                        retired: true,
580                    }),
581                    Kind::Get => self
582                        .store
583                        .batch_get_into(&local_keys, self.host, &local_ranges),
584                    Kind::Put => self
585                        .store
586                        .batch_put_from(&local_keys, self.host, &local_ranges),
587                };
588                let checked = called.map_err(|error| error.0).and_then(|result| {
589                    let valid = result.success.len() == local.len()
590                        && result.object_failures.len() == local.len()
591                        && !result
592                            .success
593                            .iter()
594                            .zip(&result.object_failures)
595                            .any(|(&ok, &failed)| ok && failed)
596                        && (job.kind == Kind::Get || !result.object_failures.contains(&true));
597                    if valid {
598                        Ok(result)
599                    } else {
600                        Err("malformed L3 result".to_owned())
601                    }
602                });
603                let result = match checked {
604                    Ok(result) => {
605                        // A retired failure is an object or capacity fact,
606                        // never backend unavailability.
607                        if job.kind == Kind::Query || result.retired {
608                            self.health.success(token);
609                        } else {
610                            self.health.failure(token);
611                        }
612                        result
613                    }
614                    Err(error) => {
615                        tracing::error!(
616                            kind = job.kind.name(),
617                            op_id = job.op_id,
618                            %error,
619                            "L3 operation failed"
620                        );
621                        self.health.failure(token);
622                        IoResult {
623                            success: vec![false; local.len()],
624                            retired: job.kind == Kind::Query || self.store.failures_retire(),
625                            object_failures: vec![false; local.len()],
626                        }
627                    }
628                };
629                self.lock().counters[job.kind.index()].backend_seconds += (self.clock)() - started;
630                result
631            }
632        };
633        // Keys without native work here are neutral successes.
634        let mut success = vec![true; keys];
635        let mut object_failures = vec![false; keys];
636        for (k, &position) in local.iter().enumerate() {
637            success[position] = result.success[k];
638            object_failures[position] = result.object_failures[k];
639        }
640        IoResult {
641            success,
642            retired: result.retired,
643            object_failures,
644        }
645    }
646}
647
648/// The Host-only L3 executor of one rank. Dropping it closes it (see
649/// [`Executor::close`]), waiting a few seconds for the workers.
650pub struct Executor {
651    shared: Arc<Shared>,
652    threads: Vec<JoinHandle<()>>,
653    closed: bool,
654}
655
656impl Executor {
657    /// Start the workers over `store`, which reads and writes `host`, laid
658    /// out as `layout`; objects are named in `namespace` under
659    /// `weight_version`.
660    pub fn start(
661        store: Box<dyn Store>,
662        host: HostBuffer,
663        layout: HostLayout,
664        namespace: Namespace,
665        weight_version: &str,
666        config: ExecutorConfig,
667        clock: Clock,
668    ) -> Self {
669        assert_eq!(
670            host.len,
671            layout.bytes(),
672            "the Host pool and its layout disagree"
673        );
674        let prefix = namespace.prefix(weight_version);
675        let workers = config.query_workers + config.read_workers + config.write_workers;
676        let shared = Arc::new(Shared {
677            store,
678            host,
679            layout,
680            namespace,
681            config,
682            health: Health {
683                threshold: config.failure_threshold,
684                interval: config.recovery_interval.as_secs_f64(),
685                clock: Arc::clone(&clock),
686                state: Mutex::new(HealthState::default()),
687            },
688            clock,
689            state: Mutex::new(State {
690                running_workers: workers,
691                ..State::default()
692            }),
693            work: [Condvar::new(), Condvar::new(), Condvar::new()],
694            exited: Condvar::new(),
695            prefix: Mutex::new(prefix),
696        });
697        let mut threads = Vec::with_capacity(workers);
698        for (kind, count) in [
699            (Kind::Query, config.query_workers),
700            (Kind::Get, config.read_workers),
701            (Kind::Put, config.write_workers),
702        ] {
703            for index in 0..count {
704                let shared = Arc::clone(&shared);
705                threads.push(
706                    std::thread::Builder::new()
707                        .name(format!("l3-{}-{index}", kind.name()))
708                        .spawn(move || shared.work(kind))
709                        .expect("spawning an L3 worker"),
710                );
711            }
712        }
713        Self {
714            shared,
715            threads,
716            closed: false,
717        }
718    }
719
720    /// The limits and workers it started with.
721    pub fn config(&self) -> ExecutorConfig {
722        self.shared.config
723    }
724
725    /// The namespace prefix objects are named under now.
726    pub fn key_prefix(&self) -> String {
727        self.shared.prefix.lock().expect("L3 prefix lock").clone()
728    }
729
730    fn object_keys(&self, keys: &[StorageKey]) -> Result<Vec<String>, ExecutorError> {
731        let prefix = self.key_prefix();
732        keys.iter()
733            .map(|key| {
734                Ok(keys::object_key(
735                    &prefix,
736                    &key.content_hash,
737                    key.group,
738                    key.page_offset,
739                    self.shared.layout.key_rank(key.group)?,
740                ))
741            })
742            .collect()
743    }
744
745    /// Freeze `keys`' object names and this rank's Host ranges, in the
746    /// operation's order; a key another rank owns gets no range, and its
747    /// completion here stays neutral.
748    fn ranged_job(
749        &self,
750        op_id: u64,
751        kind: Kind,
752        keys: &[StorageKey],
753        host_blocks: &[i32],
754    ) -> Result<Job, ExecutorError> {
755        if keys.len() != host_blocks.len() {
756            return Err(ExecutorError::Invalid("a Host block per key"));
757        }
758        let ranges = keys
759            .iter()
760            .zip(host_blocks)
761            .map(|(key, &block)| self.shared.layout.owned_range(key.group, block))
762            .collect::<Result<Vec<_>, _>>()?;
763        Ok(Job {
764            op_id,
765            kind,
766            keys: self.object_keys(keys)?,
767            ranges,
768        })
769    }
770
771    /// Start a plan's storage operations, in order; none waits for I/O.
772    pub fn submit(&self, operations: &[Operation]) -> Result<(), ExecutorError> {
773        for operation in operations {
774            let job = match operation {
775                Operation::Cancel { op_id } => {
776                    self.shared.cancel(*op_id);
777                    continue;
778                }
779                Operation::Query { op_id, keys } => Job {
780                    op_id: *op_id,
781                    kind: Kind::Query,
782                    keys: self.object_keys(keys)?,
783                    ranges: Vec::new(),
784                },
785                Operation::Get {
786                    op_id,
787                    keys,
788                    host_blocks,
789                } => self.ranged_job(*op_id, Kind::Get, keys, host_blocks)?,
790                Operation::Put {
791                    op_id,
792                    keys,
793                    host_blocks,
794                } => self.ranged_job(*op_id, Kind::Put, keys, host_blocks)?,
795            };
796            self.shared.submit(job)?;
797        }
798        Ok(())
799    }
800
801    /// The operations finished since the last poll and the queues' pressure,
802    /// under one lock and without I/O. A running cancelled job still counts:
803    /// detaching its request does not retire its native I/O.
804    pub fn poll(&self) -> StoragePoll {
805        let mut state = self.shared.lock();
806        let completions: Vec<StorageCompletion> = state.results.drain(..).collect();
807        for completion in &completions {
808            state.jobs.remove(&completion.op_id);
809        }
810        let now = (self.shared.clock)();
811        let mut pressure = StoragePressure::default();
812        let mut oldest = now;
813        for job_state in state.jobs.values().filter(|job| !job.completed) {
814            oldest = oldest.min(job_state.submitted_at);
815            if job_state.started_at.is_none() {
816                pressure.queued_operations += 1;
817                pressure.queued_bytes += job_state.num_bytes as i64;
818            }
819        }
820        pressure.oldest_operation_us = ((now - oldest) * 1_000_000.0).max(0.0) as i64;
821        StoragePoll {
822            now_us: (now * 1_000_000.0) as i64,
823            completions,
824            pressure,
825        }
826    }
827
828    /// Nothing in flight, and no unproven retirement.
829    pub fn is_idle(&self) -> bool {
830        let state = self.shared.lock();
831        state.jobs.is_empty() && !state.unsafe_io
832    }
833
834    /// The counters, without backend or GPU work. Completed jobs count until
835    /// a poll delivers them.
836    pub fn snapshot(&self) -> L3ExecutorSnapshot {
837        let state = self.shared.lock();
838        let operations = KINDS
839            .iter()
840            .map(|&kind| {
841                let counters = state.counters[kind.index()];
842                let mut phases = [(0.0, 0.0); 3];
843                for job_state in state.jobs.values().filter(|job| job.job.kind == kind) {
844                    let phase = if job_state.completed {
845                        2
846                    } else if job_state.started_at.is_some() {
847                        1
848                    } else {
849                        0
850                    };
851                    phases[phase].0 += 1.0;
852                    phases[phase].1 += job_state.num_bytes as f64;
853                }
854                let by_phase = |pick: fn(&(f64, f64)) -> f64| -> Vec<(String, f64)> {
855                    ["queued", "running", "completed"]
856                        .iter()
857                        .zip(&phases)
858                        .map(|(name, phase)| ((*name).to_owned(), pick(phase)))
859                        .collect()
860                };
861                L3OperationSnapshot {
862                    kind: kind.name().to_owned(),
863                    events: [
864                        ("accepted", counters.accepted),
865                        ("backend_calls", counters.backend_calls),
866                        ("completed", counters.completed),
867                        ("cancelled_queued", counters.cancelled_queued),
868                        ("cancelled_running", counters.cancelled_running),
869                        ("rejected", counters.rejected),
870                        ("unretired", counters.unretired),
871                    ]
872                    .into_iter()
873                    .map(|(name, value)| (name.to_owned(), value as f64))
874                    .collect(),
875                    success_keys: counters.success_keys as f64,
876                    failed_keys: counters.failed_keys as f64,
877                    success_bytes: counters.success_bytes as f64,
878                    queue_seconds: counters.queue_seconds,
879                    backend_seconds: counters.backend_seconds,
880                    operations: by_phase(|phase| phase.0),
881                    bytes: by_phase(|phase| phase.1),
882                }
883            })
884            .collect();
885        drop(state);
886        let (health_state, health_epoch) = self.shared.health.snapshot();
887        L3ExecutorSnapshot {
888            operations,
889            health_state: health_state.to_owned(),
890            health_epoch: health_epoch as f64,
891            unsafe_io: self.shared.lock().unsafe_io,
892        }
893    }
894
895    /// Switch the namespace to `weight_version`, after the replica agreed to
896    /// clear the caches.
897    pub fn set_weight_version(&self, weight_version: &str) -> Result<(), ExecutorError> {
898        if !self.is_idle() {
899            return Err(ExecutorError::Busy);
900        }
901        *self.shared.prefix.lock().expect("L3 prefix lock") =
902            self.shared.namespace.prefix(weight_version);
903        Ok(())
904    }
905
906    /// Delete the namespace's objects once idle: true when they are gone.
907    pub fn delete_namespace(&self) -> bool {
908        if !self.is_idle() {
909            return false;
910        }
911        let prefix = format!("{}_", self.key_prefix());
912        self.shared.store.remove_by_prefix(&prefix)
913    }
914
915    /// Stop the workers within `timeout` and release the store; a later
916    /// call does nothing. If a worker is still in native I/O, or some I/O
917    /// never proved its retirement, the store stays connected for the rest
918    /// of the process and the caller must keep the Host pool alive too.
919    pub fn close(&mut self, timeout: Duration) -> Result<(), ExecutorError> {
920        if self.closed {
921            return Ok(());
922        }
923        self.closed = true;
924        let deadline = Instant::now() + timeout;
925        let mut state = self.shared.lock();
926        state.stopping = true;
927        for work in &self.shared.work {
928            work.notify_all();
929        }
930        while state.running_workers > 0 {
931            let left = deadline.saturating_duration_since(Instant::now());
932            if left.is_zero() {
933                break;
934            }
935            state = self
936                .shared
937                .exited
938                .wait_timeout(state, left)
939                .expect("L3 executor lock")
940                .0;
941        }
942        let retired = state.running_workers == 0 && !state.unsafe_io;
943        drop(state);
944        if !retired {
945            // Only process exit releases an unretired native transport: the
946            // store, and with it the registration of the pool, stay.
947            std::mem::forget(Arc::clone(&self.shared));
948            return Err(ExecutorError::Unretired);
949        }
950        for thread in self.threads.drain(..) {
951            let _ = thread.join();
952        }
953        self.shared.store.close();
954        Ok(())
955    }
956}
957
958impl Drop for Executor {
959    fn drop(&mut self) {
960        if let Err(error) = self.close(Duration::from_secs(5)) {
961            tracing::error!(%error, "closing the L3 executor");
962        }
963    }
964}
965
966#[cfg(test)]
967mod tests {
968    use std::sync::atomic::{AtomicUsize, Ordering};
969
970    use super::*;
971    use crate::{layout::GroupLayout, store::MemoryStore, store::StoreError};
972
973    /// A one-shot latch (`threading.Event`).
974    #[derive(Default)]
975    struct Event {
976        set: Mutex<bool>,
977        changed: Condvar,
978    }
979
980    impl Event {
981        fn set(&self) {
982            *self.set.lock().unwrap() = true;
983            self.changed.notify_all();
984        }
985
986        fn wait(&self, timeout: Duration) -> bool {
987            let guard = self.set.lock().unwrap();
988            *self
989                .changed
990                .wait_timeout_while(guard, timeout, |set| !*set)
991                .unwrap()
992                .0
993        }
994    }
995
996    type ExistsFn = dyn Fn(&[String]) -> Result<Vec<bool>, StoreError> + Send + Sync;
997    type IoFn =
998        dyn Fn(&[String], HostBuffer, &[Range]) -> Result<IoResult, StoreError> + Send + Sync;
999    /// The gets and puts a store ran, in order, with their keys.
1000    type Calls = Arc<Mutex<Vec<(&'static str, Vec<String>)>>>;
1001
1002    /// A memory store whose calls a test may replace, recording the gets
1003    /// and puts it runs and how often it is closed.
1004    #[derive(Default)]
1005    struct TestStore {
1006        memory: Arc<MemoryStore>,
1007        exists: Option<Box<ExistsFn>>,
1008        get: Option<Box<IoFn>>,
1009        put: Option<Box<IoFn>>,
1010        /// Clearing a namespace fails.
1011        remove_fails: bool,
1012        failures_retire: bool,
1013        calls: Calls,
1014        closes: Arc<AtomicUsize>,
1015    }
1016
1017    impl TestStore {
1018        fn new() -> Self {
1019            Self {
1020                failures_retire: true,
1021                ..Self::default()
1022            }
1023        }
1024    }
1025
1026    impl Store for TestStore {
1027        fn failures_retire(&self) -> bool {
1028            self.failures_retire
1029        }
1030
1031        fn batch_exists(&self, keys: &[String]) -> Result<Vec<bool>, StoreError> {
1032            match &self.exists {
1033                Some(exists) => exists(keys),
1034                None => self.memory.batch_exists(keys),
1035            }
1036        }
1037
1038        fn batch_get_into(
1039            &self,
1040            keys: &[String],
1041            host: HostBuffer,
1042            ranges: &[Range],
1043        ) -> Result<IoResult, StoreError> {
1044            self.calls.lock().unwrap().push(("get", keys.to_vec()));
1045            match &self.get {
1046                Some(get) => get(keys, host, ranges),
1047                None => self.memory.batch_get_into(keys, host, ranges),
1048            }
1049        }
1050
1051        fn batch_put_from(
1052            &self,
1053            keys: &[String],
1054            host: HostBuffer,
1055            ranges: &[Range],
1056        ) -> Result<IoResult, StoreError> {
1057            self.calls.lock().unwrap().push(("put", keys.to_vec()));
1058            match &self.put {
1059                Some(put) => put(keys, host, ranges),
1060                None => self.memory.batch_put_from(keys, host, ranges),
1061            }
1062        }
1063
1064        fn remove_by_prefix(&self, prefix: &str) -> bool {
1065            !self.remove_fails && self.memory.remove_by_prefix(prefix)
1066        }
1067
1068        fn close(&self) {
1069            self.closes.fetch_add(1, Ordering::SeqCst);
1070        }
1071    }
1072
1073    const CONFIG: ExecutorConfig = ExecutorConfig {
1074        max_operations: 32,
1075        max_keys_per_operation: 256,
1076        query_workers: 2,
1077        read_workers: 2,
1078        write_workers: 1,
1079        failure_threshold: 1,
1080        recovery_interval: Duration::from_secs(30),
1081    };
1082
1083    /// Group 0 has 8-byte blocks, group 1 16-byte ones, one each per LCM
1084    /// block; with `shard_count` owners of group 0, this is owner
1085    /// `owner_rank`.
1086    fn layout(shard_count: usize, owner_rank: usize, tp_rank: usize) -> HostLayout {
1087        HostLayout {
1088            groups: vec![
1089                GroupLayout {
1090                    packing: 1,
1091                    block_bytes: 8,
1092                    shard_count,
1093                },
1094                GroupLayout {
1095                    packing: 1,
1096                    block_bytes: 16,
1097                    shard_count: 1,
1098                },
1099            ],
1100            lcm_block_bytes: 24,
1101            num_lcm_blocks: 4,
1102            owner_rank,
1103            tp_rank,
1104        }
1105    }
1106
1107    /// An executor over its own Host pool.
1108    struct Harness {
1109        executor: Option<Executor>,
1110        pool: Vec<u8>,
1111    }
1112
1113    impl Harness {
1114        fn start(
1115            store: TestStore,
1116            config: ExecutorConfig,
1117            layout: HostLayout,
1118            clock: Clock,
1119        ) -> Self {
1120            let mut pool = vec![0_u8; layout.bytes()];
1121            let host = HostBuffer {
1122                base: pool.as_mut_ptr() as usize,
1123                len: pool.len(),
1124            };
1125            let executor = Executor::start(
1126                Box::new(store),
1127                host,
1128                layout,
1129                Namespace {
1130                    deployment_id: "deployment".to_owned(),
1131                    has_draft: false,
1132                },
1133                "v1",
1134                config,
1135                clock,
1136            );
1137            Self {
1138                executor: Some(executor),
1139                pool,
1140            }
1141        }
1142
1143        fn new(store: TestStore, config: ExecutorConfig) -> Self {
1144            Self::start(store, config, layout(1, 0, 0), monotonic_clock())
1145        }
1146
1147        fn executor(&self) -> &Executor {
1148            self.executor.as_ref().unwrap()
1149        }
1150
1151        /// The next `count` completions, within three seconds.
1152        fn wait(&self, count: usize) -> Vec<StorageCompletion> {
1153            let deadline = Instant::now() + Duration::from_secs(3);
1154            let mut completions = Vec::new();
1155            while completions.len() < count {
1156                assert!(Instant::now() < deadline, "{completions:?}");
1157                completions.extend(self.executor().poll().completions);
1158                std::thread::sleep(Duration::from_millis(1));
1159            }
1160            assert_eq!(completions.len(), count, "{completions:?}");
1161            completions
1162        }
1163
1164        fn close(&mut self, timeout: Duration) -> Result<(), ExecutorError> {
1165            match self.executor.take() {
1166                Some(mut executor) => executor.close(timeout),
1167                None => Ok(()),
1168            }
1169        }
1170    }
1171
1172    impl Drop for Harness {
1173        fn drop(&mut self) {
1174            // Stop the workers before the pool goes; a test that expects an
1175            // unretired close left nothing to stop.
1176            let _ = self.close(Duration::from_secs(2));
1177        }
1178    }
1179
1180    fn key(hash: &str) -> StorageKey {
1181        StorageKey {
1182            group: 0,
1183            content_hash: hash.to_owned(),
1184            page_offset: 0,
1185        }
1186    }
1187
1188    fn query(op_id: u64, hashes: &[&str]) -> Operation {
1189        Operation::Query {
1190            op_id,
1191            keys: hashes.iter().map(|hash| key(hash)).collect(),
1192        }
1193    }
1194
1195    fn get(op_id: u64, hash: &str, block: i32) -> Operation {
1196        Operation::Get {
1197            op_id,
1198            keys: vec![key(hash)],
1199            host_blocks: vec![block],
1200        }
1201    }
1202
1203    fn put(op_id: u64, hash: &str, block: i32) -> Operation {
1204        Operation::Put {
1205            op_id,
1206            keys: vec![key(hash)],
1207            host_blocks: vec![block],
1208        }
1209    }
1210
1211    fn row(snapshot: &L3ExecutorSnapshot, kind: &str) -> L3OperationSnapshot {
1212        snapshot
1213            .operations
1214            .iter()
1215            .find(|row| row.kind == kind)
1216            .cloned()
1217            .unwrap()
1218    }
1219
1220    fn value(pairs: &[(String, f64)], name: &str) -> f64 {
1221        pairs.iter().find(|(key, _)| key == name).unwrap().1
1222    }
1223
1224    fn fake_clock(now: &Arc<Mutex<f64>>) -> Clock {
1225        let now = Arc::clone(now);
1226        Arc::new(move || *now.lock().unwrap())
1227    }
1228
1229    #[test]
1230    fn stale_reports_cannot_reopen_a_failed_recovery() {
1231        let now = Arc::new(Mutex::new(1.0));
1232        let health = Health {
1233            threshold: 1,
1234            interval: 10.0,
1235            clock: fake_clock(&now),
1236            state: Mutex::default(),
1237        };
1238        let old = health.begin().unwrap();
1239        let failing = health.begin().unwrap();
1240        health.failure(failing);
1241        health.success(old);
1242        assert!(health.begin().is_none());
1243        *now.lock().unwrap() = 12.0;
1244        let probe = health.begin().unwrap();
1245        assert!(probe.recovery);
1246        assert!(health.begin().is_none());
1247        health.failure(probe);
1248        // This success belongs to the failed epoch.
1249        health.success(probe);
1250        assert!(health.begin().is_none());
1251        assert_eq!(health.snapshot().0, "open");
1252        *now.lock().unwrap() = 23.0;
1253        let recovered = health.begin().unwrap();
1254        assert!(recovered.recovery && health.begin().is_none());
1255        health.success(recovered);
1256        assert!(!health.begin().unwrap().recovery);
1257    }
1258
1259    #[test]
1260    fn a_snapshot_cannot_take_the_recovery_probe() {
1261        let now = Arc::new(Mutex::new(0.0));
1262        let health = Health {
1263            threshold: 1,
1264            interval: 1.0,
1265            clock: fake_clock(&now),
1266            state: Mutex::default(),
1267        };
1268        health.failure(health.begin().unwrap());
1269        *now.lock().unwrap() = 2.0;
1270        for _ in 0..3 {
1271            assert_eq!(health.snapshot().0, "open");
1272        }
1273        let probe = health.begin().unwrap();
1274        assert!(probe.recovery);
1275        assert_eq!(health.snapshot().0, "half_open");
1276        health.success(probe);
1277        assert_eq!(health.snapshot().0, "healthy");
1278    }
1279
1280    #[test]
1281    fn a_slow_query_blocks_neither_another_worker_nor_the_submitter() {
1282        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1283        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1284        let slow = keys::object_key(&keys::key_prefix("deployment", "v1", ""), "slow", 0, 0, 0);
1285        let store = TestStore {
1286            exists: Some(Box::new(move |keys| {
1287                if keys == [slow.clone()] {
1288                    s.set();
1289                    assert!(r.wait(Duration::from_secs(3)));
1290                }
1291                Ok(vec![true; keys.len()])
1292            })),
1293            ..TestStore::new()
1294        };
1295        let harness = Harness::new(store, CONFIG);
1296        harness.executor().submit(&[query(1, &["slow"])]).unwrap();
1297        assert!(started.wait(Duration::from_secs(1)));
1298        harness.executor().submit(&[query(2, &["fast"])]).unwrap();
1299        let fast = &harness.wait(1)[0];
1300        assert_eq!((fast.op_id, fast.success.as_slice()), (2, &[true][..]));
1301        release.set();
1302        assert_eq!(harness.wait(1)[0].op_id, 1);
1303    }
1304
1305    #[test]
1306    fn a_cancelled_queued_get_never_reaches_the_store() {
1307        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1308        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1309        let store = TestStore {
1310            get: Some(Box::new(move |keys, _, _| {
1311                s.set();
1312                assert!(r.wait(Duration::from_secs(3)));
1313                Ok(IoResult {
1314                    success: vec![true; keys.len()],
1315                    retired: true,
1316                    object_failures: vec![false; keys.len()],
1317                })
1318            })),
1319            ..TestStore::new()
1320        };
1321        let calls = Arc::clone(&store.calls);
1322        let harness = Harness::new(
1323            store,
1324            ExecutorConfig {
1325                read_workers: 1,
1326                ..CONFIG
1327            },
1328        );
1329        harness.executor().submit(&[get(1, "a", 1)]).unwrap();
1330        assert!(started.wait(Duration::from_secs(1)));
1331        harness
1332            .executor()
1333            .submit(&[get(2, "b", 2), Operation::Cancel { op_id: 2 }])
1334            .unwrap();
1335        let cancelled = &harness.wait(1)[0];
1336        assert_eq!(cancelled.op_id, 2);
1337        assert!(cancelled.retired);
1338        assert_eq!(cancelled.success, [false]);
1339        assert_eq!(cancelled.object_failures, [false]);
1340        // The only worker is still inside a's I/O.
1341        assert_eq!(calls.lock().unwrap().len(), 1);
1342        release.set();
1343        assert_eq!(harness.wait(1)[0].op_id, 1);
1344        assert_eq!(calls.lock().unwrap().len(), 1);
1345    }
1346
1347    #[test]
1348    fn a_failing_query_degrades_without_killing_its_worker() {
1349        let store = TestStore {
1350            exists: Some(Box::new(|_| Err(StoreError("unavailable".to_owned())))),
1351            ..TestStore::new()
1352        };
1353        let harness = Harness::new(store, CONFIG);
1354        harness.executor().submit(&[query(1, &["a"])]).unwrap();
1355        let failed = &harness.wait(1)[0];
1356        assert!(failed.retired);
1357        assert_eq!(failed.success, [false]);
1358        harness.executor().submit(&[query(2, &["b"])]).unwrap();
1359        assert_eq!(harness.wait(1)[0].op_id, 2);
1360    }
1361
1362    #[test]
1363    fn unproven_retirement_disables_io_and_refuses_a_clean_close() {
1364        for success in [false, true] {
1365            let store = TestStore {
1366                get: Some(Box::new(move |_, _, _| {
1367                    Ok(IoResult {
1368                        success: vec![success],
1369                        retired: false,
1370                        object_failures: vec![false],
1371                    })
1372                })),
1373                ..TestStore::new()
1374            };
1375            let closes = Arc::clone(&store.closes);
1376            let mut harness = Harness::new(store, CONFIG);
1377            harness.executor().submit(&[get(1, "a", 1)]).unwrap();
1378            assert!(!harness.wait(1)[0].retired);
1379            assert!(!harness.executor().is_idle());
1380            assert_eq!(harness.executor().snapshot().health_state, "open");
1381            harness.executor().submit(&[get(2, "b", 2)]).unwrap();
1382            // No new I/O starts.
1383            let refused = &harness.wait(1)[0];
1384            assert!(refused.retired);
1385            assert_eq!(refused.success, [false]);
1386            assert!(matches!(
1387                harness.close(Duration::from_secs(2)),
1388                Err(ExecutorError::Unretired)
1389            ));
1390            // The store stays connected: only process exit releases it.
1391            assert_eq!(closes.load(Ordering::SeqCst), 0);
1392            // The pool must outlive the leaked workers.
1393            std::mem::forget(std::mem::take(&mut harness.pool));
1394        }
1395    }
1396
1397    #[test]
1398    fn independent_executors_share_objects_through_the_store() {
1399        let memory = Arc::new(MemoryStore::new());
1400        let writer = Harness::new(
1401            TestStore {
1402                memory: Arc::clone(&memory),
1403                ..TestStore::new()
1404            },
1405            CONFIG,
1406        );
1407        let reader = Harness::new(
1408            TestStore {
1409                memory: Arc::clone(&memory),
1410                ..TestStore::new()
1411            },
1412            CONFIG,
1413        );
1414        // Block 1 of group 0 is the pool's first 8 bytes.
1415        let pool = writer.pool.as_ptr() as *mut u8;
1416        // SAFETY: the pool is live and no job runs yet.
1417        #[allow(unsafe_code)]
1418        unsafe {
1419            std::ptr::copy_nonoverlapping(b"abcdefgh".as_ptr(), pool, 8);
1420        }
1421        writer.executor().submit(&[put(1, "shared", 1)]).unwrap();
1422        assert_eq!(writer.wait(1)[0].success, [true]);
1423        // Block 3 sits at the third LCM block.
1424        reader.executor().submit(&[get(1, "shared", 3)]).unwrap();
1425        assert_eq!(reader.wait(1)[0].success, [true]);
1426        assert_eq!(&reader.pool[48..56], b"abcdefgh");
1427        assert_eq!(&reader.pool[..8], &[0; 8]);
1428    }
1429
1430    #[test]
1431    fn operation_credits_bound_what_is_in_flight() {
1432        let harness = Harness::new(
1433            TestStore::new(),
1434            ExecutorConfig {
1435                max_operations: 1,
1436                ..CONFIG
1437            },
1438        );
1439        harness.executor().submit(&[query(1, &["a"])]).unwrap();
1440        // Completed but not polled still counts.
1441        std::thread::sleep(Duration::from_millis(20));
1442        assert!(matches!(
1443            harness.executor().submit(&[query(2, &["b"])]),
1444            Err(ExecutorError::Credits)
1445        ));
1446        harness.wait(1);
1447    }
1448
1449    #[test]
1450    fn the_key_limit_and_increasing_ids_are_enforced() {
1451        let harness = Harness::new(
1452            TestStore::new(),
1453            ExecutorConfig {
1454                max_keys_per_operation: 2,
1455                ..CONFIG
1456            },
1457        );
1458        assert!(matches!(
1459            harness.executor().submit(&[query(1, &["a", "b", "c"])]),
1460            Err(ExecutorError::Invalid(_))
1461        ));
1462        harness.executor().submit(&[query(1, &["a", "b"])]).unwrap();
1463        assert_eq!(harness.wait(1)[0].success, [false, false]);
1464        assert!(matches!(
1465            harness.executor().submit(&[query(1, &["a"])]),
1466            Err(ExecutorError::Invalid(_))
1467        ));
1468    }
1469
1470    #[test]
1471    fn a_retired_get_miss_is_an_object_failure_not_a_backend_fault() {
1472        let harness = Harness::new(TestStore::new(), CONFIG);
1473        harness.executor().submit(&[get(1, "evicted", 1)]).unwrap();
1474        let miss = &harness.wait(1)[0];
1475        assert!(miss.retired);
1476        assert_eq!(miss.success, [false]);
1477        assert_eq!(miss.object_failures, [true]);
1478        assert_eq!(harness.executor().snapshot().health_state, "healthy");
1479    }
1480
1481    #[test]
1482    fn snapshots_count_cancelled_queued_work_and_late_completions() {
1483        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1484        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1485        let store = TestStore {
1486            get: Some(Box::new(move |keys, _, _| {
1487                s.set();
1488                assert!(r.wait(Duration::from_secs(3)));
1489                Ok(IoResult {
1490                    success: vec![true; keys.len()],
1491                    retired: true,
1492                    object_failures: vec![false; keys.len()],
1493                })
1494            })),
1495            ..TestStore::new()
1496        };
1497        let harness = Harness::new(
1498            store,
1499            ExecutorConfig {
1500                read_workers: 1,
1501                ..CONFIG
1502            },
1503        );
1504        let executor = harness.executor();
1505        executor.submit(&[get(1, "running", 1)]).unwrap();
1506        assert!(started.wait(Duration::from_secs(1)));
1507        // A group-1 block is 16 bytes.
1508        executor
1509            .submit(&[Operation::Get {
1510                op_id: 2,
1511                keys: vec![StorageKey {
1512                    group: 1,
1513                    content_hash: "queued".to_owned(),
1514                    page_offset: 0,
1515                }],
1516                host_blocks: vec![1],
1517            }])
1518            .unwrap();
1519        let snapshot = row(&executor.snapshot(), "get");
1520        assert_eq!(
1521            snapshot.operations,
1522            [
1523                ("queued".to_owned(), 1.0),
1524                ("running".to_owned(), 1.0),
1525                ("completed".to_owned(), 0.0)
1526            ]
1527        );
1528        assert_eq!(value(&snapshot.bytes, "queued"), 16.0);
1529        assert_eq!(value(&snapshot.bytes, "running"), 8.0);
1530        executor
1531            .submit(&[
1532                Operation::Cancel { op_id: 2 },
1533                Operation::Cancel { op_id: 2 },
1534                Operation::Cancel { op_id: 1 },
1535            ])
1536            .unwrap();
1537        harness.wait(1);
1538        let cancelled = row(&executor.snapshot(), "get");
1539        assert_eq!(value(&cancelled.events, "cancelled_queued"), 1.0);
1540        assert_eq!(value(&cancelled.events, "cancelled_running"), 1.0);
1541        assert_eq!(value(&cancelled.events, "backend_calls"), 1.0);
1542        assert_eq!(value(&cancelled.operations, "running"), 1.0);
1543        release.set();
1544        let deadline = Instant::now() + Duration::from_secs(3);
1545        while value(&row(&executor.snapshot(), "get").operations, "completed") != 1.0 {
1546            assert!(Instant::now() < deadline);
1547            std::thread::sleep(Duration::from_millis(1));
1548        }
1549        let completed = row(&executor.snapshot(), "get");
1550        assert_eq!(value(&completed.bytes, "completed"), 8.0);
1551        // I/O success does not imply publication.
1552        assert_eq!(completed.success_bytes, 8.0);
1553        assert_eq!((completed.success_keys, completed.failed_keys), (1.0, 1.0));
1554        assert!(completed.queue_seconds >= 0.0 && completed.backend_seconds > 0.0);
1555        harness.wait(1);
1556        let drained = row(&executor.snapshot(), "get");
1557        assert!(drained.operations.iter().all(|(_, count)| *count == 0.0));
1558    }
1559
1560    #[test]
1561    fn an_open_backend_rejects_jobs_visibly_without_calling_it() {
1562        let harness = Harness::new(TestStore::new(), CONFIG);
1563        let executor = harness.executor();
1564        let shared = &executor.shared;
1565        shared.health.failure(shared.health.begin().unwrap());
1566        executor.submit(&[put(1, "rejected", 1)]).unwrap();
1567        assert_eq!(harness.wait(1)[0].success, [false]);
1568        let put_row = row(&executor.snapshot(), "put");
1569        assert_eq!(value(&put_row.events, "rejected"), 1.0);
1570        assert_eq!(value(&put_row.events, "backend_calls"), 0.0);
1571        assert_eq!((put_row.success_bytes, put_row.backend_seconds), (0.0, 0.0));
1572    }
1573
1574    #[test]
1575    fn pressure_tracks_the_queue_and_cancelled_native_work_until_it_retires() {
1576        let now = Arc::new(Mutex::new(1.0));
1577        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1578        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1579        let store = TestStore {
1580            get: Some(Box::new(move |keys, _, _| {
1581                s.set();
1582                assert!(r.wait(Duration::from_secs(5)));
1583                Ok(IoResult {
1584                    success: vec![true; keys.len()],
1585                    retired: true,
1586                    object_failures: vec![false; keys.len()],
1587                })
1588            })),
1589            ..TestStore::new()
1590        };
1591        let harness = Harness::start(
1592            store,
1593            ExecutorConfig {
1594                read_workers: 1,
1595                ..CONFIG
1596            },
1597            layout(1, 0, 0),
1598            fake_clock(&now),
1599        );
1600        let executor = harness.executor();
1601        executor.submit(&[get(1, "active", 1)]).unwrap();
1602        assert!(started.wait(Duration::from_secs(1)));
1603        executor.submit(&[get(2, "queued", 2)]).unwrap();
1604        *now.lock().unwrap() = 1.75;
1605        let poll = executor.poll();
1606        assert!(poll.completions.is_empty());
1607        assert_eq!(
1608            poll.pressure,
1609            StoragePressure {
1610                queued_operations: 1,
1611                queued_bytes: 8,
1612                oldest_operation_us: 750_000
1613            }
1614        );
1615        executor.submit(&[Operation::Cancel { op_id: 2 }]).unwrap();
1616        let poll = executor.poll();
1617        assert_eq!(
1618            poll.completions.iter().map(|c| c.op_id).collect::<Vec<_>>(),
1619            [2]
1620        );
1621        assert_eq!(
1622            poll.pressure,
1623            StoragePressure {
1624                queued_operations: 0,
1625                queued_bytes: 0,
1626                oldest_operation_us: 750_000
1627            }
1628        );
1629        // Cancelling running native work does not retire it.
1630        executor.submit(&[Operation::Cancel { op_id: 1 }]).unwrap();
1631        assert_eq!(executor.poll().pressure.oldest_operation_us, 750_000);
1632        assert_eq!(executor.snapshot().health_state, "healthy");
1633        release.set();
1634        assert!(harness.wait(1)[0].retired);
1635        assert_eq!(executor.poll().pressure, StoragePressure::default());
1636        assert!(executor.is_idle());
1637    }
1638
1639    #[test]
1640    fn query_pressure_counts_metadata_jobs_without_payload_bytes() {
1641        let now = Arc::new(Mutex::new(1.0));
1642        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1643        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1644        let store = TestStore {
1645            exists: Some(Box::new(move |keys| {
1646                s.set();
1647                assert!(r.wait(Duration::from_secs(5)));
1648                Ok(vec![false; keys.len()])
1649            })),
1650            ..TestStore::new()
1651        };
1652        let harness = Harness::start(
1653            store,
1654            ExecutorConfig {
1655                query_workers: 1,
1656                ..CONFIG
1657            },
1658            layout(1, 0, 0),
1659            fake_clock(&now),
1660        );
1661        let executor = harness.executor();
1662        executor.submit(&[query(1, &["a"])]).unwrap();
1663        assert!(started.wait(Duration::from_secs(1)));
1664        executor.submit(&[query(2, &["b"])]).unwrap();
1665        *now.lock().unwrap() = 2.0;
1666        assert_eq!(
1667            executor.poll().pressure,
1668            StoragePressure {
1669                queued_operations: 1,
1670                queued_bytes: 0,
1671                oldest_operation_us: 1_000_000
1672            }
1673        );
1674        release.set();
1675        assert!(harness.wait(2).iter().all(|c| c.retired));
1676        assert_eq!(executor.poll().pressure.oldest_operation_us, 0);
1677    }
1678
1679    #[test]
1680    fn a_rejected_or_failed_get_reports_no_object_failure() {
1681        // Through the open backend, a full queue and a failing store.
1682        for failure in ["health", "queue", "error"] {
1683            let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1684            let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1685            let blocking = failure == "queue";
1686            let store = TestStore {
1687                get: (failure != "health").then(|| {
1688                    Box::new(move |keys: &[String], _: HostBuffer, _: &[Range]| {
1689                        if blocking && keys[0].contains("_blocker") {
1690                            s.set();
1691                            assert!(r.wait(Duration::from_secs(3)));
1692                        }
1693                        if blocking {
1694                            Ok(IoResult {
1695                                success: vec![true; keys.len()],
1696                                retired: true,
1697                                object_failures: vec![false; keys.len()],
1698                            })
1699                        } else {
1700                            Err(StoreError("unavailable".to_owned()))
1701                        }
1702                    }) as Box<IoFn>
1703                }),
1704                ..TestStore::new()
1705            };
1706            let harness = Harness::new(
1707                store,
1708                ExecutorConfig {
1709                    max_operations: 2,
1710                    read_workers: 1,
1711                    ..CONFIG
1712                },
1713            );
1714            let executor = harness.executor();
1715            let target = match failure {
1716                "health" => {
1717                    let shared = &executor.shared;
1718                    shared.health.failure(shared.health.begin().unwrap());
1719                    1
1720                }
1721                "queue" => {
1722                    // A stale entry of a cancelled job keeps its queue slot
1723                    // until a worker passes it.
1724                    executor.submit(&[get(1, "blocker", 1)]).unwrap();
1725                    assert!(started.wait(Duration::from_secs(1)));
1726                    executor
1727                        .submit(&[get(2, "stale", 2), Operation::Cancel { op_id: 2 }])
1728                        .unwrap();
1729                    harness.wait(1);
1730                    executor
1731                        .submit(&[get(3, "stale", 3), Operation::Cancel { op_id: 3 }])
1732                        .unwrap();
1733                    harness.wait(1);
1734                    4
1735                }
1736                _ => 1,
1737            };
1738            executor.submit(&[get(target, "present", 4)]).unwrap();
1739            let result = &harness.wait(1)[0];
1740            assert_eq!(result.op_id, target);
1741            assert!(result.retired);
1742            assert_eq!(result.success, [false]);
1743            assert_eq!(result.object_failures, [false]);
1744            if failure != "error" {
1745                let get_row = row(&executor.snapshot(), "get");
1746                let calls = if failure == "queue" { 1.0 } else { 0.0 };
1747                assert_eq!(value(&get_row.events, "backend_calls"), calls);
1748            }
1749            release.set();
1750        }
1751    }
1752
1753    #[test]
1754    fn malformed_facts_cannot_publish() {
1755        for (success, object_failures) in [
1756            (vec![true], vec![]),
1757            (vec![true], vec![true]),
1758            (vec![], vec![false]),
1759        ] {
1760            let store = TestStore {
1761                get: Some(Box::new(move |_, _, _| {
1762                    Ok(IoResult {
1763                        success: success.clone(),
1764                        retired: true,
1765                        object_failures: object_failures.clone(),
1766                    })
1767                })),
1768                ..TestStore::new()
1769            };
1770            let harness = Harness::new(store, CONFIG);
1771            harness.executor().submit(&[get(1, "a", 1)]).unwrap();
1772            let result = &harness.wait(1)[0];
1773            assert_eq!(result.success, [false]);
1774            assert_eq!(result.object_failures, [false]);
1775            assert_eq!(harness.executor().snapshot().health_state, "open");
1776        }
1777    }
1778
1779    #[test]
1780    fn a_put_never_reports_object_failures() {
1781        let store = TestStore {
1782            get: None,
1783            ..TestStore::new()
1784        };
1785        let harness = Harness::new(store, CONFIG);
1786        harness.executor().submit(&[put(1, "a", 1)]).unwrap();
1787        let result = &harness.wait(1)[0];
1788        assert_eq!(
1789            (result.success.as_slice(), result.retired),
1790            (&[true][..], true)
1791        );
1792        assert_eq!(result.object_failures, [false]);
1793    }
1794
1795    #[test]
1796    fn close_waits_for_native_retirement_then_closes_the_store_once() {
1797        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1798        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1799        let memory = Arc::new(MemoryStore::new());
1800        let reader = Arc::clone(&memory);
1801        let store = TestStore {
1802            memory: Arc::clone(&memory),
1803            get: Some(Box::new(move |keys, host, ranges| {
1804                s.set();
1805                assert!(r.wait(Duration::from_secs(3)));
1806                reader.batch_get_into(keys, host, ranges)
1807            })),
1808            ..TestStore::new()
1809        };
1810        let closes = Arc::clone(&store.closes);
1811        let mut harness = Harness::new(store, CONFIG);
1812        let executor = harness.executor();
1813        let object = keys::object_key(&executor.key_prefix(), "a", 0, 0, 0);
1814        let mut source = b"abcdefgh".to_vec();
1815        memory
1816            .batch_put_from(
1817                &[object],
1818                HostBuffer {
1819                    base: source.as_mut_ptr() as usize,
1820                    len: 8,
1821                },
1822                &[Range { offset: 0, size: 8 }],
1823            )
1824            .unwrap();
1825        executor.submit(&[get(1, "a", 1)]).unwrap();
1826        assert!(started.wait(Duration::from_secs(1)));
1827        // The poll does not wait for the store.
1828        assert!(executor.poll().completions.is_empty());
1829        release.set();
1830        assert_eq!(harness.wait(1)[0].success, [true]);
1831        assert_eq!(&harness.pool[..8], b"abcdefgh");
1832        harness.close(Duration::from_secs(2)).unwrap();
1833        harness.close(Duration::from_secs(2)).unwrap();
1834        assert_eq!(closes.load(Ordering::SeqCst), 1);
1835    }
1836
1837    #[test]
1838    fn a_close_timeout_keeps_the_store_and_the_pool() {
1839        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1840        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1841        let store = TestStore {
1842            get: Some(Box::new(move |keys, _, _| {
1843                s.set();
1844                assert!(r.wait(Duration::from_secs(3)));
1845                Ok(IoResult {
1846                    success: vec![true; keys.len()],
1847                    retired: true,
1848                    object_failures: vec![false; keys.len()],
1849                })
1850            })),
1851            ..TestStore::new()
1852        };
1853        let closes = Arc::clone(&store.closes);
1854        let mut harness = Harness::new(store, CONFIG);
1855        harness.executor().submit(&[get(1, "a", 1)]).unwrap();
1856        assert!(started.wait(Duration::from_secs(1)));
1857        assert!(matches!(
1858            harness.close(Duration::ZERO),
1859            Err(ExecutorError::Unretired)
1860        ));
1861        assert_eq!(closes.load(Ordering::SeqCst), 0);
1862        // The worker still writes the pool after the close.
1863        std::mem::forget(std::mem::take(&mut harness.pool));
1864        release.set();
1865    }
1866
1867    #[test]
1868    fn a_namespace_changes_only_while_idle() {
1869        let (started, release) = (Arc::new(Event::default()), Arc::new(Event::default()));
1870        let (s, r) = (Arc::clone(&started), Arc::clone(&release));
1871        let store = TestStore {
1872            exists: Some(Box::new(move |keys| {
1873                s.set();
1874                assert!(r.wait(Duration::from_secs(3)));
1875                Ok(vec![false; keys.len()])
1876            })),
1877            ..TestStore::new()
1878        };
1879        let harness = Harness::new(store, CONFIG);
1880        let executor = harness.executor();
1881        let first = executor.key_prefix();
1882        assert_eq!(first, keys::key_prefix("deployment", "v1", ""));
1883        executor.submit(&[query(1, &["a"])]).unwrap();
1884        assert!(started.wait(Duration::from_secs(1)));
1885        assert!(matches!(
1886            executor.set_weight_version("v2"),
1887            Err(ExecutorError::Busy)
1888        ));
1889        assert!(!executor.delete_namespace());
1890        release.set();
1891        harness.wait(1);
1892        executor.set_weight_version("v2").unwrap();
1893        assert_eq!(
1894            executor.key_prefix(),
1895            keys::key_prefix("deployment", "v2", "")
1896        );
1897        assert!(executor.delete_namespace());
1898    }
1899
1900    #[test]
1901    fn clearing_the_namespace_deletes_its_objects_and_keeps_its_name() {
1902        let harness = Harness::new(TestStore::new(), CONFIG);
1903        let executor = harness.executor();
1904        executor.submit(&[put(1, "h", 1)]).unwrap();
1905        assert_eq!(harness.wait(1)[0].success, [true]);
1906        let prefix = executor.key_prefix();
1907        assert!(executor.delete_namespace());
1908        assert_eq!(executor.key_prefix(), prefix);
1909        executor.submit(&[query(2, &["h"])]).unwrap();
1910        assert_eq!(harness.wait(1)[0].success, [false]);
1911    }
1912
1913    #[test]
1914    fn a_failed_clear_keeps_the_namespace_and_its_objects() {
1915        let store = TestStore {
1916            remove_fails: true,
1917            ..TestStore::new()
1918        };
1919        let harness = Harness::new(store, CONFIG);
1920        let executor = harness.executor();
1921        executor.submit(&[put(1, "h", 1)]).unwrap();
1922        assert_eq!(harness.wait(1)[0].success, [true]);
1923        let prefix = executor.key_prefix();
1924        assert!(!executor.delete_namespace());
1925        assert_eq!(executor.key_prefix(), prefix);
1926        executor.submit(&[query(2, &["h"])]).unwrap();
1927        assert_eq!(harness.wait(1)[0].success, [true]);
1928    }
1929
1930    #[test]
1931    fn a_new_weight_version_publishes_under_a_new_namespace() {
1932        let store = TestStore::new();
1933        let memory = Arc::clone(&store.memory);
1934        let harness = Harness::new(store, CONFIG);
1935        let executor = harness.executor();
1936        executor.submit(&[put(1, "h", 1)]).unwrap();
1937        assert_eq!(harness.wait(1)[0].success, [true]);
1938        let old = keys::object_key(&executor.key_prefix(), "h", 0, 0, 0);
1939        executor.set_weight_version("v2").unwrap();
1940        // The new checkpoint's pages are not the old one's.
1941        executor.submit(&[query(2, &["h"])]).unwrap();
1942        assert_eq!(harness.wait(1)[0].success, [false]);
1943        executor.submit(&[put(3, "h", 1)]).unwrap();
1944        assert_eq!(harness.wait(1)[0].success, [true]);
1945        let new = keys::object_key(&executor.key_prefix(), "h", 0, 0, 0);
1946        assert_ne!(new, old);
1947        assert_eq!(memory.batch_exists(&[old, new]).unwrap(), [true, true]);
1948    }
1949
1950    #[test]
1951    fn a_rejected_put_retires_and_keeps_the_backend_in_service() {
1952        let store = TestStore::new();
1953        let memory = Arc::clone(&store.memory);
1954        let pressure = keys::object_key(
1955            &keys::key_prefix("deployment", "v1", ""),
1956            "pressure",
1957            0,
1958            0,
1959            0,
1960        );
1961        let rejected = pressure.clone();
1962        let store = TestStore {
1963            // A full store rejects `pressure`'s allocation before reading
1964            // its bytes.
1965            put: Some(Box::new(move |keys, host, ranges| {
1966                let mut success = Vec::new();
1967                for (key, range) in keys.iter().zip(ranges) {
1968                    success.push(
1969                        *key != rejected
1970                            && memory
1971                                .batch_put_from(
1972                                    std::slice::from_ref(key),
1973                                    host,
1974                                    std::slice::from_ref(range),
1975                                )?
1976                                .success[0],
1977                    );
1978                }
1979                Ok(IoResult {
1980                    object_failures: vec![false; keys.len()],
1981                    success,
1982                    retired: true,
1983                })
1984            })),
1985            ..store
1986        };
1987        let memory = Arc::clone(&store.memory);
1988        let harness = Harness::new(store, CONFIG);
1989        let executor = harness.executor();
1990        executor
1991            .submit(&[Operation::Put {
1992                op_id: 1,
1993                keys: vec![key("pressure"), key("saved")],
1994                host_blocks: vec![1, 2],
1995            }])
1996            .unwrap();
1997        let done = harness.wait(1).remove(0);
1998        assert_eq!(done.success, [false, true]);
1999        assert!(done.retired);
2000        assert_eq!(memory.batch_exists(&[pressure]).unwrap(), [false]);
2001        // Neither a quarantine nor an open circuit: the scheduler offers
2002        // the page again later.
2003        let snapshot = executor.snapshot();
2004        assert_eq!(snapshot.health_state, "healthy");
2005        assert!(!snapshot.unsafe_io);
2006        executor.submit(&[put(2, "retry", 3)]).unwrap();
2007        assert_eq!(harness.wait(1)[0].success, [true]);
2008    }
2009
2010    #[test]
2011    fn page_keys_follow_the_shard_and_state_keys_follow_the_rank() {
2012        let store = Arc::new(MemoryStore::new());
2013        let executors: Vec<Harness> = [0, 1, 2]
2014            .into_iter()
2015            .map(|rank| {
2016                Harness::start(
2017                    TestStore {
2018                        memory: Arc::clone(&store),
2019                        ..TestStore::new()
2020                    },
2021                    CONFIG,
2022                    layout(2, rank % 2, rank),
2023                    monotonic_clock(),
2024                )
2025            })
2026            .collect();
2027        let keys_of = |harness: &Harness, group: u32| {
2028            harness
2029                .executor()
2030                .object_keys(&[StorageKey {
2031                    group,
2032                    content_hash: "h".to_owned(),
2033                    page_offset: 0,
2034                }])
2035                .unwrap()
2036        };
2037        let (a, b, other) = (&executors[0], &executors[1], &executors[2]);
2038        assert_eq!(keys_of(a, 0), keys_of(b, 0));
2039        assert_ne!(keys_of(a, 0), keys_of(other, 0));
2040        assert_ne!(keys_of(a, 1), keys_of(b, 1));
2041    }
2042
2043    #[test]
2044    fn a_sharded_block_moves_to_its_new_owner_through_the_store() {
2045        let store = Arc::new(MemoryStore::new());
2046        let harnesses = |base: &str| -> Vec<Harness> {
2047            let _ = base;
2048            (0..2)
2049                .map(|rank| {
2050                    Harness::start(
2051                        TestStore {
2052                            memory: Arc::clone(&store),
2053                            ..TestStore::new()
2054                        },
2055                        CONFIG,
2056                        layout(2, rank, rank),
2057                        monotonic_clock(),
2058                    )
2059                })
2060                .collect()
2061        };
2062        let writers = harnesses("writer");
2063        // Block 2 of group 0 is rank 1's local block 1: the pool's first 8
2064        // bytes.
2065        let pool = writers[1].pool.as_ptr() as *mut u8;
2066        // SAFETY: the pool is live and no job runs yet.
2067        #[allow(unsafe_code)]
2068        unsafe {
2069            std::ptr::copy_nonoverlapping(b"KV01KV01".as_ptr(), pool, 8);
2070        }
2071        for writer in &writers {
2072            writer.executor().submit(&[put(1, "h", 2)]).unwrap();
2073            let fact = &writer.wait(1)[0];
2074            assert!(fact.success == [true] && fact.retired);
2075        }
2076        assert_eq!(store.keys().len(), 1);
2077        // Block 3 belongs to rank 0 as its local block 2, at the second LCM
2078        // block.
2079        let readers = harnesses("reader");
2080        for reader in &readers {
2081            reader.executor().submit(&[get(1, "h", 3)]).unwrap();
2082            let fact = &reader.wait(1)[0];
2083            assert!(fact.success == [true] && fact.retired);
2084            assert_eq!(fact.object_failures, [false]);
2085        }
2086        assert_eq!(&readers[0].pool[24..32], b"KV01KV01");
2087        assert!(readers[1].pool.iter().all(|&byte| byte == 0));
2088    }
2089
2090    #[test]
2091    fn a_non_owner_is_neutral_and_moves_no_bytes() {
2092        let store = TestStore::new();
2093        let calls = Arc::clone(&store.calls);
2094        let harness = Harness::start(store, CONFIG, layout(2, 1, 1), monotonic_clock());
2095        // Block 3 belongs to rank 0.
2096        harness.executor().submit(&[get(1, "h", 3)]).unwrap();
2097        let fact = &harness.wait(1)[0];
2098        assert!(fact.success == [true] && fact.retired && fact.object_failures == [false]);
2099        assert!(calls.lock().unwrap().is_empty());
2100        let get_row = row(&harness.executor().snapshot(), "get");
2101        assert_eq!((get_row.success_keys, get_row.success_bytes), (0.0, 0.0));
2102    }
2103
2104    #[test]
2105    fn a_job_without_native_work_cannot_recover_an_open_backend() {
2106        let store = TestStore {
2107            exists: Some(Box::new(|_| {
2108                Err(StoreError("metadata unavailable".to_owned()))
2109            })),
2110            ..TestStore::new()
2111        };
2112        let calls = Arc::clone(&store.calls);
2113        let harness = Harness::start(store, CONFIG, layout(2, 1, 1), monotonic_clock());
2114        let executor = harness.executor();
2115        executor.submit(&[query(1, &["h"])]).unwrap();
2116        assert_eq!(harness.wait(1)[0].success, [false]);
2117        let before = executor.snapshot();
2118        assert_ne!(before.health_state, "healthy");
2119        executor.submit(&[get(2, "h", 3)]).unwrap();
2120        assert_eq!(harness.wait(1)[0].success, [true]);
2121        let after = executor.snapshot();
2122        assert_eq!(
2123            (after.health_state, after.health_epoch),
2124            (before.health_state, before.health_epoch)
2125        );
2126        assert!(calls.lock().unwrap().is_empty());
2127    }
2128
2129    #[test]
2130    fn mixed_owner_results_keep_their_logical_order() {
2131        let store = TestStore::new();
2132        let memory = Arc::clone(&store.memory);
2133        let calls = Arc::clone(&store.calls);
2134        let harness = Harness::start(store, CONFIG, layout(2, 0, 0), monotonic_clock());
2135        let executor = harness.executor();
2136        let keys = [
2137            StorageKey {
2138                group: 0,
2139                content_hash: "absent-peer".to_owned(),
2140                page_offset: 0,
2141            },
2142            StorageKey {
2143                group: 0,
2144                content_hash: "absent-local".to_owned(),
2145                page_offset: 0,
2146            },
2147            StorageKey {
2148                group: 1,
2149                content_hash: "state".to_owned(),
2150                page_offset: 0,
2151            },
2152        ];
2153        let names = executor.object_keys(&keys).unwrap();
2154        let mut payload = vec![7_u8; 16];
2155        memory
2156            .batch_put_from(
2157                &names[2..],
2158                HostBuffer {
2159                    base: payload.as_mut_ptr() as usize,
2160                    len: 16,
2161                },
2162                &[Range {
2163                    offset: 0,
2164                    size: 16,
2165                }],
2166            )
2167            .unwrap();
2168        // Block 2 of the sharded group is rank 1's; block 3 is rank 0's.
2169        executor
2170            .submit(&[Operation::Get {
2171                op_id: 1,
2172                keys: keys.to_vec(),
2173                host_blocks: vec![2, 3, 1],
2174            }])
2175            .unwrap();
2176        let fact = &harness.wait(1)[0];
2177        assert_eq!(fact.success, [true, false, true]);
2178        assert_eq!(fact.object_failures, [false, true, false]);
2179        assert!(fact.retired);
2180        let get_row = row(&executor.snapshot(), "get");
2181        assert_eq!(
2182            (
2183                get_row.success_keys,
2184                get_row.failed_keys,
2185                get_row.success_bytes
2186            ),
2187            (1.0, 1.0, 16.0)
2188        );
2189        assert_eq!(calls.lock().unwrap()[0].1, names[1..].to_vec());
2190    }
2191
2192    #[test]
2193    fn a_failing_store_and_its_cooldown_keep_non_owners_neutral() {
2194        let store = TestStore {
2195            get: Some(Box::new(|_, _, _| {
2196                Err(StoreError("read backend unavailable".to_owned()))
2197            })),
2198            ..TestStore::new()
2199        };
2200        let calls = Arc::clone(&store.calls);
2201        let harness = Harness::start(
2202            store,
2203            ExecutorConfig {
2204                recovery_interval: Duration::from_secs(60),
2205                ..CONFIG
2206            },
2207            layout(2, 0, 0),
2208            monotonic_clock(),
2209        );
2210        let executor = harness.executor();
2211        let keys = vec![key("peer"), key("local")];
2212        for op_id in [1, 2] {
2213            executor
2214                .submit(&[Operation::Get {
2215                    op_id,
2216                    keys: keys.clone(),
2217                    host_blocks: vec![2, 3],
2218                }])
2219                .unwrap();
2220            let fact = &harness.wait(1)[0];
2221            assert_eq!(fact.success, [true, false]);
2222            assert!(fact.retired && fact.object_failures == [false, false]);
2223        }
2224        assert_eq!(calls.lock().unwrap().len(), 1);
2225        let get_row = row(&executor.snapshot(), "get");
2226        assert_eq!(
2227            (
2228                get_row.failed_keys,
2229                get_row.success_keys,
2230                get_row.success_bytes
2231            ),
2232            (2.0, 0.0, 0.0)
2233        );
2234    }
2235}