"use strict"; import * as net from "net"; import {EventEmitter} from "events"; import {FrameKind, IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult, IPythonStackFrame, IStepCommand} from "./Common/Contracts"; import {IPythonBreakpoint, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, IBreakpointCommand, IChildEnumCommand} from "./Common/Contracts"; import {PythonEvaluationResultReprKind, IExecutionCommand, enum_EXCEPTION_STATE} from "./Common/Contracts"; import {Commands} from "./ProxyCommands"; import {IdDispenser} from "../common/idDispenser"; import {PythonProcessCallbackHandler} from "./PythonProcessCallbackHandler"; import {SocketStream} from "../common/comms/SocketStream"; export class PythonProcess extends EventEmitter implements IPythonProcess { private id: number; public get Id(): number { return this.id; } private guid: string; public get Guid(): string { return this.guid; } private hasExited: boolean; public get HasExited(): boolean { return this.hasExited; } private _mainThread: IPythonThread; public get MainThread(): IPythonThread { return this._mainThread; } private _lastExecutedThread: IPythonThread; public get LastExecutedThread(): IPythonThread { return this._lastExecutedThread; } private _idDispenser: IdDispenser; private _threads: Map; public get Threads(): Map { return this._threads; } public PendingChildEnumCommands: Map; public PendingExecuteCommands: Map; private executeCommandsQueue: IExecutionCommand[]; private callbackHandler: PythonProcessCallbackHandler; private stream: SocketStream = null; private programDirectory: string; public get ProgramDirectory(): string { return this.programDirectory; } constructor(id: number, guid: string, programDirectory: string) { super(); this.id = id; this.guid = guid; this._threads = new Map(); this._idDispenser = new IdDispenser(); this.PendingChildEnumCommands = new Map(); this.PendingExecuteCommands = new Map(); this.programDirectory = programDirectory; this.executeCommandsQueue = []; } public Kill() { if (!this.isRemoteProcess && this.pid && typeof this.pid === "number") { try { let kill = require("tree-kill"); kill(this.pid); this.pid = null; } catch (ex) { } } } public Terminate() { this.stream.Write(Commands.ExitCommandBytes); } public Detach() { this.stream.Write(Commands.DetachCommandBytes); } private guidRead: boolean; private statusRead: boolean; private pidRead: boolean; private pid: number; private isRemoteProcess: Boolean; public Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean = false): boolean { this.isRemoteProcess = isRemoteProcess; if (this.stream === null) { this.stream = new SocketStream(socket, buffer); } else { this.stream.Append(buffer); } if (!isRemoteProcess) { if (!this.guidRead) { this.stream.BeginTransaction(); let guid = this.stream.ReadString(); if (this.stream.HasInsufficientDataForReading) { this.stream.RollBackTransaction(); return false; } this.guidRead = true; this.stream.EndTransaction(); } if (!this.statusRead) { this.stream.BeginTransaction(); let result = this.stream.ReadInt32(); if (this.stream.HasInsufficientDataForReading) { this.stream.RollBackTransaction(); return false; } this.statusRead = true; this.stream.EndTransaction(); } if (!this.pidRead) { this.stream.BeginTransaction(); this.pid = this.stream.ReadInt32(); if (this.stream.HasInsufficientDataForReading) { this.stream.RollBackTransaction(); return false; } this.pidRead = true; this.stream.EndTransaction(); } } this.callbackHandler = new PythonProcessCallbackHandler(this, this.stream, this._idDispenser); this.callbackHandler.on("detach", () => this.emit("detach")); this.callbackHandler.on("last", () => this.emit("last")); this.callbackHandler.on("moduleLoaded", arg => this.emit("moduleLoaded", arg)); this.callbackHandler.on("asyncBreakCompleted", arg => this.emit("asyncBreakCompleted", arg)); this.callbackHandler.on("threadCreated", arg => this.emit("threadCreated", arg)); this.callbackHandler.on("threadExited", arg => this.emit("threadExited", arg)); this.callbackHandler.on("stepCompleted", arg => this.onPythonStepCompleted(arg)); this.callbackHandler.on("breakpointSet", arg => this.onBreakpointSet(arg, true)); this.callbackHandler.on("breakpointNotSet", arg => this.onBreakpointSet(arg, false)); this.callbackHandler.on("output", (pyThread, output) => this.emit("output", pyThread, output)); this.callbackHandler.on("exceptionRaised", (pyThread, ex, brkType) => { this._lastExecutedThread = pyThread; this.emit("exceptionRaised", pyThread, ex, brkType); }); this.callbackHandler.on("breakpointHit", (pyThread, breakpointId) => this.onBreakpointHit(pyThread, breakpointId)); this.callbackHandler.on("processLoaded", arg => { this._mainThread = arg; this._lastExecutedThread = this._mainThread; this.emit("processLoaded", arg); }); this.callbackHandler.HandleIncomingData(); return true; } public HandleIncomingData(buffer: Buffer) { this.stream.Append(buffer); if (!this.isRemoteProcess) { if (!this.guidRead) { this.stream.RollBackTransaction(); let guid = this.stream.ReadString(); if (this.stream.HasInsufficientDataForReading) { return; } this.guidRead = true; this.stream.EndTransaction(); } if (!this.statusRead) { this.stream.BeginTransaction(); let result = this.stream.ReadInt32(); if (this.stream.HasInsufficientDataForReading) { this.stream.RollBackTransaction(); return; } this.statusRead = true; this.stream.EndTransaction(); } if (!this.pidRead) { this.stream.BeginTransaction(); this.pid = this.stream.ReadInt32(); if (this.stream.HasInsufficientDataForReading) { this.stream.RollBackTransaction(); return; } this.pidRead = true; this.stream.EndTransaction(); } } this.callbackHandler.HandleIncomingData(); } // #region Step Commands private onPythonStepCompleted(pyThread: IPythonThread) { this._lastExecutedThread = pyThread; this.emit("stepCompleted", pyThread); } private sendStepCommand(threadId: number, command: Buffer) { this.stream.Write(command); this.stream.WriteInt64(threadId); } public SendExceptionInfo(defaultBreakOnMode: enum_EXCEPTION_STATE, breakOn: Map) { this.stream.Write(Commands.SetExceptionInfoCommandBytes); this.stream.WriteInt32(defaultBreakOnMode); if (breakOn === null || breakOn === undefined) { this.stream.WriteInt32(0); } else { this.stream.WriteInt32(breakOn.size); breakOn.forEach((value, key) => { this.stream.WriteInt32(value); this.stream.WriteString(key); }); } } public SendStepOver(threadId: number) { return this.sendStepCommand(threadId, Commands.StepOverCommandBytes); } public SendStepOut(threadId: number) { return this.sendStepCommand(threadId, Commands.StepOutCommandBytes); } public SendStepInto(threadId: number) { return this.sendStepCommand(threadId, Commands.StepIntoCommandBytes); } // #endregion private onBreakpointHit(pyThread: IPythonThread, breakpointId: number) { this._lastExecutedThread = pyThread; this.emit("breakpointHit", pyThread, breakpointId); } private onBreakpointSet(breakpointId: number, success: boolean) { // Find the last breakpoint command associated with this breakpoint let index = this.breakpointCommands.findIndex(cmd => cmd.Id === breakpointId); if (index === -1) { // Hmm this is not possible, log this exception and carry on this.emit("error", "command.breakpoint.hit", `Uknown Breakpoit Id ${breakpointId}`); return; } let cmd = this.breakpointCommands.splice(index, 1)[0]; if (success) { cmd.PromiseResolve(); } else { cmd.PromiseReject(); } } private breakpointCommands: IBreakpointCommand[] = []; public DisableBreakPoint(breakpoint: IPythonBreakpoint) { if (breakpoint.IsDjangoBreakpoint) { this.stream.Write(Commands.RemoveDjangoBreakPointCommandBytes); } else { this.stream.Write(Commands.RemoveBreakPointCommandBytes); } this.stream.WriteInt32(breakpoint.LineNo); this.stream.WriteInt32(breakpoint.Id); if (breakpoint.IsDjangoBreakpoint) { this.stream.WriteString(breakpoint.Filename); } } public BindBreakpoint(brkpoint: IPythonBreakpoint): Promise { return new Promise((resolve, reject) => { let bkCmd: IBreakpointCommand = { Id: brkpoint.Id, PromiseResolve: resolve, PromiseReject: reject }; this.breakpointCommands.push(bkCmd); if (brkpoint.IsDjangoBreakpoint) { this.stream.Write(Commands.AddDjangoBreakPointCommandBytes); } else { this.stream.Write(Commands.SetBreakPointCommandBytes); } this.stream.WriteInt32(brkpoint.Id); this.stream.WriteInt32(brkpoint.LineNo); this.stream.WriteString(brkpoint.Filename); if (brkpoint.IsDjangoBreakpoint) { // Bining django breakpoints don't return any responses // Assume it worked resolve(); } else { this.SendCondition(brkpoint); this.SendPassCount(brkpoint); } }); } private SendCondition(breakpoint: IPythonBreakpoint) { this.stream.WriteInt32(breakpoint.ConditionKind); this.stream.WriteString(breakpoint.Condition || ""); } private SendPassCount(breakpoint: IPythonBreakpoint) { // DebugWriteCommand("Send BP pass count"); this.stream.WriteInt32(breakpoint.PassCountKind); this.stream.WriteInt32(breakpoint.PassCount); } public SendResumeThread(threadId: number) { return this.sendStepCommand(threadId, Commands.ResumeThreadCommandBytes); } public SendContinue(): Promise { return new Promise(resolve => { this.stream.Write(Commands.ResumeAllCommandBytes); resolve(); }); } public AutoResumeThread(threadId: number) { } public SendClearStepping(threadId: number) { } public Break() { this.stream.Write(Commands.BreakAllCommandBytes); } public ExecuteText(text: string, reprKind: PythonEvaluationResultReprKind, stackFrame: IPythonStackFrame): Promise { return new Promise((resolve, reject) => { let executeId = this._idDispenser.Allocate(); let cmd: IExecutionCommand = { Id: executeId, Text: text, Frame: stackFrame, PromiseResolve: resolve, PromiseReject: reject, ReprKind: reprKind }; this.executeCommandsQueue.push(cmd); this.ProcessPendingExecuteCommands(); }); } public ProcessPendingExecuteCommands() { if (this.executeCommandsQueue.length === 0 || this.PendingExecuteCommands.size > 0) { return; } const cmd = this.executeCommandsQueue.shift(); this.PendingExecuteCommands.set(cmd.Id, cmd); this.stream.Write(Commands.ExecuteTextCommandBytes); this.stream.WriteString(cmd.Text); this.stream.WriteInt64(cmd.Frame.Thread.Id); this.stream.WriteInt32(cmd.Frame.FrameId); this.stream.WriteInt32(cmd.Id); this.stream.WriteInt32(cmd.Frame.Kind); this.stream.WriteInt32(cmd.ReprKind); } public EnumChildren(text: string, stackFrame: IPythonStackFrame, timeout: number): Promise { return new Promise((resolve, reject) => { let executeId = this._idDispenser.Allocate(); if (typeof (executeId) !== "number") { let y = ""; } let cmd: IChildEnumCommand = { Id: executeId, Frame: stackFrame, PromiseResolve: resolve, PromiseReject: reject }; this.PendingChildEnumCommands.set(executeId, cmd); setTimeout(() => { if (this.PendingChildEnumCommands.has(executeId)) { this.PendingChildEnumCommands.delete(executeId); } let seconds = timeout / 1000; reject(`Enumerating children for ${text} timed out after ${seconds} seconds.`); }, timeout); this.stream.Write(Commands.GetChildrenCommandBytes); this.stream.WriteString(text); this.stream.WriteInt64(stackFrame.Thread.Id); this.stream.WriteInt32(stackFrame.FrameId); this.stream.WriteInt32(executeId); this.stream.WriteInt32(stackFrame.Kind); }); } public SetLineNumber(pythonStackFrame: IPythonStackFrame, lineNo: number) { } }