Skip to content

Do not allocate local storage for deleted disks#10754

Open
jmpesp wants to merge 5 commits into
oxidecomputer:mainfrom
jmpesp:no_zombie_allocations
Open

Do not allocate local storage for deleted disks#10754
jmpesp wants to merge 5 commits into
oxidecomputer:mainfrom
jmpesp:no_zombie_allocations

Conversation

@jmpesp

@jmpesp jmpesp commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

During the sled resource insertion query, do not perform local storage allocations if the higher level disk has been deleted. It is very unlikely that an instance reservation and disk delete would race this way (as disk delete requires a detach first), but we did see it in the field! Amazing really...

Fixes oxidecomputer/customer-support#1321

During the sled resource insertion query, do not perform local storage
allocations if the higher level disk has been deleted. It is very
unlikely that an instance reservation and disk delete would race this
way (as disk delete requires a detach first), but we _did_ see it in the
field! Amazing really...

Fixes oxidecomputer#1321
@jmpesp jmpesp requested review from hawkw and smklein July 6, 2026 21:30
Comment on lines +589 to +593
.first_async(&*conn)
.await
.map_err(|e| {
public_error_from_diesel(e, ErrorHandler::Server)
})?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will error out if the disk has been hard-deleted. is that the behavior we want, or should we instead treat a NotFound as being equivalent to the time_deleted being set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're currently only soft-deleting disk records so I'm happy leaving this as is.

.first_async(&*conn)
.await
.map_err(|e| {
public_error_from_diesel(e, ErrorHandler::Server)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be worth adding something to the error here to say what failed, like:

Suggested change
public_error_from_diesel(e, ErrorHandler::Server)
public_error_from_diesel(e, ErrorHandler::Server).internal_context(format!("failed to look up disk {disk_id}")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k, done in 5383be7


let zpools_for_sled = datastore
.zpool_get_for_sled_reservation(opctx, self.sled_target)
.await?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, might be nice to slap an internal_context on this so we know what failed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

let zpools_for_sled = datastore
.zpool_get_for_sled_reservation(opctx, self.sled_target)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be worth adding a version of this that takes a &conn so we can reuse the connection we already checked out of the pool --- especially since we will keep holding onto it for the entire duration of this funcion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, and even more so due to the Backends exist, and appear online, but all claims are used errors showing up in CI. Done in 0a9df40

Comment on lines +4747 to +4748
// Allow `transaction_async`; this is a test, and does not need to retry
#[allow(clippy::disallowed_methods)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take it or leave it: it might be a little nicer to put this specifically on the transaction_async call, rather than allowing it for the entire function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I originally did this wrong and saw

error[E0658]: attributes on expressions are experimental
    --> nexus/db-queries/src/db/datastore/sled.rs:4752:13
     |
4752 |             #[allow(clippy::disallowed_methods)]
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information

For more information about this error, try `rustc --explain E0658`.

because I tried to put it over the call to transaction_async instaed of over the let .... Changed in 6fa00f7

.await
.unwrap();

assert!(disks_with_zombie_allocations.is_empty());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take it or leave it: maybe

Suggested change
assert!(disks_with_zombie_allocations.is_empty());
assert_eq!(disks_with_zombie_allocations, &[]);

so that the panic message will also include the list of disks with zombie allocations, if this assert fails?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea. Turns out though that you have to be very explicit about the type of the empty Vec. Done in c9d158d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants