use crate::debug_client::DebugClient;
use std::process::exit;
use crate::run_state::{RunState, Output};
use std::collections::HashSet;
use serde_json::Value;
use std::fmt;
use crate::debug_client::Param;
use crate::debug_client::Command::{*};
use crate::debug_client::Response;
use crate::debug_client::Event::{*};
use crate::debug_client::Response::{*};
pub struct Debugger {
client: &'static dyn DebugClient,
input_breakpoints: HashSet<(usize, usize)>,
block_breakpoints: HashSet<(usize, usize)>,
output_breakpoints: HashSet<(usize, String)>,
break_at_job: usize,
function_breakpoints: HashSet<usize>,
}
#[derive(Debug, Clone)]
enum BlockType {
OutputBlocked,
UnreadySender, }
#[derive(Debug, Clone)]
struct BlockerNode {
process_id: usize,
io_number: usize,
blocktype: BlockType,
blockers: Vec<BlockerNode>,
}
impl BlockerNode {
fn new(process_id: usize, io_number: usize, blocktype: BlockType) -> Self {
BlockerNode {
process_id,
io_number,
blocktype,
blockers: vec!(),
}
}
}
impl fmt::Display for BlockerNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.blocktype {
BlockType::OutputBlocked => write!(f, " -> #{}", self.process_id),
BlockType::UnreadySender => write!(f, " <- #{}", self.process_id)
}
}
}
impl Debugger {
pub fn new(client: &'static dyn DebugClient) -> Self {
Debugger {
client,
input_breakpoints: HashSet::<(usize, usize)>::new(),
block_breakpoints: HashSet::<(usize, usize)>::new(),
output_breakpoints: HashSet::<(usize, String)>::new(),
break_at_job: std::usize::MAX,
function_breakpoints: HashSet::<usize>::new(),
}
}
pub fn start(&mut self, state: &RunState) -> (bool, bool) {
self.client.send_event(Start);
self.wait_for_command(state, None)
}
pub fn job_completed(&self, output: &Output) {
self.client.send_event(
JobCompleted(output.job_id, output.function_id, output.result.0.clone())
);
}
pub fn check_prior_to_job(&mut self, state: &RunState, next_job_id: usize, function_id: usize) -> (bool, bool) {
if self.break_at_job == next_job_id ||
self.function_breakpoints.contains(&function_id) {
self.client.send_event(PriorToSendingJob(next_job_id, function_id));
self.print(state, Some(Param::Numeric(function_id)));
return self.wait_for_command(state, Some(state.jobs()));
}
(false, false)
}
pub fn check_on_block_creation(&mut self, state: &RunState, blocking_id: usize,
blocking_io_number: usize, blocked_id: usize) {
if self.block_breakpoints.contains(&(blocked_id, blocking_id)) {
self.client.send_event(BlockBreakpoint(blocked_id, blocking_id, blocking_io_number));
self.wait_for_command(state, Some(state.jobs()));
}
}
pub fn check_prior_to_send(&mut self, state: &RunState, source_process_id: usize, output_route: &str,
value: &Value, destination_id: usize, input_number: usize) {
if self.output_breakpoints.contains(&(source_process_id, output_route.to_string())) ||
self.input_breakpoints.contains(&(destination_id, input_number)) {
self.client.send_event(SendingValue(
source_process_id, value.clone(), destination_id, input_number));
self.client.send_event(DataBreakpoint(source_process_id, output_route.to_string(),
value.clone(), destination_id, input_number));
self.wait_for_command(state, Some(state.jobs()));
}
}
pub fn error(&mut self, state: &RunState, error_message: String) {
self.client.send_event(RuntimeError(error_message));
self.wait_for_command(state, Some(state.jobs()));
}
pub fn panic(&mut self, state: &RunState, output: Output) {
self.client.send_event(Panic(output));
self.wait_for_command(state, Some(state.jobs()));
}
pub fn end(&mut self, state: &RunState) -> (bool, bool) {
self.client.send_event(End);
self.deadlock_inspection(state);
self.wait_for_command(state, None)
}
fn wait_for_command(&mut self, state: &RunState, jobs_sent: Option<usize>) -> (bool, bool) {
loop {
match self.client.get_command(jobs_sent) {
GetState => {
}
GetFunctionState(_id) => {
}
Breakpoint(param) =>
self.client.send_response(self.add_breakpoint(state, param)),
Delete(param) =>
self.client.send_response(self.delete_breakpoint(param)),
Inspect =>
self.client.send_response(self.inspect(state)),
List =>
self.client.send_response(self.list_breakpoints()),
Print(param) =>
self.client.send_response(self.print(state, param)),
ExitDebugger => {
self.client.send_response(Exiting);
exit(1);
}
Continue => {
if jobs_sent.is_some() {
self.client.send_response(Ack);
return (false, false);
}
},
RunReset => {
if jobs_sent.is_some() {
self.client.send_response(self.reset());
return (false, true);
} else {
self.client.send_response(Running);
return (false, false);
}
},
Step(param) => {
if jobs_sent.is_some() {
self.client.send_response(self.step(state, param));
return (true, false);
}
},
};
}
}
fn add_breakpoint(&mut self, state: &RunState, param: Option<Param>) -> Response {
let mut response = String::new();
match param {
None => response.push_str("'break' command must specify a process id to break on\n"),
Some(Param::Numeric(process_id)) => {
if process_id > state.num_functions() {
response.push_str(&format!("There is no process with id '{}' to set a breakpoint on\n",
process_id));
} else {
self.function_breakpoints.insert(process_id);
response.push_str(&format!("Set process breakpoint on Function #{}\n",
process_id));
}
}
Some(Param::Input((dest_id, input_number))) => {
response.push_str(&format!("Set data breakpoint on process #{} receiving data on input: {}\n",
dest_id, input_number));
self.input_breakpoints.insert((dest_id, input_number));
}
Some(Param::Block((blocked_id, blocking_id))) => {
response.push_str(&format!("Set block breakpoint for Function #{} being blocked by Function #{}\n",
blocked_id, blocking_id));
self.block_breakpoints.insert((blocked_id, blocking_id));
}
Some(Param::Output((source_id, source_output_route))) => {
response.push_str(&format!("Set data breakpoint on process #{} sending data via output: '/{}'\n",
source_id, source_output_route));
self.output_breakpoints.insert((source_id, source_output_route));
}
Some(Param::Wildcard) => {
response.push_str("To break on every process, you can just single step using 's' command\n");
}
}
Message(response)
}
fn delete_breakpoint(&mut self, param: Option<Param>) -> Response {
let mut response = String::new();
match param {
None => response.push_str("No process id specified\n"),
Some(Param::Numeric(process_number)) => {
if self.function_breakpoints.remove(&process_number) {
response.push_str(&format!("Breakpoint on process #{} was deleted\n", process_number));
} else {
response.push_str("No breakpoint number '{}' exists\n");
}
}
Some(Param::Input((dest_id, input_number))) => {
self.input_breakpoints.remove(&(dest_id, input_number));
response.push_str("Inputs breakpoint removed\n");
}
Some(Param::Block((blocked_id, blocking_id))) => {
self.input_breakpoints.remove(&(blocked_id, blocking_id));
response.push_str("Inputs breakpoint removed\n");
}
Some(Param::Output((source_id, source_output_route))) => {
self.output_breakpoints.remove(&(source_id, source_output_route));
response.push_str("Output breakpoint removed\n");
}
Some(Param::Wildcard) => {
self.output_breakpoints.clear();
self.input_breakpoints.clear();
self.function_breakpoints.clear();
response.push_str("Deleted all breakpoints\n");
}
}
Message(response)
}
fn list_breakpoints(&self) -> Response {
let mut response = String::new();
let mut breakpoints = false;
if !self.function_breakpoints.is_empty() {
breakpoints = true;
response.push_str("Function Breakpoints: \n");
for process_id in &self.function_breakpoints {
response.push_str(&format!("\tFunction #{}\n", process_id));
}
}
if !self.output_breakpoints.is_empty() {
breakpoints = true;
response.push_str("Output Breakpoints: \n");
for (process_id, route) in &self.output_breakpoints {
response.push_str(&format!("\tOutput #{}/{}\n", process_id, route));
}
}
if !self.input_breakpoints.is_empty() {
breakpoints = true;
response.push_str("Input Breakpoints: \n");
for (process_id, input_number) in &self.input_breakpoints {
response.push_str(&format!("\tInput #{}:{}\n", process_id, input_number));
}
}
if !self.block_breakpoints.is_empty() {
breakpoints = true;
response.push_str("Block Breakpoints: \n");
for (blocked_id, blocking_id) in &self.block_breakpoints {
response.push_str(&format!("\tBlock #{}->#{}\n", blocked_id, blocking_id));
}
}
if !breakpoints {
response.push_str("No Breakpoints set. Use the 'b' command to set a breakpoint. Use 'h' for help.\n");
}
Message(response)
}
fn inspect(&self, state: &RunState) -> Response {
let mut response = String::new();
response.push_str("Running inspections\n");
response.push_str("Running deadlock inspection\n");
response.push_str(&self.deadlock_inspection(state));
Message(response)
}
fn print(&self, state: &RunState, param: Option<Param>) -> Response {
let mut response = String::new();
match param {
None => response.push_str(&format!("{}\n", state)),
Some(Param::Numeric(function_id)) |
Some(Param::Block((function_id, _))) => {
response.push_str(&self.print_function(state, function_id));
}
Some(Param::Input((function_id, _))) => {
response.push_str(&self.print_function(state, function_id));
}
Some(Param::Wildcard) => {
response.push_str(&self.print_all_processes(state))
}
Some(Param::Output(_)) => response.push_str(
"Cannot display the output of a process until it is executed. \
Set a breakpoint on the process by id and then step over it")
}
Message(response)
}
fn reset(&mut self) -> Response {
self.break_at_job = std::usize::MAX;
Resetting
}
fn step(&mut self, state: &RunState, steps: Option<Param>) -> Response {
return match steps {
None => {
self.break_at_job = state.jobs() + 1;
Ack
}
Some(Param::Numeric(steps)) => {
self.break_at_job = state.jobs() + steps;
Ack
}
_ => {
Error("Did not understand step command parameter\n".into())
}
};
}
fn find_blockers(&self, state: &RunState, process_id: usize) -> Vec<BlockerNode> {
let mut blockers: Vec<BlockerNode> = state.get_output_blockers(process_id).iter().map(|(id, io)|
BlockerNode::new(*id, *io, BlockType::OutputBlocked)).collect();
let input_blockers: Vec<BlockerNode> = state.get_input_blockers(process_id).iter().map(|(id, io)|
BlockerNode::new(*id, *io, BlockType::UnreadySender)).collect();
blockers.extend(input_blockers);
blockers
}
fn traverse_blocker_tree(&self, state: &RunState, visited_nodes: &mut Vec<usize>,
root_node_id: usize, node: &mut BlockerNode) -> Vec<BlockerNode> {
visited_nodes.push(node.process_id);
node.blockers = self.find_blockers(state, node.process_id);
for blocker in &mut node.blockers {
if blocker.process_id == root_node_id {
return vec!(blocker.clone()); }
if !visited_nodes.contains(&blocker.process_id) {
let mut blocker_subtree = self.traverse_blocker_tree(state, visited_nodes,
root_node_id, blocker);
if blocker_subtree.len() > 0 {
blocker_subtree.insert(0, blocker.clone());
return blocker_subtree;
}
}
}
vec!()
}
fn display_set(root_node: &BlockerNode, node_set: Vec<BlockerNode>) -> String {
let mut display_string = String::new();
display_string.push_str(&format!("#{}", root_node.process_id));
for node in node_set {
display_string.push_str(&format!("{}", node));
}
display_string
}
fn deadlock_inspection(&self, state: &RunState) -> String {
let mut response = String::new();
for blocked_process_id in state.get_blocked() {
let mut root_node = BlockerNode::new(*blocked_process_id, 0, BlockType::OutputBlocked);
let mut visited_nodes = vec!();
let deadlock_set = self.traverse_blocker_tree(state, &mut visited_nodes,
*blocked_process_id, &mut root_node);
if deadlock_set.len() > 0 {
response.push_str(&format!("{}\n", Self::display_set(&root_node, deadlock_set)));
}
}
response
}
fn print_function(&self, state: &RunState, function_id: usize) -> String {
let mut response = String::new();
let function = state.get(function_id);
response.push_str(&format!("{}", function));
response.push_str(&state.display_state(function_id));
response
}
fn print_all_processes(&self, state: &RunState) -> String {
let mut response = String::new();
for id in 0..state.num_functions() {
response.push_str(&self.print_function(state, id));
}
response
}
}