Skip to content

Commit

Permalink
Check memory usage only if jedi is used and reduce frequency of these…
Browse files Browse the repository at this point in the history
… checks (#1510)

Fixes #1277
* Use settimeout instead of setinterval (the requests were getting piled up with setinterval).
* I've reduced the frequency of checks from 2 seconds to 15 seconds.
* Check memory usage only if jedi is used>
  • Loading branch information
DonJayamanne authored Apr 27, 2018
1 parent bffcbc1 commit 15f2a14
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 18 deletions.
1 change: 1 addition & 0 deletions news/2 Fixes/1277.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduce the frequency within which the memory usage of the language server is checked, also ensure memory usage is not checked unless language server functionality is used.
58 changes: 40 additions & 18 deletions src/client/providers/jediProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ChildProcess } from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as pidusage from 'pidusage';
import { setInterval } from 'timers';
import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode';
import { PythonSettings } from '../common/configSettings';
import { debounce, swallowExceptions } from '../common/decorators';
Expand Down Expand Up @@ -147,6 +146,8 @@ export class JediProxy implements Disposable {
private logger: ILogger;
private ignoreJediMemoryFootprint: boolean = false;
private pidUsageFailures = { timer: new StopWatch(), counter: 0 };
private lastCmdIdProcessed?: number;
private lastCmdIdProcessedForPidUsage?: number;

public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) {
this.workspacePath = workspacePath;
Expand All @@ -157,12 +158,7 @@ export class JediProxy implements Disposable {
this.initialized = createDeferred<void>();
this.startLanguageServer().then(() => this.initialized.resolve()).ignoreErrors();

// Check memory footprint periodically. Do not check on every request due to
// the performance impact. See https://github.com/soyuka/pidusage - on Windows
// it is using wmic which means spawning cmd.exe process on every request.
if (this.shouldCheckJediMemoryFootprint()) {
setInterval(() => this.checkJediMemoryFootprint(), 2000);
}
this.checkJediMemoryFootprint().ignoreErrors();
}

private static getProperty<T>(o: object, name: string): T {
Expand Down Expand Up @@ -220,32 +216,58 @@ export class JediProxy implements Disposable {
if (this.ignoreJediMemoryFootprint || this.pythonSettings.jediMemoryLimit === -1) {
return false;
}
if (this.lastCmdIdProcessedForPidUsage && this.lastCmdIdProcessed &&
this.lastCmdIdProcessedForPidUsage === this.lastCmdIdProcessed) {
// If no more commands were processed since the last time,
// then there's no need to check again.
return false;
}
return true;
}
private checkJediMemoryFootprint() {
private async checkJediMemoryFootprint() {
// Check memory footprint periodically. Do not check on every request due to
// the performance impact. See https://github.com/soyuka/pidusage - on Windows
// it is using wmic which means spawning cmd.exe process on every request.
if (this.pythonSettings.jediMemoryLimit === -1) {
return;
}

await this.checkJediMemoryFootprintImpl();
setTimeout(() => this.checkJediMemoryFootprint(), 15 * 1000);
}
private async checkJediMemoryFootprintImpl(): Promise<void> {
if (!this.proc || this.proc.killed) {
return;
}
if (!this.shouldCheckJediMemoryFootprint()) {
return;
}
this.lastCmdIdProcessedForPidUsage = this.lastCmdIdProcessed;

// Do not run pidusage over and over, wait for it to finish.
const deferred = createDeferred<void>();
pidusage.stat(this.proc.pid, async (err, result) => {
if (err) {
this.pidUsageFailures.counter += 1;
// If this function fails 5 times in the last 30 seconds, lets not try ever again.
if (this.pidUsageFailures.timer.elapsedTime > 30 * 1000) {
this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 5;
// If this function fails 2 times in the last 60 seconds, lets not try ever again.
if (this.pidUsageFailures.timer.elapsedTime > 60 * 1000) {
this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 2;
this.pidUsageFailures.counter = 0;
this.pidUsageFailures.timer.reset();
}
return console.error('Python Extension: (pidusage)', err);
}
const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192);
if (result && result.memory > limit * 1024 * 1024) {
this.logger.logWarning(`IntelliSense process memory consumption exceeded limit of ${limit} MB and process will be restarted.\nThe limit is controlled by the 'python.jediMemoryLimit' setting.`);
await this.restartLanguageServer();
console.error('Python Extension: (pidusage)', err);
} else {
const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192);
if (result && result.memory > limit * 1024 * 1024) {
this.logger.logWarning(`IntelliSense process memory consumption exceeded limit of ${limit} MB and process will be restarted.\nThe limit is controlled by the 'python.jediMemoryLimit' setting.`);
await this.restartLanguageServer();
}
}

deferred.resolve();
});

return deferred.promise;
}

@swallowExceptions('JediProxy')
Expand Down Expand Up @@ -374,7 +396,7 @@ export class JediProxy implements Disposable {
if (cmd === null) {
return;
}

this.lastCmdIdProcessed = cmd.id;
if (JediProxy.getProperty<object>(response, 'arguments')) {
this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1);
return;
Expand Down

0 comments on commit 15f2a14

Please sign in to comment.