-
Notifications
You must be signed in to change notification settings - Fork 116
Require both KVStore
and KVStoreSync
implementations, switch BP to be fully-async
#633
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?
Conversation
👋 Thanks for assigning @joostjager as a reviewer! |
2adf4a7
to
296b55c
Compare
296b55c
to
85198d8
Compare
This now builds based on the just-merged lightningdevkit/rust-lightning#4069. We have yet to add write-version tracking for VSS. |
c4251d4
to
7158653
Compare
7158653
to
8c2ff8f
Compare
464e6b7
to
9035b71
Compare
Should be good for review. For the |
9035b71
to
750ab87
Compare
Rebased to address conflicts post-#652. |
if primary_namespace.is_empty() { | ||
key.to_owned() | ||
} else { | ||
format!("{}#{}#{}", primary_namespace, secondary_namespace, key) |
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.
Is it necessary to make a string out of this? It doesn't need to be mapped to a filename like for fs_store, so maybe it can also simply be a tuple?
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.
I think so, as the HashMap
needs to hold an owned value. We could have it be a (String, String, String)
, but that's worse.
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.
Is that worse? The string concatenation looks a bit unnecessary. Or make it a struct that is used as the key?
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.
Is that worse? The string concatenation looks a bit unnecessary. Or make it a struct that is used as the key?
It at least requires three individual allocations instead of one? I.e. more clutter on the heap, and probably also some slowdown?
FWIW, I mirrored what we do for the obfuscated key. Unfortunately it's not super easy to just reuse that.
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 ideal indeed, the heap allocs
src/io/vss_store.rs
Outdated
|
||
Ok(self.storable_builder.deconstruct(storable)?.0) | ||
|
||
self.execute_locked_read(locking_key, async move || { |
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.
I think we don't need the lock for reading?
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.
Dropped.
750ab87
to
70404ed
Compare
Addressed pending comments, probably still need to see why the CI job start hanging again. |
LGTM, can squash |
70404ed
to
6c3fdf3
Compare
Squashed without further changes. |
let secondary_namespace = secondary_namespace.to_string(); | ||
let key = key.to_string(); | ||
let inner = Arc::clone(&self.inner); | ||
let fut = tokio::task::spawn_blocking(move || { |
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.
During final look, I am now again wondering if this function is actually preserving order?
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, good point, it seems it would depend on how tokio exactly schedules the blocking tasks. I considered a few other options, but now simply also followed the tried and true 'write version locking' approach here, as we already use that in VSS and FS stores.
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 but isn't this completely unnecessary because sqlite has its own global lock? With FS and VSS there is the actual possibility of parallel execution.
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 but isn't this completely unnecessary because sqlite has its own global lock? With FS and VSS there is the actual possibility of parallel execution.
Well, I first thought so, too, but I think the issue is that tokio gives no guarantee in which order spawned tasks are executed/scheduled. I.e., AFAICT it could happen that we spawn to writes w1, w2
but the task for w2
gets polled first, acquiring the Mutex
first. Same goes for the case where multiple writes wait on the same connection lock: say w1
currently holds the Mutex
and two more writes w2, w3
would get get queued, AFAIU there is no guarantee that when w1
drops the lock w2
always acquires the lock next.
TLDR: it seems we unfortunately need to do the version
dance here, too. Maybe there's an easier mechanism in the SQLite case (e.g., prepare
should technically take care of that, but we can't lock the connection Mutex
outside of the spawned task as the guard is not Send
) we could lean on to guarantee the ordered writes, but applying the same approach seemed simplest for now?
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.
I just refuse to believe that we need that version dance really for this problem. Isn't block_in_place
meant for this? If we ensure that the write has happened before the fn returns, it doesn't matter that during that execution there may be other writes that get processed in a certain order?
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? How do you imagine block_in_place
to work here? block_in_place
takes a future and drives it to completion, i.e., makes it a blocking operation. For the async KVStore
we however exactly need a future, not a blocking operation, which is what spawn_blocking
does for us.
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.
Does it? I saw
pub fn block_in_place<F, R>(f: F) -> R where F: FnOnce() -> R
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.
Does it? I saw
pub fn block_in_place<F, R>(f: F) -> R where F: FnOnce() -> R
Sorry, please replace block_in_place
with block_on
above. block_in_place
is simply a wrapper that spawns a blocking task on the outer runtime context so that the inner block_on
call doesn't starve (that is, if it is indeed called on the same runtime, which it isn't always).
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.
Discussed offline that even though block_in_place
may work with caveats, we do prefer to implement sqlite async. And that for that to work, we need the versioning.
.. first step to make review easier.
.. as we're gonna reuse the `async` `_internal` methods shortly.
.. where the former holds the latter in an `Arc` that can be used in async/`Future` contexts more easily.
We implement the async `KVStore` trait for `VssStore`.
6c3fdf3
to
1a55ab8
Compare
.. to be easier reusable via `KVStore` also
.. where the former holds the latter in an `Arc` that can be used in async/`Future` contexts more easily.
.. to be easier reusable via `KVStore` also
.. where the former holds the latter in an `Arc` that can be used in async/`Future` contexts more easily.
As an intermediary step, we require any store to implement both `KVStore` and `KVStoreSync`, allowing us to switch over step-by-step. We already switch to the fully-async background processor variant here.
1a55ab8
to
8dada08
Compare
Addressed remaining comments and changed base to |
CI breakage is unrelated (#654) |
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, | ||
) -> String { | ||
if primary_namespace.is_empty() { | ||
key.to_owned() |
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.
And secondary ns?
Is this actually used without a namespace?
io::Error::new(io::ErrorKind::Other, msg) | ||
})?; | ||
Ok(()) | ||
}) |
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.
Indent formatting issue?
Squash = ok |
As an intermediary step towards making our IO fully async, we now require any store to implement both
KVStore
andKVStoreSync
, which allows us to switch over to the fully-async background processor and take further migration steps bit-by-bit when we make more and more of the core codebaseasync
.To this end, we refactor
VssStore
andSqliteStore
to implementKVStore
TODOs:
KVStore
forTestStore
upstream to fix testsKVStore
implementation.. draft until then.