Coverage Report

Created: 2026-07-21 15:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/src/bin/nativelink.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use core::net::SocketAddr;
16
use core::time::Duration;
17
use std::collections::{HashMap, HashSet};
18
use std::io::ErrorKind;
19
use std::sync::Arc;
20
21
use async_lock::Mutex as AsyncMutex;
22
use axum::Router;
23
use axum::http::Uri;
24
use clap::Parser;
25
use futures::FutureExt;
26
use futures::future::{BoxFuture, Either, OptionFuture, TryFutureExt, try_join_all};
27
use hyper::StatusCode;
28
use hyper_util::rt::TokioTimer;
29
use hyper_util::rt::tokio::TokioIo;
30
use hyper_util::server::conn::auto;
31
use hyper_util::service::TowerToHyperService;
32
use mimalloc::MiMalloc;
33
use nativelink_config::cas_server::{
34
    CasConfig, CasStoreConfig, GlobalConfig, HttpCompressionAlgorithm, ListenerConfig,
35
    SchedulerConfig, ServerConfig, StoreConfig, WithInstanceName, WorkerConfig,
36
};
37
use nativelink_config::stores::ConfigDigestHashFunction;
38
use nativelink_error::{Code, Error, ResultExt, make_err, make_input_err};
39
use nativelink_scheduler::default_scheduler_factory::scheduler_factory;
40
use nativelink_service::ac_server::AcServer;
41
use nativelink_service::bep_server::BepServer;
42
use nativelink_service::bytestream_server::ByteStreamServer;
43
use nativelink_service::capabilities_server::CapabilitiesServer;
44
use nativelink_service::cas_server::CasServer;
45
use nativelink_service::execution_server::ExecutionServer;
46
use nativelink_service::fetch_server::FetchServer;
47
use nativelink_service::health_server::HealthServer;
48
use nativelink_service::push_server::PushServer;
49
use nativelink_service::wire_compression::RemoteCacheCompressionInstances;
50
use nativelink_service::worker_api_server::WorkerApiServer;
51
use nativelink_store::default_store_factory::store_factory;
52
use nativelink_store::store_manager::StoreManager;
53
use nativelink_util::common::fs::set_open_file_limit;
54
use nativelink_util::digest_hasher::{DigestHasherFunc, set_default_digest_hasher_func};
55
use nativelink_util::health_utils::HealthRegistryBuilder;
56
use nativelink_util::origin_event_publisher::OriginEventPublisher;
57
#[cfg(target_family = "unix")]
58
use nativelink_util::shutdown_guard::Priority;
59
use nativelink_util::shutdown_guard::ShutdownGuard;
60
use nativelink_util::store_trait::{
61
    DEFAULT_DIGEST_SIZE_HEALTH_CHECK_CFG, set_default_digest_size_health_check,
62
};
63
use nativelink_util::task::TaskExecutor;
64
use nativelink_util::telemetry::init_tracing;
65
use nativelink_util::{background_spawn, fs, spawn};
66
use nativelink_worker::local_worker::new_local_worker;
67
use rustls_pki_types::pem::PemObject;
68
use rustls_pki_types::{CertificateRevocationListDer, PrivateKeyDer};
69
use tokio::net::{TcpListener, TcpSocket};
70
use tokio::select;
71
#[cfg(target_family = "unix")]
72
use tokio::signal::unix::{SignalKind, signal};
73
use tokio::sync::oneshot::Sender;
74
use tokio::sync::{broadcast, mpsc, oneshot};
75
use tokio_rustls::TlsAcceptor;
76
use tokio_rustls::rustls::pki_types::CertificateDer;
77
use tokio_rustls::rustls::server::WebPkiClientVerifier;
78
use tokio_rustls::rustls::{RootCertStore, ServerConfig as TlsServerConfig};
79
use tonic::codec::CompressionEncoding;
80
use tonic::service::Routes;
81
use tracing::{error, error_span, info, trace_span, warn};
82
83
#[global_allocator]
84
static GLOBAL: MiMalloc = MiMalloc;
85
86
/// Note: This must be kept in sync with the documentation in `AdminConfig::path`.
87
const DEFAULT_ADMIN_API_PATH: &str = "/admin";
88
89
// Note: This must be kept in sync with the documentation in `HealthConfig::path`.
90
const DEFAULT_HEALTH_STATUS_CHECK_PATH: &str = "/status";
91
92
// Note: This must be kept in sync with the documentation in
93
// `OriginEventsConfig::max_event_queue_size`.
94
const DEFAULT_MAX_QUEUE_EVENTS: usize = 0x0001_0000;
95
96
/// Broadcast Channel Capacity
97
/// Note: The actual capacity may be greater than the provided capacity.
98
const BROADCAST_CAPACITY: usize = 1;
99
100
0
fn install_default_rustls_crypto_provider() {
101
0
    drop(tokio_rustls::rustls::crypto::ring::default_provider().install_default());
102
0
}
103
104
/// Bind a [`TcpListener`] with `IP_FREEBIND` set.
105
0
fn bind_freebind(socket_addr: SocketAddr) -> Result<TcpListener, std::io::Error> {
106
0
    let socket = match socket_addr {
107
0
        SocketAddr::V4(_) => TcpSocket::new_v4(),
108
0
        SocketAddr::V6(_) => TcpSocket::new_v6(),
109
0
    }?;
110
0
    fs::set_freebind(&socket)?;
111
0
    socket.bind(socket_addr)?;
112
0
    socket.listen(1024)
113
0
}
114
115
/// Backend for bazel remote execution / cache API.
116
#[derive(Parser, Debug)]
117
#[clap(
118
    author = "Trace Machina, Inc. <nativelink@tracemachina.com>",
119
    version,
120
    about,
121
    long_about = None
122
)]
123
struct Args {
124
    /// Config file to use.
125
    #[clap(value_parser)]
126
    config_file: String,
127
}
128
129
trait RoutesExt {
130
    fn add_optional_service<S>(self, svc: Option<S>) -> Self
131
    where
132
        S: tower::Service<
133
                axum::http::Request<tonic::body::Body>,
134
                Error = core::convert::Infallible,
135
            > + tonic::server::NamedService
136
            + Clone
137
            + Send
138
            + Sync
139
            + 'static,
140
        S::Response: axum::response::IntoResponse,
141
        S::Future: Send + 'static;
142
}
143
144
impl RoutesExt for Routes {
145
0
    fn add_optional_service<S>(mut self, svc: Option<S>) -> Self
146
0
    where
147
0
        S: tower::Service<
148
0
                axum::http::Request<tonic::body::Body>,
149
0
                Error = core::convert::Infallible,
150
0
            > + tonic::server::NamedService
151
0
            + Clone
152
0
            + Send
153
0
            + Sync
154
0
            + 'static,
155
0
        S::Response: axum::response::IntoResponse,
156
0
        S::Future: Send + 'static,
157
    {
158
0
        if let Some(svc) = svc {
159
0
            self = self.add_service(svc);
160
0
        }
161
0
        self
162
0
    }
163
}
164
165
/// If this value changes update the documentation in the config definition.
166
const DEFAULT_MAX_DECODING_MESSAGE_SIZE: usize = 4 * 1024 * 1024;
167
168
macro_rules! service_setup {
169
    ($service: expr, $http_config: ident) => {{
170
        let mut service = $service;
171
        let max_decoding_message_size = if $http_config.max_decoding_message_size == 0 {
172
            DEFAULT_MAX_DECODING_MESSAGE_SIZE
173
        } else {
174
            $http_config.max_decoding_message_size
175
        };
176
        service = service.max_decoding_message_size(max_decoding_message_size);
177
        let send_algo = &$http_config.compression.send_compression_algorithm;
178
        if let Some(encoding) = into_encoding(send_algo.unwrap_or(HttpCompressionAlgorithm::None)) {
179
            service = service.send_compressed(encoding);
180
        }
181
        for encoding in $http_config
182
            .compression
183
            .accepted_compression_algorithms
184
            .iter()
185
            // Filter None values.
186
0
            .filter_map(|from: &HttpCompressionAlgorithm| into_encoding(*from))
187
        {
188
            service = service.accept_compressed(encoding);
189
        }
190
        service
191
    }};
192
}
193
194
0
async fn inner_main(
195
0
    cfg: CasConfig,
196
0
    shutdown_tx: broadcast::Sender<ShutdownGuard>,
197
0
    scheduler_shutdown_tx: Sender<()>,
198
0
) -> Result<(), Error> {
199
0
    const fn into_encoding(from: HttpCompressionAlgorithm) -> Option<CompressionEncoding> {
200
0
        match from {
201
0
            HttpCompressionAlgorithm::Gzip => Some(CompressionEncoding::Gzip),
202
0
            HttpCompressionAlgorithm::None => None,
203
        }
204
0
    }
205
206
0
    let health_registry_builder =
207
0
        Arc::new(AsyncMutex::new(HealthRegistryBuilder::new("nativelink")));
208
209
0
    let store_manager = Arc::new(StoreManager::new());
210
    {
211
0
        let mut health_registry_lock = health_registry_builder.lock().await;
212
213
0
        for StoreConfig { name, spec } in cfg.stores {
214
0
            let health_component_name = format!("stores/{name}");
215
0
            let mut health_register_store =
216
0
                health_registry_lock.sub_builder(&health_component_name);
217
0
            let store = store_factory(&spec, &store_manager, Some(&mut health_register_store))
218
0
                .await
219
0
                .err_tip(|| format!("Failed to create store '{name}'"))?;
220
0
            store_manager
221
0
                .add_store(&name, store)
222
0
                .err_tip(|| format!("Failed to add store '{name}'"))?;
223
        }
224
0
        store_manager.run_post_init().await?;
225
    }
226
227
0
    let mut root_futures: Vec<BoxFuture<Result<(), Error>>> = Vec::new();
228
229
0
    let maybe_origin_event_tx = cfg
230
0
        .experimental_origin_events
231
0
        .as_ref()
232
0
        .map(|origin_events_cfg| {
233
0
            let mut max_queued_events = origin_events_cfg.max_event_queue_size;
234
0
            if max_queued_events == 0 {
235
0
                max_queued_events = DEFAULT_MAX_QUEUE_EVENTS;
236
0
            }
237
0
            let (tx, rx) = mpsc::channel(max_queued_events);
238
0
            let store_name = origin_events_cfg.publisher.store.as_str();
239
0
            let store = store_manager.get_store(store_name).err_tip(|| {
240
0
                format!("Could not get store {store_name} for origin event publisher")
241
0
            })?;
242
243
0
            root_futures.push(Box::pin(
244
0
                OriginEventPublisher::new(store, rx, shutdown_tx.clone())
245
0
                    .run()
246
0
                    .map(Ok),
247
0
            ));
248
249
0
            Ok::<_, Error>(tx)
250
0
        })
251
0
        .transpose()?;
252
253
0
    let mut action_schedulers = HashMap::new();
254
0
    let mut worker_schedulers = HashMap::new();
255
0
    for SchedulerConfig { name, spec } in cfg.schedulers.iter().flatten() {
256
0
        let (maybe_action_scheduler, maybe_worker_scheduler) =
257
0
            scheduler_factory(spec, &store_manager, maybe_origin_event_tx.as_ref())
258
0
                .await
259
0
                .err_tip(|| format!("Failed to create scheduler '{name}'"))?;
260
0
        if let Some(action_scheduler) = maybe_action_scheduler {
261
0
            action_schedulers.insert(name.clone(), action_scheduler.clone());
262
0
        }
263
0
        if let Some(worker_scheduler) = maybe_worker_scheduler {
264
0
            worker_schedulers.insert(name.clone(), worker_scheduler.clone());
265
0
        }
266
    }
267
268
0
    let server_cfgs: Vec<ServerConfig> = cfg.servers.into_iter().collect();
269
270
    // The capabilities service advertises chunking support for CAS instances
271
    // that may be served from a different server block (e.g. behind an L7
272
    // router), so collect the CAS configs across all blocks.
273
0
    let all_cas_configs: Vec<WithInstanceName<CasStoreConfig>> = server_cfgs
274
0
        .iter()
275
0
        .filter_map(|server_cfg| server_cfg.services.as_ref())
276
0
        .filter_map(|services| services.cas.as_deref())
277
0
        .flatten()
278
0
        .cloned()
279
0
        .collect();
280
281
0
    for server_cfg in server_cfgs {
282
0
        let services = server_cfg
283
0
            .services
284
0
            .err_tip(|| "'services' must be configured")?;
285
286
        // Currently we only support http as our socket type.
287
0
        let ListenerConfig::Http(http_config) = server_cfg.listener;
288
289
0
        let execution_server = services
290
0
            .execution
291
0
            .as_ref()
292
0
            .map(|cfg| ExecutionServer::new(cfg, &action_schedulers, &store_manager))
293
0
            .transpose()
294
0
            .err_tip(|| "Could not create Execution service")?;
295
296
0
        let capabilities_configs = services.capabilities.as_deref().unwrap_or_default();
297
0
        let remote_cache_compression_instances =
298
0
            RemoteCacheCompressionInstances::from_capabilities_configs(capabilities_configs);
299
300
0
        let tonic_services = Routes::builder()
301
0
            .routes()
302
0
            .add_optional_service(
303
0
                services
304
0
                    .ac
305
0
                    .map_or(Ok(None), |cfg| {
306
0
                        AcServer::new(&cfg, &store_manager)
307
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
308
0
                    })
309
0
                    .err_tip(|| "Could not create AC service")?,
310
            )
311
0
            .add_optional_service(
312
0
                services
313
0
                    .cas
314
0
                    .as_deref()
315
0
                    .map_or(Ok(None), |cfg| {
316
0
                        CasServer::new(cfg, &store_manager, &remote_cache_compression_instances)
317
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
318
0
                    })
319
0
                    .err_tip(|| "Could not create CAS service")?,
320
            )
321
0
            .add_optional_service(
322
0
                execution_server
323
0
                    .clone()
324
0
                    .map(|v| service_setup!(v.into_service(), http_config)),
325
            )
326
0
            .add_optional_service(
327
0
                execution_server.map(|v| service_setup!(v.into_operations_service(), http_config)),
328
            )
329
0
            .add_optional_service(
330
0
                services
331
0
                    .fetch
332
0
                    .map_or(Ok(None), |cfg| {
333
0
                        FetchServer::new(&cfg, &store_manager)
334
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
335
0
                    })
336
0
                    .err_tip(|| "Could not create Fetch service")?,
337
            )
338
0
            .add_optional_service(
339
0
                services
340
0
                    .push
341
0
                    .map_or(Ok(None), |cfg| {
342
0
                        PushServer::new(&cfg, &store_manager)
343
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
344
0
                    })
345
0
                    .err_tip(|| "Could not create Push service")?,
346
            )
347
0
            .add_optional_service(
348
0
                services
349
0
                    .bytestream
350
0
                    .map_or(Ok(None), |cfg| {
351
0
                        ByteStreamServer::new(
352
0
                            &cfg,
353
0
                            &store_manager,
354
0
                            &remote_cache_compression_instances,
355
                        )
356
0
                        .map(|v| Some(service_setup!(v.into_service(), http_config)))
357
0
                    })
358
0
                    .err_tip(|| "Could not create ByteStream service")?,
359
            )
360
0
            .add_optional_service(
361
0
                OptionFuture::from(services.capabilities.as_ref().map(|cfg| {
362
0
                    CapabilitiesServer::new(
363
0
                        cfg,
364
0
                        &action_schedulers,
365
0
                        &remote_cache_compression_instances,
366
0
                        &all_cas_configs,
367
                    )
368
0
                }))
369
0
                .await
370
0
                .map_or(Ok::<Option<CapabilitiesServer>, Error>(None), |server| {
371
0
                    Ok(Some(server?))
372
0
                })
373
0
                .err_tip(|| "Could not create Capabilities service")?
374
0
                .map(|v| service_setup!(v.into_service(), http_config)),
375
            )
376
0
            .add_optional_service(
377
0
                services
378
0
                    .worker_api
379
0
                    .map_or(Ok(None), |cfg| {
380
0
                        WorkerApiServer::new(&cfg, &worker_schedulers)
381
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
382
0
                    })
383
0
                    .err_tip(|| "Could not create WorkerApi service")?,
384
            )
385
0
            .add_optional_service(
386
0
                services
387
0
                    .experimental_bep
388
0
                    .map_or(Ok(None), |cfg| {
389
0
                        BepServer::new(&cfg, &store_manager)
390
0
                            .map(|v| Some(service_setup!(v.into_service(), http_config)))
391
0
                    })
392
0
                    .err_tip(|| "Could not create BEP service")?,
393
            );
394
395
0
        let health_registry = health_registry_builder.lock().await.build();
396
397
0
        let mut svc =
398
0
            tonic_services
399
0
                .into_axum_router()
400
0
                .layer(nativelink_util::telemetry::OtlpLayer::new(
401
0
                    server_cfg.experimental_identity_header.required,
402
                ));
403
404
0
        if let Some(health_cfg) = services.health {
405
0
            let path = if health_cfg.path.is_empty() {
406
0
                DEFAULT_HEALTH_STATUS_CHECK_PATH
407
            } else {
408
0
                &health_cfg.path
409
            };
410
0
            svc = svc.route_service(path, HealthServer::new(health_registry, &health_cfg));
411
0
        }
412
413
0
        if let Some(admin_config) = services.admin {
414
0
            let path = if admin_config.path.is_empty() {
415
0
                DEFAULT_ADMIN_API_PATH
416
            } else {
417
0
                &admin_config.path
418
            };
419
0
            let worker_schedulers = Arc::new(worker_schedulers.clone());
420
0
            svc = svc.nest_service(
421
0
                path,
422
0
                Router::new().route(
423
0
                    "/scheduler/{instance_name}/set_drain_worker/{worker_id}/{is_draining}",
424
0
                    axum::routing::post(
425
0
                        move |params: axum::extract::Path<(String, String, String)>| async move {
426
0
                            let (instance_name, worker_id, is_draining) = params.0;
427
0
                            (async move {
428
0
                                let is_draining = match is_draining.as_str() {
429
0
                                    "0" => false,
430
0
                                    "1" => true,
431
                                    _ => {
432
0
                                        return Err(make_err!(
433
0
                                            Code::Internal,
434
0
                                            "{} is neither 0 nor 1",
435
0
                                            is_draining
436
0
                                        ));
437
                                    }
438
                                };
439
0
                                worker_schedulers
440
0
                                    .get(&instance_name)
441
0
                                    .err_tip(|| {
442
0
                                        format!(
443
                                            "Can not get an instance with the name of '{}'",
444
0
                                            &instance_name
445
                                        )
446
0
                                    })?
447
0
                                    .clone()
448
0
                                    .set_drain_worker(&worker_id.clone().into(), is_draining)
449
0
                                    .await?;
450
0
                                Ok::<_, Error>(format!("Draining worker {worker_id}"))
451
                            })
452
0
                            .await
453
0
                            .map_err(|e| {
454
0
                                Err::<String, _>((
455
0
                                    StatusCode::INTERNAL_SERVER_ERROR,
456
0
                                    format!("Error: {e:?}"),
457
0
                                ))
458
0
                            })
459
0
                        },
460
                    ),
461
                ),
462
            );
463
0
        }
464
465
        // This is the default service that executes if no other endpoint matches.
466
0
        svc = svc.fallback(|uri: Uri| async move {
467
0
            warn!("No route for {uri}");
468
0
            (StatusCode::NOT_FOUND, format!("No route for {uri}"))
469
0
        });
470
471
        // Configure our TLS acceptor if we have TLS configured.
472
0
        let maybe_tls_acceptor = http_config.tls.map_or(Ok(None), |tls_config| {
473
0
            fn read_cert(cert_file: &str) -> Result<Vec<CertificateDer<'static>>, Error> {
474
0
                let mut cert_reader = std::io::BufReader::new(
475
0
                    std::fs::File::open(cert_file)
476
0
                        .err_tip(|| format!("Could not open cert file {cert_file}"))?,
477
                );
478
0
                let certs = CertificateDer::pem_reader_iter(&mut cert_reader)
479
0
                    .collect::<Result<Vec<CertificateDer<'_>>, _>>()
480
0
                    .err_tip(|| format!("Could not extract certs from file {cert_file}"))?;
481
0
                Ok(certs)
482
0
            }
483
0
            let certs = read_cert(&tls_config.cert_file)?;
484
0
            let mut key_reader = std::io::BufReader::new(
485
0
                std::fs::File::open(&tls_config.key_file)
486
0
                    .err_tip(|| format!("Could not open key file {}", tls_config.key_file))?,
487
            );
488
0
            let key = match PrivateKeyDer::from_pem_reader(&mut key_reader)
489
0
                .err_tip(|| format!("Could not extract key(s) from file {}", tls_config.key_file))?
490
            {
491
0
                PrivateKeyDer::Pkcs8(key) => key.into(),
492
0
                PrivateKeyDer::Sec1(key) => key.into(),
493
0
                PrivateKeyDer::Pkcs1(key) => key.into(),
494
                _ => {
495
0
                    return Err(make_err!(
496
0
                        Code::Internal,
497
0
                        "No keys found in file {}",
498
0
                        tls_config.key_file
499
0
                    ));
500
                }
501
            };
502
0
            if PrivateKeyDer::from_pem_reader(&mut key_reader).is_ok() {
503
0
                return Err(make_err!(
504
0
                    Code::InvalidArgument,
505
0
                    "Expected 1 key in file {}",
506
0
                    tls_config.key_file
507
0
                ));
508
0
            }
509
0
            let verifier = if let Some(client_ca_file) = &tls_config.client_ca_file {
510
0
                let mut client_auth_roots = RootCertStore::empty();
511
0
                for cert in read_cert(client_ca_file)? {
512
0
                    client_auth_roots.add(cert).map_err(|e| {
513
0
                        Error::from_std_err(Code::Internal, &e).append("Could not read client CA")
514
0
                    })?;
515
                }
516
0
                let crls = if let Some(client_crl_file) = &tls_config.client_crl_file {
517
0
                    let mut crl_reader = std::io::BufReader::new(
518
0
                        std::fs::File::open(client_crl_file)
519
0
                            .err_tip(|| format!("Could not open CRL file {client_crl_file}"))?,
520
                    );
521
0
                    CertificateRevocationListDer::pem_reader_iter(&mut crl_reader)
522
0
                        .collect::<Result<_, _>>()
523
0
                        .err_tip(|| format!("Could not extract CRLs from file {client_crl_file}"))?
524
                } else {
525
0
                    Vec::new()
526
                };
527
0
                WebPkiClientVerifier::builder(Arc::new(client_auth_roots))
528
0
                    .with_crls(crls)
529
0
                    .build()
530
0
                    .map_err(|e| {
531
0
                        Error::from_std_err(Code::Internal, &e)
532
0
                            .append("Could not create WebPkiClientVerifier")
533
0
                    })?
534
            } else {
535
0
                WebPkiClientVerifier::no_client_auth()
536
            };
537
0
            let mut config = TlsServerConfig::builder()
538
0
                .with_client_cert_verifier(verifier)
539
0
                .with_single_cert(certs, key)
540
0
                .map_err(|e| {
541
0
                    Error::from_std_err(Code::Internal, &e)
542
0
                        .append("Could not create TlsServerConfig")
543
0
                })?;
544
545
0
            config.alpn_protocols.push("h2".into());
546
0
            Ok(Some(TlsAcceptor::from(Arc::new(config))))
547
0
        })?;
548
549
0
        let socket_addr = http_config
550
0
            .socket_address
551
0
            .parse::<SocketAddr>()
552
0
            .map_err(|e| {
553
0
                Error::from_std_err(Code::InvalidArgument, &e)
554
0
                    .append(format!("Invalid address '{}'", http_config.socket_address))
555
0
            })?;
556
0
        let tcp_listener = if http_config.freebind {
557
0
            bind_freebind(socket_addr)
558
        } else {
559
0
            TcpListener::bind(&socket_addr).await
560
        }
561
0
        .map_err(|e| match e.kind() {
562
0
            ErrorKind::AddrInUse => make_err!(
563
0
                Code::AlreadyExists,
564
                "Address '{socket_addr}' is already in use by another process.",
565
            ),
566
0
            ErrorKind::PermissionDenied => make_err!(
567
0
                Code::PermissionDenied,
568
                "Permission denied. You may need root privileges to bind to address '{socket_addr}'.",
569
            ),
570
            ErrorKind::InvalidInput => {
571
0
                make_input_err!("The provided address '{socket_addr}' is invalid.")
572
            }
573
0
            _ => Error::from_std_err(Code::Internal, &e)
574
0
                .append(format!("Failed to bind to socket address '{socket_addr}'")),
575
0
        })?;
576
0
        let mut http = auto::Builder::new(TaskExecutor::default());
577
0
        http.http2().timer(TokioTimer::new());
578
579
0
        let http_config = &http_config.advanced_http;
580
0
        if let Some(value) = http_config.http2_keep_alive_interval {
581
0
            http.http2()
582
0
                .keep_alive_interval(Duration::from_secs(u64::from(value)));
583
0
        }
584
585
0
        if let Some(value) = http_config.experimental_http2_max_pending_accept_reset_streams {
586
0
            http.http2()
587
0
                .max_pending_accept_reset_streams(usize::try_from(value).err_tip(
588
                    || "Could not convert experimental_http2_max_pending_accept_reset_streams",
589
0
                )?);
590
0
        }
591
0
        if let Some(value) = http_config.experimental_http2_initial_stream_window_size {
592
0
            http.http2().initial_stream_window_size(value);
593
0
        }
594
0
        if let Some(value) = http_config.experimental_http2_initial_connection_window_size {
595
0
            http.http2().initial_connection_window_size(value);
596
0
        }
597
0
        if let Some(value) = http_config.experimental_http2_adaptive_window {
598
0
            http.http2().adaptive_window(value);
599
0
        }
600
0
        if let Some(value) = http_config.experimental_http2_max_frame_size {
601
0
            http.http2().max_frame_size(value);
602
0
        }
603
0
        if let Some(value) = http_config.experimental_http2_max_concurrent_streams {
604
0
            http.http2().max_concurrent_streams(value);
605
0
        }
606
0
        if let Some(value) = http_config.experimental_http2_keep_alive_timeout_s {
607
0
            http.http2()
608
0
                .keep_alive_timeout(Duration::from_secs(u64::from(value)));
609
0
        }
610
0
        if let Some(value) = http_config.experimental_http2_max_send_buf_size {
611
0
            http.http2().max_send_buf_size(
612
0
                usize::try_from(value).err_tip(|| "Could not convert http2_max_send_buf_size")?,
613
            );
614
0
        }
615
0
        if http_config.experimental_http2_enable_connect_protocol == Some(true) {
616
0
            http.http2().enable_connect_protocol();
617
0
        }
618
0
        if let Some(value) = http_config.experimental_http2_max_header_list_size {
619
0
            http.http2().max_header_list_size(value);
620
0
        }
621
0
        info!("Ready, listening on {socket_addr}",);
622
0
        root_futures.push(Box::pin(async move {
623
            loop {
624
0
                select! {
625
0
                    accept_result = tcp_listener.accept() => {
626
0
                        match accept_result {
627
0
                            Ok((tcp_stream, remote_addr)) => {
628
0
                                info!(
629
                                    target: "nativelink::services",
630
                                    ?remote_addr,
631
                                    ?socket_addr,
632
                                    "Client connected"
633
                                );
634
635
0
                                let (http, svc, maybe_tls_acceptor) =
636
0
                                    (http.clone(), svc.clone(), maybe_tls_acceptor.clone());
637
638
0
                                background_spawn!(
639
                                    name: "http_connection",
640
0
                                    fut: error_span!(
641
                                        "http_connection",
642
                                        remote_addr = %remote_addr,
643
                                        socket_addr = %socket_addr,
644
0
                                    ).in_scope(|| async move {
645
0
                                        let serve_connection = if let Some(tls_acceptor) = maybe_tls_acceptor {
646
0
                                            match tls_acceptor.accept(tcp_stream).await {
647
0
                                                Ok(tls_stream) => Either::Left(http.serve_connection(
648
0
                                                    TokioIo::new(tls_stream),
649
0
                                                    TowerToHyperService::new(svc),
650
0
                                                )),
651
0
                                                Err(err) => {
652
0
                                                    error!(?err, "Failed to accept tls stream");
653
0
                                                    return;
654
                                                }
655
                                            }
656
                                        } else {
657
0
                                            Either::Right(http.serve_connection(
658
0
                                                TokioIo::new(tcp_stream),
659
0
                                                TowerToHyperService::new(svc),
660
0
                                            ))
661
                                        };
662
663
0
                                        if let Err(err) = serve_connection.await {
664
0
                                            error!(
665
                                                target: "nativelink::services",
666
                                                ?err,
667
                                                "Failed running service"
668
                                            );
669
0
                                        }
670
0
                                    }),
671
                                    target: "nativelink::services",
672
                                    ?remote_addr,
673
                                    ?socket_addr,
674
                                );
675
                            },
676
0
                            Err(err) => {
677
0
                                error!(?err, "Failed to accept tcp connection");
678
                            }
679
                        }
680
                    },
681
                }
682
            }
683
            // Unreachable
684
        }));
685
    }
686
687
    {
688
        // We start workers after our TcpListener is setup so if our worker connects to one
689
        // of these services it will be able to connect.
690
0
        let worker_cfgs = cfg.workers.unwrap_or_default();
691
0
        let mut worker_names = HashSet::with_capacity(worker_cfgs.len());
692
0
        for (i, worker_cfg) in worker_cfgs.into_iter().enumerate() {
693
0
            let spawn_fut = match worker_cfg {
694
0
                WorkerConfig::Local(local_worker_cfg) => {
695
0
                    let fast_slow_store = store_manager
696
0
                        .get_store(&local_worker_cfg.cas_fast_slow_store)
697
0
                        .err_tip(|| {
698
0
                            format!(
699
                                "Failed to find store for cas_store_ref in worker config : {}",
700
                                local_worker_cfg.cas_fast_slow_store
701
                            )
702
0
                        })?;
703
704
0
                    let maybe_ac_store = if let Some(ac_store_ref) =
705
0
                        &local_worker_cfg.upload_action_result.ac_store
706
                    {
707
0
                        Some(store_manager.get_store(ac_store_ref).err_tip(|| {
708
0
                            format!("Failed to find store for ac_store in worker config : {ac_store_ref}")
709
0
                        })?)
710
                    } else {
711
0
                        None
712
                    };
713
                    // Note: Defaults to fast_slow_store if not specified. If this ever changes it must
714
                    // be updated in config documentation for the `historical_results_store` the field.
715
0
                    let historical_store = if let Some(cas_store_ref) = &local_worker_cfg
716
0
                        .upload_action_result
717
0
                        .historical_results_store
718
                    {
719
0
                        store_manager.get_store(cas_store_ref).err_tip(|| {
720
0
                                format!(
721
                                "Failed to find store for historical_results_store in worker config : {cas_store_ref}"
722
                            )
723
0
                            })?
724
                    } else {
725
0
                        fast_slow_store.clone()
726
                    };
727
0
                    let local_worker = new_local_worker(
728
0
                        Arc::new(local_worker_cfg),
729
0
                        fast_slow_store,
730
0
                        maybe_ac_store,
731
0
                        historical_store,
732
0
                    )
733
0
                    .await
734
0
                    .err_tip(|| "Could not make LocalWorker")?;
735
736
0
                    let name = if local_worker.name().is_empty() {
737
0
                        format!("worker_{i}")
738
                    } else {
739
0
                        local_worker.name().clone()
740
                    };
741
742
0
                    if worker_names.contains(&name) {
743
0
                        Err(make_input_err!(
744
0
                            "Duplicate worker name '{}' found in config",
745
0
                            name
746
0
                        ))?;
747
0
                    }
748
0
                    worker_names.insert(name.clone());
749
0
                    let shutdown_rx = shutdown_tx.subscribe();
750
0
                    let fut = trace_span!("worker_ctx", worker_name = %name)
751
0
                        .in_scope(|| local_worker.run(shutdown_rx));
752
0
                    spawn!("worker", fut, ?name)
753
                }
754
            };
755
0
            root_futures.push(Box::pin(spawn_fut.map_ok_or_else(|e| Err(e.into()), |v| v)));
756
        }
757
    }
758
759
    // Set up a shutdown handler for the worker schedulers.
760
0
    let mut shutdown_rx = shutdown_tx.subscribe();
761
0
    root_futures.push(Box::pin(async move {
762
0
        if let Ok(shutdown_guard) = shutdown_rx.recv().await {
763
0
            let _ = scheduler_shutdown_tx.send(());
764
0
            for (_name, scheduler) in worker_schedulers {
765
0
                scheduler.shutdown(shutdown_guard.clone()).await;
766
            }
767
0
        }
768
0
        Ok(())
769
0
    }));
770
771
0
    if let Err(e) = try_join_all(root_futures).await {
772
0
        panic!("{e:?}");
773
0
    }
774
775
0
    Ok(())
776
0
}
777
778
0
fn get_config() -> Result<CasConfig, Error> {
779
0
    let args = Args::parse();
780
0
    CasConfig::try_from_json5_file(&args.config_file)
781
0
}
782
783
0
fn main() -> Result<(), Box<dyn core::error::Error>> {
784
0
    install_default_rustls_crypto_provider();
785
786
    // Set QoS to USER_INITIATED on the main thread *before* the tokio
787
    // runtime is built so the spawned worker threads inherit P-core
788
    // scheduling preference via pthread QoS inheritance on Apple
789
    // Silicon. `on_thread_start` below is a belt-and-suspenders hook
790
    // for any thread that misses the inherited class (e.g. tokio
791
    // blocking pool threads created lazily). No-op on non-macOS.
792
0
    let _ = nativelink_worker::qos::set_user_initiated();
793
794
    #[expect(clippy::disallowed_methods, reason = "starting main runtime")]
795
0
    let runtime = tokio::runtime::Builder::new_multi_thread()
796
0
        .on_thread_start(|| {
797
0
            let _ = nativelink_worker::qos::set_user_initiated();
798
0
        })
799
0
        .enable_all()
800
0
        .build()?;
801
802
    // The OTLP exporters need to run in a Tokio context
803
    // Do this first so all the other logging works
804
    #[expect(clippy::disallowed_methods, reason = "tracing init on main runtime")]
805
0
    runtime.block_on(async { tokio::spawn(async { init_tracing().await }).await? })?;
806
807
0
    let mut cfg = get_config()?;
808
809
0
    let global_cfg = if let Some(global_cfg) = &mut cfg.global {
810
0
        if global_cfg.max_open_files == 0 {
811
0
            global_cfg.max_open_files = fs::DEFAULT_OPEN_FILE_LIMIT;
812
0
        }
813
0
        if global_cfg.default_digest_size_health_check == 0 {
814
0
            global_cfg.default_digest_size_health_check = DEFAULT_DIGEST_SIZE_HEALTH_CHECK_CFG;
815
0
        }
816
817
0
        *global_cfg
818
    } else {
819
0
        GlobalConfig {
820
0
            max_open_files: fs::DEFAULT_OPEN_FILE_LIMIT,
821
0
            default_digest_hash_function: None,
822
0
            default_digest_size_health_check: DEFAULT_DIGEST_SIZE_HEALTH_CHECK_CFG,
823
0
        }
824
    };
825
0
    set_open_file_limit(global_cfg.max_open_files);
826
0
    set_default_digest_hasher_func(DigestHasherFunc::from(
827
0
        global_cfg
828
0
            .default_digest_hash_function
829
0
            .unwrap_or(ConfigDigestHashFunction::Sha256),
830
0
    ))?;
831
0
    set_default_digest_size_health_check(global_cfg.default_digest_size_health_check)?;
832
833
    // Initiates the shutdown process by broadcasting the shutdown signal via the `oneshot::Sender` to all listeners.
834
    // Each listener will perform its cleanup and then drop its `oneshot::Sender`, signaling completion.
835
    // Once all `oneshot::Sender` instances are dropped, the worker knows it can safely terminate.
836
0
    let (shutdown_tx, _) = broadcast::channel::<ShutdownGuard>(BROADCAST_CAPACITY);
837
    #[cfg(target_family = "unix")]
838
0
    let shutdown_tx_clone = shutdown_tx.clone();
839
    #[cfg(target_family = "unix")]
840
0
    let mut shutdown_guard = ShutdownGuard::default();
841
842
    #[expect(clippy::disallowed_methods, reason = "signal handler on main runtime")]
843
0
    runtime.spawn(async move {
844
0
        tokio::signal::ctrl_c()
845
0
            .await
846
0
            .expect("Failed to listen to SIGINT");
847
0
        eprintln!("User terminated process via SIGINT");
848
0
        std::process::exit(130);
849
    });
850
851
    #[allow(unused_variables)]
852
0
    let (scheduler_shutdown_tx, scheduler_shutdown_rx) = oneshot::channel();
853
854
    #[cfg(target_family = "unix")]
855
    #[expect(clippy::disallowed_methods, reason = "signal handler on main runtime")]
856
0
    runtime.spawn(async move {
857
0
        signal(SignalKind::terminate())
858
0
            .expect("Failed to listen to SIGTERM")
859
0
            .recv()
860
0
            .await;
861
0
        warn!("Process terminated via SIGTERM");
862
0
        drop(shutdown_tx_clone.send(shutdown_guard.clone()));
863
0
        scheduler_shutdown_rx
864
0
            .await
865
0
            .expect("Failed to receive scheduler shutdown");
866
0
        let () = shutdown_guard.wait_for(Priority::P0).await;
867
0
        warn!("Successfully shut down nativelink.");
868
0
        std::process::exit(143);
869
    });
870
871
    #[expect(clippy::disallowed_methods, reason = "waiting on everything to finish")]
872
0
    runtime
873
0
        .block_on(async {
874
0
            trace_span!("main")
875
0
                .in_scope(|| async { inner_main(cfg, shutdown_tx, scheduler_shutdown_tx).await })
876
0
                .await
877
0
        })
878
0
        .err_tip(|| "main() function failed")?;
879
0
    Ok(())
880
0
}