1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/// Reentrancy handler for WASM single-threaded environment
///
/// In WASM, we're single-threaded but SQLite's VFS callbacks can trigger
/// nested storage access. This module provides utilities to handle such
/// reentrancy gracefully by queueing operations.
use std::cell::RefCell;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use wasm_bindgen_futures::spawn_local;
type DeferredOperation = Pin<Box<dyn Future<Output = ()> + 'static>>;
thread_local! {
/// Queue of deferred operations that couldn't run due to reentrancy
static DEFERRED_OPS: RefCell<VecDeque<DeferredOperation>> = RefCell::new(VecDeque::new());
/// Flag indicating if we're currently processing deferred operations
static PROCESSING_DEFERRED: RefCell<bool> = RefCell::new(false);
}
/// Try to borrow a RefCell mutably, and if it fails due to reentrancy,
/// defer the operation to be executed later
pub fn with_reentrancy_handler<T, F, R>(
cell: &RefCell<T>,
operation_name: &str,
mut operation: F,
) -> Result<R, String>
where
F: FnMut(&mut T) -> R,
R: 'static + Send,
{
// Try to borrow immediately
match cell.try_borrow_mut() {
Ok(mut borrowed) => {
log::debug!("REENTRANCY: {} - Direct execution", operation_name);
let result = operation(&mut *borrowed);
// After completing, process any deferred operations
drop(borrowed);
process_deferred_operations();
Ok(result)
}
Err(_) => {
// Reentrancy detected - this operation must be retried
log::warn!(
"REENTRANCY: {} - Detected reentrancy, operation will be retried",
operation_name
);
// For now, we'll return an error that causes a retry at a higher level
// In a more sophisticated implementation, we could queue the operation
Err(format!("REENTRANCY: {} requires retry", operation_name))
}
}
}
/// Process any deferred operations that were queued due to reentrancy
fn process_deferred_operations() {
// Prevent recursive processing
let already_processing = PROCESSING_DEFERRED.with(|p| {
let was_processing = *p.borrow();
if !was_processing {
*p.borrow_mut() = true;
}
was_processing
});
if already_processing {
return;
}
// Process all deferred operations
loop {
let next_op = DEFERRED_OPS.with(|ops| ops.borrow_mut().pop_front());
match next_op {
Some(op) => {
log::debug!("REENTRANCY: Processing deferred operation");
spawn_local(op);
}
None => break,
}
}
PROCESSING_DEFERRED.with(|p| *p.borrow_mut() = false);
}
/// Defer an async operation to be executed when the current operation completes
pub fn defer_operation<F>(operation: F)
where
F: Future<Output = ()> + 'static,
{
DEFERRED_OPS.with(|ops| {
ops.borrow_mut().push_back(Box::pin(operation));
});
log::debug!("REENTRANCY: Operation deferred for later execution");
}
/// Retry mechanism for operations that fail due to reentrancy
pub async fn retry_on_reentrancy<T, F, Fut>(
mut operation: F,
max_retries: u32,
operation_name: &str,
) -> Result<T, String>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T, String>>,
{
let mut retry_count = 0;
let mut delay_ms = 1;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(e) if e.contains("REENTRANCY") && retry_count < max_retries => {
retry_count += 1;
log::info!(
"REENTRANCY: {} - Retry {}/{} after {}ms",
operation_name,
retry_count,
max_retries,
delay_ms
);
// Exponential backoff with small delays
let promise = js_sys::Promise::new(&mut |resolve, _| {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
&resolve,
delay_ms as i32,
)
.unwrap();
});
wasm_bindgen_futures::JsFuture::from(promise).await.ok();
delay_ms = (delay_ms * 2).min(100); // Cap at 100ms
}
Err(e) => return Err(e),
}
}
}