Skip to content

Commit 502967c

Browse files
authored
Use data-bearing enum variants pattern in negotiation (#1727)
During negotiation, there are certain pieces of data which are only relevant in particular states: - Before connecting, the client has a `oneshot::Sender<()>` which kicks off the connection when triggered - When waiting for a quorum, each client stores a `RegionMetadata`, to be used when computing reconciliation Previously, these two pieces of data were always present in the `DownstairsClient` object, and were checked (or unwrapped) at the relevant points in negotiation. This PR moves them into the relevant `NegotiationState` variants, so they are _guaranteed_ to be present at the right times. Unfortunately, `NegotiationState` is also used (as a member of `DsState`) in serialization, for both OpenAPI calls and DTrace probes. We can't serialize or log a oneshot sender (or region metadata), which is a problem! The **vast majority** of this PR is separating the serialization-friendly subset of data (`DsState` and `NegotiationState`) from the data-bearing variants (new `DsStateData` and `NegotiationStateData`). There's also one subtlety: the PR adds a new `NegotiationState::WaitConnect(oneshot::Sender<()>)`. Previously, `NegotiationState::Start` represented both "waiting for connection oneshot to fire" and "waiting for connection to the Downstairs" (depending on whether `ClientTaskHandle::client_connect_tx` was present or not). This change makes those two sub-states distinct.
1 parent 957349a commit 502967c

File tree

11 files changed

+448
-347
lines changed

11 files changed

+448
-347
lines changed

Cargo.lock

Lines changed: 4 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ slog-term = { version = "2.9" }
9999
static_assertions = "1.1.0"
100100
statistical = "1.0.0"
101101
subprocess = "0.2.9"
102-
strum = "0.27"
102+
strum = { version = "0.27", features = ["derive"] }
103103
strum_macros = "0.27"
104104
tempfile = "3"
105105
test-strategy = "0.4.1"

cmon/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ enum Action {
8686
Repair,
8787
}
8888

89-
// Translate a DsState into a three letter string for printing.
89+
/// Translate a [`DsState`] into a three letter string for printing.
9090
fn short_state(dss: DsState) -> String {
9191
match dss {
9292
DsState::Connecting {

openapi/crucible-control.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@
280280
]
281281
},
282282
"DsState": {
283-
"description": "High-level states for a Downstairs\n\nThe state machine for a Downstairs is relatively simple:\n\n```text ┌────────────┐ ┌────► LiveRepair ├─────┐ start ┌─────────┴┐ └─────┬──────┘ ┌─▼──────┐ ────►│Connecting│ │ │Stopping│ └─▲───────┬┘ ┌─────▼──────┐ └─▲────┬─┘ │ └────► Active ├─────┘ │ │ └─────┬──────┘ │ │ │ │ └─────────────────◄┴─────────────────┘ ```\n\nComplexity is hidden in the `Connecting` state, which wraps a [`NegotiationState`] implementing the negotiation state machine.",
283+
"description": "High-level states for a Downstairs\n\nThe state machine for a Downstairs is relatively simple:\n\n```text ┌────────────┐ ┌────► LiveRepair ├─────┐ start ┌─────────┴┐ └─────┬──────┘ ┌─▼──────┐ ────►│Connecting│ │ │Stopping│ └─▲───────┬┘ ┌─────▼──────┐ └─▲────┬─┘ │ └────► Active ├─────┘ │ │ └─────┬──────┘ │ │ │ │ └─────────────────◄┴─────────────────┘ ```\n\nComplexity is hidden in the `Connecting` state, which wraps a [`NegotiationStateData`] implementing the negotiation state machine.\n\nThis is a *generic* downstairs state; it's specialized to either [`DsStateData`] (which is data-bearing) or [`DsState`] (which is serializable). The `#[derive(..)]` annotations for serialization only apply if the generic parameter `N` is serializable, so it only applies to `DsState`.",
284284
"oneOf": [
285285
{
286286
"description": "New connection",
@@ -384,8 +384,23 @@
384384
]
385385
},
386386
"NegotiationState": {
387-
"description": "Tracks client negotiation progress\n\nThe exact path through negotiation depends on the [`ConnectionMode`].\n\nThere are three main paths, shown below:\n\n```text ┌───────┐ │ Start │ └───┬───┘ │ ┌───────▼────────┐ │ WaitForPromote │ └───────┬────────┘ │ ┌────────▼──────────┐ │ WaitForRegionInfo │ └──┬──────────────┬─┘ Offline │ │ New / Faulted / Replaced (replay) │ ┌────▼────────────┐ │ │GetExtentVersions│ │ └─┬─────────────┬─┘ │ │ New │ Faulted / Replaced │ ┌──────▼───┐ ┌────▼──────────┐ │ │WaitQuorum│ │LiveRepairReady│ │ └────┬─────┘ └────┬──────────┘ │ │ │ │ ┌────▼────┐ │ │ │Reconcile│ │ │ └────┬────┘ │ │ │ │ │ ┌───▼──┐ │ └─────► Done ◄────────────┘ └──────┘ ```\n\n`Done` isn't actually present in the state machine; it's indicated by returning a [`NegotiationResult`] other than [`NegotiationResult::NotDone`].",
387+
"description": "Auto-generated discriminant enum variants",
388388
"oneOf": [
389+
{
390+
"description": "One-shot sender to ask the client to open its connection\n\nThis is used to hold the client (without connecting) in cases where we have deliberately deactivated this client.",
391+
"type": "object",
392+
"properties": {
393+
"type": {
394+
"type": "string",
395+
"enum": [
396+
"wait_connect"
397+
]
398+
}
399+
},
400+
"required": [
401+
"type"
402+
]
403+
},
389404
{
390405
"description": "After connecting, waiting to hear `YesItsMe` from the client\n\nOnce this message is heard, sends `PromoteToActive` and transitions to `WaitForPromote`",
391406
"type": "object",

upstairs/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ schemars.workspace = true
4343
semver.workspace = true
4444
serde.workspace = true
4545
serde_json.workspace = true
46-
slog.workspace = true
4746
slog-async.workspace = true
4847
slog-dtrace.workspace = true
4948
slog-term.workspace = true
50-
tokio.workspace = true
51-
tokio-util.workspace = true
49+
slog.workspace = true
50+
strum.workspace = true
5251
tokio-rustls.workspace = true
52+
tokio-util.workspace = true
53+
tokio.workspace = true
5354
toml.workspace = true
5455
tracing.workspace = true
5556
usdt.workspace = true

0 commit comments

Comments
 (0)