Skip to content

Commit d09b709

Browse files
authored
[2/n] [nexus] move background task definitions into a shared crate (#8003)
1 parent 7929b70 commit d09b709

File tree

13 files changed

+241
-146
lines changed

13 files changed

+241
-146
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ members = [
7777
"nexus-sled-agent-shared",
7878
"nexus/authz-macros",
7979
"nexus/auth",
80+
"nexus/background-task-interface",
8081
"nexus/db-errors",
8182
"nexus/db-fixed-data",
8283
"nexus/db-lookup",
@@ -227,6 +228,7 @@ default-members = [
227228
"nexus-sled-agent-shared",
228229
"nexus/authz-macros",
229230
"nexus/auth",
231+
"nexus/background-task-interface",
230232
"nexus/db-errors",
231233
"nexus/db-fixed-data",
232234
"nexus/db-lookup",
@@ -500,6 +502,7 @@ mg-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "8
500502
ddm-admin-client = { git = "https://github.com/oxidecomputer/maghemite", rev = "8452936a53c3b16e53cbbf4e34e5e59899afc965" }
501503
multimap = "0.10.0"
502504
nexus-auth = { path = "nexus/auth" }
505+
nexus-background-task-interface = { path = "nexus/background-task-interface" }
503506
nexus-client = { path = "clients/nexus-client" }
504507
nexus-config = { path = "nexus-config" }
505508
nexus-db-errors = { path = "nexus/db-errors" }

nexus/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ ipnetwork.workspace = true
5252
itertools.workspace = true
5353
lldpd_client.workspace = true
5454
macaddr.workspace = true
55+
nexus-background-task-interface.workspace = true
5556
# Not under "dev-dependencies"; these also need to be implemented for
5657
# integration tests.
5758
nexus-config.workspace = true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "nexus-background-task-interface"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MPL-2.0"
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
omicron-workspace-hack.workspace = true
12+
thiserror.workspace = true
13+
tokio.workspace = true
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use std::sync::{
6+
Arc,
7+
atomic::{AtomicBool, Ordering},
8+
};
9+
10+
use thiserror::Error;
11+
use tokio::sync::Notify;
12+
13+
/// Activates a background task
14+
///
15+
/// For more on what this means, see the documentation at
16+
/// `nexus/src/app/background/mod.rs`.
17+
///
18+
/// Activators are created with [`Activator::new()`] and then wired up to
19+
/// specific background tasks using Nexus's `Driver::register()`. If you call
20+
/// `Activator::activate()` before the activator is wired up to a background
21+
/// task, then once the Activator _is_ wired up to a task, that task will
22+
/// immediately be activated.
23+
///
24+
/// Activators are designed specifically so they can be created before the
25+
/// corresponding task has been created and then wired up with just an
26+
/// `&Activator` (not a `&mut Activator`). See the
27+
/// `nexus/src/app/background/mod.rs` documentation for more on why.
28+
#[derive(Clone)]
29+
pub struct Activator(Arc<ActivatorInner>);
30+
31+
/// Shared state for an `Activator`.
32+
struct ActivatorInner {
33+
pub(super) notify: Notify,
34+
pub(super) wired_up: AtomicBool,
35+
}
36+
37+
impl Activator {
38+
/// Create an activator that is not yet wired up to any background task
39+
pub fn new() -> Activator {
40+
Self(Arc::new(ActivatorInner {
41+
notify: Notify::new(),
42+
wired_up: AtomicBool::new(false),
43+
}))
44+
}
45+
46+
/// Activate the background task that this Activator has been wired up to
47+
///
48+
/// If this Activator has not yet been wired up, then whenever it _is_ wired
49+
/// up, that task will be immediately activated.
50+
pub fn activate(&self) {
51+
self.0.notify.notify_one();
52+
}
53+
54+
/// Sets the task as wired up.
55+
///
56+
/// Returns an error if the task was already wired up.
57+
pub fn mark_wired_up(&self) -> Result<(), AlreadyWiredUpError> {
58+
match self.0.wired_up.compare_exchange(
59+
false,
60+
true,
61+
Ordering::SeqCst,
62+
Ordering::SeqCst,
63+
) {
64+
Ok(false) => Ok(()),
65+
Ok(true) => unreachable!(
66+
"on success, the return value is always \
67+
the previous value (false)"
68+
),
69+
Err(true) => Err(AlreadyWiredUpError {}),
70+
Err(false) => unreachable!(
71+
"on failure, the return value is always \
72+
the previous and current value (true)"
73+
),
74+
}
75+
}
76+
77+
/// Blocks until the background task that this Activator has been wired up
78+
/// to is activated.
79+
///
80+
/// If this Activator has not yet been wired up, then whenever it _is_ wired
81+
/// up, that task will be immediately activated.
82+
pub async fn activated(&self) {
83+
debug_assert!(
84+
self.0.wired_up.load(Ordering::SeqCst),
85+
"nothing should await activation from an activator that hasn't \
86+
been wired up"
87+
);
88+
self.0.notify.notified().await
89+
}
90+
}
91+
92+
/// Indicates that an activator was wired up more than once.
93+
#[derive(Debug, Error)]
94+
#[error("activator was already wired up")]
95+
pub struct AlreadyWiredUpError {}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use crate::Activator;
6+
7+
/// Interface for activating various background tasks and read data that they
8+
/// expose to Nexus at-large
9+
pub struct BackgroundTasks {
10+
// Handles to activate specific background tasks
11+
pub task_internal_dns_config: Activator,
12+
pub task_internal_dns_servers: Activator,
13+
pub task_external_dns_config: Activator,
14+
pub task_external_dns_servers: Activator,
15+
pub task_metrics_producer_gc: Activator,
16+
pub task_external_endpoints: Activator,
17+
pub task_nat_cleanup: Activator,
18+
pub task_bfd_manager: Activator,
19+
pub task_inventory_collection: Activator,
20+
pub task_support_bundle_collector: Activator,
21+
pub task_physical_disk_adoption: Activator,
22+
pub task_decommissioned_disk_cleaner: Activator,
23+
pub task_phantom_disks: Activator,
24+
pub task_blueprint_loader: Activator,
25+
pub task_blueprint_executor: Activator,
26+
pub task_blueprint_rendezvous: Activator,
27+
pub task_crdb_node_id_collector: Activator,
28+
pub task_service_zone_nat_tracker: Activator,
29+
pub task_switch_port_settings_manager: Activator,
30+
pub task_v2p_manager: Activator,
31+
pub task_region_replacement: Activator,
32+
pub task_region_replacement_driver: Activator,
33+
pub task_instance_watcher: Activator,
34+
pub task_instance_updater: Activator,
35+
pub task_instance_reincarnation: Activator,
36+
pub task_service_firewall_propagation: Activator,
37+
pub task_abandoned_vmm_reaper: Activator,
38+
pub task_vpc_route_manager: Activator,
39+
pub task_saga_recovery: Activator,
40+
pub task_lookup_region_port: Activator,
41+
pub task_region_snapshot_replacement_start: Activator,
42+
pub task_region_snapshot_replacement_garbage_collection: Activator,
43+
pub task_region_snapshot_replacement_step: Activator,
44+
pub task_region_snapshot_replacement_finish: Activator,
45+
pub task_tuf_artifact_replication: Activator,
46+
pub task_read_only_region_replacement_start: Activator,
47+
48+
// Handles to activate background tasks that do not get used by Nexus
49+
// at-large. These background tasks are implementation details as far as
50+
// the rest of Nexus is concerned. These handles don't even really need to
51+
// be here, but it's convenient.
52+
pub task_internal_dns_propagation: Activator,
53+
pub task_external_dns_propagation: Activator,
54+
}
55+
56+
impl BackgroundTasks {
57+
/// Activate the specified background task
58+
///
59+
/// If the task is currently running, it will be activated again when it
60+
/// finishes.
61+
pub fn activate(&self, task: &Activator) {
62+
task.activate();
63+
}
64+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Common interface for describint and activating Nexus background tasks.
6+
//!
7+
//! This crate defines the [`BackgroundTasks`] type, which lists out all of the
8+
//! background tasks within Nexus, and provides handles to activate them.
9+
//!
10+
//! For more about background tasks, see the documentation at
11+
//! `nexus/src/app/background/mod.rs`.
12+
13+
mod activator;
14+
mod init;
15+
16+
pub use activator::*;
17+
pub use init::*;

nexus/src/app/background/driver.rs

Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use futures::FutureExt;
1515
use futures::StreamExt;
1616
use futures::future::BoxFuture;
1717
use futures::stream::FuturesUnordered;
18+
use nexus_background_task_interface::Activator;
1819
use nexus_db_queries::context::OpContext;
1920
use nexus_types::internal_api::views::ActivationReason;
2021
use nexus_types::internal_api::views::CurrentStatus;
@@ -23,12 +24,8 @@ use nexus_types::internal_api::views::LastResult;
2324
use nexus_types::internal_api::views::LastResultCompleted;
2425
use nexus_types::internal_api::views::TaskStatus;
2526
use std::collections::BTreeMap;
26-
use std::sync::Arc;
27-
use std::sync::atomic::AtomicBool;
28-
use std::sync::atomic::Ordering;
2927
use std::time::Duration;
3028
use std::time::Instant;
31-
use tokio::sync::Notify;
3229
use tokio::sync::watch;
3330
use tokio::time::MissedTickBehavior;
3431

@@ -118,17 +115,10 @@ impl Driver {
118115
// requested. The caller provides their own Activator, which just
119116
// provides a specific Notify for us to use here.
120117
let activator = taskdef.activator;
121-
if let Err(previous) = activator.0.wired_up.compare_exchange(
122-
false,
123-
true,
124-
Ordering::SeqCst,
125-
Ordering::SeqCst,
126-
) {
118+
if let Err(error) = activator.mark_wired_up() {
127119
panic!(
128-
"attempted to wire up the same background task handle \
129-
twice (previous \"wired_up\" = {}): currently attempting \
130-
to wire it up to task {:?}",
131-
previous, name
120+
"{error}: currently attempting to wire it up to task {:?}",
121+
name
132122
);
133123
}
134124

@@ -141,7 +131,7 @@ impl Driver {
141131
let task_exec = TaskExec::new(
142132
taskdef.period,
143133
taskdef.task_impl,
144-
Arc::clone(&activator.0),
134+
activator.clone(),
145135
opctx,
146136
status_tx,
147137
);
@@ -241,59 +231,6 @@ pub struct TaskDefinition<'a, N: ToString, D: ToString> {
241231
pub activator: &'a Activator,
242232
}
243233

244-
/// Activates a background task
245-
///
246-
/// See [`crate::app::background`] module-level documentation for more on what
247-
/// that means.
248-
///
249-
/// Activators are created with [`Activator::new()`] and then wired up to
250-
/// specific background tasks using [`Driver::register()`]. If you call
251-
/// `Activator::activate()` before the activator is wired up to a background
252-
/// task, then once the Activator _is_ wired up to a task, that task will
253-
/// immediately be activated.
254-
///
255-
/// Activators are designed specifically so they can be created before the
256-
/// corresponding task has been created and then wired up with just an
257-
/// `&Activator` (not a `&mut Activator`). See the [`super::init`] module-level
258-
/// documentation for more on why.
259-
#[derive(Clone)]
260-
pub struct Activator(Arc<ActivatorInner>);
261-
262-
/// Shared state for an `Activator`.
263-
struct ActivatorInner {
264-
pub(super) notify: Notify,
265-
pub(super) wired_up: AtomicBool,
266-
}
267-
268-
impl Activator {
269-
/// Create an activator that is not yet wired up to any background task
270-
pub fn new() -> Activator {
271-
Self(Arc::new(ActivatorInner {
272-
notify: Notify::new(),
273-
wired_up: AtomicBool::new(false),
274-
}))
275-
}
276-
277-
/// Activate the background task that this Activator has been wired up to
278-
///
279-
/// If this Activator has not yet been wired up with [`Driver::register()`],
280-
/// then whenever it _is_ wired up, that task will be immediately activated.
281-
pub fn activate(&self) {
282-
self.0.notify.notify_one();
283-
}
284-
}
285-
286-
impl ActivatorInner {
287-
async fn activated(&self) {
288-
debug_assert!(
289-
self.wired_up.load(Ordering::SeqCst),
290-
"nothing should await activation from an activator that hasn't \
291-
been wired up"
292-
);
293-
self.notify.notified().await
294-
}
295-
}
296-
297234
/// Encapsulates state needed by the background tokio task to manage activation
298235
/// of the background task
299236
struct TaskExec {
@@ -303,7 +240,7 @@ struct TaskExec {
303240
imp: Box<dyn BackgroundTask>,
304241
/// used to receive notifications from the Driver that someone has requested
305242
/// explicit activation
306-
activation: Arc<ActivatorInner>,
243+
activation: Activator,
307244
/// passed through to the background task impl when activated
308245
opctx: OpContext,
309246
/// used to send current status back to the Driver
@@ -316,7 +253,7 @@ impl TaskExec {
316253
fn new(
317254
period: Duration,
318255
imp: Box<dyn BackgroundTask>,
319-
activation: Arc<ActivatorInner>,
256+
activation: Activator,
320257
opctx: OpContext,
321258
status_tx: watch::Sender<TaskStatus>,
322259
) -> TaskExec {

0 commit comments

Comments
 (0)