Skip to content

Instance state change gets 404 during start saga; Nexus marks VMM as Failed but propolis keeps running. #11560

Description

@paudmir

While executing tests under our test_all_disk_types.py in Dogfood, we observed a couple of crashes that spawned from race conditions between start sagas and state changes.

From the the core file:

::status
debugging core file of propolis-server (64-bit) from oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba
initial argv: /opt/oxide/propolis-server/bin/propolis-server run /opt/oxide/propolis-server/b
threading model: native threads
status: process terminated by SIGABRT (Abort), pid=17579 uid=0 code=-1

::stack ! demangle
libc.so.1`_lwp_kill+0xa()
libc.so.1`raise+0x22(6)
libc.so.1`abort+0x58()
~std::sys::pal::unix::abort_internal+8()
~std::process::abort+8()
~__rustc::__rust_abort+8()
~__rustc::__rust_start_panic+8()
__rustc::rust_panic+0xd()
std::panicking::panic_with_hook+0x113()
std::panicking::panic_handler::{closure#0}+0x41()
~std::sys::backtrace::__rust_end_short_backtrace::<std::panicking::panic_handler::{closure#0}, !>+8()
~__rustc::rust_begin_unwind+0x1b()
~core::panicking::panic_fmt+0x2a()
~<crucible::client::DownstairsClient>::continue_negotiation+0x1380()
<crucible::upstairs::Upstairs>::on_client_message+0x291()
<crucible::upstairs::Upstairs>::apply+0x11f9()
crucible::up_main::{closure#1}+0x593()
<tokio::runtime::task::core::Core<crucible::up_main::{closure#1}, alloc::sync::Arc<tokio::runtime::scheduler::current_thread::Handle>>>::poll+0x32()
<tokio::runtime::task::harness::Harness<crucible::up_main::{closure#1}, alloc::sync::Arc<tokio::runtime::scheduler::multi_thread::handle::Handle>>>::poll+0x5b()
<tokio::runtime::scheduler::multi_thread::worker::Context>::run_task+0x13f()
<tokio::runtime::scheduler::multi_thread::worker::Context>::run+0x88b()
<tokio::runtime::context::scoped::Scoped<tokio::runtime::scheduler::Context>>::set::<tokio::runtime::scheduler::multi_thread::worker::run::{closure#0}::{closure#0}, ()>+0x27()
tokio::runtime::context::runtime::enter_runtime::<tokio::runtime::scheduler::multi_thread::worker::run::{closure#0}, ()>+0x1cd()
tokio::runtime::scheduler::multi_thread::worker::run+0xa0()
<tokio::runtime::task::core::Core<tokio::runtime::blocking::task::BlockingTask<<tokio::runtime::scheduler::multi_thread::worker::Launch>::launch::{closure#0}>, tokio::runtime::blocking::schedule::BlockingSchedule>>::poll+0x6c()
<tokio::runtime::task::harness::Harness<tokio::runtime::blocking::task::BlockingTask<<tokio::runtime::scheduler::multi_thread::worker::Launch>::launch::{closure#0}>, tokio::runtime::blocking::schedule::BlockingSchedule>>::poll+0x8a()
<tokio::runtime::blocking::pool::Inner>::run+0xf3()
std::sys::backtrace::__rust_begin_short_backtrace::<<tokio::runtime::blocking::pool::Spawner>::spawn_thread::{closure#0}, ()>+0x3e()
<std::thread::lifecycle::spawn_unchecked<<tokio::runtime::blocking::pool::Spawner>::spawn_thread::{closure#0}, ()>::{closure#1} as core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}+0x50()
<std::sys::thread::unix::Thread>::new::thread_start+0x1dd()
libc.so.1`_thrp_setup+0x77(fffffc7feea35a40)
libc.so.1`_lwp_start()

In Nexus logs, we see that we attempt to stop the instance, but we get a sled agent API error (404)

2026-10-16 23:06:29.120Z INFO 3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de/11297 (dropshot_external) on oxz_nexus_3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de: set intended instance state
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    intended_state = running
    method = POST
    req_id = 1f800877-6ee0-4325-8f8c-9cc6c81814d0
    uri = /v1/instances/alldisks-instance-main-4539/start?project=product-assurance
2026-10-16 23:06:29.321Z INFO 3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de/11297 (ServerContext) on oxz_nexus_3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de: sled reservation starting
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    propolis_id = 6daa866f-254c-4d05-96cd-a21d5a8418ba
    reservation_reason = start
2026-10-16 23:06:44.825Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: set intended instance state
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    intended_state = stopped
    method = POST
    req_id = 509fbcbd-8eec-4877-a541-32c8cb1672f0
    uri = /v1/instances/alldisks-instance-main-4539/stop?project=product-assurance
2026-10-16 23:06:44.841Z ERRO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: marking VMM failed due to sled agent API error
    error = Error Response: status: 404 Not Found; headers: {"content-type": "application/json", "x-request-id": "9505f6a8-60a3-427b-a503-137054255c9a", "content-length": "120", "date": "Fri, 16 Oct 2026 23:06:44 GMT"}; value: Error { error_code: Some("NO_SUCH_INSTANCE"), message: "Not Found", request_id: "9505 \u{2026}
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    method = POST
    reason = no_such_instance
    req_id = 509fbcbd-8eec-4877-a541-32c8cb1672f0
    uri = /v1/instances/alldisks-instance-main-4539/stop?project=product-assurance
    vmm_id = 6daa866f-254c-4d05-96cd-a21d5a8418ba
2026-10-16 23:06:44.929Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: marked VMM as Failed, preparing update saga
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    method = POST
    reason = NoSuchInstance
    req_id = 509fbcbd-8eec-4877-a541-32c8cb1672f0
    uri = /v1/instances/alldisks-instance-main-4539/stop?project=product-assurance
    vmm_id = 6daa866f-254c-4d05-96cd-a21d5a8418ba
2026-10-16 23:06:52.631Z INFO 3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de/11297 (ServerContext) on oxz_nexus_3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de: start saga: ensuring instance is registered on sled
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    saga_id = 0c16f444-38ce-4a46-a7ee-dff1eb4cd9fd
    saga_name = instance-start
    start_reason = User
2026-10-16 23:06:53.641Z INFO 3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de/11297 (ServerContext) on oxz_nexus_3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de: start saga: ensuring instance is running
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    saga_id = 0c16f444-38ce-4a46-a7ee-dff1eb4cd9fd
    saga_name = instance-start
    start_reason = User

Reincarnation failed as well

2026-10-16 23:07:01.131Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (ServerContext) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: attempting to reincarnate instance...
    background_task = instance_reincarnation
    instance_id = 6ad10715-8625-46be-9658-0d4fc53a2f55
    instance_state = Failed
    last_auto_restarted_at = None
    reincarnation_reason = instance failed
2026-10-16 23:07:01.183Z WARN 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: request completed after handler was already cancelled
    error_message_external = The instance was running but is no longer reachable. It is being moved to the Failed state.
    error_message_internal = Error Response: status: 404 Not Found; headers: {"content-type": "application/json", "x-request-id": "9505f6a8-60a3-427b-a503-137054255c9a", "content-length": "120", "date": "Fri, 16 Oct 2026 23:06:44 GMT"}; value: Error { error_code: Some("NO_SUCH_INSTANCE"), message: "Not Found", request_id: "9505 \u{2026}
    method = POST
    req_id = 509fbcbd-8eec-4877-a541-32c8cb1672f0
    response_code = 503
    uri = /v1/instances/alldisks-instance-main-4539/stop?project=product-assurance

Later on, we see Propolis initializing the VM and creating the disks:

2026-10-16 23:07:01.308Z INFO propolis-server/17579 on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: Running v0.1.0-939 (93e04e1) master, bhyve API v18, viona API v7
2026-10-16 23:07:01.609Z INFO propolis-server/17579 on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: initializing new VM
    bootrom = /opt/oxide/propolis-server/blob/OVMF_CODE.fd
    use_reservoir = true
2026-10-16 23:07:01.708Z INFO propolis-server/17579 on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: Creating Crucible disk
    backend_id = 2388b54f-0ce8-42e6-9058-5151a445ceae
2026-10-16 23:07:01.757Z INFO propolis-server/17579 on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: Creating Crucible disk
    backend_id = 4499f9ce-25ba-47a5-8547-a10eaa25c613
2026-10-16 23:07:01.758Z INFO propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: Crucible 4499f9ce-25ba-47a5-8547-a10eaa25c613 has session id: 27c6918d-a71a-40dd-adb1-d6a525d93af0

Orphan disk gets deleted:

2026-10-17 00:26:28.504Z INFO 924cf6fe-d2ae-4150-b4bf-7091503eb242/5252 (ServerContext) on oxz_nexus_924cf6fe-d2ae-4150-b4bf-7091503eb242: saga create
    saga_id = a77bedfa-daac-4aa5-b49e-2159faac70d4
    saga_name = disk-delete
    sec_id = 924cf6fe-d2ae-4150-b4bf-7091503eb242
2026-10-17 00:26:29.866Z INFO 3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de/11297 (ServerContext) on oxz_nexus_3cdb3ab4-1a81-4ee3-9c84-2bacc3e9d5de: received downstairs_client_stopped_notification from upstairs 4499f9ce-25ba-47a5-8547-a10eaa25c613 for downstairs 86433a59-7cb8-4b1d-9d57-37d3a923f23a: DownstairsClientStopped { time: 2026-10-17T00:26:29.863442960Z, reason: Finished }
2026-10-17 00:26:29.983Z INFO 924cf6fe-d2ae-4150-b4bf-7091503eb242/5252 (ServerContext) on oxz_nexus_924cf6fe-d2ae-4150-b4bf-7091503eb242: received downstairs_client_stopped_notification from upstairs 4499f9ce-25ba-47a5-8547-a10eaa25c613 for downstairs 758a2326-8ee8-47ed-9708-84eeabc9f01c: DownstairsClientStopped { time: 2026-10-17T00:26:29.866091696Z, reason: Finished }
2026-10-17 00:26:30.075Z INFO 924cf6fe-d2ae-4150-b4bf-7091503eb242/5252 (ServerContext) on oxz_nexus_924cf6fe-d2ae-4150-b4bf-7091503eb242: received downstairs_client_stopped_notification from upstairs 4499f9ce-25ba-47a5-8547-a10eaa25c613 for downstairs 572c862d-6ca3-492e-a69e-87cacdcaf4e9: DownstairsClientStopped { time: 2026-10-17T00:26:30.030490642Z, reason: Finished }
2026-10-17 00:26:36.644Z INFO 924cf6fe-d2ae-4150-b4bf-7091503eb242/5252 (ServerContext) on oxz_nexus_924cf6fe-d2ae-4150-b4bf-7091503eb242: saga finished
    result = success
    saga_id = a77bedfa-daac-4aa5-b49e-2159faac70d4
    saga_name = disk-delete
    sec_id = 924cf6fe-d2ae-4150-b4bf-7091503eb242
2026-10-17 00:26:36.644Z INFO 924cf6fe-d2ae-4150-b4bf-7091503eb242/5252 (ServerContext) on oxz_nexus_924cf6fe-d2ae-4150-b4bf-7091503eb242: saga finished
    saga_id = a77bedfa-daac-4aa5-b49e-2159faac70d4
    saga_name = disk-delete

About ~4h later (and after all three downstairs were dropped), a different region answers and the upstairs panic

2026-10-17 00:26:29.863Z WARN propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: downstairs disconnected
     = downstairs
    client = 0
2026-10-17 00:26:29.865Z WARN propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: downstairs disconnected
     = downstairs
    client = 1
2026-10-17 00:26:30.030Z WARN propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: downstairs disconnected
     = downstairs
    client = 2
2026-10-17 04:55:22.968Z INFO propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: connecting to [fd00:1122:3344:109::4]:19025
     = downstairs
    client = 2
2026-10-17 04:55:22.969Z INFO propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: ds_connection connected from Ok([fd00:1122:3344:128::1:25cc]:51694)
     = downstairs
    client = 2
2026-10-17 04:55:22.970Z INFO propolis-server/17579 (crucible-4499f9ce-25ba-47a5-8547-a10eaa25c613) on oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba: downstairs client at Some([fd00:1122:3344:109::4]:19025) has region UUID 56ec88cb-ac5e-4858-8d8e-906480aeeca4
     = downstairs
    client = 2
thread 'tokio-rt-vmm' (13) panicked at /home/build/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/ad8a317/upstairs/src/client.rs:1488:37:
New client:2 uuid:56ec88cb-ac5e-4858-8d8e-906480aeeca4 does not match existing 572c862d-6ca3-492e-a69e-87cacdcaf4e9 ds_state:Connecting { state: WaitForRegionInfo, mode: Offline } up_state:Active
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Core can be found at /staff/dogfood/BRM22250001/core.oxz_propolis-server_6daa866f-254c-4d05-96cd-a21d5a8418ba.propolis-server.17579.1792212924

Zone bundle can be found at /staff/dogfood/BRM22250001/omicron-11560/6daa866f.tar.gz

Activity

  1. paudmir commented on Oct 22, 2026

    @paudmir
    ContributorAuthor

    Similar race condition for the core at /staff/dogfood/BRM22250001/omicron-11560/core.oxz_propolis-server_cc50860d-2eea-410e-a820-cbb63a32658e.propolis-server.6245.1792278272
    Stack: https://github.com/oxidecomputer/product-assurance/issues/727#issuecomment-6327701929

    VMM record is deleted and an attempt to reincarnate pops up

    2026-10-17 23:03:42.458Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (ServerContext) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: instance update (VMM destroyed): marking VMM record deleted
        instance_id = ab173424-17fa-4e96-8200-4c0d37eb6fa4
        propolis_id = 6bb75bd5-99e9-46ec-93a4-989b421aabf5
        saga_id = 836463a4-42a3-44f4-8beb-c60f7111fd29
        saga_name = instance-update
    2026-10-17 23:03:42.905Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (ServerContext) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: attempting to reincarnate instance...
        background_task = instance_reincarnation
        instance_id = ab173424-17fa-4e96-8200-4c0d37eb6fa4
        instance_state = Failed
        last_auto_restarted_at = None
        reincarnation_reason = instance failed
    2026-10-17 23:03:42.945Z WARN 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: request completed after handler was already cancelled
        error_message_external = The instance was running but is no longer reachable. It is being moved to the Failed state.
        error_message_internal = Error Response: status: 404 Not Found; headers: {"content-type": "application/json", "x-request-id": "4d0897e0-f420-43eb-8df6-69da4f017557", "content-length": "120", "date": "Sat, 17 Oct 2026 23:03:26 GMT"}; value: Error { error_code: Some("NO_SUCH_INSTANCE"), message: "Not Found", request_id: "4d08 \u{2026}
        method = POST
        req_id = 57786059-fd7d-49b2-9ce7-c921960e2b2c
        response_code = 503
        uri = /v1/instances/alldisks-instance-main-68346/stop?project=product-assurance
    2026-10-17 23:03:43.151Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (ServerContext) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: sled reservation starting
        instance_id = ab173424-17fa-4e96-8200-4c0d37eb6fa4
        propolis_id = bdf6f9c1-243d-446d-97eb-bdb9c260896d
        reservation_reason = start
    2026-10-17 23:03:43.642Z INFO 61420090-2657-4888-9d57-8f7e36f652a4/4199 (dropshot_external) on oxz_nexus_61420090-2657-4888-9d57-8f7e36f652a4: set intended instance state
        instance_id = ab173424-17fa-4e96-8200-4c0d37eb6fa4
        intended_state = stopped
        method = POST
        req_id = 12de76eb-fd3e-4e6b-8914-1b7374a96dd9
        uri = /v1/instances/alldisks-instance-main-68346/stop?project=product-assurance
    

    A minute later, propolis show a YouAreNoLongerActive warning and an error mentioning the scrubbing of volume failed.

    2026-10-17 23:04:30.806Z ERRO propolis-server/6245 (crucible-15f13a6e-4a7d-4537-bd6d-73489f37321d) on oxz_propolis-server_cc50860d-2eea-410e-a820-cbb63a32658e: received NoLongerActive 15f13a6e-4a7d-4537-bd6d-73489f37321d 71712c8d-61a5-419b-927d-f1dcd114f1fd 3
         = downstairs
        client = 2
    2026-10-17 23:04:30.807Z WARN propolis-server/6245 (crucible-15f13a6e-4a7d-4537-bd6d-73489f37321d) on oxz_propolis-server_cc50860d-2eea-410e-a820-cbb63a32658e: saw YouAreNoLongerActive with our gen 2 < 3 and same upstairs UUID
         = downstairs
        client = 2
    2026-10-17 23:04:30.807Z WARN propolis-server/6245 (crucible-15f13a6e-4a7d-4537-bd6d-73489f37321d) on oxz_propolis-server_cc50860d-2eea-410e-a820-cbb63a32658e: disabling clients due to NoLongerActive
    2026-10-17 23:04:30.822Z ERRO propolis-server/6245 (crucible-15f13a6e-4a7d-4537-bd6d-73489f37321d) on oxz_propolis-server_cc50860d-2eea-410e-a820-cbb63a32658e: Scrub of volume 15f13a6e-4a7d-4537-bd6d-73489f37321d failed: Upstairs is not active
    thread 'tokio-rt-vmm' (10) panicked at /home/build/.cargo/git/checkouts/crucible-0a48bd218bc2bbbc/bd62980/upstairs/src/client.rs:853:18:
    8c2fe16b-2342-4eb5-98c2-63ef729aa871 [0] enqueue should not be called from state Connecting { state: WaitConnect, mode: New }
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    Zone bundle can be found at /staff/dogfood/BRM22250001/omicron-11560/cc50860d.tar.gz

  2. changed the title [-]Instance state change get 404 during start saga; Nexus marks VMM as Failed but propolis keeps running.[/-] [+]Instance state change gets 404 during start saga; Nexus marks VMM as Failed but propolis keeps running.[/+] on Oct 23, 2026
  3. hawkw commented on Oct 24, 2026

    @hawkw
    Member

    Do we know what's attempting to stop the instances here? Is it an automated product-assurance thing? Nexus is supposed to fail calls to stop an instance that hasn't started yet, but perhaps we've broken that check somehow.

  4. hawkw commented on Oct 24, 2026

    @hawkw
    Member

    I think this logic is probably wrong:

    | InstanceStateChangeRequest::Reboot
    | InstanceStateChangeRequest::Stop => match effective_state {
    InstanceState::Creating
    | InstanceState::Starting

    What I think is happening here is that we are allowing the stop request through to the sled-agent when the VMM's state is (probably) Creating, because a Creating VMM is represented externally as a Starting instance:

    // - If the instance has a VMM but the VMM is "creating", return
    // `InstanceState::Starting`, rather than
    // `InstanceState::Creating`.
    // If we are still creating the VMM, this is because we are
    // attempting to *start* the instance; instances may be created
    // without creating a VMM to run them, and then started later.
    (
    InstanceState::Vmm,
    Some(VmmState::Creating | VmmState::Starting),
    ) => instance::InstanceState::Starting,

    However, when the VMM is Creating, we may not yet have sent an instance-ensure request to the sled-agent to create that VMM, so sled-agent doesn't know it's supposed to have a Propolis process for it yet. In that state, the sled-agent will (correctly) inform Nexus that it doesn't know about that VMM, at which point Nexus will move it to Failed. Only once the VMM has moved to Starting do we know that the sled-agent is aware of it; we should reject requests to stop an instance while the VMM is in the Creating state.

  5. added
    bugSomething that isn't working.
    nexusRelated to nexus
    on Oct 24, 2026
  6. added theissue type on Oct 24, 2026
  7. paudmir commented on Oct 26, 2026

    @paudmir
    ContributorAuthor

    4. Do we know what's attempting to stop the instances here? Is it an automated product-assurance thing? Nexus is supposed to fail calls to stop an instance that hasn't started yet, but perhaps we've broken that check somehow.

    Yups, this s an automated product-assurance thing :) https://github.com/oxidecomputer/product-assurance/blob/main/pytest_tests/tests/functional/test_all_disk_types.py#L502

  8. hawkw commented on Oct 27, 2026

    @hawkw
    Member

    Doing some more digging into why the start saga keeps running in this case. I think I know what's going on there too.

    When the VMM has been marked as Failed by the stop request, which bumped its generation number, the subsequent write to the VMM record when the instance-start saga's sis_ensure_registered action tries to advance the VMM from Creating to Starting fails because the generation number has advanced by the transition to Failed. However, when we write the new VMM state here:

    let err = err.clone();
    let new_runtime = new_runtime.clone();
    async move {
    let vmm_update_result = self
    .vmm_update_runtime_on_connection(
    &conn,
    &vmm_id,
    new_runtime,
    )
    .await?;
    let found_vmm = vmm_update_result.found;
    let vmm_updated = match vmm_update_result.status {
    UpdateStatus::Updated => true,
    UpdateStatus::NotUpdatedButExists => false
    };
    let migration_out_updated = match migration_out {
    Some(migration) => {
    let r = self.migration_update_source_on_connection(
    &conn, &vmm_id, migration,
    )
    .await?;

    we just...say "okay, it wasn't updated", and don't indicate that it has transitioned to a state that is incompatible with the update we tried to apply. Then, because notify_vmm_updated succeeded, the call to Nexus::instance_ensure_registered here returns a db::model::Vmm that's...synthesized from the state we received from the sled-agent, rather than reflective of what's actually in the DB:

    Ok(state) => {
    self.notify_vmm_updated(opctx, *propolis_id, &state).await?;
    let runtime = state.vmm_state;
    Ok(db::model::Vmm {
    time_state_updated: runtime.time_updated,
    generation: runtime.generation.into(),
    state: DbVmmState::from(runtime.state),
    failure_reason: db::model::VmmFailureReason::from_vmm_state(
    runtime.state,
    ),
    ..initial_vmm.clone()
    })

    Basically, we are assuming that because notify_vmm_updated did not fail, whatever we were trying to write is actually the instance's state, but because that function returns Ok when the generation number has not advanced (intended to make it idempotent), we are essentially lying about what the state that's been written is. Then, the saga just keeps on going under the assumption that the VMM has advanced to Starting, when it really should unwind since the VMM is now Failed.

    I think notify_vmm_updated and the DataStore::vmm_and_migration_update_runtime function it calls need to return the VmmRuntimeState` in the db, regardless of whether or not the generation has been advanced by that call. That way, the caller can see if the state they were trying to write has made it to the database, and (in the saga case), we can unwind if the VMM has failed or been destroyed.

  9. self-assigned this
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething that isn't working.nexusRelated to nexus

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions