Skip to content

Commit c8a3e7c

Browse files
committed
cri - add browser process in cleanupHandler for processes for quarto exitWithCleanup logic
As of 2024-10 and chrome v130, there is a problem where Chrome client `close()` function won't return correctly, skipping the normal code part of closing a chromium process started. So processes were not killed. Now those browser processes are added to the same cleanup handler used by `execProcess()` logic so that in case of something going wrong, they are killed and closed at last resort when Quarto exists.
1 parent c96fe72 commit c8a3e7c

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

news/changelog-1.6.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ All changes included in 1.6:
111111

112112
- ([#11135](https://github.com/quarto-dev/quarto-cli/issues/11135)): Use `--headless=old` mode for Chromium to avoid recent issues with the new `--headless` mode. Setting `--headless=new` can be configured with `QUARTO_CHROMIUM_HEADLESS_MODE=new` environment variable, however it is not recommended new headless mode seems to be unstable. Only use to be unblocked of a situation (like `QUARTO_CHROMIUM_HEADLESS_MODE="none"` if you use an old chrome version somehow that don't support `--headless=old`).
113113
- ([#10170](https://github.com/quarto-dev/quarto-cli/issues/10170)): Quarto should find chrome executable automatically on most OS. If this is does not find it, or a specific version is needed, set `QUARTO_CHROMIUM` environment variable to the executable path.
114+
- Quarto now makes sure that all started chromium instances are closed when the process ends, no matter how it ends (success, error, or interruption).
114115

115116
## Other Fixes and Improvements
116117

src/core/cri/cri.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { InternalError } from "../lib/error.ts";
1717
import { getenv } from "../env.ts";
1818
import { kRenderFileLifetime } from "../../config/constants.ts";
1919
import { debug } from "../../deno_ral/log.ts";
20+
import {
21+
registerForExitCleanup,
22+
unregisterForExitCleanup,
23+
} from "../process.ts";
2024

2125
async function waitForServer(port: number, timeout = 3000) {
2226
const interval = 50;
@@ -101,6 +105,9 @@ export async function criClient(appPath?: string, port?: number) {
101105
];
102106
const browser = Deno.run({ cmd, stdout: "piped", stderr: "piped" });
103107

108+
// Register for cleanup inside exitWithCleanup() in case something goes wrong
109+
const thisProcessId = registerForExitCleanup(browser);
110+
104111
if (!(await waitForServer(port as number))) {
105112
let msg = "Couldn't find open server.";
106113
// Printing more error information if chrome process errored
@@ -121,7 +128,13 @@ export async function criClient(appPath?: string, port?: number) {
121128
const result = {
122129
close: async () => {
123130
await client.close();
124-
browser.close();
131+
// FIXME: 2024-10
132+
// We have a bug where `client.close()` doesn't return properly and we don't go below
133+
// meaning the `browser` process is not killed here, and it will be handled in exitWithCleanup().
134+
135+
browser.kill(); // Chromium headless won't terminate on its own, so we need to send kill signal
136+
browser.close(); // Closing the browser Deno process
137+
unregisterForExitCleanup(thisProcessId); // All went well so not need to cleanup on quarto exit
125138
},
126139

127140
rawClient: () => client,

src/core/process.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ const processList = new Map<number, Deno.Process>();
1414
let processCount = 0;
1515
let cleanupRegistered = false;
1616

17+
export function registerForExitCleanup(process: Deno.Process) {
18+
const thisProcessId = ++processCount; // don't risk repeated PIDs
19+
processList.set(thisProcessId, process);
20+
return thisProcessId;
21+
}
22+
23+
export function unregisterForExitCleanup(processId: number) {
24+
processList.delete(processId);
25+
}
26+
1727
function ensureCleanup() {
1828
if (!cleanupRegistered) {
1929
cleanupRegistered = true;
@@ -61,12 +71,11 @@ export async function execProcess(
6171
stdout: typeof (options.stdout) === "number" ? options.stdout : "piped",
6272
stderr: typeof (options.stderr) === "number" ? options.stderr : "piped",
6373
});
64-
const thisProcessId = ++processCount; // don't risk repeated PIDs
65-
processList.set(thisProcessId, process);
74+
const thisProcessId = registerForExitCleanup(process);
6675

6776
if (stdin !== undefined) {
6877
if (!process.stdin) {
69-
processList.delete(processCount);
78+
unregisterForExitCleanup(thisProcessId);
7079
throw new Error("Process stdin not available");
7180
}
7281
// write in 4k chunks (deno observed to overflow at > 64k)
@@ -170,7 +179,7 @@ export async function execProcess(
170179
// close the process
171180
process.close();
172181

173-
processList.delete(thisProcessId);
182+
unregisterForExitCleanup(thisProcessId);
174183

175184
debug(`[execProcess] Success: ${status.success}, code: ${status.code}`);
176185

0 commit comments

Comments
 (0)