Skip to content

Commit c7578bd

Browse files
authored
Debugger: a few updates to the debugger crate. (#12684)
This PR upstreams a few changes from my WIP debug-component branch to the async-wrapper in the debugger crate: - The inner debuggee body takes a future that need not be `'static`; this is important later in the integration. - Two race conditions fixed: (i) wait for initial debuggee body startup before sending a `with_store` query; and (ii) allow `with_store` queries after receiving a `Finished` message, which may happen when processing final debugger commands after the program completes. - As a result of some shuffling around future lifetimes and other type-system head-banging, as well as the termination race-condition fixed, the `Store<T>` is not transferred to the debuggee body and back via the `JoinHandle`; rather it is passed back in a message upon completion.
1 parent 31f4507 commit c7578bd

File tree

1 file changed

+106
-93
lines changed

1 file changed

+106
-93
lines changed

crates/debugger/src/lib.rs

Lines changed: 106 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,11 @@
99
//! In the future, this crate will also provide a WIT-level API and
1010
//! world in which to run debugger components.
1111
12-
use std::{any::Any, sync::Arc};
13-
use tokio::{
14-
sync::{Mutex, mpsc},
15-
task::JoinHandle,
16-
};
12+
use std::{any::Any, future::Future, pin::Pin, sync::Arc};
13+
use tokio::sync::{Mutex, mpsc};
1714
use wasmtime::{
18-
AsContextMut, DebugEvent, DebugHandler, ExnRef, OwnedRooted, Result, Store, StoreContextMut,
19-
Trap,
15+
AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
16+
StoreContextMut, Trap,
2017
};
2118

2219
/// A `Debugger` wraps up state associated with debugging the code
@@ -31,13 +28,15 @@ use wasmtime::{
3128
/// state. One runs until the next event suspends execution by
3229
/// invoking `Debugger::run`.
3330
pub struct Debugger<T: Send + 'static> {
34-
/// The inner task that this debugger wraps.
35-
inner: Option<JoinHandle<Result<Store<T>>>>,
31+
/// A handle to the Engine that the debuggee store lives within.
32+
engine: Engine,
3633
/// State: either a task handle or the store when passed out of
3734
/// the complete task.
3835
state: DebuggerState,
36+
/// The store, once complete.
37+
store: Option<Store<T>>,
3938
in_tx: mpsc::Sender<Command<T>>,
40-
out_rx: mpsc::Receiver<Response>,
39+
out_rx: mpsc::Receiver<Response<T>>,
4140
}
4241

4342
/// State machine from the perspective of the outer logic.
@@ -75,6 +74,8 @@ pub struct Debugger<T: Send + 'static> {
7574
/// ```
7675
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7776
enum DebuggerState {
77+
/// Inner body has just been started.
78+
Initial,
7879
/// Inner body is running in an async task and not in a debugger
7980
/// callback. Outer logic is waiting for a `Response::Paused` or
8081
/// `Response::Complete`.
@@ -128,15 +129,15 @@ enum Command<T: 'static> {
128129
Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
129130
}
130131

131-
enum Response {
132+
enum Response<T: 'static> {
132133
Paused(DebugRunResult),
133134
QueryResponse(Box<dyn Any + Send>),
134-
Finished,
135+
Finished(Store<T>),
135136
}
136137

137138
struct HandlerInner<T: Send + 'static> {
138139
in_rx: Mutex<mpsc::Receiver<Command<T>>>,
139-
out_tx: mpsc::Sender<Response>,
140+
out_tx: mpsc::Sender<Response<T>>,
140141
}
141142

142143
struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);
@@ -201,40 +202,47 @@ impl<T: Send + 'static> Debugger<T> {
201202
///
202203
/// When paused, the holder of this object can access the `Store`
203204
/// indirectly by providing a closure
204-
pub fn new<F, I>(mut store: Store<T>, inner: F) -> Debugger<T>
205+
pub fn new<F>(mut store: Store<T>, inner: F) -> Debugger<T>
205206
where
206-
I: Future<Output = Result<Store<T>>> + Send + 'static,
207-
F: for<'a> FnOnce(Store<T>) -> I + Send + 'static,
207+
F: for<'a> FnOnce(
208+
&'a mut Store<T>,
209+
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
210+
+ Send
211+
+ 'static,
208212
{
209-
let (in_tx, mut in_rx) = mpsc::channel(1);
213+
let engine = store.engine().clone();
214+
let (in_tx, in_rx) = mpsc::channel(1);
210215
let (out_tx, out_rx) = mpsc::channel(1);
211216

212-
let inner = tokio::spawn(async move {
213-
// Receive one "continue" command on the inbound channel
214-
// before continuing.
215-
match in_rx.recv().await {
216-
Some(cmd) => {
217-
assert!(matches!(cmd, Command::Continue));
218-
}
219-
None => {
220-
// Premature exit due to closed channel. Just drop `inner`.
221-
wasmtime::bail!("Debugger channel dropped");
222-
}
223-
}
224-
217+
tokio::spawn(async move {
218+
// Create the handler that's invoked from within the async
219+
// debug-event callback.
225220
let out_tx_clone = out_tx.clone();
226-
store.set_debug_handler(Handler(Arc::new(HandlerInner {
221+
let handler = Handler(Arc::new(HandlerInner {
227222
in_rx: Mutex::new(in_rx),
228223
out_tx,
229-
})));
230-
let result = inner(store).await;
231-
let _ = out_tx_clone.send(Response::Finished).await;
224+
}));
225+
226+
// Emulate a breakpoint at startup.
227+
log::trace!("inner debuggee task: first breakpoint");
228+
handler
229+
.handle(store.as_context_mut(), DebugEvent::Breakpoint)
230+
.await;
231+
log::trace!("inner debuggee task: first breakpoint resumed");
232+
233+
// Now invoke the actual inner body.
234+
store.set_debug_handler(handler);
235+
log::trace!("inner debuggee task: running `inner`");
236+
let result = inner(&mut store).await;
237+
log::trace!("inner debuggee task: done with `inner`");
238+
let _ = out_tx_clone.send(Response::Finished(store)).await;
232239
result
233240
});
234241

235242
Debugger {
236-
inner: Some(inner),
237-
state: DebuggerState::Paused,
243+
engine,
244+
state: DebuggerState::Initial,
245+
store: None,
238246
in_tx,
239247
out_rx,
240248
}
@@ -248,12 +256,35 @@ impl<T: Send + 'static> Debugger<T> {
248256
}
249257
}
250258

259+
/// Get the Engine associated with the debuggee.
260+
pub fn engine(&self) -> &Engine {
261+
&self.engine
262+
}
263+
264+
async fn wait_for_initial(&mut self) -> Result<()> {
265+
if let DebuggerState::Initial = &self.state {
266+
// Need to receive and discard first `Paused`.
267+
let response = self
268+
.out_rx
269+
.recv()
270+
.await
271+
.ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
272+
assert!(matches!(response, Response::Paused(_)));
273+
self.state = DebuggerState::Paused;
274+
}
275+
Ok(())
276+
}
277+
251278
/// Run the inner body until the next debug event.
252279
///
253280
/// This method is cancel-safe, and no events will be lost.
254281
pub async fn run(&mut self) -> Result<DebugRunResult> {
255282
log::trace!("running: state is {:?}", self.state);
283+
284+
self.wait_for_initial().await?;
285+
256286
match self.state {
287+
DebuggerState::Initial => unreachable!(),
257288
DebuggerState::Paused => {
258289
log::trace!("sending Continue");
259290
self.in_tx
@@ -311,9 +342,10 @@ impl<T: Send + 'static> Debugger<T> {
311342
.ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
312343

313344
match response {
314-
Response::Finished => {
345+
Response::Finished(store) => {
315346
log::trace!("got Finished");
316347
self.state = DebuggerState::Complete;
348+
self.store = Some(store);
317349
Ok(DebugRunResult::Finished)
318350
}
319351
Response::Paused(result) => {
@@ -362,9 +394,14 @@ impl<T: Send + 'static> Debugger<T> {
362394
&mut self,
363395
f: F,
364396
) -> Result<R> {
365-
assert!(!self.is_complete());
397+
if let Some(store) = self.store.as_mut() {
398+
return Ok(f(store.as_context_mut()));
399+
}
400+
401+
self.wait_for_initial().await?;
366402

367403
match self.state {
404+
DebuggerState::Initial => unreachable!(),
368405
DebuggerState::Queried => {
369406
// Earlier query canceled; drop its response first.
370407
let response =
@@ -387,6 +424,7 @@ impl<T: Send + 'static> Debugger<T> {
387424
}
388425
}
389426

427+
log::trace!("sending query in with_store");
390428
self.in_tx
391429
.send(Command::Query(Box::new(|store| Box::new(f(store)))))
392430
.await
@@ -405,29 +443,6 @@ impl<T: Send + 'static> Debugger<T> {
405443

406444
Ok(*resp.downcast::<R>().expect("type mismatch"))
407445
}
408-
409-
/// Drop the Debugger once complete, returning the inner `Store`
410-
/// around which it was wrapped.
411-
///
412-
/// Only valid to invoke once `run()` returns
413-
/// `DebugRunResult::Finished` or after calling `finish()` (which
414-
/// finishes execution while dropping all further debug events).
415-
///
416-
/// This is cancel-safe, but if canceled, the Store is lost.
417-
pub async fn take_store(&mut self) -> Result<Option<Store<T>>> {
418-
match self.state {
419-
DebuggerState::Complete => {
420-
let inner = match self.inner.take() {
421-
Some(inner) => inner,
422-
None => return Ok(None),
423-
};
424-
let mut store = inner.await??;
425-
store.clear_debug_handler();
426-
Ok(Some(store))
427-
}
428-
_ => panic!("Invalid state: debugger not yet complete"),
429-
}
430-
}
431446
}
432447

433448
/// The result of one call to `Debugger::run()`.
@@ -482,16 +497,18 @@ mod test {
482497
let instance = Instance::new_async(&mut store, &module, &[]).await?;
483498
let main = instance.get_func(&mut store, "main").unwrap();
484499

485-
let mut debugger = Debugger::new(store, move |mut store| async move {
486-
let mut results = [Val::I32(0)];
487-
store.edit_breakpoints().unwrap().single_step(true).unwrap();
488-
main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
489-
.await?;
490-
assert_eq!(results[0].unwrap_i32(), 3);
491-
main.call_async(&mut store, &[Val::I32(3), Val::I32(4)], &mut results[..])
492-
.await?;
493-
assert_eq!(results[0].unwrap_i32(), 7);
494-
Ok(store)
500+
let mut debugger = Debugger::new(store, move |store| {
501+
Box::pin(async move {
502+
let mut results = [Val::I32(0)];
503+
store.edit_breakpoints().unwrap().single_step(true).unwrap();
504+
main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
505+
.await?;
506+
assert_eq!(results[0].unwrap_i32(), 3);
507+
main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
508+
.await?;
509+
assert_eq!(results[0].unwrap_i32(), 7);
510+
Ok(())
511+
})
495512
});
496513

497514
let event = debugger.run().await?;
@@ -642,14 +659,6 @@ mod test {
642659

643660
assert!(debugger.is_complete());
644661

645-
// Ensure the store still works and the debug handler is
646-
// removed.
647-
let mut store = debugger.take_store().await?.unwrap();
648-
let mut results = [Val::I32(0)];
649-
main.call_async(&mut store, &[Val::I32(10), Val::I32(20)], &mut results[..])
650-
.await?;
651-
assert_eq!(results[0].unwrap_i32(), 30);
652-
653662
Ok(())
654663
}
655664

@@ -676,13 +685,15 @@ mod test {
676685
let instance = Instance::new_async(&mut store, &module, &[]).await?;
677686
let main = instance.get_func(&mut store, "main").unwrap();
678687

679-
let mut debugger = Debugger::new(store, move |mut store| async move {
680-
let mut results = [Val::I32(0)];
681-
store.edit_breakpoints().unwrap().single_step(true).unwrap();
682-
main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
683-
.await?;
684-
assert_eq!(results[0].unwrap_i32(), 3);
685-
Ok(store)
688+
let mut debugger = Debugger::new(store, move |store| {
689+
Box::pin(async move {
690+
let mut results = [Val::I32(0)];
691+
store.edit_breakpoints().unwrap().single_step(true).unwrap();
692+
main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
693+
.await?;
694+
assert_eq!(results[0].unwrap_i32(), 3);
695+
Ok(())
696+
})
686697
});
687698

688699
debugger.finish().await?;
@@ -714,13 +725,15 @@ mod test {
714725
let instance = Instance::new_async(&mut store, &module, &[]).await?;
715726
let main = instance.get_func(&mut store, "main").unwrap();
716727

717-
let mut debugger = Debugger::new(store, move |mut store| async move {
718-
let mut results = [Val::I32(0)];
719-
store.edit_breakpoints().unwrap().single_step(true).unwrap();
720-
main.call_async(&mut store, &[Val::I32(1), Val::I32(2)], &mut results[..])
721-
.await?;
722-
assert_eq!(results[0].unwrap_i32(), 3);
723-
Ok(store)
728+
let mut debugger = Debugger::new(store, move |store| {
729+
Box::pin(async move {
730+
let mut results = [Val::I32(0)];
731+
store.edit_breakpoints().unwrap().single_step(true).unwrap();
732+
main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
733+
.await?;
734+
assert_eq!(results[0].unwrap_i32(), 3);
735+
Ok(())
736+
})
724737
});
725738

726739
// Step once, then drop everything at the end of this

0 commit comments

Comments
 (0)