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

Use a task queue to ensure code blocks are executed sequentially #510

Merged
merged 10 commits into from
Sep 9, 2024
1 change: 1 addition & 0 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,7 @@
"markdown-it": "^13.0.1",
"markdown-it-highlightjs": "^4.0.1",
"nanoid": "^4.0.0",
"p-queue": "^8.0.1",
"picomatch": "^2.3.1",
"quarto-core": "*",
"quarto-lsp": "*",
Expand Down
69 changes: 69 additions & 0 deletions apps/vscode/src/host/execute-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* execute-queue.ts
*
* Copyright (C) 2024 by Posit Software, PBC
*
* Unless you have received this program directly from Posit Software pursuant
* to the terms of a commercial license agreement with Posit Software, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/

import PQueue from 'p-queue';

/**
* Language specific execution queue
*
* A singleton class that constructs and manages multiple execution queues, identified by
* their `key` (typically the language name).
*/
export class ExecuteQueue {
/// Singleton instance
private static _instance: ExecuteQueue;

/// Maps a `key` to its `queue`
private _queues = new Map<string, PQueue>();

/**
* Constructor
*
* Private since we only want one of these. Access using `instance()` instead.
*/
private constructor() { }

/**
* Accessor for the singleton instance
*
* Creates it if it doesn't exist.
*/
static get instance(): ExecuteQueue {
if (!ExecuteQueue._instance) {
// Initialize if we've never accessed it
ExecuteQueue._instance = new ExecuteQueue();
}

return ExecuteQueue._instance;
}

/**
* Add a `callback` for execution on `key`'s task queue
*
* Returns a promise that resolves when the task finishes
*/
async add(key: string, callback: () => Promise<void>): Promise<void> {
let queue = this._queues.get(key);

if (queue === undefined) {
// If we've never initialized this key's queue, do so now.
// Limit `concurrency` to 1, because we don't want tasks run out of order.
queue = new PQueue({ concurrency: 1 });
this._queues.set(key, queue);
}

return queue.add(callback);
}
}
28 changes: 20 additions & 8 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as hooks from 'positron';

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

Expand Down Expand Up @@ -56,14 +57,26 @@ export function hooksExtensionHost() : ExtensionHost {
case "r":
return {
execute: async (blocks: string[], _editorUri?: vscode.Uri) : Promise<void> => {
for (const block of blocks) {
let code = block;
if (language === "python" && isKnitrDocument(document, engine)) {
language = "r";
code = pythonWithReticulate(block);
const runtime = hooksApi()?.runtime;

if (runtime === undefined) {
// Can't do anything without a runtime
return;
}

if (language === "python" && isKnitrDocument(document, engine)) {
language = "r";
blocks = blocks.map(pythonWithReticulate);
}
DavisVaughan marked this conversation as resolved.
Show resolved Hide resolved

// Our callback executes each block sequentially
const callback = async () => {
for (const block of blocks) {
await runtime.executeCode(language, block, false);
}
await hooksApi()?.runtime.executeCode(language, code, false);
}
}

await ExecuteQueue.instance.add(language, callback);
},
executeSelection: async () : Promise<void> => {
await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', {languageId: language});
Expand Down Expand Up @@ -166,4 +179,3 @@ async function getStatementRange(
position
);
}

18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4306,6 +4306,11 @@ eventemitter3@^4.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==

eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==

exceljs@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.3.0.tgz#939bc0d4c59c200acadb7051be34d25c109853c4"
Expand Down Expand Up @@ -6342,6 +6347,19 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"

p-queue@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8"
integrity sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==
dependencies:
eventemitter3 "^5.0.1"
p-timeout "^6.1.2"

p-timeout@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.2.tgz#22b8d8a78abf5e103030211c5fc6dee1166a6aa5"
integrity sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==

pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
Expand Down