Skip to content

Commit f15c1f9

Browse files
authored
Use a task queue to ensure code blocks are executed sequentially (#510)
* Only `await` on the last code block This ensures that all of the blocks are sent over in a single sequential batch, with no way for anyone else to execute their own block between two of ours, which would cause ordering issues * Introduce a task queue for running `blocks` * Abstract out a `callback` * Move the queue to its own file * Simplify API and add ability to have multiple queues * `await` calls to `run()` * Switch to more official PQueue Lmao at the fact that I implemented this from scratch * Rename to `ExecuteQueue` * Tweak docs * Delete `queue.ts`
1 parent 8ec3fc8 commit f15c1f9

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

apps/vscode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@
13561356
"markdown-it": "^13.0.1",
13571357
"markdown-it-highlightjs": "^4.0.1",
13581358
"nanoid": "^4.0.0",
1359+
"p-queue": "^8.0.1",
13591360
"picomatch": "^2.3.1",
13601361
"quarto-core": "*",
13611362
"quarto-lsp": "*",

apps/vscode/src/host/execute-queue.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* execute-queue.ts
3+
*
4+
* Copyright (C) 2024 by Posit Software, PBC
5+
*
6+
* Unless you have received this program directly from Posit Software pursuant
7+
* to the terms of a commercial license agreement with Posit Software, then
8+
* this program is licensed to you under the terms of version 3 of the
9+
* GNU Affero General Public License. This program is distributed WITHOUT
10+
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11+
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12+
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13+
*
14+
*/
15+
16+
import PQueue from 'p-queue';
17+
18+
/**
19+
* Language specific execution queue
20+
*
21+
* A singleton class that constructs and manages multiple execution queues, identified by
22+
* their `key` (typically the language name).
23+
*/
24+
export class ExecuteQueue {
25+
/// Singleton instance
26+
private static _instance: ExecuteQueue;
27+
28+
/// Maps a `key` to its `queue`
29+
private _queues = new Map<string, PQueue>();
30+
31+
/**
32+
* Constructor
33+
*
34+
* Private since we only want one of these. Access using `instance()` instead.
35+
*/
36+
private constructor() { }
37+
38+
/**
39+
* Accessor for the singleton instance
40+
*
41+
* Creates it if it doesn't exist.
42+
*/
43+
static get instance(): ExecuteQueue {
44+
if (!ExecuteQueue._instance) {
45+
// Initialize if we've never accessed it
46+
ExecuteQueue._instance = new ExecuteQueue();
47+
}
48+
49+
return ExecuteQueue._instance;
50+
}
51+
52+
/**
53+
* Add a `callback` for execution on `key`'s task queue
54+
*
55+
* Returns a promise that resolves when the task finishes
56+
*/
57+
async add(key: string, callback: () => Promise<void>): Promise<void> {
58+
let queue = this._queues.get(key);
59+
60+
if (queue === undefined) {
61+
// If we've never initialized this key's queue, do so now.
62+
// Limit `concurrency` to 1, because we don't want tasks run out of order.
63+
queue = new PQueue({ concurrency: 1 });
64+
this._queues.set(key, queue);
65+
}
66+
67+
return queue.add(callback);
68+
}
69+
}

apps/vscode/src/host/hooks.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as hooks from 'positron';
1818

1919
import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider } from '.';
2020
import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors';
21+
import { ExecuteQueue } from './execute-queue';
2122
import { MarkdownEngine } from '../markdown/engine';
2223
import { virtualDoc, virtualDocUri, adjustedPosition } from "../vdoc/vdoc";
2324

@@ -56,14 +57,26 @@ export function hooksExtensionHost() : ExtensionHost {
5657
case "r":
5758
return {
5859
execute: async (blocks: string[], _editorUri?: vscode.Uri) : Promise<void> => {
59-
for (const block of blocks) {
60-
let code = block;
61-
if (language === "python" && isKnitrDocument(document, engine)) {
62-
language = "r";
63-
code = pythonWithReticulate(block);
60+
const runtime = hooksApi()?.runtime;
61+
62+
if (runtime === undefined) {
63+
// Can't do anything without a runtime
64+
return;
65+
}
66+
67+
if (language === "python" && isKnitrDocument(document, engine)) {
68+
language = "r";
69+
blocks = blocks.map(pythonWithReticulate);
70+
}
71+
72+
// Our callback executes each block sequentially
73+
const callback = async () => {
74+
for (const block of blocks) {
75+
await runtime.executeCode(language, block, false);
6476
}
65-
await hooksApi()?.runtime.executeCode(language, code, false);
66-
}
77+
}
78+
79+
await ExecuteQueue.instance.add(language, callback);
6780
},
6881
executeSelection: async () : Promise<void> => {
6982
await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', {languageId: language});
@@ -166,4 +179,3 @@ async function getStatementRange(
166179
position
167180
);
168181
}
169-

yarn.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4306,6 +4306,11 @@ eventemitter3@^4.0.0:
43064306
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
43074307
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
43084308

4309+
eventemitter3@^5.0.1:
4310+
version "5.0.1"
4311+
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
4312+
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
4313+
43094314
exceljs@^4.3.0:
43104315
version "4.3.0"
43114316
resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.3.0.tgz#939bc0d4c59c200acadb7051be34d25c109853c4"
@@ -6342,6 +6347,19 @@ p-locate@^5.0.0:
63426347
dependencies:
63436348
p-limit "^3.0.2"
63446349

6350+
p-queue@^8.0.1:
6351+
version "8.0.1"
6352+
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8"
6353+
integrity sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==
6354+
dependencies:
6355+
eventemitter3 "^5.0.1"
6356+
p-timeout "^6.1.2"
6357+
6358+
p-timeout@^6.1.2:
6359+
version "6.1.2"
6360+
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.2.tgz#22b8d8a78abf5e103030211c5fc6dee1166a6aa5"
6361+
integrity sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==
6362+
63456363
pako@~1.0.2:
63466364
version "1.0.11"
63476365
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"

0 commit comments

Comments
 (0)