Skip to content

Commit a4e9763

Browse files
authored
feat(node-runtime-worker-thread): Allow to interrupt evalutate calls COMPASS-4562 (#653)
* feat(node-runtime-worker-thread): Do not allow concurrent evaluation calls * feat(node-runtime-worker-thread): Allow to interrupt async evaluation of worker runtime * feat(node-runtime-worker-thread): Expose interrupt method on main thread runtime * feat(node-runtime-worker-thread): Pass interruption handle from worker to the main thread to allow sync evaluation interruption * feat(node-runtime-worker-thread): Allow to interrupt sync evaluations from main thread * fix(node-runtime-worker-thread): Use filepaths instead file source when spawning child process and worker It's not required anymore to use inlined source and this allows the library to properly resolve interruptor when used in compass-shell plugin as a dependency * build(compass-shell): Rebuild interruptor before starting dev version of compass shell * chore(node-runtime-worker-thread): More methods exposed -> more listeners * chore(compass-shell): Enable react devtools in compass-shell dev mode * feat(browser-repl, compass-shell): Cancel running evaluations when Ctrl-C is pressed * feat(node-runtime-worker-thread): Throw when trying to lock the lock that's already locked * fix(node-runtime-worker-thread): Increase timeout for a flaky test
1 parent 47c35a1 commit a4e9763

File tree

19 files changed

+695
-157
lines changed

19 files changed

+695
-157
lines changed

packages/browser-repl/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@mongosh/browser-runtime-core": "0.0.0-dev.0",
4343
"@mongosh/history": "0.0.0-dev.0",
4444
"@mongosh/i18n": "0.0.0-dev.0",
45+
"@mongosh/node-runtime-worker-thread": "0.0.0-dev.0",
4546
"@mongosh/service-provider-core": "0.0.0-dev.0",
4647
"@mongosh/shell-evaluator": "0.0.0-dev.0",
4748
"pretty-bytes": "^5.3.0",

packages/browser-repl/src/components/editor.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface EditorProps {
2020
onArrowDownOnLastLine(): void | Promise<void>;
2121
onChange(value: string): void | Promise<void>;
2222
onClearCommand(): void | Promise<void>;
23+
onSigInt(): Promise<boolean>;
2324
operationInProgress: boolean;
2425
setInputRef?(ref: { editor?: HTMLElement }): void;
2526
value: string;
@@ -32,6 +33,7 @@ export class Editor extends Component<EditorProps> {
3233
onArrowDownOnLastLine: noop,
3334
onChange: noop,
3435
onClearCommand: noop,
36+
onSigInt: noop,
3537
operationInProgress: false,
3638
value: '',
3739
moveCursorToTheEndOfInput: false
@@ -142,6 +144,15 @@ export class Editor extends Component<EditorProps> {
142144
name: 'clearShell',
143145
bindKey: { win: 'Ctrl-L', mac: 'Command-L' },
144146
exec: this.props.onClearCommand
147+
},
148+
{
149+
name: 'SIGINT',
150+
bindKey: { win: 'Ctrl-C', mac: 'Ctrl-C' },
151+
exec: this.props.onSigInt,
152+
// Types don't have it but it exists
153+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
154+
// @ts-ignore
155+
readOnly: true,
145156
}
146157
]}
147158
width="100%"

packages/browser-repl/src/components/shell-input.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface ShellInputProps {
1616
operationInProgress?: boolean;
1717
prompt?: string;
1818
setInputRef?(ref: { editor?: HTMLElement }): void;
19+
onSigInt?(): Promise<boolean>;
1920
}
2021

2122
interface ShellInputState {
@@ -131,21 +132,28 @@ export class ShellInput extends Component<ShellInputProps, ShellInputState> {
131132
/>);
132133
}
133134

134-
const editor = (<Editor
135-
autocompleter={this.props.autocompleter}
136-
onArrowUpOnFirstLine={this.historyBack}
137-
onArrowDownOnLastLine={this.historyNext}
138-
onChange={this.onChange}
139-
onEnter={this.onEnter}
140-
onClearCommand={this.props.onClearCommand}
141-
setInputRef={this.props.setInputRef}
142-
value={this.state.currentValue}
143-
operationInProgress={this.props.operationInProgress}
144-
moveCursorToTheEndOfInput={this.state.didLoadHistoryItem}
145-
/>);
135+
const editor = (
136+
<Editor
137+
autocompleter={this.props.autocompleter}
138+
onArrowUpOnFirstLine={this.historyBack}
139+
onArrowDownOnLastLine={this.historyNext}
140+
onChange={this.onChange}
141+
onEnter={this.onEnter}
142+
onClearCommand={this.props.onClearCommand}
143+
setInputRef={this.props.setInputRef}
144+
value={this.state.currentValue}
145+
operationInProgress={this.props.operationInProgress}
146+
moveCursorToTheEndOfInput={this.state.didLoadHistoryItem}
147+
onSigInt={this.props.onSigInt}
148+
/>
149+
);
146150

147151
const className = classnames(styles['shell-input']);
148152

149-
return <LineWithIcon className={className} icon={prompt}>{editor}</LineWithIcon>;
153+
return (
154+
<LineWithIcon className={className} icon={prompt}>
155+
{editor}
156+
</LineWithIcon>
157+
);
150158
}
151159
}

packages/browser-repl/src/components/shell.tsx

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import classnames from 'classnames';
33
import { PasswordPrompt } from './password-prompt';
44
import { ShellInput } from './shell-input';
55
import { ShellOutput, ShellOutputEntry } from './shell-output';
6-
import { Runtime } from '@mongosh/browser-runtime-core';
6+
import type { Runtime } from '@mongosh/browser-runtime-core';
77
import { changeHistory } from '@mongosh/history';
8+
import type { WorkerRuntime } from '@mongosh/node-runtime-worker-thread';
89

910
const styles = require('./shell.less');
1011

1112
interface ShellProps {
1213
/* The runtime used to evaluate code.
13-
*/
14-
runtime: Runtime;
14+
*/
15+
runtime: Runtime | WorkerRuntime;
1516

1617
/* A function called each time the output changes with an array of
1718
* ShellOutputEntryes.
@@ -110,7 +111,7 @@ export class Shell extends Component<ShellProps, ShellState> {
110111
}
111112

112113
private evaluate = async(code: string): Promise<ShellOutputEntry> => {
113-
let outputLine : ShellOutputEntry;
114+
let outputLine: ShellOutputEntry;
114115

115116
try {
116117
this.props.runtime.setEvaluationListener(this);
@@ -135,7 +136,7 @@ export class Shell extends Component<ShellProps, ShellState> {
135136
private async updateShellPrompt(): Promise<void> {
136137
let shellPrompt = '>';
137138
try {
138-
shellPrompt = await this.props.runtime.getShellPrompt() ?? '>';
139+
shellPrompt = (await this.props.runtime.getShellPrompt()) ?? '>';
139140
} catch (e) {
140141
// Just ignore errors when getting the prompt...
141142
}
@@ -276,36 +277,55 @@ export class Shell extends Component<ShellProps, ShellState> {
276277
}
277278
};
278279

280+
private onSigInt = (): Promise<boolean> => {
281+
if (
282+
this.state.operationInProgress &&
283+
(this.props.runtime as WorkerRuntime).interrupt
284+
) {
285+
return (this.props.runtime as WorkerRuntime).interrupt();
286+
}
287+
288+
return Promise.resolve(false);
289+
};
290+
279291
renderInput(): JSX.Element {
280292
if (this.state.passwordPrompt) {
281-
return (<PasswordPrompt
282-
onFinish={this.onFinishPasswordPrompt}
283-
onCancel={this.onCancelPasswordPrompt}
284-
prompt={this.state.passwordPrompt}
285-
/>);
293+
return (
294+
<PasswordPrompt
295+
onFinish={this.onFinishPasswordPrompt}
296+
onCancel={this.onCancelPasswordPrompt}
297+
prompt={this.state.passwordPrompt}
298+
/>
299+
);
286300
}
287-
return (<ShellInput
288-
prompt={this.state.shellPrompt}
289-
autocompleter={this.props.runtime}
290-
history={this.state.history}
291-
onClearCommand={this.onClearCommand}
292-
onInput={this.onInput}
293-
operationInProgress={this.state.operationInProgress}
294-
setInputRef={(ref: {editor?: HTMLElement}): void => { this.shellInputRef = ref;}}
295-
/>);
301+
302+
return (
303+
<ShellInput
304+
prompt={this.state.shellPrompt}
305+
autocompleter={this.props.runtime}
306+
history={this.state.history}
307+
onClearCommand={this.onClearCommand}
308+
onInput={this.onInput}
309+
operationInProgress={this.state.operationInProgress}
310+
setInputRef={(ref: { editor?: HTMLElement }): void => {
311+
this.shellInputRef = ref;
312+
}}
313+
onSigInt={this.onSigInt}
314+
/>
315+
);
296316
}
297317

298318
render(): JSX.Element {
299319
return (
300-
<div
301-
className={classnames(styles.shell)}
302-
onClick={this.onShellClicked}
303-
>
320+
<div className={classnames(styles.shell)} onClick={this.onShellClicked}>
304321
<div>
305-
<ShellOutput
306-
output={this.state.output} />
322+
<ShellOutput output={this.state.output} />
307323
</div>
308-
<div ref={(el): void => { this.shellInputElement = el; }}>
324+
<div
325+
ref={(el): void => {
326+
this.shellInputElement = el;
327+
}}
328+
>
309329
{this.renderInput()}
310330
</div>
311331
</div>

packages/compass-shell/electron/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
const {app, BrowserWindow} = require('electron');
33
const path = require('path');
44
const url = require('url');
5+
const {
6+
default: installDevtool,
7+
REACT_DEVELOPER_TOOLS,
8+
} = require('electron-devtools-installer');
59

610
// Keep a global reference of the window object, if you don't, the window will
711
// be closed automatically when the JavaScript object is garbage collected.
@@ -63,7 +67,9 @@ function createWindow() {
6367
// This method will be called when Electron has finished
6468
// initialization and is ready to create browser windows.
6569
// Some APIs can only be used after this event occurs.
66-
app.on('ready', createWindow);
70+
app.on('ready', () =>
71+
installDevtool(REACT_DEVELOPER_TOOLS).finally(createWindow)
72+
);
6773

6874
// Quit when all windows are closed.
6975
app.on('window-all-closed', () => {

0 commit comments

Comments
 (0)