Skip to content

Commit a61782f

Browse files
committed
frontend/latex/jobExec: refactoring, finally
1 parent 0ad2e0e commit a61782f

File tree

5 files changed

+109
-190
lines changed

5 files changed

+109
-190
lines changed

src/packages/frontend/frame-editors/latex-editor/knitr.ts

+7-44
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ import {
1313
ExecOutput,
1414
} from "@cocalc/frontend/frame-editors/generic/client";
1515
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
16-
import { TIMEOUT_LATEX_JOB_S } from "./constants";
1716
import { Error as ErrorLog, ProcessedLatexLog } from "./latex-log-parser";
1817
import { BuildLog } from "./types";
19-
import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
20-
import { gatherJobInfo } from "./util";
18+
import { runJob } from "./util";
2119

2220
// this still respects the environment variables and init files
2321
const R_CMD = "R";
@@ -39,50 +37,15 @@ export async function knitr(
3937
const { directory, filename } = parse_path(path);
4038
const expr = `require(knitr); opts_knit$set(concordance = TRUE, progress = FALSE); knit("${filename}")`;
4139
status(`${expr}`);
42-
const job_info = await exec({
43-
timeout: TIMEOUT_LATEX_JOB_S,
40+
41+
return runJob({
42+
project_id,
4443
command: R_CMD,
4544
args: [...R_ARGS, expr],
46-
bash: true, // so timeout is enforced by ulimit
47-
project_id: project_id,
48-
path: directory,
49-
err_on_exit: false,
50-
aggregate: time ? { value: time } : undefined, // one might think to aggregate on hash, but the output could be random!
51-
async_call: true,
45+
rundir: directory,
46+
aggregate: time ? { value: time } : undefined,
47+
set_job_info,
5248
});
53-
54-
if (job_info.type !== "async") {
55-
// this is not an async job. This could happen for old projects.
56-
return job_info;
57-
}
58-
59-
set_job_info(job_info);
60-
gatherJobInfo(project_id, job_info, set_job_info);
61-
62-
while (true) {
63-
try {
64-
const output = await exec({
65-
project_id,
66-
async_get: job_info.job_id,
67-
async_await: true,
68-
async_stats: true,
69-
});
70-
if (output.type !== "async") {
71-
throw new Error("output: not an async task");
72-
}
73-
set_job_info(output);
74-
return output;
75-
} catch (err) {
76-
if (err === TIMEOUT_CALLING_PROJECT) {
77-
// this will be fine, hopefully. We continue trying to get a reply.
78-
await new Promise((done) => setTimeout(done, 100));
79-
} else {
80-
throw new Error(
81-
"Unable to complete compilation. Check the project and try again...",
82-
);
83-
}
84-
}
85-
}
8649
}
8750

8851
/**

src/packages/frontend/frame-editors/latex-editor/latexmk.ts

+7-52
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@ Convert LaTeX file to PDF using latexmk.
88
*/
99

1010
import { exec } from "@cocalc/frontend/frame-editors/generic/client";
11-
import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
1211
import type { ExecOutput } from "@cocalc/util/db-schema/projects";
1312
import { change_filename_extension, path_split } from "@cocalc/util/misc";
1413
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
15-
import { TIMEOUT_LATEX_JOB_S } from "./constants";
16-
import { BuildLog } from "./types";
17-
import { gatherJobInfo, pdf_path } from "./util";
14+
import { pdf_path, runJob } from "./util";
1815

1916
export async function latexmk(
2017
project_id: string,
@@ -38,59 +35,17 @@ export async function latexmk(
3835
status([command].concat(args).join(" "));
3936
}
4037

41-
const job_info = await exec({
42-
bash: true, // we use ulimit so that the timeout on the backend is *enforced* via ulimit!!
43-
timeout: TIMEOUT_LATEX_JOB_S,
38+
// Step 1: Wait for the launched job to finish
39+
const output = await runJob({
40+
project_id,
4441
command,
4542
args,
46-
project_id,
47-
path: head,
48-
err_on_exit: false,
43+
rundir: head,
4944
aggregate: time,
50-
async_call: true,
45+
set_job_info,
5146
});
5247

53-
// Step 1: Wait for the launched job to finish
54-
let output: BuildLog;
55-
if (job_info.type !== "async") {
56-
output = job_info;
57-
} else {
58-
set_job_info(job_info);
59-
gatherJobInfo(project_id, job_info, set_job_info);
60-
61-
if (typeof job_info.pid !== "number") {
62-
throw new Error("Unable to spawn LaTeX compile job.");
63-
}
64-
65-
while (true) {
66-
try {
67-
output = await exec({
68-
project_id,
69-
async_get: job_info.job_id,
70-
async_await: true,
71-
async_stats: true,
72-
});
73-
// console.log("LaTeX/latexmk: got output=", output);
74-
if (output.type !== "async") {
75-
throw new Error("not an async job");
76-
}
77-
set_job_info(output);
78-
break;
79-
} catch (err) {
80-
// console.log("latexmk/while err=", err);
81-
if (err === TIMEOUT_CALLING_PROJECT) {
82-
// this will be fine, hopefully. We continue trying to get a reply
83-
await new Promise((done) => setTimeout(done, 100));
84-
} else {
85-
throw new Error(
86-
"Unable to complete compilation. Check the project and try again...",
87-
);
88-
}
89-
}
90-
}
91-
}
92-
93-
// Step 2: do a copy operation
48+
// Step 2: do a copy operation, if we run this in an output_directory (somewhere in /tmp)
9449
if (output_directory != null) {
9550
// We use cp instead of `ln -sf` so the file persists after project restart.
9651
// Using a symlink would be faster and more efficient *while editing*,

src/packages/frontend/frame-editors/latex-editor/pythontex.ts

+10-50
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@ Run PythonTeX
88
*/
99

1010
import { parse_path } from "@cocalc/frontend/frame-editors/frame-tree/util";
11-
import {
12-
exec,
13-
ExecOutput,
14-
} from "@cocalc/frontend/frame-editors/generic/client";
15-
// import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
16-
import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
11+
import { ExecOutput } from "@cocalc/frontend/frame-editors/generic/client";
1712
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
18-
import { TIMEOUT_LATEX_JOB_S } from "./constants";
1913
import { Error as ErrorLog, ProcessedLatexLog } from "./latex-log-parser";
2014
import { BuildLog } from "./types";
21-
import { gatherJobInfo } from "./util";
15+
import { runJob } from "./util";
2216

2317
// command documentation
2418
//
@@ -47,50 +41,16 @@ export async function pythontex(
4741
const command = `$(which {pythontex3,pythontex} | head -1) ${args}`;
4842
status(`pythontex[3] ${args}`);
4943
const aggregate = time && !force ? { value: time } : undefined;
50-
const job_info = await exec({
51-
timeout: TIMEOUT_LATEX_JOB_S,
52-
bash: true, // timeout is enforced by ulimit
53-
command,
54-
env: { MPLBACKEND: "Agg" }, // for python plots -- https://github.com/sagemathinc/cocalc/issues/4203
55-
project_id: project_id,
56-
path: output_directory || directory,
57-
err_on_exit: false,
44+
45+
return runJob({
46+
project_id,
5847
aggregate,
59-
async_call: true,
48+
command,
49+
rundir: output_directory || directory,
50+
set_job_info,
51+
// for python plots -- https://github.com/sagemathinc/cocalc/issues/4203
52+
env: { MPLBACKEND: "Agg" },
6053
});
61-
62-
if (job_info.type !== "async") {
63-
// this is not an async job. This could happen for old projects.
64-
return job_info;
65-
}
66-
67-
set_job_info(job_info);
68-
gatherJobInfo(project_id, job_info, set_job_info);
69-
70-
while (true) {
71-
try {
72-
const output = await exec({
73-
project_id,
74-
async_get: job_info.job_id,
75-
async_await: true,
76-
async_stats: true,
77-
});
78-
if (output.type !== "async") {
79-
throw new Error("output type is not async exec");
80-
}
81-
set_job_info(output);
82-
return output;
83-
} catch (err) {
84-
if (err === TIMEOUT_CALLING_PROJECT) {
85-
// this will be fine, hopefully. We continue trying to get a reply.
86-
await new Promise((done) => setTimeout(done, 100));
87-
} else {
88-
throw new Error(
89-
"Unable to complete compilation. Check the project and try again...",
90-
);
91-
}
92-
}
93-
}
9454
}
9555

9656
/*

src/packages/frontend/frame-editors/latex-editor/sagetex.ts

+6-43
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import {
1414
exec,
1515
ExecOutput,
1616
} from "@cocalc/frontend/frame-editors/generic/client";
17-
import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
1817
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
19-
import { TIMEOUT_LATEX_JOB_S } from "./constants";
2018
import { Error as ErrorLog, ProcessedLatexLog } from "./latex-log-parser";
2119
import { BuildLog } from "./types";
22-
import { gatherJobInfo } from "./util";
20+
import { runJob } from "./util";
2321

2422
function sagetex_file(base: string): string {
2523
return base + ".sagetex.sage";
@@ -58,50 +56,15 @@ export async function sagetex(
5856
const { base, directory } = parse_path(path); // base, directory, filename
5957
const s = sagetex_file(base);
6058
status(`sage ${s}`);
61-
const job_info = await exec({
62-
timeout: TIMEOUT_LATEX_JOB_S,
63-
bash: true, // so timeout is enforced by ulimit
59+
60+
return runJob({
61+
project_id,
6462
command: "sage",
6563
args: [s],
66-
project_id: project_id,
67-
path: output_directory || directory,
68-
err_on_exit: false,
64+
set_job_info,
65+
rundir: output_directory || directory,
6966
aggregate: hash ? { value: hash } : undefined,
70-
async_call: true,
7167
});
72-
73-
if (job_info.type !== "async") {
74-
// this is not an async job. This could happen for old projects.
75-
return job_info;
76-
}
77-
78-
set_job_info(job_info);
79-
gatherJobInfo(project_id, job_info, set_job_info);
80-
81-
while (true) {
82-
try {
83-
const output = await exec({
84-
project_id,
85-
async_get: job_info.job_id,
86-
async_await: true,
87-
async_stats: true,
88-
});
89-
if (output.type !== "async") {
90-
throw new Error("output type is not async exec");
91-
}
92-
set_job_info(output);
93-
return output;
94-
} catch (err) {
95-
if (err === TIMEOUT_CALLING_PROJECT) {
96-
// this will be fine, hopefully. We continue trying to get a reply.
97-
await new Promise((done) => setTimeout(done, 100));
98-
} else {
99-
throw new Error(
100-
"Unable to complete compilation. Check the project and try again...",
101-
);
102-
}
103-
}
104-
}
10568
}
10669

10770
/* example error

src/packages/frontend/frame-editors/latex-editor/util.ts

+79-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55

66
// data and functions specific to the latex editor.
77

8-
import { exec } from "@cocalc/frontend/frame-editors/generic/client";
8+
import {
9+
exec,
10+
ExecOpts,
11+
ExecOutput,
12+
} from "@cocalc/frontend/frame-editors/generic/client";
913
import { separate_file_extension } from "@cocalc/util/misc";
1014
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
15+
// import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
16+
import { TIMEOUT_CALLING_PROJECT } from "@cocalc/util/consts/project";
17+
import { ExecOptsBlocking } from "@cocalc/util/db-schema/projects";
18+
import { TIMEOUT_LATEX_JOB_S } from "./constants";
1119

1220
export function pdf_path(path: string): string {
1321
// if it is already a pdf, don't change the upper/lower casing -- #4562
@@ -92,3 +100,73 @@ export async function gatherJobInfo(
92100
return;
93101
}
94102
}
103+
104+
interface RunJobOpts {
105+
aggregate: ExecOptsBlocking["aggregate"];
106+
args?: string[];
107+
command: string;
108+
env?: { [key: string]: string };
109+
project_id: string;
110+
rundir: string; // a directory! (output_directory if in /tmp, or the directory of the file's path)
111+
set_job_info: (info: ExecuteCodeOutputAsync) => void;
112+
timeout?: number;
113+
}
114+
115+
export async function runJob(opts: RunJobOpts): Promise<ExecOutput> {
116+
const { aggregate, args, command, env, project_id, rundir, set_job_info } =
117+
opts;
118+
119+
const haveArgs = Array.isArray(args);
120+
121+
const job: ExecOpts = {
122+
aggregate,
123+
args,
124+
async_call: true,
125+
bash: !haveArgs,
126+
command,
127+
env,
128+
err_on_exit: false,
129+
path: rundir,
130+
project_id,
131+
timeout: TIMEOUT_LATEX_JOB_S,
132+
};
133+
134+
const job_info = await exec(job);
135+
136+
if (job_info.type !== "async") {
137+
// this is not an async job. This could happen for old projects.
138+
return job_info;
139+
}
140+
141+
if (typeof job_info.pid !== "number") {
142+
throw new Error("Unable to spawn compile job.");
143+
}
144+
145+
set_job_info(job_info);
146+
gatherJobInfo(project_id, job_info, set_job_info);
147+
148+
while (true) {
149+
try {
150+
const output = await exec({
151+
project_id,
152+
async_get: job_info.job_id,
153+
async_await: true,
154+
async_stats: true,
155+
});
156+
if (output.type !== "async") {
157+
throw new Error("output type is not async exec");
158+
}
159+
set_job_info(output);
160+
return output;
161+
} catch (err) {
162+
if (err === TIMEOUT_CALLING_PROJECT) {
163+
// this will be fine, hopefully. We continue trying to get a reply.
164+
await new Promise((done) => setTimeout(done, 100));
165+
} else {
166+
throw new Error(
167+
"Unable to complete compilation. Check the project and try again...",
168+
);
169+
}
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)