Public dry run dryrun-20b59dc46b72
Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19 Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
This commit is contained in:
commit
2bef715211
221 changed files with 79792 additions and 0 deletions
814
crates/disasmer-dap/src/adapter.rs
Normal file
814
crates/disasmer-dap/src/adapter.rs
Normal file
|
|
@ -0,0 +1,814 @@
|
|||
use std::io::{self, BufReader};
|
||||
|
||||
use anyhow::Result;
|
||||
use disasmer_core::DebugRuntimeState;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::breakpoints::{
|
||||
freeze_all, freeze_all_at_line, next_breakpoint_after, position_confirmed_breakpoint_stop,
|
||||
request_thread, request_thread_id, resolve_breakpoints_for_source,
|
||||
restart_requires_whole_process, simulated_freeze_failure_thread, step_thread,
|
||||
stopped_thread_for_breakpoint,
|
||||
};
|
||||
use crate::dap_protocol::{initialize_capabilities, read_message, DapWriter};
|
||||
use crate::demo_backend::{
|
||||
is_explicit_demo_backend, start_simulated_backend, LINUX_THREAD, MAIN_THREAD,
|
||||
};
|
||||
use crate::runtime_client::{
|
||||
attach_services_runtime, create_debug_epoch, relaunch_services_main_runtime, restart_task,
|
||||
resume_debug_epoch, run_live_services_runtime, run_local_services_runtime,
|
||||
wait_for_debug_epoch_frozen, wait_for_debug_epoch_resumed, wait_for_services_runtime_outcome,
|
||||
RuntimeContinuationOutcome,
|
||||
};
|
||||
use crate::variables::variables_response;
|
||||
use crate::virtual_model::{AdapterState, DapSessionMode, RuntimeBackend};
|
||||
|
||||
pub(crate) fn run_adapter() -> Result<()> {
|
||||
let mut writer = DapWriter::new();
|
||||
let mut reader = BufReader::new(io::stdin());
|
||||
let mut state = AdapterState::default();
|
||||
let mut _local_runtime_session = None;
|
||||
|
||||
while let Some(request) = read_message(&mut reader)? {
|
||||
let command = request
|
||||
.get("command")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
match command {
|
||||
"initialize" => writer.response(&request, true, initialize_capabilities())?,
|
||||
"launch" | "attach" => {
|
||||
let args = request
|
||||
.get("arguments")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| json!({}));
|
||||
state.entry = args
|
||||
.get("entry")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("build")
|
||||
.to_owned();
|
||||
let project = args
|
||||
.get("project")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or(".")
|
||||
.to_owned();
|
||||
let runtime_backend = match runtime_backend_from_launch_arg(
|
||||
args.get("runtimeBackend").and_then(Value::as_str),
|
||||
) {
|
||||
Ok(runtime_backend) => runtime_backend,
|
||||
Err(message) => {
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let coordinator_endpoint = args
|
||||
.get("coordinatorEndpoint")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_owned);
|
||||
let source_path = args
|
||||
.get("sourcePath")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_owned);
|
||||
if let Err(err) = state.configure_launch(
|
||||
project,
|
||||
state.entry.clone(),
|
||||
runtime_backend,
|
||||
coordinator_endpoint,
|
||||
source_path,
|
||||
) {
|
||||
writer.error_response(&request, err.to_string())?;
|
||||
continue;
|
||||
}
|
||||
state.restart_existing = args
|
||||
.get("restartExisting")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
if let Some(process_id) = args.get("processId").and_then(Value::as_str) {
|
||||
state.process = disasmer_core::ProcessId::new(process_id);
|
||||
}
|
||||
if command == "attach" {
|
||||
state.session_mode = DapSessionMode::Attach;
|
||||
state.command_status =
|
||||
format!("attached to existing virtual process {}", state.process);
|
||||
}
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.event("initialized", json!({}))?;
|
||||
}
|
||||
"setBreakpoints" => {
|
||||
let requested_source_path = request
|
||||
.get("arguments")
|
||||
.and_then(|value| value.get("source"))
|
||||
.and_then(|value| value.get("path"))
|
||||
.and_then(Value::as_str);
|
||||
let requested_lines = request
|
||||
.get("arguments")
|
||||
.and_then(|value| value.get("breakpoints"))
|
||||
.and_then(Value::as_array)
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| item.get("line").and_then(Value::as_i64))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let response = resolve_breakpoints_for_source(
|
||||
&mut state,
|
||||
requested_source_path,
|
||||
requested_lines,
|
||||
)
|
||||
.iter()
|
||||
.map(|breakpoint| breakpoint.to_dap())
|
||||
.collect::<Vec<_>>();
|
||||
writer.response(&request, true, json!({ "breakpoints": response }))?;
|
||||
}
|
||||
"setExceptionBreakpoints" => {
|
||||
writer.response(&request, true, json!({ "breakpoints": [] }))?;
|
||||
}
|
||||
"configurationDone" => {
|
||||
if state.session_mode == DapSessionMode::Attach {
|
||||
match state.runtime_backend {
|
||||
RuntimeBackend::Simulated => {
|
||||
state.command_status =
|
||||
format!("attached to existing virtual process {}", state.process);
|
||||
}
|
||||
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices => {
|
||||
match attach_services_runtime(&state) {
|
||||
Ok(record) => state.apply_attach_record(record),
|
||||
Err(err) => {
|
||||
writer.error_response(
|
||||
&request,
|
||||
format!("services runtime attach failed: {err:#}"),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match state.runtime_backend {
|
||||
RuntimeBackend::LocalServices => match run_local_services_runtime(&state) {
|
||||
Ok((record, session)) => {
|
||||
state.apply_runtime_record(record);
|
||||
_local_runtime_session = Some(session);
|
||||
}
|
||||
Err(err) => {
|
||||
writer.error_response(
|
||||
&request,
|
||||
format!("local services runtime launch failed: {err:#}"),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
RuntimeBackend::LiveServices => match run_live_services_runtime(&state) {
|
||||
Ok(record) => {
|
||||
state.apply_runtime_record(record);
|
||||
}
|
||||
Err(err) => {
|
||||
writer.error_response(
|
||||
&request,
|
||||
format!("live services runtime launch failed: {err:#}"),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
RuntimeBackend::Simulated => {
|
||||
start_simulated_backend(&mut state);
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.response(&request, true, json!({}))?;
|
||||
let session_message = if state.session_mode == DapSessionMode::Attach {
|
||||
format!(
|
||||
"Attached to Disasmer virtual process {} with {:?} runtime\n",
|
||||
state.process, state.runtime_backend
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Disasmer bundle refreshed for entry `{}`; starting virtual process {} with {:?} runtime\n",
|
||||
state.entry, state.process, state.runtime_backend
|
||||
)
|
||||
};
|
||||
writer.output("console", session_message)?;
|
||||
if !state.breakpoints.is_empty() {
|
||||
let stopped_thread = stopped_thread_for_breakpoint(&state);
|
||||
if matches!(
|
||||
state.runtime_backend,
|
||||
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
|
||||
) && state.coordinator_debug_epoch.is_some()
|
||||
{
|
||||
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Disasmer Debug Epoch all-stop confirmed by every active participant",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
match freeze_all(&mut state, stopped_thread, None) {
|
||||
Ok(()) => {
|
||||
if let Err(err) = record_coordinator_debug_epoch(
|
||||
&mut state,
|
||||
stopped_thread,
|
||||
"breakpoint",
|
||||
) {
|
||||
let message = format!("coordinator debug epoch failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Disasmer Debug Epoch all-stop",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Err(failure) => {
|
||||
let message = failure.message();
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writer.event("terminated", json!({}))?;
|
||||
}
|
||||
}
|
||||
"threads" => {
|
||||
let threads = state
|
||||
.threads
|
||||
.values()
|
||||
.map(|thread| {
|
||||
json!({
|
||||
"id": thread.id,
|
||||
"name": format!("{}/{}", state.process, thread.name),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
writer.response(&request, true, json!({ "threads": threads }))?;
|
||||
}
|
||||
"stackTrace" => {
|
||||
let thread = request_thread(&request, &state);
|
||||
let source_path = crate::source::stack_source_path(&state);
|
||||
let frame_name = thread
|
||||
.runtime_stack_frames
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}::run", thread.name));
|
||||
writer.response(
|
||||
&request,
|
||||
true,
|
||||
json!({
|
||||
"stackFrames": [{
|
||||
"id": thread.frame_id,
|
||||
"name": frame_name,
|
||||
"line": thread.line,
|
||||
"column": 1,
|
||||
"source": {
|
||||
"name": crate::source::source_name(&source_path),
|
||||
"path": source_path,
|
||||
"presentationHint": "normal"
|
||||
}
|
||||
}],
|
||||
"totalFrames": 1
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
"source" => match crate::source::source_response(&state, &request) {
|
||||
Ok(body) => writer.response(&request, true, body)?,
|
||||
Err(err) => writer.error_response(&request, err.to_string())?,
|
||||
},
|
||||
"scopes" => {
|
||||
let frame_id = request
|
||||
.get("arguments")
|
||||
.and_then(|value| value.get("frameId"))
|
||||
.and_then(Value::as_i64)
|
||||
.unwrap_or(1000 + crate::demo_backend::MAIN_THREAD);
|
||||
let thread = state
|
||||
.threads
|
||||
.values()
|
||||
.find(|thread| thread.frame_id == frame_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| state.threads[&crate::demo_backend::MAIN_THREAD].clone());
|
||||
writer.response(
|
||||
&request,
|
||||
true,
|
||||
json!({
|
||||
"scopes": [
|
||||
{
|
||||
"name": "Source Locals",
|
||||
"variablesReference": thread.locals_ref,
|
||||
"expensive": false,
|
||||
"presentationHint": "locals"
|
||||
},
|
||||
{
|
||||
"name": "Wasm Frame Locals",
|
||||
"variablesReference": thread.wasm_locals_ref,
|
||||
"expensive": false,
|
||||
"presentationHint": "locals"
|
||||
},
|
||||
{
|
||||
"name": "Task Args and Handles",
|
||||
"variablesReference": thread.args_ref,
|
||||
"expensive": false,
|
||||
"presentationHint": "arguments"
|
||||
},
|
||||
{
|
||||
"name": "Disasmer Runtime",
|
||||
"variablesReference": thread.runtime_ref,
|
||||
"expensive": false
|
||||
},
|
||||
{
|
||||
"name": "Recent Output",
|
||||
"variablesReference": thread.output_ref,
|
||||
"expensive": false
|
||||
}
|
||||
]
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
"variables" => {
|
||||
let reference = request
|
||||
.get("arguments")
|
||||
.and_then(|value| value.get("variablesReference"))
|
||||
.and_then(Value::as_i64)
|
||||
.unwrap_or(0);
|
||||
writer.response(&request, true, variables_response(&state, reference))?;
|
||||
}
|
||||
"evaluate" => {
|
||||
let expression = request
|
||||
.get("arguments")
|
||||
.and_then(|value| value.get("expression"))
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
let result = match expression {
|
||||
"virtual_process_id" => state.process.to_string(),
|
||||
"debug_epoch" => state.epoch.to_string(),
|
||||
"command_status" => state.command_status.clone(),
|
||||
_ => "not available".to_owned(),
|
||||
};
|
||||
writer.response(
|
||||
&request,
|
||||
true,
|
||||
json!({ "result": result, "variablesReference": 0 }),
|
||||
)?;
|
||||
}
|
||||
"pause" => {
|
||||
let stopped_thread = default_thread_id(&state);
|
||||
match freeze_all(
|
||||
&mut state,
|
||||
stopped_thread,
|
||||
simulated_freeze_failure_thread(&request),
|
||||
) {
|
||||
Ok(()) => {
|
||||
if let Err(err) =
|
||||
record_coordinator_debug_epoch(&mut state, stopped_thread, "pause")
|
||||
{
|
||||
let message = format!("coordinator debug epoch failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "pause",
|
||||
"description": "Disasmer Debug Epoch pause",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Err(failure) => {
|
||||
let message = failure.message();
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
"continue" => {
|
||||
let thread_id =
|
||||
request_thread_id(&request).unwrap_or_else(|| default_thread_id(&state));
|
||||
let next_breakpoint = next_breakpoint_after(&state, thread_id);
|
||||
let previous_debug_epoch = state.coordinator_debug_epoch.unwrap_or(state.epoch);
|
||||
if let Err(err) = resume_coordinator_epoch(&mut state) {
|
||||
let message = format!("coordinator debug epoch resume failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
for thread in state.threads.values_mut() {
|
||||
if thread.state == DebugRuntimeState::Frozen {
|
||||
thread.state = DebugRuntimeState::Running;
|
||||
}
|
||||
}
|
||||
writer.response(&request, true, json!({ "allThreadsContinued": true }))?;
|
||||
writer.event(
|
||||
"continued",
|
||||
json!({
|
||||
"threadId": thread_id,
|
||||
"allThreadsContinued": true,
|
||||
}),
|
||||
)?;
|
||||
if matches!(
|
||||
state.runtime_backend,
|
||||
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
|
||||
) {
|
||||
match wait_for_services_runtime_outcome(&state, previous_debug_epoch) {
|
||||
Ok(RuntimeContinuationOutcome::Breakpoint(record)) => {
|
||||
state.apply_runtime_record(record);
|
||||
let stopped_thread = stopped_thread_for_breakpoint(&state);
|
||||
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Disasmer Debug Epoch all-stop confirmed by every active participant",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Ok(RuntimeContinuationOutcome::Terminal(record)) => {
|
||||
state.apply_runtime_record(record);
|
||||
if state.last_task_failed {
|
||||
writer.output("stderr", format!("{}\n", state.command_status))?;
|
||||
}
|
||||
writer.event("terminated", json!({}))?;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = format!("continued runtime observation failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "exception",
|
||||
"description": message,
|
||||
"threadId": thread_id,
|
||||
"allThreadsStopped": false,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some((stopped_thread, line)) = next_breakpoint {
|
||||
match freeze_all_at_line(&mut state, stopped_thread, line, None) {
|
||||
Ok(()) => {
|
||||
if let Err(err) = record_coordinator_debug_epoch(
|
||||
&mut state,
|
||||
stopped_thread,
|
||||
"breakpoint",
|
||||
) {
|
||||
let message = format!("coordinator debug epoch failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Disasmer Debug Epoch all-stop",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Err(failure) => {
|
||||
let message = failure.message();
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writer.event("terminated", json!({}))?;
|
||||
}
|
||||
}
|
||||
"next" | "stepIn" | "stepOut" => {
|
||||
if state.runtime_backend != RuntimeBackend::Simulated {
|
||||
writer.error_response(
|
||||
&request,
|
||||
"source stepping is not yet available from the executing node; use continue or pause instead of a synthetic step",
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let thread_id = request_thread_id(&request).unwrap_or(LINUX_THREAD);
|
||||
let description = match command {
|
||||
"next" => "step over",
|
||||
"stepIn" => "step in",
|
||||
"stepOut" => "step out",
|
||||
_ => "step",
|
||||
};
|
||||
step_thread(&mut state, thread_id, description);
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "step",
|
||||
"description": format!("Disasmer Debug Epoch {description}"),
|
||||
"threadId": thread_id,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
"restart" | "restartFrame" => {
|
||||
let thread_id = request_thread_id(&request)
|
||||
.or_else(|| {
|
||||
request
|
||||
.get("arguments")
|
||||
.and_then(|arguments| arguments.get("frameId"))
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|frame_id| {
|
||||
state
|
||||
.threads
|
||||
.values()
|
||||
.find(|thread| thread.frame_id == frame_id)
|
||||
.map(|thread| thread.id)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| default_thread_id(&state));
|
||||
if restart_requires_whole_process(&request) {
|
||||
let message = format!(
|
||||
"Incompatible source edit requires whole virtual-process restart for {}",
|
||||
state.process
|
||||
);
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
continue;
|
||||
}
|
||||
let restarting_failed_task = state
|
||||
.threads
|
||||
.get(&thread_id)
|
||||
.is_some_and(|thread| matches!(thread.state, DebugRuntimeState::Failed(_)));
|
||||
if state.runtime_backend != RuntimeBackend::Simulated {
|
||||
if thread_id == MAIN_THREAD && restarting_failed_task {
|
||||
match relaunch_services_main_runtime(&state) {
|
||||
Ok(record) => {
|
||||
state.apply_runtime_record(record);
|
||||
let stopped_thread = stopped_thread_for_breakpoint(&state);
|
||||
position_confirmed_breakpoint_stop(&mut state, stopped_thread);
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.output(
|
||||
"console",
|
||||
"Rebuilt the current bundle and restarted the failed coordinator main from its entry boundary\n",
|
||||
)?;
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Restarted main from the rebuilt bundle; every active participant confirmed all-stop",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = format!("coordinator main restart failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match restart_task_through_coordinator(&mut state, thread_id) {
|
||||
Ok(message) => {
|
||||
let previous_debug_epoch =
|
||||
state.coordinator_debug_epoch.unwrap_or(state.epoch);
|
||||
if let Some(thread) = state.threads.get_mut(&thread_id) {
|
||||
thread.state = DebugRuntimeState::Running;
|
||||
thread.recent_output.push(message.clone());
|
||||
}
|
||||
state.last_task_failed = false;
|
||||
state.command_status = message.clone();
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.output("console", format!("{message}\n"))?;
|
||||
if matches!(
|
||||
state.runtime_backend,
|
||||
RuntimeBackend::LocalServices | RuntimeBackend::LiveServices
|
||||
) {
|
||||
match wait_for_services_runtime_outcome(
|
||||
&state,
|
||||
previous_debug_epoch,
|
||||
) {
|
||||
Ok(RuntimeContinuationOutcome::Breakpoint(record)) => {
|
||||
state.apply_runtime_record(record);
|
||||
let stopped_thread = stopped_thread_for_breakpoint(&state);
|
||||
position_confirmed_breakpoint_stop(
|
||||
&mut state,
|
||||
stopped_thread,
|
||||
);
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "breakpoint",
|
||||
"description": "Restarted task reached a Wasm probe and every active participant confirmed all-stop",
|
||||
"threadId": stopped_thread,
|
||||
"allThreadsStopped": true,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Ok(RuntimeContinuationOutcome::Terminal(record)) => {
|
||||
state.apply_runtime_record(record);
|
||||
writer.event("terminated", json!({}))?;
|
||||
}
|
||||
Err(err) => {
|
||||
writer.event(
|
||||
"stopped",
|
||||
json!({
|
||||
"reason": "exception",
|
||||
"description": format!("restarted runtime observation failed: {err:#}"),
|
||||
"threadId": thread_id,
|
||||
"allThreadsStopped": false,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let message = format!("coordinator task restart failed: {err:#}");
|
||||
writer.output("stderr", format!("{message}\n"))?;
|
||||
writer.error_response(&request, message)?;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some(thread) = state.threads.get_mut(&thread_id) {
|
||||
thread.state = DebugRuntimeState::Running;
|
||||
thread
|
||||
.recent_output
|
||||
.push("task restarted from VFS checkpoint".to_owned());
|
||||
}
|
||||
if restarting_failed_task {
|
||||
state.last_task_failed = false;
|
||||
}
|
||||
state.command_status = format!(
|
||||
"{} task restarted from compatible VFS checkpoint",
|
||||
if restarting_failed_task {
|
||||
"failed"
|
||||
} else {
|
||||
"selected"
|
||||
}
|
||||
);
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.output(
|
||||
"console",
|
||||
format!(
|
||||
"Restarted {} task from compatible VFS checkpoint on thread {thread_id}\n",
|
||||
if restarting_failed_task {
|
||||
"failed"
|
||||
} else {
|
||||
"selected"
|
||||
}
|
||||
),
|
||||
)?;
|
||||
}
|
||||
"disconnect" | "terminate" => {
|
||||
writer.response(&request, true, json!({}))?;
|
||||
writer.event("terminated", json!({}))?;
|
||||
break;
|
||||
}
|
||||
_ => writer.error_response(&request, format!("unsupported DAP command: {command}"))?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restart_task_through_coordinator(state: &mut AdapterState, thread_id: i64) -> Result<String> {
|
||||
let task = state
|
||||
.threads
|
||||
.get(&thread_id)
|
||||
.or_else(|| state.threads.get(&default_thread_id(state)))
|
||||
.map(|thread| thread.task.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("selected debug thread does not map to a virtual task"))?;
|
||||
let record = restart_task(state, &task)?;
|
||||
if !record.accepted {
|
||||
let mut message = format!(
|
||||
"coordinator refused task restart for `{task}`: {}",
|
||||
record.message
|
||||
);
|
||||
if record.requires_whole_process_restart {
|
||||
message.push_str("; whole virtual-process restart required");
|
||||
}
|
||||
if record.active_task {
|
||||
message.push_str("; selected task is still active");
|
||||
}
|
||||
if record.completed_event_observed {
|
||||
message.push_str("; only terminal task-event metadata is available");
|
||||
}
|
||||
return Err(anyhow::anyhow!(message));
|
||||
}
|
||||
if !record.clean_boundary_available {
|
||||
return Err(anyhow::anyhow!(
|
||||
"coordinator accepted task restart for `{task}` without a clean checkpoint boundary"
|
||||
));
|
||||
}
|
||||
let restarted_task = record.restarted_task_instance.ok_or_else(|| {
|
||||
anyhow::anyhow!("coordinator accepted task restart without a new task-instance ID")
|
||||
})?;
|
||||
if let Some(thread) = state.threads.get_mut(&thread_id) {
|
||||
thread.task = restarted_task.clone();
|
||||
}
|
||||
state.debug_probes =
|
||||
crate::breakpoints::load_bundle_debug_probes(&state.project, &state.source_path);
|
||||
Ok(format!(
|
||||
"Coordinator restarted task `{task}` as `{restarted_task}` from the rebuilt bundle and a clean runtime checkpoint boundary"
|
||||
))
|
||||
}
|
||||
|
||||
fn record_coordinator_debug_epoch(
|
||||
state: &mut AdapterState,
|
||||
stopped_thread: i64,
|
||||
reason: &str,
|
||||
) -> Result<()> {
|
||||
if state.runtime_backend == RuntimeBackend::Simulated {
|
||||
return Ok(());
|
||||
}
|
||||
let stopped_task = state
|
||||
.threads
|
||||
.get(&stopped_thread)
|
||||
.map(|thread| thread.task.clone())
|
||||
.unwrap_or_else(|| state.threads[&default_thread_id(state)].task.clone());
|
||||
let record = create_debug_epoch(state, &stopped_task, reason)?;
|
||||
let status = wait_for_debug_epoch_frozen(state, record.epoch)?;
|
||||
state.epoch = record.epoch;
|
||||
state.coordinator_debug_epoch = Some(record.epoch);
|
||||
let previous_status = state.command_status.clone();
|
||||
state.command_status = format!(
|
||||
"{previous_status}; debug epoch {} {} after {}/{} signed participant freeze acknowledgements",
|
||||
status.epoch,
|
||||
status.command,
|
||||
status.acknowledgements.len(),
|
||||
status.expected_tasks
|
||||
);
|
||||
if let Some(thread) = state.threads.get_mut(&stopped_thread) {
|
||||
thread.recent_output.push(format!(
|
||||
"coordinator debug epoch {} reached all-stop after {}/{} signed participant acknowledgements",
|
||||
status.epoch,
|
||||
status.acknowledgements.len(),
|
||||
status.expected_tasks
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_thread_id(state: &AdapterState) -> i64 {
|
||||
if state.runtime_backend == RuntimeBackend::Simulated
|
||||
&& state.threads.contains_key(&LINUX_THREAD)
|
||||
{
|
||||
LINUX_THREAD
|
||||
} else {
|
||||
MAIN_THREAD
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_coordinator_epoch(state: &mut AdapterState) -> Result<()> {
|
||||
let Some(epoch) = state.coordinator_debug_epoch else {
|
||||
return Ok(());
|
||||
};
|
||||
if state.runtime_backend == RuntimeBackend::Simulated {
|
||||
return Ok(());
|
||||
}
|
||||
let record = resume_debug_epoch(state, epoch)?;
|
||||
let status = wait_for_debug_epoch_resumed(state, epoch)?;
|
||||
state.coordinator_debug_epoch = None;
|
||||
state.command_status = format!(
|
||||
"debug epoch {} resumed after {}/{} signed participant acknowledgements",
|
||||
status.epoch,
|
||||
status.acknowledgements.len(),
|
||||
status.expected_tasks
|
||||
);
|
||||
let status_thread = default_thread_id(state);
|
||||
if let Some(thread) = state.threads.get_mut(&status_thread) {
|
||||
thread.recent_output.push(format!(
|
||||
"coordinator debug epoch {} resumed with {} after {}/{} acknowledgements ({} affected task(s))",
|
||||
status.epoch,
|
||||
status.command,
|
||||
status.acknowledgements.len(),
|
||||
status.expected_tasks,
|
||||
record.affected_tasks
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn runtime_backend_from_launch_arg(
|
||||
value: Option<&str>,
|
||||
) -> Result<RuntimeBackend, String> {
|
||||
match value {
|
||||
None | Some("local-services") => Ok(RuntimeBackend::LocalServices),
|
||||
Some("live-services") => Ok(RuntimeBackend::LiveServices),
|
||||
Some(value) if is_explicit_demo_backend(value) => Ok(RuntimeBackend::Simulated),
|
||||
Some(value) => Err(format!(
|
||||
"unsupported Disasmer runtimeBackend `{value}`; use local-services, live-services, or explicit demo"
|
||||
)),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue