-
Notifications
You must be signed in to change notification settings - Fork 30.7k
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
chore: log js stacks from unresponsive window #241390
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -540,6 +540,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
private pendingLoadConfig: INativeWindowConfiguration | undefined; | ||
private wasLoaded = false; | ||
|
||
private JScallStackCollector: NodeJS.Timeout | undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💄 : Class members first letter are lowercase unless maybe its a static constant. |
||
|
||
constructor( | ||
config: IWindowCreationOptions, | ||
@ILogService logService: ILogService, | ||
|
@@ -653,6 +655,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
|
||
// Window error conditions to handle | ||
this._register(Event.fromNodeEventEmitter(this._win, 'unresponsive')(() => this.onWindowError(WindowError.UNRESPONSIVE))); | ||
this._register(Event.fromNodeEventEmitter(this._win, 'responsive')(() => this.onWindowError(WindowError.RESPONSIVE))); | ||
this._register(Event.fromNodeEventEmitter(this._win.webContents, 'render-process-gone', (event, details) => details)(details => this.onWindowError(WindowError.PROCESS_GONE, { ...details }))); | ||
this._register(Event.fromNodeEventEmitter(this._win.webContents, 'did-fail-load', (event, exitCode, reason) => ({ exitCode, reason }))(({ exitCode, reason }) => this.onWindowError(WindowError.LOAD, { reason, exitCode }))); | ||
|
||
|
@@ -728,6 +731,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
} | ||
|
||
private async onWindowError(error: WindowError.UNRESPONSIVE): Promise<void>; | ||
private async onWindowError(error: WindowError.RESPONSIVE): Promise<void>; | ||
private async onWindowError(error: WindowError.PROCESS_GONE, details: { reason: string; exitCode: number }): Promise<void>; | ||
private async onWindowError(error: WindowError.LOAD, details: { reason: string; exitCode: number }): Promise<void>; | ||
private async onWindowError(type: WindowError, details?: { reason?: string; exitCode?: number }): Promise<void> { | ||
|
@@ -739,6 +743,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
case WindowError.UNRESPONSIVE: | ||
this.logService.error('CodeWindow: detected unresponsive'); | ||
break; | ||
case WindowError.RESPONSIVE: | ||
this.logService.error('CodeWindow: recovered from unresponsive'); | ||
break; | ||
case WindowError.LOAD: | ||
this.logService.error(`CodeWindow: failed to load (reason: ${details?.reason || '<unknown>'}, code: ${details?.exitCode || '<unknown>'})`); | ||
break; | ||
|
@@ -797,6 +804,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
return; | ||
} | ||
|
||
// Interrupt V8 and collect JavaScript stack | ||
this.startCollectingJScallStacks(); | ||
|
||
// Show Dialog | ||
const { response, checkboxChecked } = await this.dialogMainService.showMessageBox({ | ||
type: 'warning', | ||
|
@@ -813,6 +823,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
// Handle choice | ||
if (response !== 2 /* keep waiting */) { | ||
const reopen = response === 0; | ||
this.stopCollectingJScallStacks(); | ||
await this.destroyWindow(reopen, checkboxChecked); | ||
} | ||
} | ||
|
@@ -845,6 +856,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
await this.destroyWindow(reopen, checkboxChecked); | ||
} | ||
break; | ||
case WindowError.RESPONSIVE: | ||
this.stopCollectingJScallStacks(); | ||
break; | ||
} | ||
} | ||
|
||
|
@@ -958,6 +972,23 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { | |
} | ||
} | ||
|
||
private async startCollectingJScallStacks(): Promise<void> { | ||
if (this.JScallStackCollector) { | ||
return; | ||
} | ||
this.JScallStackCollector = setInterval(async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a |
||
const stack = await this._win.webContents.mainFrame.collectJavaScriptCallStack(); | ||
this.logService.error('CodeWindow unresponsive : ', stack); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we remember the last printed stack and simply ignore it when it always repeats? |
||
}, 1000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this stop at one point printing? What if the user leaves this running? Maybe the interval is also quite short and do we know that the stack would be any different after collecting it once? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we stop printing in 2 cases currently,
I am thinking of the following scenarios of unresponsive windows,
The call stack api basically interrupts V8 at a point in time and collects the current executing stack, since it is a single sample it would be hard to reason either of those cases. Hence I am thinking of collecting samples at an interval till the stop printing cases kick in. The interval I chose was just the defaults for most sampling profilers. But what I don't is aggregate the stacks so it would indeed make the console noisy.
Instead of ignoring, what if we aggregate a count for each repetitive stacks and only display the result at the end of the stack collection period. Something like
where N = Unresponsive time end - Unresponsive time start / Sample interval There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would just stop after 10 iterations or so, reducing the risk of filling the log forever. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you clarify the situation where the unresponsive interval will never end ? My understanding is that we always prompt a dialog and user has to choose an action to close or reopen at some point. Also my new suggestion is to print the log only once at the end of the unresponsive session. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
+1 on that. I would not print as you capture but aggregate (by count and maybe timestamp) so that you can print a summary. Either when responsive again, confirmed, or after a long'ish freeze time (in case never recovers). With that don't flood the log and have trace-like information |
||
} | ||
|
||
private async stopCollectingJScallStacks(): Promise<void> { | ||
if (this.JScallStackCollector) { | ||
clearInterval(this.JScallStackCollector); | ||
this.JScallStackCollector = undefined; | ||
} | ||
} | ||
|
||
addTabbedWindow(window: ICodeWindow): void { | ||
if (isMacintosh && window.win) { | ||
this._win.addTabbedWindow(window.win); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be using the same code as above for
COI.CoopAndCope
?