Coverage Report

Created: 2026-11-13 23:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-sim/src/world.rs
Line
Count
Source
1
// Copyright 2026 Trace Machina, Inc. 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
//! The production components of one run, wired in-process: the
16
//! `SimpleScheduler` on the chosen backend, the `ExecutionServer` and the
17
//! `WorkerApiServer` in front of it, `LocalWorker`s over the in-process
18
//! network, and client tasks that drive `Execute` the way Bazel does.
19
20
use core::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering};
21
use core::time::Duration;
22
use std::collections::HashMap;
23
use std::sync::Arc;
24
25
use futures::StreamExt;
26
use nativelink_config::cas_server::{
27
    EndpointConfig, ExecutionConfig, LocalWorkerConfig, WithInstanceName, WorkerApiConfig,
28
    WorkerProperty,
29
};
30
use nativelink_config::schedulers::SimpleSpec;
31
use nativelink_config::stores::{EvictionPolicy, MemorySpec, RedisSpec, StoreSpec};
32
use nativelink_error::{Code, Error, ResultExt};
33
use nativelink_proto::build::bazel::remote::execution::v2::execution_server::Execution;
34
use nativelink_proto::build::bazel::remote::execution::v2::{
35
    Action as ActionProto, Command, Directory, ExecuteOperationMetadata, ExecuteRequest,
36
    ExecuteResponse, ExecutionPolicy, Platform, WaitExecutionRequest, digest_function, platform,
37
};
38
use nativelink_proto::google::longrunning::{Operation, operation};
39
use nativelink_redis_tester::FakeRedisBackend;
40
use nativelink_scheduler::known_platform_property_provider::KnownPlatformPropertyProvider;
41
use nativelink_scheduler::memory_awaited_action_db::{MemoryAwaitedActionDb, OperationIdFn};
42
use nativelink_scheduler::simple_scheduler::SimpleScheduler;
43
use nativelink_scheduler::store_awaited_action_db::StoreAwaitedActionDb;
44
use nativelink_scheduler::worker_scheduler::WorkerScheduler;
45
use nativelink_service::execution_server::ExecutionServer;
46
use nativelink_service::worker_api_server::{NowFn, WorkerApiServer};
47
use nativelink_store::ac_utils::serialize_and_upload_message;
48
use nativelink_store::default_store_factory::store_factory;
49
use nativelink_store::redis_store::{RedisStore, RedisSubscriptionManager};
50
use nativelink_store::store_manager::StoreManager;
51
use nativelink_util::action_messages::OperationId;
52
use nativelink_util::background_spawn;
53
use nativelink_util::common::DigestInfo;
54
use nativelink_util::digest_hasher::DigestHasherFunc;
55
use nativelink_util::operation_state_manager::{
56
    ClientStateManager, OperationFilter, OperationStageFlags, OrderDirection,
57
};
58
use nativelink_util::shutdown_guard::ShutdownGuard;
59
use nativelink_util::store_trait::{SchedulerStore, Store, StoreLike};
60
use nativelink_worker::local_worker::LocalWorker;
61
use prost::Message as _;
62
use tokio::sync::{Notify, broadcast, watch};
63
use tokio::task::JoinHandle;
64
use tonic::{Code as TonicCode, Request};
65
66
use crate::actions::{Action, Registry, SimActionsManager};
67
use crate::clock::{SimClock, SimInstant};
68
use crate::net::{InProcessWorkerClient, Link, Net};
69
use crate::trace::{Event, Outcome, Stage, Trace};
70
71
const INSTANCE_NAME: &str = "sim";
72
const SCHEDULER_NAME: &str = "sim";
73
const CAS_NAME: &str = "cas";
74
75
/// Which `AwaitedActionDb` the scheduler runs on.
76
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
77
pub enum Backend {
78
    Memory,
79
    /// `StoreAwaitedActionDb` over a `RedisStore` on the fake Redis.
80
    FakeRedis,
81
}
82
83
/// One kind of simulated worker.
84
#[derive(Clone, Debug)]
85
pub struct WorkerSpec {
86
    pub(crate) max_inflight: u64,
87
    pub(crate) properties: Vec<(String, String)>,
88
    /// The worker's endpoint timeout; it keeps alive every half of it.
89
    pub(crate) keepalive_timeout_s: f32,
90
}
91
92
impl Default for WorkerSpec {
93
130
    fn default() -> Self {
94
130
        Self {
95
130
            max_inflight: 1,
96
130
            properties: Vec::new(),
97
130
            keepalive_timeout_s: 5.0,
98
130
        }
99
130
    }
100
}
101
102
impl WorkerSpec {
103
    #[must_use]
104
86
    pub const fn max_inflight(mut self, max_inflight: u64) -> Self {
105
86
        self.max_inflight = max_inflight;
106
86
        self
107
86
    }
108
109
    #[must_use]
110
2
    pub fn property(mut self, name: &str, value: &str) -> Self {
111
2
        self.properties.push((name.to_string(), value.to_string()));
112
2
        self
113
2
    }
114
115
    #[must_use]
116
0
    pub const fn keepalive_timeout_s(mut self, seconds: f32) -> Self {
117
0
        self.keepalive_timeout_s = seconds;
118
0
        self
119
0
    }
120
}
121
122
const CLIENT_RUNNING: u8 = 0;
123
const CLIENT_DONE: u8 = 1;
124
const CLIENT_GONE: u8 = 2;
125
126
struct Client {
127
    state: Arc<AtomicU8>,
128
    gone: watch::Sender<bool>,
129
    action: Action,
130
}
131
132
struct Worker {
133
    task: JoinHandle<Result<(), Error>>,
134
    manager: Arc<SimActionsManager>,
135
    spec: WorkerSpec,
136
    // Held so the worker's shutdown channel stays open.
137
    _shutdown: broadcast::Sender<ShutdownGuard>,
138
}
139
140
struct Scheduler {
141
    client: Arc<SimpleScheduler>,
142
    /// The fake Redis listener this incarnation reached the store through.
143
    redis_port: Option<u16>,
144
    // Held so the worker side lives as long as the servers in `Net` do.
145
    _worker: Arc<dyn WorkerScheduler>,
146
}
147
148
/// Everything one run of a scenario on one backend owns.
149
pub struct World {
150
    backend: Backend,
151
    spec: SimpleSpec,
152
    net: Arc<Net>,
153
    trace: Trace,
154
    registry: Arc<Registry>,
155
    store_manager: Arc<StoreManager>,
156
    cas: Store,
157
    redis: Option<FakeRedisBackend<RedisSubscriptionManager>>,
158
    scheduler: Option<Scheduler>,
159
    workers: Vec<Worker>,
160
    clients: HashMap<String, Client>,
161
    next_worker_id: Arc<AtomicU64>,
162
    next_operation_id: Arc<AtomicU64>,
163
    /// Matching passes, and the most store commands between two of them.
164
    passes: Arc<AtomicU64>,
165
    max_commands_per_pass: Arc<AtomicU64>,
166
    /// Passes before this time are not measured.
167
    measure_from_ms: Arc<AtomicU64>,
168
    seed: u64,
169
}
170
171
impl core::fmt::Debug for World {
172
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
173
0
        f.debug_struct("World")
174
0
            .field("backend", &self.backend)
175
0
            .finish_non_exhaustive()
176
0
    }
177
}
178
179
impl World {
180
182
    pub(crate) async fn new(
181
182
        backend: Backend,
182
182
        spec: SimpleSpec,
183
182
        seed: u64,
184
182
        trace: Trace,
185
182
    ) -> Result<Self, Error> {
186
182
        let registry = Arc::new(Registry::default());
187
182
        let net = Net::new(registry.clone(), trace.clone());
188
182
        let store_manager = Arc::new(StoreManager::new());
189
182
        store_manager.add_store(
190
182
            CAS_NAME,
191
182
            store_factory(
192
182
                &StoreSpec::Memory(MemorySpec::default()),
193
182
                &store_manager,
194
182
                None,
195
182
            )
196
182
            .await
?0
,
197
0
        )?;
198
182
        let cas = store_manager
199
182
            .get_store(CAS_NAME)
200
182
            .err_tip(|| "The CAS was just added")
?0
;
201
182
        let redis = match backend {
202
114
            Backend::Memory => None,
203
68
            Backend::FakeRedis => Some(FakeRedisBackend::new()),
204
        };
205
182
        let mut world = Self {
206
182
            backend,
207
182
            spec,
208
182
            net,
209
182
            trace,
210
182
            registry,
211
182
            store_manager,
212
182
            cas,
213
182
            redis,
214
182
            scheduler: None,
215
182
            workers: Vec::new(),
216
182
            clients: HashMap::new(),
217
182
            next_worker_id: Arc::new(AtomicU64::new(1)),
218
182
            next_operation_id: Arc::new(AtomicU64::new(1)),
219
182
            passes: Arc::new(AtomicU64::new(0)),
220
182
            max_commands_per_pass: Arc::new(AtomicU64::new(0)),
221
182
            measure_from_ms: Arc::new(AtomicU64::new(0)),
222
182
            seed,
223
182
        };
224
182
        world.scheduler_up().await
?0
;
225
182
        Ok(world)
226
182
    }
227
228
0
    pub const fn backend(&self) -> Backend {
229
0
        self.backend
230
0
    }
231
232
182
    pub const fn spec(&self) -> &SimpleSpec {
233
182
        &self.spec
234
182
    }
235
236
190
    pub const fn trace(&self) -> &Trace {
237
190
        &self.trace
238
190
    }
239
240
188
    pub const fn redis(&self) -> Option<&FakeRedisBackend<RedisSubscriptionManager>> {
241
188
        self.redis.as_ref()
242
188
    }
243
244
182
    pub fn passes(&self) -> u64 {
245
182
        self.passes.load(Ordering::Acquire)
246
182
    }
247
248
182
    pub fn max_commands_per_pass(&self) -> u64 {
249
182
        self.max_commands_per_pass.load(Ordering::Acquire)
250
182
    }
251
252
    /// Measures store commands per pass only from `ms` on.
253
180
    pub(crate) fn measure_from(&self, ms: u64) {
254
180
        self.measure_from_ms.store(ms, Ordering::Release);
255
180
    }
256
257
182
    pub(crate) fn worker_specs(&self) -> Vec<(String, WorkerSpec)> {
258
182
        self.workers
259
182
            .iter()
260
182
            .enumerate()
261
1.19k
            .
map182
(|(i, w)| (worker_name(i), w.spec.clone()))
262
182
            .collect()
263
182
    }
264
265
    /// Builds the scheduler and its servers and starts serving.
266
187
    pub(crate) async fn scheduler_up(&mut self) -> Result<(), Error> {
267
187
        if self.scheduler.is_some() {
268
0
            return Ok(());
269
187
        }
270
187
        let notify = Arc::new(Notify::new());
271
187
        let passes = self.passes.clone();
272
187
        let max_commands = self.max_commands_per_pass.clone();
273
187
        let measure_from_ms = self.measure_from_ms.clone();
274
187
        let redis = self.redis.clone();
275
35.0k
        let 
on_pass187
= move || {
276
35.0k
            passes.fetch_add(1, Ordering::AcqRel);
277
35.0k
            if let Some(
redis5.54k
) = &redis {
278
5.54k
                let commands: u64 = redis.command_counts().values().sum();
279
5.54k
                redis.reset_command_counts();
280
5.54k
                if SimClock::now_ms() >= measure_from_ms.load(Ordering::Acquire) {
281
3.10k
                    max_commands.fetch_max(commands, Ordering::AcqRel);
282
3.10k
                
}2.44k
283
29.5k
            }
284
35.0k
            tokio::time::sleep(Duration::from_millis(1))
285
35.0k
        };
286
187
        let mut redis_port = None;
287
187
        let (client, worker) = match self.backend {
288
116
            Backend::Memory => SimpleScheduler::new_with_fleet_exchange_interval(
289
116
                &self.spec,
290
116
                MemoryAwaitedActionDb::new_with_operation_id_fn(
291
116
                    &EvictionPolicy::default(),
292
116
                    notify.clone(),
293
                    SimInstant::now,
294
116
                    OperationIdFn::new(operation_ids(self.next_operation_id.clone())),
295
                ),
296
116
                on_pass,
297
116
                notify,
298
                SimInstant::now,
299
116
                None,
300
116
                None,
301
116
                Duration::from_secs(5),
302
            ),
303
            Backend::FakeRedis => {
304
71
                let fake = self
305
71
                    .redis
306
71
                    .clone()
307
71
                    .err_tip(|| "FakeRedis backend without a fake")
?0
;
308
                // A listener per incarnation, so stopping this scheduler can
309
                // take its connections with it.
310
71
                let port = fake.clone().run().await;
311
71
                redis_port = Some(port);
312
71
                let store = RedisStore::new_standard(RedisSpec {
313
71
                    addresses: vec![format!("redis://127.0.0.1:{port}")],
314
71
                    experimental_pub_sub_channel: Some("sim_channel".to_string()),
315
71
                    ..Default::default()
316
71
                })
317
71
                .await
?0
;
318
71
                fake.set_subscription_manager(store.subscription_manager().await
?0
);
319
71
                let db = StoreAwaitedActionDb::new(
320
71
                    store,
321
71
                    notify.clone(),
322
71
                    SimInstant::now,
323
71
                    operation_ids(self.next_operation_id.clone()),
324
71
                    self.spec.retain_completed_for_s,
325
71
                    self.spec.client_action_timeout_s,
326
71
                    false,
327
71
                )
328
71
                .await
?0
;
329
71
                SimpleScheduler::new_with_fleet_exchange_interval(
330
71
                    &self.spec,
331
71
                    db,
332
71
                    on_pass,
333
71
                    notify,
334
                    SimInstant::now,
335
71
                    None,
336
71
                    None,
337
71
                    Duration::from_secs(5),
338
                )
339
            }
340
        };
341
342
187
        let mut providers: HashMap<String, Arc<dyn KnownPlatformPropertyProvider>> = HashMap::new();
343
187
        providers.insert(SCHEDULER_NAME.to_string(), client.clone());
344
187
        let execution = ExecutionServer::new(
345
187
            &[WithInstanceName {
346
187
                instance_name: INSTANCE_NAME.to_string(),
347
187
                config: ExecutionConfig {
348
187
                    cas_store: CAS_NAME.to_string(),
349
187
                    scheduler: SCHEDULER_NAME.to_string(),
350
187
                    experimental_buck2_invocation_isolation: false,
351
187
                    execute_stream_heartbeat_s: 0,
352
187
                },
353
187
            }],
354
187
            &providers,
355
187
            &self.store_manager,
356
0
        )?
357
187
        .with_now_fn(SimClock::now);
358
359
187
        let mut schedulers: HashMap<String, Arc<dyn WorkerScheduler>> = HashMap::new();
360
187
        schedulers.insert(SCHEDULER_NAME.to_string(), worker.clone());
361
187
        let config = WorkerApiConfig {
362
187
            scheduler: SCHEDULER_NAME.to_string(),
363
187
            disable_kill_revoked_operations: false,
364
187
            kill_revoked_operations_interval_s: 0,
365
187
        };
366
187
        let now_fn: Arc<NowFn> = Arc::new(Box::new(since_epoch));
367
187
        WorkerApiServer::spawn_timers(&config, &schedulers, &now_fn);
368
187
        let next_worker_id = self.next_worker_id.clone();
369
187
        let worker_api =
370
187
            WorkerApiServer::new_with_now_fn(&config, &schedulers, Box::new(since_epoch), [0; 6])
?0
371
1.98k
                .
with_worker_id_fn187
(
Box::new187
(move || {
372
1.98k
                    format!("{}", next_worker_id.fetch_add(1, Ordering::AcqRel))
373
1.98k
                }));
374
375
187
        self.net.serve(Arc::new(worker_api), execution);
376
187
        self.scheduler = Some(Scheduler {
377
187
            client,
378
187
            redis_port,
379
187
            _worker: worker,
380
187
        });
381
187
        self.trace.record(Event::SchedulerUp);
382
187
        Ok(())
383
187
    }
384
385
    /// Stops serving and drops this process's scheduler; every connection
386
    /// to it closes. The store, Redis or memory, is what survives.
387
    /// What a dead process leaves behind is only the store: its tasks may
388
    /// linger in this runtime, but they lose the servers and, on Redis,
389
    /// every connection to the store.
390
187
    pub(crate) fn scheduler_down(&mut self) {
391
187
        if let Some(scheduler) = self.scheduler.take() {
392
187
            self.net.unserve();
393
187
            if let (Some(
redis71
), Some(
port71
)) = (&self.redis, scheduler.redis_port) {
394
71
                redis.retire_listener(port);
395
116
            }
396
187
            self.trace.record(Event::SchedulerDown);
397
0
        }
398
187
    }
399
400
226
    pub(crate) fn add_workers(&mut self, count: usize, spec: &WorkerSpec) {
401
226
        for _ in 0..count {
402
1.19k
            let index = self.workers.len();
403
1.19k
            let name = worker_name(index);
404
1.19k
            let config = LocalWorkerConfig {
405
1.19k
                name: name.clone(),
406
1.19k
                max_inflight_tasks: spec.max_inflight,
407
1.19k
                platform_properties: spec
408
1.19k
                    .properties
409
1.19k
                    .iter()
410
1.19k
                    .map(|(k, v)| (
k6
.
clone6
(),
WorkerProperty::Values(6
vec!6
[v.clone()])))
411
1.19k
                    .collect(),
412
1.19k
                worker_api_endpoint: EndpointConfig {
413
1.19k
                    timeout: Some(spec.keepalive_timeout_s),
414
1.19k
                    ..Default::default()
415
1.19k
                },
416
1.19k
                ..Default::default()
417
            };
418
1.19k
            let manager = Arc::new(SimActionsManager::new(&name, self.net.clone()));
419
1.19k
            let net = self.net.clone();
420
1.19k
            let connect_name = name.clone();
421
            // A fixed sequence per worker, so backoffs repeat run to run.
422
1.19k
            let jitter = AtomicU32::new(
423
1.19k
                u32::try_from(self.seed.wrapping_add(index as u64 * 7919) % 1000).unwrap_or(0),
424
            );
425
1.19k
            let worker = LocalWorker::new_with_connection_factory_and_actions_manager(
426
1.19k
                Arc::new(config),
427
1.19k
                manager.clone(),
428
1.99k
                
Box::new1.19k
(move || {
429
1.99k
                    let client = InProcessWorkerClient::connect(&connect_name, &net);
430
1.99k
                    Box::pin(async move { client })
431
1.99k
                }),
432
1.19k
                Box::new(|duration| 
Box::pin807
(
tokio::time::sleep807
(
duration807
))),
433
            )
434
1.19k
            .with_jitter_fn(Box::new(move || 
jitter807
.
fetch_add807
(379,
Ordering::AcqRel807
)));
435
1.19k
            let (shutdown, shutdown_rx) = broadcast::channel(1);
436
1.19k
            let trace = self.trace.clone();
437
1.19k
            let stopped_name = name.clone();
438
1.19k
            let task = background_spawn!("sim_worker", async move {
439
1.19k
                let 
result0
= worker.run(shutdown_rx).await;
440
0
                trace.record(Event::WorkerStopped {
441
0
                    worker: stopped_name,
442
0
                });
443
0
                result
444
0
            });
445
1.19k
            self.trace.record(Event::WorkerStarted { worker: name });
446
1.19k
            self.workers.push(Worker {
447
1.19k
                task,
448
1.19k
                manager,
449
1.19k
                spec: spec.clone(),
450
1.19k
                _shutdown: shutdown,
451
1.19k
            });
452
        }
453
226
    }
454
455
    /// The worker process dies: its task is aborted and its connection
456
    /// closes.
457
71
    pub(crate) fn kill_worker(&self, index: usize) {
458
71
        if let Some(worker) = self.workers.get(index) {
459
71
            if worker.task.is_finished() {
460
4
                return;
461
67
            }
462
67
            worker.task.abort();
463
67
            self.net.cut(&worker_name(index));
464
67
            self.trace.record(Event::WorkerStopped {
465
67
                worker: worker_name(index),
466
67
            });
467
0
        }
468
71
    }
469
470
69
    pub(crate) fn cut_worker(&self, index: usize) {
471
69
        self.net.cut(&worker_name(index));
472
69
    }
473
474
112
    pub(crate) fn set_link(&self, index: usize, link: Link) {
475
112
        self.net.set_link(&worker_name(index), link);
476
112
    }
477
478
15.9k
    pub(crate) async fn execute(&mut self, op: &str, action: &Action) -> Result<(), Error> {
479
15.9k
        let action_digest = self.upload(action).await
?0
;
480
15.9k
        self.trace.record(Event::Submit {
481
15.9k
            op: op.to_string(),
482
15.9k
            action: action.label(),
483
15.9k
            priority: action.priority,
484
15.9k
        });
485
15.9k
        let request = ExecuteRequest {
486
15.9k
            instance_name: INSTANCE_NAME.to_string(),
487
15.9k
            skip_cache_lookup: !action.cacheable,
488
15.9k
            action_digest: Some(action_digest.into()),
489
15.9k
            execution_policy: Some(ExecutionPolicy {
490
15.9k
                priority: action.priority,
491
15.9k
            }),
492
15.9k
            results_cache_policy: None,
493
15.9k
            digest_function: digest_function::Value::Sha256.into(),
494
15.9k
            inline_stdout: false,
495
15.9k
            inline_stderr: false,
496
15.9k
            inline_output_files: Vec::new(),
497
15.9k
        };
498
15.9k
        let state = Arc::new(AtomicU8::new(CLIENT_RUNNING));
499
15.9k
        let (gone, gone_rx) = watch::channel(false);
500
15.9k
        drop(background_spawn!(
501
            "sim_client",
502
15.9k
            run_client(
503
15.9k
                self.net.clone(),
504
15.9k
                op.to_string(),
505
15.9k
                request,
506
15.9k
                state.clone(),
507
15.9k
                gone_rx
508
            )
509
        ));
510
15.9k
        self.clients.insert(
511
15.9k
            op.to_string(),
512
15.9k
            Client {
513
15.9k
                state,
514
15.9k
                gone,
515
15.9k
                action: action.clone(),
516
15.9k
            },
517
        );
518
15.9k
        Ok(())
519
15.9k
    }
520
521
4.05k
    pub(crate) fn client_gone(&self, op: &str) {
522
4.05k
        if let Some(
client4.03k
) = self.clients.get(op) {
523
4.03k
            client.gone.send_replace(true);
524
4.03k
        
}22
525
4.05k
    }
526
527
15.9k
    pub(crate) fn action_of(&self, op: &str) -> Option<Action> {
528
15.9k
        self.clients.get(op).map(|c| c.action.clone())
529
15.9k
    }
530
531
15.9k
    async fn upload(&self, action: &Action) -> Result<DigestInfo, Error> {
532
15.9k
        let command = Command {
533
15.9k
            arguments: vec!["sim".to_string(), action.label()],
534
15.9k
            ..Default::default()
535
15.9k
        };
536
15.9k
        let command_digest = serialize_and_upload_message(
537
15.9k
            &command,
538
15.9k
            self.cas.as_pin(),
539
15.9k
            &mut DigestHasherFunc::Sha256.hasher(),
540
15.9k
        )
541
15.9k
        .await
?0
;
542
15.9k
        let input_root_digest = serialize_and_upload_message(
543
15.9k
            &Directory::default(),
544
15.9k
            self.cas.as_pin(),
545
15.9k
            &mut DigestHasherFunc::Sha256.hasher(),
546
15.9k
        )
547
15.9k
        .await
?0
;
548
15.9k
        let proto = ActionProto {
549
15.9k
            command_digest: Some(command_digest.into()),
550
15.9k
            input_root_digest: Some(input_root_digest.into()),
551
15.9k
            timeout: action.timeout.map(prost_types_duration),
552
15.9k
            do_not_cache: !action.cacheable,
553
15.9k
            platform: (!action.properties.is_empty()).then(|| Platform {
554
2
                properties: action
555
2
                    .properties
556
2
                    .iter()
557
2
                    .map(|(name, value)| platform::Property {
558
2
                        name: name.clone(),
559
2
                        value: value.clone(),
560
2
                    })
561
2
                    .collect(),
562
2
            }),
563
15.9k
            ..Default::default()
564
        };
565
15.9k
        let digest = serialize_and_upload_message(
566
15.9k
            &proto,
567
15.9k
            self.cas.as_pin(),
568
15.9k
            &mut DigestHasherFunc::Sha256.hasher(),
569
15.9k
        )
570
15.9k
        .await
?0
;
571
15.9k
        self.registry.insert(digest, action.clone());
572
15.9k
        Ok(digest)
573
15.9k
    }
574
575
    /// Clients still waiting on an operation.
576
30.5k
    fn clients_waiting(&self) -> usize {
577
30.5k
        self.clients
578
30.5k
            .values()
579
10.1M
            .
filter30.5k
(|c| c.state.load(Ordering::Acquire) == CLIENT_RUNNING)
580
30.5k
            .count()
581
30.5k
    }
582
583
    /// Executions the live workers have not finished.
584
4.69k
    fn executions_running(&self) -> usize {
585
4.69k
        self.workers
586
4.69k
            .iter()
587
21.7k
            .
filter4.69k
(|w| !w.task.is_finished())
588
20.2k
            .
map4.69k
(|w| w.manager.running())
589
4.69k
            .sum()
590
4.69k
    }
591
592
    /// Advances until every client has its answer, no worker is running
593
    /// anything and nothing has reached the trace for `QUIET`, or `budget`
594
    /// runs out. Returns whether it got quiet.
595
182
    pub(crate) async fn settle(&self, clock: &SimClock, budget: Duration) -> bool {
596
        const QUIET: Duration = Duration::from_secs(2);
597
182
        let poll = Duration::from_millis(100);
598
182
        let mut waited = Duration::ZERO;
599
182
        let mut quiet_for = Duration::ZERO;
600
182
        let mut seen = self.trace.len();
601
        loop {
602
30.5k
            let idle = self.clients_waiting() == 0 && 
self.executions_running() == 04.51k
;
603
30.5k
            let records = self.trace.len();
604
30.5k
            if idle && 
records == seen3.80k
{
605
3.65k
                if quiet_for >= QUIET {
606
176
                    return true;
607
3.47k
                }
608
26.9k
            } else {
609
26.9k
                quiet_for = Duration::ZERO;
610
26.9k
            }
611
30.3k
            seen = records;
612
30.3k
            if waited >= budget {
613
6
                return false;
614
30.3k
            }
615
30.3k
            clock.advance(poll).await;
616
30.3k
            waited += poll;
617
30.3k
            if idle {
618
3.62k
                quiet_for += poll;
619
26.7k
            }
620
        }
621
182
    }
622
623
    /// Records what the scheduler has executing next to what the workers
624
    /// run.
625
182
    pub(crate) async fn record_ledger(&self) -> Result<(), Error> {
626
182
        let scheduler_executing = match &self.scheduler {
627
182
            Some(scheduler) => {
628
182
                let stream = scheduler
629
182
                    .client
630
182
                    .filter_operations(OperationFilter {
631
182
                        stages: OperationStageFlags::Executing,
632
182
                        // The Redis backend only lists in this order.
633
182
                        order_by_priority_direction: Some(OrderDirection::Desc),
634
182
                        ..Default::default()
635
182
                    })
636
182
                    .await
?0
;
637
182
                stream.count().await
638
            }
639
0
            None => 0,
640
        };
641
182
        self.trace.record(Event::Ledger {
642
182
            scheduler_executing,
643
182
            workers_running: self.executions_running(),
644
182
        });
645
182
        Ok(())
646
182
    }
647
648
    /// Stops every task this world started, so the runtime can go.
649
182
    pub(crate) fn shutdown(&mut self) {
650
15.9k
        for client in 
self.clients182
.
values182
() {
651
15.9k
            client.gone.send_replace(true);
652
15.9k
        }
653
1.19k
        for (index, worker) in 
self.workers.iter()182
.
enumerate182
() {
654
1.19k
            worker.task.abort();
655
1.19k
            self.net.cut(&worker_name(index));
656
1.19k
        }
657
182
        self.scheduler_down();
658
182
    }
659
}
660
661
/// Operation ids in order, `op-1` first, carried over a restart so a new
662
/// scheduler never reuses one.
663
187
fn operation_ids(next: Arc<AtomicU64>) -> impl Fn() -> OperationId + Clone + Send + Sync + 'static {
664
16.1k
    move || OperationId::from(format!("op-{}", next.fetch_add(1, Ordering::AcqRel)))
665
187
}
666
667
/// The virtual time since the Unix epoch, as `WorkerApiServer` reads it.
668
#[allow(clippy::unnecessary_wraps, reason = "the shape `NowFn` takes")]
669
110k
fn since_epoch() -> Result<Duration, Error> {
670
110k
    Ok(SimClock::now()
671
110k
        .duration_since(std::time::UNIX_EPOCH)
672
110k
        .unwrap_or_default())
673
110k
}
674
675
4.00k
pub(crate) fn worker_name(index: usize) -> String {
676
4.00k
    format!("w{index}")
677
4.00k
}
678
679
0
fn prost_types_duration(duration: Duration) -> prost_types::Duration {
680
0
    prost_types::Duration {
681
0
        seconds: i64::try_from(duration.as_secs()).unwrap_or(i64::MAX),
682
0
        nanos: i32::try_from(duration.subsec_nanos()).unwrap_or(0),
683
0
    }
684
0
}
685
686
40.9k
fn stage_of(operation: &Operation) -> Stage {
687
40.9k
    operation
688
40.9k
        .metadata
689
40.9k
        .as_ref()
690
40.9k
        .and_then(|any| ExecuteOperationMetadata::decode(any.value.as_slice()).ok())
691
40.9k
        .map_or(Stage::Unknown, |metadata| Stage::from_proto(metadata.stage))
692
40.9k
}
693
694
9.96k
fn outcome_of(operation: &Operation) -> Outcome {
695
9.96k
    match &operation.result {
696
0
        Some(operation::Result::Error(status)) => Outcome::Failed(status.code),
697
9.96k
        Some(operation::Result::Response(any)) => {
698
9.96k
            match ExecuteResponse::decode(any.value.as_slice()) {
699
9.96k
                Ok(response) => match response.status {
700
9.96k
                    Some(
status2
) if status.code !=
02
=>
Outcome::Failed(status.code)2
,
701
9.96k
                    _ => Outcome::Exited(response.result.map_or(0, |r| r.exit_code)),
702
                },
703
0
                Err(_) => Outcome::Failed(Code::Internal as i32),
704
            }
705
        }
706
0
        None => Outcome::Failed(Code::Unknown as i32),
707
    }
708
9.96k
}
709
710
/// How many times a client retries a call that failed outright before it
711
/// gives up, as Bazel's `--remote_retries` does.
712
const CLIENT_RETRIES: u32 = 5;
713
const CLIENT_RETRY_DELAY: Duration = Duration::from_secs(1);
714
715
/// One client operation: `Execute`, then read the stream to the end. A
716
/// stream that ends or fails early is re-subscribed with `WaitExecution`;
717
/// one the server no longer knows is executed again.
718
15.9k
async fn run_client(
719
15.9k
    net: Arc<Net>,
720
15.9k
    op: String,
721
15.9k
    request: ExecuteRequest,
722
15.9k
    state: Arc<AtomicU8>,
723
15.9k
    mut gone: watch::Receiver<bool>,
724
15.9k
) {
725
15.9k
    let trace = net.trace().clone();
726
15.9k
    let mut name: Option<String> = None;
727
15.9k
    let mut failures = 0;
728
15.9k
    let mut last_stage = None;
729
    loop {
730
16.6k
        if *gone.borrow() {
731
0
            break;
732
16.6k
        }
733
16.6k
        let Some(
server16.5k
) = net.execution() else {
734
3
            if wait_or_gone(&mut gone, CLIENT_RETRY_DELAY).await {
735
0
                break;
736
3
            }
737
3
            continue;
738
        };
739
16.5k
        let mut epoch = net.epoch();
740
16.5k
        let start_epoch = *epoch.borrow_and_update();
741
16.5k
        let response = match &name {
742
16.1k
            None => server.execute(Request::new(request.clone())).await,
743
409
            Some(name) => {
744
409
                server
745
409
                    .wait_execution(Request::new(WaitExecutionRequest { name: name.clone() }))
746
409
                    .await
747
            }
748
        };
749
16.5k
        let 
mut stream16.3k
= match response {
750
16.3k
            Ok(response) => response.into_inner(),
751
206
            Err(status) => {
752
206
                if name.is_some() && status.code() == TonicCode::NotFound {
753
206
                    trace.record(Event::ClientResubmit { op: op.clone() });
754
206
                    name = None;
755
206
                    last_stage = None;
756
206
                    continue;
757
0
                }
758
0
                failures += 1;
759
0
                if failures > CLIENT_RETRIES {
760
0
                    trace.record(Event::ClientDone {
761
0
                        op: op.clone(),
762
0
                        outcome: Outcome::Failed(status.code() as i32),
763
0
                    });
764
0
                    state.store(CLIENT_DONE, Ordering::Release);
765
0
                    return;
766
0
                }
767
0
                trace.record(Event::ClientReconnect {
768
0
                    op: op.clone(),
769
0
                    code: status.code() as i32,
770
0
                });
771
0
                if wait_or_gone(&mut gone, CLIENT_RETRY_DELAY).await {
772
0
                    break;
773
0
                }
774
0
                continue;
775
            }
776
        };
777
        loop {
778
47.3k
            tokio::select! {
779
47.3k
                
item40.9k
= stream.next() => match
item40.9k
{
780
40.9k
                    Some(Ok(operation)) => {
781
40.9k
                        name = Some(operation.name.clone());
782
40.9k
                        let seen = stage_of(&operation);
783
40.9k
                        if last_stage != Some(seen) {
784
35.4k
                            trace.record(Event::ClientStage { op: op.clone(), stage: seen });
785
35.4k
                            last_stage = Some(seen);
786
35.4k
                        
}5.52k
787
40.9k
                        if operation.done {
788
9.96k
                            trace.record(Event::ClientDone {
789
9.96k
                                op: op.clone(),
790
9.96k
                                outcome: outcome_of(&operation),
791
9.96k
                            });
792
9.96k
                            state.store(CLIENT_DONE, Ordering::Release);
793
9.96k
                            return;
794
30.9k
                        }
795
                    }
796
6
                    Some(Err(status)) => {
797
6
                        trace.record(Event::ClientReconnect { op: op.clone(), code: status.code() as i32 });
798
6
                        break;
799
                    }
800
                    None => {
801
0
                        trace.record(Event::ClientReconnect { op: op.clone(), code: -1 });
802
0
                        break;
803
                    }
804
                },
805
47.3k
                
changed4.01k
= gone.changed() => {
806
4.01k
                    if changed.is_err() || *gone.borrow() {
807
4.01k
                        drop(stream);
808
4.01k
                        trace.record(Event::ClientGone { op: op.clone() });
809
4.01k
                        state.store(CLIENT_GONE, Ordering::Release);
810
4.01k
                        return;
811
0
                    }
812
                }
813
47.3k
                
changed403
= epoch.changed() => {
814
403
                    if changed.is_err() || *epoch.borrow() != start_epoch {
815
403
                        trace.record(Event::ClientReconnect { op: op.clone(), code: Code::Unavailable as i32 });
816
403
                        break;
817
0
                    }
818
                }
819
            }
820
        }
821
409
        last_stage = None;
822
409
        if wait_or_gone(&mut gone, CLIENT_RETRY_DELAY).await {
823
0
            break;
824
409
        }
825
    }
826
0
    trace.record(Event::ClientGone { op });
827
0
    state.store(CLIENT_GONE, Ordering::Release);
828
13.9k
}
829
830
/// Sleeps `delay`; true when the client left meanwhile.
831
412
async fn wait_or_gone(gone: &mut watch::Receiver<bool>, delay: Duration) -> bool {
832
412
    tokio::select! {
833
412
        () = tokio::time::sleep(delay) => *gone.borrow(),
834
412
        
changed0
= gone.changed() =>
changed0
.
is_err0
() ||
*gone.borrow()0
,
835
    }
836
412
}