Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change default behavior for debugging of Flask #2954

Merged
merged 11 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,12 @@ matrix:
python: "3.7-dev"
env: MULTIROOT_WORKSPACE_TEST=true
allow_failures:
- os: linux
python: "2.7"
env: DEBUGGER_TEST=true
- os: linux
python: "2.7"
env: DEBUGGER_TEST_RELEASE=true
- os: linux
python: "3.6-dev"
env: DEBUGGER_TEST=true
- os: linux
python: "3.6-dev"
env: DEBUGGER_TEST_RELEASE=true
- os: linux
python: "3.7-dev"
env: DEBUGGER_TEST=true
- os: linux
python: "3.7-dev"
env: DEBUGGER_TEST_RELEASE=true
Expand Down
1 change: 1 addition & 0 deletions news/1 Enhancements/2326.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for automatic reloading of flask in the debugger.
1 change: 1 addition & 0 deletions news/1 Enhancements/80.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for multi process debugging.
16 changes: 7 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,10 @@
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development",
"FLASK_DEBUG": "0"
"FLASK_DEBUG": "1"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
"run"
],
"jinja": true
}
Expand Down Expand Up @@ -688,12 +686,12 @@
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py"
},
"FLASK_APP": "app.py",
"FLASK_ENV": "development",
"FLASK_DEBUG": "1"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
"run"
],
"jinja": true
},
Expand Down
18 changes: 18 additions & 0 deletions src/client/common/process/proc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ import { ExecutionResult, IBufferDecoder, IProcessService, ObservableExecutionRe

export class ProcessService implements IProcessService {
constructor(private readonly decoder: IBufferDecoder, private readonly env?: EnvironmentVariables) { }
public static isAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
public static kill(pid: number): void {
// tslint:disable-next-line:no-require-imports
const killProcessTree = require('tree-kill');
try {
killProcessTree(pid);
} catch {
// Ignore.
}

}
public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult<string> {
const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING;
delete options.encoding;
Expand Down
21 changes: 0 additions & 21 deletions src/client/debugger/debugAdapter/Common/Contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,6 @@

'use strict';

// tslint:disable:interface-name member-access no-single-line-block-comment no-any no-stateless-class member-ordering prefer-method-signature no-unnecessary-class

import { OutputEvent } from 'vscode-debugadapter';
import { DebuggerPerformanceTelemetry, DebuggerTelemetry } from '../../../telemetry/types';

export class TelemetryEvent extends OutputEvent {
body!: {
/** The category of output (such as: 'console', 'stdout', 'stderr', 'telemetry'). If not specified, 'console' is assumed. */
category: string;
/** The output to report. */
output: string;
/** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */
data?: any;
};
constructor(output: string, data?: DebuggerTelemetry | DebuggerPerformanceTelemetry) {
super(output, 'telemetry');
if (data) {
this.body.data = data;
}
}
}
export interface IDebugServer {
port: number;
host?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
'use strict';

import { DebugSession } from 'vscode-debugadapter';
import { LaunchRequestArguments } from '../../types';
import { IKnownLaunchRequestArguments, LaunchRequestArguments } from '../../types';
import { IDebugLauncherScriptProvider } from '../types';
import { LocalDebugClient } from './LocalDebugClient';

Expand All @@ -14,8 +14,16 @@ export class LocalDebugClientV2 extends LocalDebugClient {
}
protected buildDebugArguments(cwd: string, debugPort: number): string[] {
const launcher = this.launcherScriptProvider.getLauncherFilePath();
const noDebugArg = this.args.noDebug ? ['--nodebug'] : [];
return [launcher, ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()];
const additionalPtvsdArgs: string[] = [];
if (this.args.noDebug) {
additionalPtvsdArgs.push('--nodebug');
}
const multiProcessPropety: keyof IKnownLaunchRequestArguments = 'multiProcess';
const multiProcess = (this.args as Object).hasOwnProperty(multiProcessPropety) ? this.args.multiProcess : true;
if (multiProcess) {
additionalPtvsdArgs.push('--multiprocess');
}
return [launcher, ...additionalPtvsdArgs, '--client', '--host', 'localhost', '--port', debugPort.toString()];
}
protected buildStandardArguments() {
const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IPlatformService } from '../../../common/platform/types';
import { IServiceContainer } from '../../../ioc/types';
import { sendTelemetryEvent } from '../../../telemetry';
import { DEBUGGER } from '../../../telemetry/constants';
import { DebuggerTelemetryV2 } from '../../../telemetry/types';
import { DebuggerTelemetry } from '../../../telemetry/types';
import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../../types';
import { BaseConfigurationProvider } from './baseProvider';
import { IConfigurationProviderUtils } from './types';
Expand Down Expand Up @@ -127,7 +127,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide
return (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK') ? true : false;
}
private sendTelemetry(trigger: 'launch' | 'attach', debugConfiguration: Partial<LaunchRequestArguments & AttachRequestArguments>) {
const telemetryProps: DebuggerTelemetryV2 = {
const telemetryProps: DebuggerTelemetry = {
trigger,
console: debugConfiguration.console,
hasEnvVars: typeof debugConfiguration.env === 'object' && Object.keys(debugConfiguration.env).length > 0,
Expand Down
31 changes: 31 additions & 0 deletions src/client/debugger/extension/hooks/childProcessAttachHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import { DebugSessionCustomEvent } from 'vscode';
import { swallowExceptions } from '../../../common/utils/decorators';
import { PTVSDEvents } from './constants';
import { ChildProcessLaunchData, IChildProcessAttachService, IDebugSessionEventHandlers } from './types';

/**
* This class is responsible for automatically attaching the debugger to any
* child processes launched. I.e. this is the classs responsible for multi-proc debugging.
* @export
* @class ChildProcessAttachEventHandler
* @implements {IDebugSessionEventHandlers}
*/
@injectable()
export class ChildProcessAttachEventHandler implements IDebugSessionEventHandlers {
constructor(@inject(IChildProcessAttachService) private readonly childProcessAttachService: IChildProcessAttachService) { }

@swallowExceptions('Handle child process launch')
public async handleCustomEvent(event: DebugSessionCustomEvent): Promise<void> {
if (!event || event.event !== PTVSDEvents.ChildProcessLaunched) {
return;
}
const data = event.body! as ChildProcessLaunchData;
await this.childProcessAttachService.attach(data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,29 @@
'use strict';

import { inject, injectable } from 'inversify';
import { DebugConfiguration, DebugSessionCustomEvent, WorkspaceFolder } from 'vscode';
import { DebugConfiguration, WorkspaceFolder } from 'vscode';
import { IApplicationShell, IDebugService, IWorkspaceService } from '../../../common/application/types';
import { swallowExceptions } from '../../../common/utils/decorators';
import { noop } from '../../../common/utils/misc';
import { AttachRequestArguments, LaunchRequestArguments } from '../../types';
import { ICustomDebugSessionEventHandlers } from './types';

const eventName = 'ptvsd_subprocess';

type ChildProcessLaunchData = {
rootProcessId: number;
initialProcessId: number;
rootStartRequest: {
// tslint:disable-next-line:no-banned-terms
arguments: LaunchRequestArguments | AttachRequestArguments;
command: 'attach' | 'request';
seq: number;
type: string;
};
parentProcessId: number;
processId: number;
port: number;
};
import { captureTelemetry } from '../../../telemetry';
import { DEBUGGER_ATTACH_TO_CHILD_PROCESS } from '../../../telemetry/constants';
import { AttachRequestArguments } from '../../types';
import { ChildProcessLaunchData, IChildProcessAttachService } from './types';

/**
* This class is responsible for attaching the debugger to any
* child processes launched. I.e. this is the classs responsible for multi-proc debugging.
* @export
* @class ChildProcessAttachEventHandler
* @implements {IChildProcessAttachService}
*/
@injectable()
export class ChildProcessLaunchEventHandler implements ICustomDebugSessionEventHandlers {
export class ChildProcessAttachService implements IChildProcessAttachService {
constructor(@inject(IApplicationShell) private readonly appShell: IApplicationShell,
@inject(IDebugService) private readonly debugService: IDebugService,
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) { }

@swallowExceptions('Handle child process launch')
public async handleEvent(event: DebugSessionCustomEvent): Promise<void> {
if (!event || event.event !== eventName) {
return;
}
const data = event.body! as ChildProcessLaunchData;
@captureTelemetry(DEBUGGER_ATTACH_TO_CHILD_PROCESS)
public async attach(data: ChildProcessLaunchData): Promise<void> {
const folder = this.getRelatedWorkspaceFolder(data);
const debugConfig = this.getAttachConfiguration(data);
const launched = await this.debugService.startDebugging(folder, debugConfig);
Expand All @@ -55,12 +42,11 @@ export class ChildProcessLaunchEventHandler implements ICustomDebugSessionEventH
return this.workspaceService.workspaceFolders!.find(ws => ws.uri.fsPath === workspaceFolder);
}
protected getAttachConfiguration(data: ChildProcessLaunchData): AttachRequestArguments & DebugConfiguration {
const args = data.rootStartRequest.arguments;
// tslint:disable-next-line:no-any
const config = JSON.parse(JSON.stringify(data.rootStartRequest.arguments)) as any as (AttachRequestArguments & DebugConfiguration);
const config = JSON.parse(JSON.stringify(args)) as any as (AttachRequestArguments & DebugConfiguration);

if (data.rootStartRequest.arguments.request === 'attach') {
config.host = data.rootStartRequest.arguments.host!;
}
config.host = args.request === 'attach' ? args.host! : 'localhost';
config.port = data.port;
config.name = `Child Process ${data.processId}`;
config.request = 'attach';
Expand Down
12 changes: 12 additions & 0 deletions src/client/debugger/extension/hooks/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

export enum PTVSDEvents {
// Event sent by PTVSD when a child process is launched and ready to be attached to for multi-proc debugging.
ChildProcessLaunched = 'ptvsd_subprocess',

// Event sent by PTVSD when a process is started (identital to the `process` event in debugger protocol).
ProcessLaunched = 'ptvsd_process'
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
import { inject, multiInject } from 'inversify';
import { IDebugService } from '../../../common/application/types';
import { IDisposableRegistry } from '../../../common/types';
import { ICustomDebugSessionEventHandlers } from './types';
import { IDebugSessionEventHandlers } from './types';

export class CustomDebugSessionEventDispatcher {
constructor(@multiInject(ICustomDebugSessionEventHandlers) private readonly eventHandlers: ICustomDebugSessionEventHandlers[],
export class DebugSessionEventDispatcher {
constructor(@multiInject(IDebugSessionEventHandlers) private readonly eventHandlers: IDebugSessionEventHandlers[],
@inject(IDebugService) private readonly debugService: IDebugService,
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry) { }
public registerEventHandlers() {
this.disposables.push(this.debugService.onDidReceiveDebugSessionCustomEvent(e => {
this.eventHandlers.forEach(handler => handler.handleEvent(e).ignoreErrors());
this.eventHandlers.forEach(handler => handler.handleCustomEvent ? handler.handleCustomEvent(e).ignoreErrors() : undefined);
}));
this.disposables.push(this.debugService.onDidTerminateDebugSession(e => {
this.eventHandlers.forEach(handler => handler.handleTerminateEvent ? handler.handleTerminateEvent(e).ignoreErrors() : undefined);
}));
}
}
Loading