-
Notifications
You must be signed in to change notification settings - Fork 411
Move persist into async part of the sweeper #3819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -382,7 +382,7 @@ where | |
output_spender: O, change_destination_source: D, kv_store: K, logger: L, | ||
) -> Self { | ||
let outputs = Vec::new(); | ||
let sweeper_state = Mutex::new(SweeperState { outputs, best_block }); | ||
let sweeper_state = Mutex::new(SweeperState { outputs, best_block, dirty: false }); | ||
Self { | ||
sweeper_state, | ||
pending_sweep: AtomicBool::new(false), | ||
|
@@ -446,7 +446,10 @@ where | |
} | ||
self.persist_state(&*state_lock).map_err(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}) | ||
})?; | ||
state_lock.dirty = false; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Returns a list of the currently tracked spendable outputs. | ||
|
@@ -503,11 +506,19 @@ where | |
|
||
// See if there is anything to sweep before requesting a change address. | ||
{ | ||
let sweeper_state = self.sweeper_state.lock().unwrap(); | ||
let mut sweeper_state = self.sweeper_state.lock().unwrap(); | ||
|
||
let cur_height = sweeper_state.best_block.height; | ||
let has_respends = sweeper_state.outputs.iter().any(|o| filter_fn(o, cur_height)); | ||
if !has_respends { | ||
// If there is nothing to sweep, we still persist the state if it is dirty. | ||
if sweeper_state.dirty { | ||
self.persist_state(&sweeper_state).map_err(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
})?; | ||
sweeper_state.dirty = false; | ||
} | ||
|
||
return Ok(()); | ||
} | ||
} | ||
|
@@ -531,7 +542,8 @@ where | |
.collect(); | ||
|
||
if respend_descriptors.is_empty() { | ||
// It could be that a tx confirmed and there is now nothing to sweep anymore. | ||
// It could be that a tx confirmed and there is now nothing to sweep anymore. If there is dirty state, | ||
// we'll persist it in the next cycle. | ||
return Ok(()); | ||
} | ||
|
||
|
@@ -563,6 +575,7 @@ where | |
self.persist_state(&sweeper_state).map_err(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
})?; | ||
sweeper_state.dirty = false; | ||
|
||
self.broadcaster.broadcast_transactions(&[&spending_tx]); | ||
} | ||
|
@@ -588,6 +601,8 @@ where | |
} | ||
true | ||
}); | ||
|
||
sweeper_state.dirty = true; | ||
joostjager marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
fn persist_state(&self, sweeper_state: &SweeperState) -> Result<(), io::Error> { | ||
|
@@ -641,13 +656,17 @@ where | |
} | ||
} | ||
} | ||
|
||
sweeper_state.dirty = true; | ||
} | ||
|
||
fn best_block_updated_internal( | ||
&self, sweeper_state: &mut SweeperState, header: &Header, height: u32, | ||
) { | ||
sweeper_state.best_block = BestBlock::new(header.block_hash(), height); | ||
self.prune_confirmed_outputs(sweeper_state); | ||
|
||
sweeper_state.dirty = true; | ||
} | ||
} | ||
|
||
|
@@ -671,12 +690,8 @@ where | |
assert_eq!(state_lock.best_block.height, height - 1, | ||
"Blocks must be connected in chain-order - the connected block height must be one greater than the previous height"); | ||
|
||
self.transactions_confirmed_internal(&mut *state_lock, header, txdata, height); | ||
self.best_block_updated_internal(&mut *state_lock, header, height); | ||
|
||
let _ = self.persist_state(&*state_lock).map_err(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}); | ||
self.transactions_confirmed_internal(&mut state_lock, header, txdata, height); | ||
self.best_block_updated_internal(&mut state_lock, header, height); | ||
} | ||
|
||
fn block_disconnected(&self, header: &Header, height: u32) { | ||
|
@@ -698,9 +713,7 @@ where | |
} | ||
} | ||
|
||
self.persist_state(&*state_lock).unwrap_or_else(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}); | ||
state_lock.dirty = true; | ||
} | ||
} | ||
|
||
|
@@ -720,9 +733,6 @@ where | |
) { | ||
let mut state_lock = self.sweeper_state.lock().unwrap(); | ||
self.transactions_confirmed_internal(&mut *state_lock, header, txdata, height); | ||
self.persist_state(&*state_lock).unwrap_or_else(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}); | ||
} | ||
|
||
fn transaction_unconfirmed(&self, txid: &Txid) { | ||
|
@@ -743,18 +753,13 @@ where | |
.filter(|o| o.status.confirmation_height() >= Some(unconf_height)) | ||
.for_each(|o| o.status.unconfirmed()); | ||
|
||
self.persist_state(&*state_lock).unwrap_or_else(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}); | ||
state_lock.dirty = true; | ||
} | ||
} | ||
|
||
fn best_block_updated(&self, header: &Header, height: u32) { | ||
let mut state_lock = self.sweeper_state.lock().unwrap(); | ||
self.best_block_updated_internal(&mut *state_lock, header, height); | ||
let _ = self.persist_state(&*state_lock).map_err(|e| { | ||
log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); | ||
}); | ||
self.best_block_updated_internal(&mut state_lock, header, height); | ||
} | ||
|
||
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> { | ||
|
@@ -783,11 +788,13 @@ where | |
struct SweeperState { | ||
outputs: Vec<TrackedSpendableOutput>, | ||
best_block: BestBlock, | ||
dirty: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We had that discussion before: I'd really prefer it if we don't mix in runtime state with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Last time, I created that isolation, but then reverted in favor of an atomic boolean. Which direction would you suggest taking it with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I would much prefer to just have another There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type of flows that I'd like to avoid is stuff like: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try out here: main...joostjager:rust-lightning:sweeper-async-persist-atomicbool I definitely feel all those cases pop up within me if I use that atomic bool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So there a two aspects here why I dislike the idea of pushing the runtime flags into
It might also be noteworthy that post- TLDR: I'd prefer to continue to have the runtime There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After more exploration of the atomic bool direction, I couldn't get rid of the suggested or real race conditions. I kept the dirty flag as part of the state, and separated it from the persistent fields. Let me know what you think. Regarding that separation, I do want to point to #3618 (comment). Opinions and also current practices vary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tnull re: (1) can you elaborate on why you see the Re: (2) I'm not sure I'm following because even if we use an atomic bool we'll still take the sweeper lock at least once in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, as mentioned above and discussed offline, we can go this direction if you guys prefer it, as long as we maintain the separation of concerns, and drop the mutable reference from
The separation of data and logic is a nice principle to uphold, as it often makes reasoning about the code and later refactorings easier. I agree that we rarely follow that principle, but the sweeper is one of the few places where we did, and consciously chose to do so during the original review discussion. Would be great to maintain that.
Right, but block connections might be driven by another concurrent task, which might lead to unnecessarily re-acrquiring the mutex guard, just to check if we still have to persist once the BP loop gets around to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think this is the case, because before #3734 (where state separation was also a topic of discussion), there was no runtime-only state in the sweeper? In the last push on this PR, I've added the state separation but not the atomic boolean. |
||
} | ||
|
||
impl_writeable_tlv_based!(SweeperState, { | ||
(0, outputs, required_vec), | ||
(2, best_block, required), | ||
(_unused, dirty, (static_value, false)), | ||
}); | ||
|
||
/// A `enum` signalling to the [`OutputSweeper`] that it should delay spending an output until a | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems this is redundant, but probably also can't hurt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving as is, to at least flag dirty in the functions that change state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not doing the RAII pattern. There are just a few places where dirty needs to be cleared, and seems fine for now.