Skip to content

Commit 26ea4a8

Browse files
committed
Don't fail at upload time when module modifies sys.path
We try to execute all the top level imports but we don't execute changes to the path so we can get crashes.
1 parent 10eafbc commit 26ea4a8

File tree

2 files changed

+49
-11
lines changed

2 files changed

+49
-11
lines changed

src/pyodide/internal/snapshot.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
MEMORY_SNAPSHOT_READER,
1414
REQUIREMENTS,
1515
} from 'pyodide-internal:metadata';
16-
import { reportError, simpleRunPython } from 'pyodide-internal:util';
16+
import {
17+
reportError,
18+
simpleRunPython,
19+
SimpleRunPythonError,
20+
} from 'pyodide-internal:util';
1721
import { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';
1822

1923
let LOADED_BASELINE_SNAPSHOT: number;
@@ -262,8 +266,22 @@ function memorySnapshotDoImports(Module: Module): string[] {
262266
const deduplicatedModules = [...new Set(importedModules)];
263267

264268
// Import the modules list so they are included in the snapshot.
265-
if (deduplicatedModules.length > 0) {
266-
simpleRunPython(Module, 'import ' + deduplicatedModules.join(','));
269+
for (const mod of deduplicatedModules) {
270+
try {
271+
// If the module manipulates sys.path or sys.meta_path, we hopefully won't be able to import
272+
// some of the things it imports. If they import a package that attempts to import from js,
273+
// then this might leave the package in an unusable state. TODO: finalizeBootstrap before
274+
// taking the snapshot.
275+
simpleRunPython(Module, `import ${mod}`);
276+
} catch (e) {
277+
if (!(e instanceof SimpleRunPythonError)) {
278+
// This probably means we segfaulted.
279+
throw e;
280+
}
281+
continue;
282+
}
283+
// Delete the imported module to avoid polluting the namespace
284+
simpleRunPython(Module, `del ${mod.split('.', 1)[0]}`);
267285
}
268286

269287
return deduplicatedModules;

src/pyodide/internal/util.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
export function reportError(e: any): never {
2-
e.stack?.split('\n').forEach((s: any) => console.warn(s));
2+
if (e instanceof SimpleRunPythonError) {
3+
// PyRun_SimpleString will have written a Python traceback to stderr.
4+
console.warn('Command failed:', e.pyCode);
5+
console.warn('Error was:');
6+
for (const line of e.pyTraceback.split('\n')) {
7+
console.warn(line);
8+
}
9+
} else {
10+
e.stack?.split('\n').forEach((s: any) => console.warn(s));
11+
}
312
throw e;
413
}
514

15+
function setErrorName(errClass: any) {
16+
Object.defineProperty(errClass.prototype, 'name', {
17+
value: errClass.name,
18+
});
19+
}
20+
21+
export class SimpleRunPythonError extends Error {
22+
pyCode: string;
23+
pyTraceback: string;
24+
constructor(pyCode: string, pyTraceback: string) {
25+
super('Failed to run Python code: ' + pyTraceback);
26+
this.pyCode = pyCode;
27+
this.pyTraceback = pyTraceback;
28+
}
29+
}
30+
setErrorName(SimpleRunPythonError);
31+
632
/**
733
* Simple as possible runPython function which works with no foreign function
834
* interface. We need to use this rather than the normal easier to use
@@ -36,13 +62,7 @@ export function simpleRunPython(
3662
// status 0: Ok
3763
// status -1: Error
3864
if (status) {
39-
// PyRun_SimpleString will have written a Python traceback to stderr.
40-
console.warn('Command failed:', code);
41-
console.warn('Error was:');
42-
for (const line of err.split('\n')) {
43-
console.warn(line);
44-
}
45-
throw new Error('Failed to run Python code: ' + err);
65+
throw new SimpleRunPythonError(code, err);
4666
}
4767
return err;
4868
}

0 commit comments

Comments
 (0)