-
Notifications
You must be signed in to change notification settings - Fork 94
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
fix(indexeddb): fix IDB cursor.continue_() call after drop #2028
Conversation
Signed-off-by: borngraced <samuelonoja970@gmail.com>
@borngraced please fix wasm lint and PR name lint (like "allow first result using IDB cursor") |
Signed-off-by: borngraced <samuelonoja970@gmail.com>
done |
Signed-off-by: borngraced <samuelonoja970@gmail.com>
Signed-off-by: borngraced <samuelonoja970@gmail.com>
Signed-off-by: borngraced <samuelonoja970@gmail.com>
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.
Great work!
At the end, it would be super nice to have unit tests for fn next
and fn first
in mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs
module.
DbCursorEvent::NextItem { | ||
result_tx, | ||
first_result_only, | ||
} => { | ||
result_tx.send(cursor.next(first_result_only).await).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.
Instead of making NextItem
complicated, what do you think adding a new event called FirstItem
?
@@ -241,7 +241,7 @@ impl CursorDriver { | |||
}) | |||
} | |||
|
|||
pub(crate) async fn next(&mut self) -> CursorResult<Option<(ItemId, Json)>> { | |||
pub(crate) async fn next(&mut self, first_result_only: bool) -> CursorResult<Option<(ItemId, Json)>> { |
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 believe this function should have consistent behavior without relying on conditional flags. Could we possibly create a new function called fn first
and retain the existing function as is? The idea is to separate the common parts, which can then be called from both this function and fn first
.
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.
thanks done this and it improved the code greatly!
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.
yeah, agree, now it looks beautiful.
Will duplicate note in the PR. Also there is a problem with |
I actually wanted to ask whether it solves the issue with cursor only when we need the first found item. I assume a more general fix is preferred because this one looks like a temporary workaround. |
It turned out that whole problem was in obtaining the first item. In CursorDriver::next method. UPD: previously when we were trying to get one item by next(), the process continued in loop |
Signed-off-by: borngraced <samuelonoja970@gmail.com>
pub(crate) async fn next(&mut self) -> CursorResult<Option<(ItemId, Json)>> {
loop {
...
match cursor_action {
CursorAction::Continue => cursor.continue_().map_to_mm(|e| CursorError::AdvanceError {
description: stringify_js_error(&e),
})?,
CursorAction::ContinueWithValue(next_value) => {
cursor
.continue_with_key(&next_value)
.map_to_mm(|e| CursorError::AdvanceError {
description: stringify_js_error(&e),
})?
},
// Don't advance the cursor.
// Here we set the `stopped` flag so we return `Ok(None)` at the next iteration immediately.
// This is required because `item_action` can be `CollectItemAction::Include`,
// and at this iteration we will return `Ok(Some)`.
CursorAction::Stop => self.stopped = true,
};
match item_action {
CursorItemAction::Include => return Ok(Some(item.into_pair())),
// Try to fetch the next item.
CursorItemAction::Skip => (),
}
}
}
/// if getting the first result is what we want
pub(crate) async fn first(&mut self) -> CursorResult<Option<(ItemId, Json)>> {
let event = match self.cursor_item_rx.next().await {
Some(event) => event,
None => return Ok(None),
};
let _cursor_event = event.map_to_mm(|e| CursorError::ErrorOpeningCursor {
description: stringify_js_error(&e),
})?;
let cursor = match cursor_from_request(&self.cursor_request)? {
Some(cursor) => cursor,
// No more items.
None => return Ok(None),
};
let (_, js_value) = match (cursor.key(), cursor.value()) {
(Ok(key), Ok(js_value)) => (key, js_value),
// No more items.
_ => return Ok(None),
};
let item: InternalItem =
deserialize_from_js(js_value).map_to_mm(|e| CursorError::ErrorDeserializingItem(e.to_string()))?;
Ok(Some(item.into_pair()))
}
```
|
@laruh i improved the impl. so you can test it again. thanks |
@borngraced recheck clippy or fmt please, CI fails |
not an issue with my pr? https://github.com/KomodoPlatform/komodo-defi-framework/actions/runs/7166781305/job/19511514652?pr=2028#step:3:8 |
Hmm lets rerun to check UPD: yeah, its not your branch issue. locally its fine. |
Signed-off-by: borngraced <samuelonoja970@gmail.com>
After many hours of debugging I found another fix which introduces a method called full explanation: #2028 (comment) |
@borngraced You can cherry pick the last commit from |
@borngraced Thanks for the great find and fix. Can we use komodo-defi-framework/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs Lines 212 to 272 in e61c41e
You can see how limit and offset is used with sqlite to get only the values required komodo-defi-framework/mm2src/mm2_main/src/database/my_swaps.rs Lines 216 to 228 in e61c41e
P.S. Maybe this enhancement can be done in another PR if it's too much to do here since it's unrelated to closure invoked recursively or after being dropped error. I actually don't know how much overhead we currently have by fetching all values.
|
We can use indexedb curosor.advance to achieve similar behavior as and something like this for limit const limit = 10;
let count = 0;
if cursor && count < limit {
cursor.continue();
count++;
} but I'll research and explore other options for sure |
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 have small note :)
UPD not so small as I thought first time
mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs
Outdated
Show resolved
Hide resolved
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.
Thanks! 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.
Thank you for the fixes! A few more comments :)
// We need to provide any constraint on the `height` property | ||
// since `ticker_height` consists of both `ticker` and `height` properties. | ||
.bound("height", BeBigUint::from(0u64), BeBigUint::from(u64::MAX)) | ||
// Cursor returns values from the lowest to highest key indexes. | ||
// But we need to get the most highest height, so reverse the cursor direction. | ||
.reverse() | ||
.where_(condition) |
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.
What would happen if there are more than one object/record that satisfies the condition? I see that we return the first object, are we sure we won't get Uncaught Error: closure invoked recursively or after being dropped
. This is part of the reason I wanted where
and limit
, so we can get more than one object that satisfies the condition, if we want this in other cases (other than the case here where we want only the first object).
P.S. If we don't get the error when there are more than one object that satisfies the condition, then the inclusion of limit and refactoring of where to return more than one object if they exist can be done in a different PR since this PR purpose is to fix the error only.
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 this seems like another enhancement that's not related to this PR.
This PR only handle case for retrieving the first item that meets a condition(if specified) which solves js exception error: Uncaught Error: closure invoked recursively or after being
But for sure this PR indeed fixed that and also optimizes calls to indexeddb
.
I will work on the later issue during next print.
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 but for a few nits! Please fix the first comment so that I can finally merge this PR.
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.
Re-approve
* evm-hd-wallet: (27 commits) Fix todo comments Fix HDAddressOps::Address trait bounds fix(indexeddb): fix IDB cursor.continue_() call after drop (KomodoPlatform#2028) security bump for `h2` (KomodoPlatform#2062) fix(makerbot): allow more than one prices url in makerbot (KomodoPlatform#2027) fix(wasm worker env): refactor direct usage of `window` (KomodoPlatform#1953) feat(nft): nft abi in withdraw_nft RPC, clear_nft_db RPC (KomodoPlatform#2039) refactor(utxo): refactor utxo output script creation (KomodoPlatform#1960) feat(ETH): balance event streaming for ETH (KomodoPlatform#2041) chore(release): bump mm2 version to 2.1.0-beta (KomodoPlatform#2044) feat(trezor): add segwit support for withdraw with trezor (KomodoPlatform#1984) chore(config): remove vscode launchjson (KomodoPlatform#2040) feat(trading-proto-upgrade): wasm DB, kickstart, refund states, v2 RPCs (KomodoPlatform#2015) feat(UTXO): balance event streaming for Electrum clients (KomodoPlatform#2013) feat(tx): add new sign_raw_transaction rpc for UTXO and EVM coins (KomodoPlatform#1930) fix(p2p): handle encode_and_sign errors (KomodoPlatform#2038) chore(release): add changelog entries for v2.0.0-beta (KomodoPlatform#2037) chore(network): write network information to stdout (KomodoPlatform#2034) fix(price_endpoints): add cached url (KomodoPlatform#2032) deps(network): sync with upstream yamux (KomodoPlatform#2030) ...
* dev: feat(zcoin): ARRR WASM implementation (KomodoPlatform#1957) feat(trading-proto-upgrade): locked amounts, kmd burn and other impl (KomodoPlatform#2046) fix(indexeddb): set stop on success cursor condition (KomodoPlatform#2067) feat(config): add `max_concurrent_connections` to mm2 config (KomodoPlatform#2063) feat(stats_swaps): add gui/mm_version in stats db (KomodoPlatform#2061) fix(indexeddb): fix IDB cursor.continue_() call after drop (KomodoPlatform#2028) security bump for `h2` (KomodoPlatform#2062) fix(makerbot): allow more than one prices url in makerbot (KomodoPlatform#2027) fix(wasm worker env): refactor direct usage of `window` (KomodoPlatform#1953) feat(nft): nft abi in withdraw_nft RPC, clear_nft_db RPC (KomodoPlatform#2039) refactor(utxo): refactor utxo output script creation (KomodoPlatform#1960) feat(ETH): balance event streaming for ETH (KomodoPlatform#2041) chore(release): bump mm2 version to 2.1.0-beta (KomodoPlatform#2044) feat(trezor): add segwit support for withdraw with trezor (KomodoPlatform#1984)
If we try to get first result from
indexeddb cursor
like thiskomodo-defi-framework/mm2src/coins/z_coin/storage/blockdb/blockdb_idb_storage.rs
Lines 90 to 98 in 272f46b
Uncaught Error: closure invoked recursively or after being dropped
on the web becauseCursorDriver
next loop is still looping due to thiskomodo-defi-framework/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs
Lines 286 to 302 in 272f46b
Cursor
is dropped as we callednext()
just once here .I found a fix which introduces a method called
where_
that takes a closure. The closure returns a boolean, allowing you to control when a loop should stop. This approach works reliably in various situations.Example - Simple Condition
When calling the
next
method once, you can easily obtain the first available resultwithout needing to continue the cursor using a simple closure like
where_first()
:Example - Complex Condition
For more complex conditions, such as deserializing an IndexedDB data, handling errors, and checking a specific condition, you can use a closure like the one below:
related: #2019