Skip to content
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

Add utils to persist scorer in BackgroundProcessor #1416

Merged
merged 2 commits into from May 4, 2022

Conversation

jurvis
Copy link
Contributor

@jurvis jurvis commented Apr 10, 2022

Addresses #1191

Implementation

  1. Added a super-trait WriteableScore that makes LockableScore writeable.
  2. Added a new parameter to BackgroundProcessor.start that takes in our new WriteableScore
  3. Added a new persist_scorer function to Persister, and implemented it with BufWriter (placeholder; to update after Pipe filesystem writes in lightning-persister through BufWriter #1404) is merged

Notes/Questions

(see diff comments)

Ok(())
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a placeholder implementation I borrowed from combining ideas from the sample and #1404. I will change it after that PR is merged.

I reckon it is probably best to use utils::write_to_file, but that currently requires D to be DiskWriteable, which may conflict with our new trait WriteableScore. Any advice?

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can implement DiskWriteable for any WriteableScore to fix it. DiskWriteable is also removed in #1417 so you can also wait for that to go in first (but I'll let you and @johncantrell97 figure out which order y'all want to land these).

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah I think you'll want to use utils::write_to_file for this. in my PR I have removed DiskWriteable and updated write_to_file to operate on any object that implements Writeable.

you'll also just need to implement persist_scorer on the blanket implementation of Persister using the new KVStorePersister trait. this just means making sure the scorer is Writeable. at that point it will automatically be implemented using write_to_file for FilesystemPersister.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@johncantrell97 got it, thanks for clarifying!

@codecov-commenter
Copy link

codecov-commenter commented Apr 10, 2022

Codecov Report

Merging #1416 (e06bfdf) into main (62edee5) will increase coverage by 0.99%.
The diff coverage is 93.33%.

@@            Coverage Diff             @@
##             main    #1416      +/-   ##
==========================================
+ Coverage   90.88%   91.87%   +0.99%     
==========================================
  Files          75       75              
  Lines       41517    47909    +6392     
  Branches    41517    47909    +6392     
==========================================
+ Hits        37734    44018    +6284     
- Misses       3783     3891     +108     
Impacted Files Coverage Δ
lightning/src/routing/scoring.rs 95.28% <ø> (+0.92%) ⬆️
lightning-background-processor/src/lib.rs 94.37% <92.85%> (-0.85%) ⬇️
lightning/src/util/persist.rs 94.73% <100.00%> (+0.98%) ⬆️
lightning/src/util/config.rs 45.05% <0.00%> (-0.78%) ⬇️
lightning/src/util/ser.rs 91.28% <0.00%> (-0.19%) ⬇️
lightning/src/ln/features.rs 99.59% <0.00%> (+0.15%) ⬆️
lightning/src/ln/msgs.rs 86.66% <0.00%> (+0.29%) ⬆️
lightning/src/ln/functional_test_utils.rs 95.96% <0.00%> (+0.40%) ⬆️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 62edee5...e06bfdf. Read the comment docs.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Thanks! Sorry for the review delay - been travelling for Bitcoin Miami.

lightning-background-processor/src/lib.rs Outdated Show resolved Hide resolved
lightning-background-processor/src/lib.rs Outdated Show resolved Hide resolved
Comment on lines 305 to 309
// Persist Scorer on exit
persister.persist_scorer(&scorer)?;

Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Persist Scorer on exit
persister.persist_scorer(&scorer)?;
Ok(())
// Persist Scorer on exit
persister.persist_scorer(&scorer)?;
Ok(())

Why the failure is ignored and reported Ok() any way? not able to catch this, sorry

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good question. I put this in in #1376 because in the past we always persist channel_manager last and will just simply return the Result from that block.

However, since we added NetworkGraph and Scorer persistence, they run depending on whether or not we can get them from net_graph_msg_handler or scorer. The compiler does not know ahead of time what will exactly run, so it complains if I do not have an Ok(()) there. In other words, Ok() does not run on failure, it just runs once it finishes attempting to persist the channel manager, scorer, and networkgraph, ignoring errors along the way.

Open to any suggestions to improve this, though!

@jurvis jurvis requested a review from TheBlueMatt April 12, 2022 16:43
@jkczyz jkczyz self-requested a review April 12, 2022 19:43
/// use the Persister to persist it.
pub trait WriteableScore<'a>: LockableScore<'a> {
/// Locks the LockableScore and writes it to disk
fn write<W: Writer>(&'a self, writer: &mut W) -> Result<(), io::Error>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, why not just make WriteableScore extend Writeable instead of having a new write function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm, I just assumed from the sample app that we wanted to make sure that the scorer is lockable so we can do .lock().write(). If WriteableScore were to just only extend Writeable, we won't be able to lock it before writing it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, but the write implementation of the LockableScore will presumably do the locking internally, which is also totally fine.

Ok(())
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can implement DiskWriteable for any WriteableScore to fix it. DiskWriteable is also removed in #1417 so you can also wait for that to go in first (but I'll let you and @johncantrell97 figure out which order y'all want to land these).

@jurvis
Copy link
Contributor Author

jurvis commented Apr 13, 2022

sounds good, thanks for your review. @johncantrell97 it looks like you have some good stuff in that PR so I'll let you merge that in first and build scorer persistence around your changes!

@TheBlueMatt
Copy link
Collaborator

Needs rebase now that #1417 has landed.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Nice! Thanks. Some relatively minor nits and minor comments.

impl<'a, U: Writeable, T: LockableScore<'a>> WriteableScore<'a> for T
where T::Locked: DerefMut<Target=U>
{
fn write<W: Writer>(&'a self, writer: &mut W) -> Result<(), io::Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: tabs not spaces.

/// use the Persister to persist it.
pub trait WriteableScore<'a>: LockableScore<'a> {
/// Locks the LockableScore and writes it to disk
fn write<W: Writer>(&'a self, writer: &mut W) -> Result<(), io::Error>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, but the write implementation of the LockableScore will presumably do the locking internally, which is also totally fine.

///
/// We need this trait to be able to pass in a scorer to `lightning-background-processor` that will enable us to
/// use the Persister to persist it.
pub trait WriteableScore<'a>: LockableScore<'a> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice, I think with this we can simplify some of the bindings changes we've been maintaining on previous versions. Don't need to bother with it here but I'll take a look as a followup after we land this.

@@ -277,6 +285,12 @@ impl BackgroundProcessor {
if let Some(ref handler) = net_graph_msg_handler {
persister.persist_graph(handler.network_graph())?;
}

// Persist Scorer on exit
if let Some(ref scorer) = scorer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: lets do this before the network graph.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be worth doing the same in the loop?

@@ -418,6 +434,10 @@ mod tests {
fn with_manager_error(self, error: std::io::ErrorKind, message: &'static str) -> Self {
Self { manager_error: Some((error, message)), ..self }
}

fn with_scorer_error(self, error: std::io::ErrorKind, message: &'static str) -> Self {
Self { scorer_error: Some((error, message)), ..self }
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like the scorer_error isn't used - need to update the persist method below.


/// Persist the given [`WriteableScore`] to disk, returning an error if persistence failed.
fn persist_scorer(&self, scorer: &'a S) -> Result<(), io::Error> {
self.persist("scorer", scorer)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Lets mention the key used here in the docs, sadly that's currently part of the public API ;/.

@jurvis jurvis force-pushed the jurvis/persist-scorer branch 3 times, most recently from bafd8f8 to 6097f18 Compare April 24, 2022 15:32
where T: LockableScore<'a> + Writeable
{}

impl<'a, T> LockableScore<'a> for Arc<T>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can be blanket, no? Instead of implementing it only for Arc we can implement it for any Deref<T> where T: LockableScore and T::Locked: Writeable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

touché, not sure why I didn't think of that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@TheBlueMatt I tried implementing a blanket using what you suggested:

impl<'a, T, S> LockableScore<'a> for S
	where S: Deref<Target = T>,
		T: LockableScore<'a>,
		T::Locked: Writeable {
	type Locked = T::Locked;

	fn lock(&'a self) -> Self::Locked {
		self.deref().lock()
	}
}

but it looks like it may be too generic as it conflicts with our implementation of LockableScore<'a> for Mutex and RefCell a couple of lines below. thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, yea, lets not go that way - instead lets just make the WriteableScore we pass to the BackgroundProcessor actually a Deref<WriteableScore>, then we can drop both of the Arc implementations. You'll need to have two bounds - one for the WriteableScore and one for the Deref as you'll want to re-use the WriteableScore bound on the `Persister.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Two comments left, then I'm happy :)

lightning/src/util/persist.rs Outdated Show resolved Hide resolved
lightning-background-processor/src/lib.rs Outdated Show resolved Hide resolved
vincenzopalazzo
vincenzopalazzo previously approved these changes May 2, 2022
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

LGTM! Can you rebase and clean up the git history here a bit? You can make it one commit if you really want, but either way lets not clean up previous commits in later ones.

lightning/src/util/persist.rs Outdated Show resolved Hide resolved
@jurvis
Copy link
Contributor Author

jurvis commented May 2, 2022

@TheBlueMatt fixed and squashed.

either way lets not clean up previous commits in later ones.

I didn't know what you meant by this, but all the commits look like they are squashable so I did it anyways.

TheBlueMatt
TheBlueMatt previously approved these changes May 2, 2022
@TheBlueMatt
Copy link
Collaborator

I didn't know what you meant by this, but all the commits look like they are squashable so I did it anyways.

Right, I just meant that you had a commit that added some code (eg the macro), and then a later commit that removed that code.

Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Looks good! Mostly just some trivial clean-ups / nits.

where M::Target: 'static + chain::Watch<Signer>,
T::Target: 'static + BroadcasterInterface,
K::Target: 'static + KeysInterface<Signer = Signer>,
F::Target: 'static + FeeEstimator,
L::Target: 'static + Logger,
S: WriteableScore<'a>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add trailing comma

where M::Target: 'static + chain::Watch<Signer>,
T::Target: 'static + BroadcasterInterface,
K::Target: 'static + KeysInterface<Signer = Signer>,
F::Target: 'static + FeeEstimator,
L::Target: 'static + Logger,
S: WriteableScore<'a>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add trailing comma

Comment on lines 146 to 148
impl<'a, T> WriteableScore<'a> for T
where T: LockableScore<'a> + Writeable
{}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can fit on one line within a 100 characters, which is our aspirational line length limit. 🙂

@@ -171,9 +173,11 @@ impl BackgroundProcessor {
NG: 'static + Deref<Target = NetGraphMsgHandler<G, CA, L>> + Send + Sync,
UMH: 'static + Deref + Send + Sync,
PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, L, UMH>> + Send + Sync,
S: 'static + Deref<Target = SC> + Send + Sync,
SC: WriteableScore<'a>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add trailing comma

@@ -277,6 +285,12 @@ impl BackgroundProcessor {
if let Some(ref handler) = net_graph_msg_handler {
persister.persist_graph(handler.network_graph())?;
}

// Persist Scorer on exit
if let Some(ref scorer) = scorer {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be worth doing the same in the loop?

@@ -411,12 +426,13 @@ mod tests {
graph_error: Option<(std::io::ErrorKind, &'static str)>,
manager_error: Option<(std::io::ErrorKind, &'static str)>,
filesystem_persister: FilesystemPersister,
scorer_error: Option<(std::io::ErrorKind, &'static str)>
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this before the prior field, which feels more logically consistent.

@@ -632,7 +663,8 @@ mod tests {
let data_dir = nodes[0].persister.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let event_handler = |_: &_| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone());
let scorer = Arc::new(Mutex::new(test_utils::TestScorer::with_penalty(0)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and later tests, if the test doesn't explicitly check scoring, can we pass in None instead? Alternatively, add it the test Node struct so it only needs to be created in one place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I wasn't able to use just None because the compiler wasn't able to infer the type:

cannot infer type for type parameter S declared on the associated function

so I added None::<Arc<Mutex<FixedPenaltyScorer>>>.

I know FixedPenaltyScorer is deprecated, however, when I tried None::<Arc<Mutex<ProbabilisticScorer<NetworkGraph, TestLogger>>>, the compiler complains:

669 | ...e(), None::<Arc<Mutex<ProbabilisticScorer<NetworkGraph, Logger>>>>);
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Deref` is not implemented for `NetworkGraph`

is that a problem?

also, should we create a follow-up issue for updating TestScorer to return a ProbabilisticScorer instead of FixedPenaltyScorer?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done. I wasn't able to use just None because the compiler wasn't able to infer the type:

cannot infer type for type parameter S declared on the associated function

so I added None::<Arc<Mutex<FixedPenaltyScorer>>>.

Hmm... right, that's a bit verbose. I'd say add a scorer field to Node and do nodes[0].scorer.clone().

I know FixedPenaltyScorer is deprecated, however, when I tried None::<Arc<Mutex<ProbabilisticScorer<NetworkGraph, TestLogger>>>, the compiler complains:

669 | ...e(), None::<Arc<Mutex<ProbabilisticScorer<NetworkGraph, Logger>>>>);
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Deref` is not implemented for `NetworkGraph`

is that a problem?

With this approach you would need to use &NetworkGraph or Arc<NetworkGraph> since the type parameter must implement Deref.

also, should we create a follow-up issue for updating TestScorer to return a ProbabilisticScorer instead of FixedPenaltyScorer?

No need. Scorer is deprecated not, FixedPenaltyScorer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jkczyz gotcha. I guess if FixedPenaltyScorer is not deprecated I won't change it to ProbabilisticScorer then. Just added the scorer field to Node. should be ready for review again :) thanks!

jkczyz
jkczyz previously approved these changes May 3, 2022
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Looks good modulo one comment.

Comment on lines 603 to 604
let scorer = Arc::new(Mutex::new(test_utils::TestScorer::with_penalty(0)));
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(scorer.clone()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can use the scorer in Node here now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops, thanks for catching that.

@@ -571,7 +600,8 @@ mod tests {
let data_dir = nodes[0].persister.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let event_handler = |_: &_| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone());
let scorer = Arc::new(Mutex::new(test_utils::TestScorer::with_penalty(0)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Will want to remove this and replace the usage below now. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤦 I'm so sorry -- fixed

jkczyz
jkczyz previously approved these changes May 3, 2022
@jurvis jurvis requested a review from TheBlueMatt May 3, 2022 21:17
@@ -277,6 +281,11 @@ impl BackgroundProcessor {
last_prune_call = Instant::now();
have_pruned = true;
}
if let Some(ref scorer) = scorer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oops, this logic is busted if we don't have a net_graph_msg_handler provided (which, okay, should be rare). Just need to move the last_prune_call and have_pruned updates out of the above if. While you're at it, can you add a trace-level log before calling persist_scorer?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this predates this change. @jurvis Could you make a separate commit for this fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, I can do that 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jkczyz done

@jurvis
Copy link
Contributor Author

jurvis commented May 3, 2022

@TheBlueMatt good catch! resolved.

@jurvis jurvis requested review from jkczyz and TheBlueMatt May 3, 2022 21:45
TheBlueMatt
TheBlueMatt previously approved these changes May 3, 2022
@jkczyz jkczyz merged commit 2324012 into lightningdevkit:main May 4, 2022
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.

None yet

6 participants