Fully switch to async KVStore persistence#919
Conversation
|
👋 Thanks for assigning @joostjager as a reviewer! |
42c16e2 to
4371f6d
Compare
05ebc28 to
911c169
Compare
1b4d24f to
c62a503
Compare
joostjager
left a comment
There was a problem hiding this comment.
Overall happy with this simplification. Big changeset though, even though not all that much is happening. Risk nonetheless.
Bigger open question for me is what the latest is on runtime abstraction and how that is going to work in combination with potential wasm support.
Also, are there sync public api calls that can now be made async such as export_pathfinding_scores?
| signer_provider: &'a test_utils::TestKeysInterface, | ||
| } | ||
|
|
||
| impl<K: KVStore + Sync> TestMonitorUpdatePersister<'_, K> { |
There was a problem hiding this comment.
Commit message says dropping the trait, but this looks more substantial?
There was a problem hiding this comment.
Ah right, move it to another commit for now, but will also put up a minor change in LDK so we can reuse the test utils wihtout rewriting this.
There was a problem hiding this comment.
That should allow us to avoid it: lightningdevkit/rust-lightning#4665
There was a problem hiding this comment.
Will allow us to simply do:
type TestMonitorPersister<K> = MonitorUpdatingPersisterAsync<
TestStoreRef<K>,
TestFutureSpawner,
&'static test_utils::TestLogger,
&'static test_utils::TestKeysInterface,
&'static test_utils::TestKeysInterface,
&'static test_utils::TestBroadcaster,
&'static test_utils::TestFeeEstimator,
>;
type TestAsyncPersister<K> = lightning::chain::chainmonitor::AsyncPersister<
TestStoreRef<K>,
TestFutureSpawner,
&'static test_utils::TestLogger,
&'static test_utils::TestKeysInterface,
&'static test_utils::TestKeysInterface,
&'static test_utils::TestBroadcaster,
&'static test_utils::TestFeeEstimator,
>;
(..)
let monitor_persister = TestMonitorPersister::new(
store,
TestFutureSpawner::new(Arc::clone(&runtime)),
&chanmon_cfg.logger,
max_pending_updates,
&chanmon_cfg.keys_manager,
&chanmon_cfg.keys_manager,
&chanmon_cfg.tx_broadcaster,
&chanmon_cfg.fee_estimator,
);
let event_notifier = Arc::new(Notifier::new());
let persister = lightning::chain::chainmonitor::AsyncPersister::new_test(
monitor_persister,
Arc::clone(&event_notifier),
);
(...)
test_utils::TestChainMonitor::new_with_event_notifier(
Some(&chanmon_cfg.chain_source),
&chanmon_cfg.tx_broadcaster,
&chanmon_cfg.logger,
&chanmon_cfg.fee_estimator,
&persister.persister,
&chanmon_cfg.keys_manager,
Arc::clone(&persister.event_notifier),
).. rather than the custom TestMonitorPersister
There was a problem hiding this comment.
Will await the discussion there. Would be nice to use the incremental persister in tests.
| pub(crate) fn create_persister<'a, K: KVStoreSync + Sync>( | ||
| store: &'a K, chanmon_cfg: &'a TestChanMonCfg, max_pending_updates: u64, | ||
| pub(crate) fn create_persister<'a, K: KVStore + Sync>( | ||
| store: &'a K, chanmon_cfg: &'a TestChanMonCfg, _max_pending_updates: u64, |
There was a problem hiding this comment.
Wasn't max pending updates exercised in a test?
There was a problem hiding this comment.
Yes, but we can't use it right now as LDK seems not to expose the right APIs to use async path with the test utils, will open a fix in a bit.
There was a problem hiding this comment.
Is it tracked somewhere?
There was a problem hiding this comment.
| ); | ||
|
|
||
| let runtime_handle = internal_runtime.handle(); | ||
| let schema_version = tokio::task::block_in_place(|| { |
There was a problem hiding this comment.
Isn't it a problem that setup can't fail fast here anymore?
There was a problem hiding this comment.
We fail fast elsewhere via setup_schema_version.
There was a problem hiding this comment.
Can't the schema_version then be stored as a normal field? It seems the code is currently halfway between lazy init and explicit setup.
There was a problem hiding this comment.
Well, no, we need to discover it based on the server-side state for backwards compatibility, as unfortunately schema version wasn't always there.
There was a problem hiding this comment.
I see you removed the lazy init and made it a field? That was the suggestion indeed
There was a problem hiding this comment.
I see you removed the lazy init and made it a field? That was the suggestion indeed
Well, we just returned to the previous behavior using the internal runtime to block_on on the version negotiation. We'll need to return to the lazy init likely in the next step though.
290b0a5 to
9ad8f2d
Compare
|
codex seems to agree on the locks
|
9ad8f2d to
8b93895
Compare
|
As discussed offline, now updated:
|
326cbf7 to
be2b74d
Compare
|
Hmm, seems Github CI might have an outage, at least it's not running right now.. |
| Handle(tokio::runtime::Handle), | ||
| } | ||
|
|
||
| pub(crate) struct StoreRuntime { |
There was a problem hiding this comment.
This can also use some serious documentation on why it is needed, even if temporary.
There was a problem hiding this comment.
Also wondering if it isn't possible to unify this with the existing Runtime wrapper? That might also make the various modes more clear: current runtime, passed runtime, new runtime, or some combo.
There was a problem hiding this comment.
Also wondering if it isn't possible to unify this with the existing
Runtimewrapper? That might also make the various modes more clear: current runtime, passed runtime, new runtime, or some combo.
Considered that, but given the plan is to remove it ASAP, I decided to keep it separate which will make the cleanup much more straightforward.
There was a problem hiding this comment.
I don't think clean up is really much different, but in the mean time reusing the Runtime does make it more clear that we have different variations of the same concept. Maybe it takes longer than we'd like to get rid of it. And can reuse the thread naming thing too.
There was a problem hiding this comment.
I assume my last comment didn't change your view on it. It's temporary, so it's fine.
| format!("{}-{}", thread_name_prefix, id) | ||
| }) | ||
| .worker_threads(worker_threads) | ||
| .max_blocking_threads(worker_threads) |
There was a problem hiding this comment.
What is the reason for this setting?
There was a problem hiding this comment.
It shouldn't matter as we shouldn't hit blocking calls in the persistence pipeline anyways now.
There was a problem hiding this comment.
Still curious why it is needed
There was a problem hiding this comment.
Still curious why it is needed
Well, I don't think it is (given we should never use blocking threads on these runtimes), but also doesn't hurt to bound the number to be more in-line with the API-configured limit.
| tokio::task::block_in_place(move || drop(internal_runtime)); | ||
| if let Some(runtime) = self.internal_runtime.take() { | ||
| if let Ok(runtime) = Arc::try_unwrap(runtime) { | ||
| runtime.shutdown_background(); |
There was a problem hiding this comment.
What does it mean if we skip shutdown here?
There was a problem hiding this comment.
I'm not sure I'm following what you mean with 'skip' in this context?
There was a problem hiding this comment.
I mean the Drop path conditionally does not call shutdown_background_tasks, so the question is what state/resources/tasks may remain when that branch is taken?
| runtime: Option<tokio::runtime::Runtime>, | ||
| } | ||
|
|
||
| impl StoreRuntime { |
There was a problem hiding this comment.
Name describes usage, not really what it does. IsolatedRuntime?
There was a problem hiding this comment.
Hmm, the thing itself also doesn't isolate the runtime, no? It's just used in an isolated manner?
There was a problem hiding this comment.
Not sure this improves anything? Note it's not the object that isolates anything really, but its usage.
There was a problem hiding this comment.
I just mean that if you want to use this generally-usable object for something else, you need to rename it even though the code stays the same.
| task.await.map_err(|e| { | ||
| io::Error::new( | ||
| io::ErrorKind::Other, | ||
| format!("PostgreSQL runtime task failed: {}", e), |
There was a problem hiding this comment.
we have the same message for all of these, would be better if it had the function name in it for easier debugging. however this is removed in the next pr so not very important
There was a problem hiding this comment.
Agreed on the duplication
There was a problem hiding this comment.
Well we had helper previously, but Joost asked to drop it and move it to StoreRuntime, which however doesn't work well either (given the error messages are store-specific), so decided to simply inline them, esp. given it's just 5 lines, and hiding them in a helper just obfuscates what happens concretely, i.e., what runtime exactly is used to await what etc..
| .await | ||
| }); | ||
| task.await.map_err(|e| { | ||
| io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e)) |
| } | ||
| } | ||
|
|
||
| struct TestSyncStoreInner { |
There was a problem hiding this comment.
Can this just be called TestStoreInner/TestStore since its all async now? Also noticed I missed adding postgres to this
There was a problem hiding this comment.
Can this just be called
TestStoreInner/TestStoresince its all async now?
No, this would be pretty confusing, as the Sync part of it is important (i.e., the purpose is to assert all stores are kept in-sync). Note we also have TestStore in LDK test utils which we used to use here, but which unfortunately is now unusable for us as we decided that KVStore path it requires some additional per-operation confirmations that we won't be able to run in LDK Node, hence why we introduced InMemoryStore.
Also noticed I missed adding postgres to this
Ah, right, feels a bit orthogonal to this PR, feel free to open a quick follow-up.
joostjager
left a comment
There was a problem hiding this comment.
Generally happy with where this landed. No blockers.
One process point: the PR is quite large and that makes review and re-review inefficient. Also managing the many discussing threads becomes unwieldy and risks loosing track of something that might be important. In my opinion better to avoid in the future.
I think the extensive comments that you and/or AI added are good context for future humans or AIs reading it.
I’ll take a look at the follow-up PR too, to see how the final async end state takes shape.
9e095d1 to
78708a3
Compare
Squashed fixups without further changes. Given how invasive the changeset is I'd like to move on this one rather fast, any remaining comments / improvements can happen in follow-ups IMO. |
|
Ah, grr, still blocked on lightningdevkit/rust-lightning#4661. |
Bump LDK to 3dfcc4cca1866c5e5d4d4eaf3b82e09584e2ce5c Co-Authored-By: HAL 9000
Read and write BDK wallet state through async KVStore helpers while keeping the current WalletPersister entry points bridged through the node runtime. This reduces the wallet persistence surface that still depends on KVStoreSync. Co-Authored-By: HAL 9000
Static invoice persistence already runs from async handlers, so use KVStore directly instead of routing those reads and writes through the blocking KVStoreSync trait. Co-Authored-By: HAL 9000
Persist peer store updates through async KVStore operations. The synchronous node APIs keep bridging at their runtime boundary while async event handling awaits peer persistence directly. Co-Authored-By: HAL 9000
Persist DataStore mutations through async KVStore operations while keeping the existing synchronous APIs bridged through the node runtime. Async event handling now awaits payment store writes directly. Co-Authored-By: HAL 9000
Persist node metric updates through async KVStore writes and await them from the chain, gossip, and scoring tasks. This removes the remaining blocking metrics writer while keeping the helper name stable. Co-Authored-By: HAL 9000
Persist the on-chain wallet through BDK's AsyncWalletPersister so wallet state writes use the async KVStore path. Existing synchronous wallet APIs keep bridging through the node runtime until their callers are made async. Co-Authored-By: HAL 9000
Open filesystem stores through the async LDK migration helper so v1-to-v2 store migration no longer depends on the blocking KVStoreSync migration path. Co-Authored-By: HAL 9000
Move the existing in-memory test store into a shared module without changing its behavior. This lets integration tests reuse it while keeping the later async TestSyncStore change separate. Co-Authored-By: HAL 9000
Exercise async KVStore operations in TestSyncStore and filesystem migration tests while keeping the temporary sync comparison path until the final KVStoreSync removal. Co-Authored-By: HAL 9000
Use a local async KVStore-backed monitor persister in store tests so the later blocking KVStore removal can drop the old synchronous MonitorUpdatingPersister path without mixing the test adapter into that change. Co-Authored-By: HAL 9000
Drop the remaining synchronous KV store trait bounds and implementations. After the preceding migrations, custom stores only need to provide async KVStore persistence, and pathfinding score export also reads from the async store. Co-Authored-By: HAL 9000
Add a crate-local runtime wrapper for store backends that need to keep their I/O isolated while shutting down safely from async contexts. Co-Authored-By: HAL 9000
Co-Authored-By: HAL 9000
Co-Authored-By: HAL 9000
Enable Tokio's eager driver handoff when building with tokio_unstable so node-owned runtimes can use the dedicated driver handoff path where available. Build binding artifacts and selected CI coverage with tokio_unstable so the cfg-gated runtime path remains exercised. Co-Authored-By: HAL 9000
Give ldk-node-created runtime workers a stable ldk-node-prefixed thread name so runtime activity is easier to identify in debuggers and logs, matching the store runtime naming convention. Co-Authored-By: HAL 9000
78708a3 to
a3e7653
Compare
Okay, now converted the |
joostjager
left a comment
There was a problem hiding this comment.
No CI. I assume the tests pass locally?
We planned to make the full switch to the async
KVStorefor a while. Here we do just that: first step-by-step moving the remaining dependencies over to useKVStore, then finally droppingKVStoreSyncand all implementations.Depends on lightningdevkit/rust-lightning#4658 (will drop the
DROPMEcommit once that's merged).