Repository navigation
make multirack-join-complete return latest bootstore generation - #11710
nicolaskagami wants to merge 4 commits into
Conversation
76a06df to
3b32f4e
Compare
andrewjstone
left a comment
There was a problem hiding this comment.
Great stuff. Love the tests.
| membership_change_still_possible, | ||
| written_bootstore_generation_rx, | ||
| rack_join_successful_tx: Some(rack_join_successful_tx), | ||
| result_rx: result_rx.shared(), |
There was a problem hiding this comment.
Neat trick using shared to allow polling this in multiple places at once, I've actually never encountered this before!
|
|
||
| // The task has only written an earlier generation. Its request is | ||
| // the same, as when an update is reverted. | ||
| task.written_bootstore_generation_tx.send_replace(1); |
There was a problem hiding this comment.
Why send_replace in these tests instead of just send? Since we're not looking at the return value, I think effectively the only difference is that unlike send it won't fail if there are no receivers, but it seems like that would be unexpected enough that maybe such an error should trigger a test failure.
There was a problem hiding this comment.
I was using send_replace in tests simply because that's what the prod code is doing (where we don't want to fail if the receivers are dead). But you're right, that should not be true in these tests so it's just a tighter test. Fix incoming.
| pub(crate) async fn complete_multirack_join( | ||
| &self, | ||
| ) -> Result<u64, RssAccessError> { | ||
| let result_rx = match &mut *self.status.lock().unwrap() { |
There was a problem hiding this comment.
I'm wondering if this sync lock becomes dangerous now that this is being changed to an async function that can block waiting for the multirack join result - do we need a manual drop call after the match?
Or...maybe it's fine because we're matching on the result of the lock() call and it goes out of scope at the end of the match block, before we call wait_for_multirack_join_result? I can't remember off the top of my head exactly how the drop rules work in this kind of situation. Even if this is actually fine though I'm thinking we might want to put in a comment to warn anyone who modifies this code in the future.
There was a problem hiding this comment.
Yeah. I wasn't super happy with the visibility here, but it's definitely safe because the lock would be dropped before. I'll change it to something clearer.
There was a problem hiding this comment.
Ah, now I remember why I did it this way. I can't have status declared outside the result_rx statement like it was before because it would make the future !Send, even if I drop it before the yield point (rust-lang/rust#57478). And if I put it inside the block I'd have to re-indent the whole thing (which I don't like much for visibility). I think it's own function would be overkill as it would leave complete_multirack_join basically empty. Anyway, I prefer just adding a comment on how it's safe.
| // the same, as when an update is reverted. | ||
| task.written_bootstore_generation_tx.send_replace(1); | ||
| assert_matches!( | ||
| rss.complete_multirack_join().now_or_never(), |
There was a problem hiding this comment.
The uses of now_or_never in these tests do make me a little nervous. It feels like it's possible these tests could be racy, because it's not easy to tell if the futures are guaranteed to resolve or if the tests just happen to work because of the way the timing (usually?) lines up. Is this just being done to time out quickly if something goes wrong? I wonder if using more conventional timeouts might be safer.
There was a problem hiding this comment.
No. Actually, these are strictly more reliable than with timeouts. If you look at how complete_multirack_join works you'll see that every use of it in tests is deterministic. That should be obvious for the Some cases, but the None cases also assert that they haven't failed yet (and poll once, which makes them ready to proceed). I think the only problem with it is that it's slightly implementation-gnostic.
Fixes: #11549
Context
What does this PR change?
multirack-join-completereturns the latestbootstore_generationwritten by the join service. Repeated requests are idempotent.multirack-join-completewaits for the join service to finish, so Nexus can definitively know the bootstore generation was correctly written. This may also be useful in the future to signal other stuff to Nexus. However, we may not want to wait (which may include finishingstart_sled_agents).multirack-join-completecould arrive while a new configuration had been accepted but not written yet. Decided with @andrewjstone to deny the join-complete request and apply the accepted config. Returns 409MultirackJoinPendingConfigUpdatein that case and Nexus should retry later.multirack-join-completereturns 409MultirackJoinGenerationUnavailable, since the generation is only kept in memory. Multirack Join Completion response should return the bootstore generation number #11549 mentions this is out of scope.Details