-
Notifications
You must be signed in to change notification settings - Fork 385
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 a background processor which is async #1657
Add a background processor which is async #1657
Conversation
It was always somewhat strange to have a bunch of notification logic in `channelmanager`, and with the next commit adding a bunch more, its moved here first.
This reverts commit 8422fdd. The `await_persistable_update_timeout(Duration::from_millis(10))` 'poll' hack is fine for now while we wait for the permanent solution to be merged into LDK. Removing usage of `get_persistence_condvar_value` allows us to move back to the version of LDK on crates.io once we upgrade LDK to v0.0.110. [0] lightningdevkit/rust-lightning#1657
d2b3331
to
765d96b
Compare
pub async fn process_events_async< | ||
'a, | ||
Signer: 'static + Sign, | ||
CA: 'static + Deref + Send + Sync, | ||
CF: 'static + Deref + Send + Sync, | ||
CW: 'static + Deref + Send + Sync, | ||
T: 'static + Deref + Send + Sync, | ||
K: 'static + Deref + Send + Sync, | ||
F: 'static + Deref + Send + Sync, | ||
G: 'static + Deref<Target = NetworkGraph<L>> + Send + Sync, | ||
L: 'static + Deref + Send + Sync, | ||
P: 'static + Deref + Send + Sync, | ||
Descriptor: 'static + SocketDescriptor + Send + Sync, | ||
CMH: 'static + Deref + Send + Sync, | ||
RMH: 'static + Deref + Send + Sync, | ||
EH: 'static + EventHandler + Send, | ||
PS: 'static + Deref + Send, | ||
M: 'static + Deref<Target = ChainMonitor<Signer, CF, T, F, L, P>> + Send + Sync, | ||
CM: 'static + Deref<Target = ChannelManager<Signer, CW, T, K, F, L>> + Send + Sync, | ||
PGS: 'static + Deref<Target = P2PGossipSync<G, CA, L>> + Send + Sync, | ||
RGS: 'static + Deref<Target = RapidGossipSync<G, L>> + Send, | ||
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>, |
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.
Wonder if there is a way to move the common type parameters to BackgroundProcessor
struct so as not to repeat this long trait bounds list. Then have an "implementation" type parameter to choose between sync and async. Then the macro could be a function without repeating all the trait bounds again.
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.
We spoke a bit offline about this, but I'm not sure how. I went down this rabbit hole initially and ended up repeating all the type stuff at least 4 times before I gave up and added a macro.
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.
Not sure if you tried this already, but would transitioning BackgroundProcessor
to be a trait
with all of the generic parameters listed as associated types and the common logic between sync and async be extracted into a trait method help?
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.
Hmm, I think you still end up with the same issue - you create a trait, which lists all the generic crap, then you create two public structs which implement that trait (which both also have all the generic crap) then you create two impl blocks, which both also have all the generic crap. So now you listed it 5 times, I think :(
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.
Perhaps we can just use a trait to group all the different generic bounds of start
and process_events_async
together and not actually implement it on BackgroundProcessor
so that we can just reference the generic bounds by the new trait?
start
's full function signature would resemble something like:
pub fn start<BPP: BackgroundProcessorParams>(
persister: BPP::PS,
event_handler: BPP::EH,
chain_monitor: BPP::M,
channel_manager: BPP::CM,
gossip_sync: GossipSync<BPP::PGS, BPP::RGS, BPP::G, BPP::CA, BPP::L>,
peer_manager: BPP::PM,
logger: BPP::L,
scorer: Option<BPP::S>,
) -> Self
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.
Sadly the trait methods would have to support being both async and sync, which I don't think we could capture in a single trait?
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.
True. That best I could find was this: https://docs.rs/maybe-async/latest/maybe_async/
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.
Heh, I suppose we could....a macro is probably more readable than a proc macro, though :(
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.
Probably could just feature gate two different versions if they are mutually exclusive, but probably not worth doing here. Just figured we could avoid the duplicative type parameters.
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.
Btw. it's probably worth mentioning here that BDK went this route to provide blocking/async chain client implementations side-by-side (cf. https://github.com/bitcoindevkit/bdk/blob/master/macros/src/lib.rs#L84-L146). IIUC, they ran into some issues when splitting off the Esplora client from the main project and are looking into providing alternatives to give the user more control which impl they want to use.
Btw, we implemented our own async background processor. Our project isn't open-source yet but I published the background processor as a gist here. Trying to make an We've had some papercuts using LDK's background processor where because the |
... as it is no longer persistence-specific (though still only used for persistence).
Cool! Hopefully the next folks after you won't have to :)
Given the ordering, delays, and error handling are very specific here and it'd be very, very easy to introduce a bug if the two diverged, I'm strongly opposed to maintaining two copies. It wouldn't reduce the amount of code we have, either, as we'd still need the whole same mess of generics we have here - we'd just have them twice in two different files. Ultimately for very finicky code, sticking to DRY is a very important goal. @jkczyz pointed out above that ideally we'd have to repeat ourselves as many times as we do (or avoid the macro), but it's not clear to me exactly how, I'd love any concrete suggestions!
Not really sure why you need a static here? Sensei has a similar problem, but simply stores a reference to the tokio runtime directly in relevant structs that implement persist traits, rather than making a static, but that's somewhat besides the point.
Yep, we want to make persist more async, see eg #1470. As you note, in general Rust is not at all supportive of mixing async and sync stuff in the same project, Rust-async and Rust-sync are basically two separate languages. Sadly for LDK we can't drop Rust-sync support (cause most of our users ultimately use language bindings rather than Rust) so that is what got built first and the best we can do is add async wrappers, which we're slowly building, though as you've noted they're still somewhat early in places. Sensei has been a trailblazer here, and has managed to work around the papercuts for the most part, though things like this PR came about because they reported where the papercuts were, which helps us prioritize what to build.
I'm not sure what exactly users need to change about the durations set here? This is also a separate discussion from sync/async, so lets move to a new issue if you have some specific tuning you'd like to see supported.
I'm not quite sure what you're suggesting here? The new method here does return a future, which is ~equivalent to a JoinHandle, though without being tokio-specific so users can use whatever async runtime they want. It also has a way to shut down the future if you can't unregister a future in your runtime, which we also use to fetch a runtime-specific sleep future. |
765d96b
to
3c1f55a
Compare
Codecov Report
@@ Coverage Diff @@
## main #1657 +/- ##
==========================================
+ Coverage 90.77% 90.84% +0.07%
==========================================
Files 80 86 +6
Lines 44763 46303 +1540
Branches 44763 46303 +1540
==========================================
+ Hits 40632 42065 +1433
- Misses 4131 4238 +107
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
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.
So after refreshing some async knowledge this basically looks good to me with nothing else to really point out. Just that one question.
5f7165a
to
ec5185e
Compare
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.
Not sure if the Executor
idea is possible, but otherwise LGTM.
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.
Feel free to squash
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.
LGTM
LGTM besides just the tests that need to be excluded for no-std. |
283e93b
to
76bb97e
Compare
Squashed the old fixups and added two new ones. |
CI is now failing with
|
76bb97e
to
4624ca7
Compare
Oops sorry. I just removed the std bounds, we can impl Future even for no-std. |
4624ca7
to
b7a3a74
Compare
Looks good to go after squash! |
Squashed without further change. |
b7a3a74
to
7ad92f4
Compare
This allows users who don't wish to block a full thread to receive persistence events. The `Future` added here is really just a trivial list of callbacks, but from that we can build a (somewhat ineffecient) std::future::Future implementation and can (at least once a mapping for Box<dyn Trait> is added) include the future in no-std bindings as well. Fixes lightningdevkit#1595
Adds a method which operates like BackgroundProcessor::start but instead of functioning by spawning a background thread it is async.
7ad92f4
to
2a5bac2
Compare
Oops, added missing include:
|
This adds a new Future (which implements std::future::Future) which stores a list of callbacks to call upon completion. This is exposed from ChannelManager as an alternative way to be woken up when persistence needs to happen. Then, we add a new method in the background processor crate which works the same as the backgroundprocessor but async instead of in a thread.
Fixes #1595