Skip to content

Improve error for fetch on top-level with dedicated snapshot#6654

Open
hoodmane wants to merge 1 commit intomainfrom
hoodmane/better-fetch-at-top-level-error
Open

Improve error for fetch on top-level with dedicated snapshot#6654
hoodmane wants to merge 1 commit intomainfrom
hoodmane/better-fetch-at-top-level-error

Conversation

@hoodmane
Copy link
Copy Markdown
Contributor

Changes error for top level fetch from:

TypeError: Illegal invocation: function called with incorrect `this` reference.

to:

Error: Disallowed operation called within global scope. Asynchronous I/O
(ex: fetch() or connect()), setting a timeout, and generating random values are
not allowed within global scope. ...

This is a workaround for the fact that we don't have pyodide/pyodide#5588 in Pyodide 0.28.2, this fix is upstreamed starting with Pyodide 0.29.0

…error

Changes error for top level fetch from:
```
TypeError: Illegal invocation: function called with incorrect `this` reference.
```
to:
```
Error: Disallowed operation called within global scope. Asynchronous I/O
(ex: fetch() or connect()), setting a timeout, and generating random values are
not allowed within global scope. ...
```

This is a workaround for the fact that we don't have pyodide/pyodide#5588 in
Pyodide 0.28.2, this fix is upstreamed starting with Pyodide 0.29.0
@hoodmane hoodmane requested review from a team as code owners April 23, 2026 22:51
Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm Bonk, and I've done a quick review of your PR.

This defers JS module imports from module evaluation time to setDoAnImport() time, and wraps globalThis with createImportProxy so that top-level fetch() calls produce the helpful "Disallowed operation" error instead of the confusing "Illegal invocation" error during snapshot creation.

Issues (by severity):

  1. Medium -- 'global this' sentinel causes broken deserialization. In importJsModulesFromSnapshot_, doImport('global this') returns the string 'global this' rather than globalThis. If a dedicated snapshot serialized a reference to e.g. globalThis.fetch, then deserializeJsModule would try 'global this'['fetch'] which is undefined. This is a latent bug that would surface if _makeSnapshot is re-enabled without fixing this. Since the comment at line 936 says "We can reenable this with Pyodide 0.29.0 and later", this will bite the next person who flips it back on.

}
async function doImport(x: string): Promise<any> {
if (x === 'global this') {
return 'global this';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning the string 'global this' means deserializeJsModule will try to walk accessor lists on a string (e.g., 'global this'['fetch']undefined). This is harmless today because _makeSnapshot is disabled, but it's a latent bug that will silently break when that flag is re-enabled for Pyodide 0.29.0.

Consider returning the actual globalThis so deserialization works correctly:

Suggested change
return 'global this';
return globalThis;

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Apr 23, 2026

I'm Bonk, and I've done a quick review of your PR.

This defers JS module imports from module evaluation time to setDoAnImport() time, and wraps globalThis with createImportProxy so that top-level fetch() calls produce the helpful "Disallowed operation" error instead of the confusing "Illegal invocation" error during snapshot creation.

Issues (by severity):

  1. Medium -- 'global this' sentinel causes broken deserialization. In importJsModulesFromSnapshot_, doImport('global this') returns the string 'global this' rather than globalThis. If a dedicated snapshot serialized a reference to e.g. globalThis.fetch, then deserializeJsModule would try 'global this'['fetch'] which is undefined. This is harmless today because _makeSnapshot is disabled, but it's a latent bug that would surface when re-enabling that flag for Pyodide 0.29.0. I posted a suggestion to return globalThis instead.

Review posted to PR #6654.

github run

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 23, 2026

Merging this PR will not alter performance

✅ 72 untouched benchmarks
⏩ 129 skipped benchmarks1


Comparing hoodmane/better-fetch-at-top-level-error (e3e6614) with main (73c75fa)

Open in CodSpeed

Footnotes

  1. 129 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.52%. Comparing base (7a7288e) to head (e3e6614).
⚠️ Report is 477 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6654      +/-   ##
==========================================
- Coverage   70.68%   66.52%   -4.17%     
==========================================
  Files         420      405      -15     
  Lines      113010   115971    +2961     
  Branches    18531    19408     +877     
==========================================
- Hits        79881    77144    -2737     
- Misses      22086    27244    +5158     
- Partials    11043    11583     +540     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get a test for this?

customSerializedObjects: CustomSerializedObjects
): Pyodide {
try {
const Module = enterJaegerSpan('instantiate_emscripten', () =>
SetupEmscripten.getModule()
);
Module.compileModuleFromReadOnlyFS = compileModuleFromReadOnlyFS;
Module.API.config.jsglobals = globalThis;
// Next line is not needed with Pyodide >= 0.29.0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you add a version check here?

);
let JS_MODULES: Record<string, any>;

export async function importJsModulesFromSnapshot(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indirection is unfortunate, anything we can do to fix it? I'd say at least give this function a different name:

Suggested change
export async function importJsModulesFromSnapshot(
export async function fillSnapshotJsModules(

Comment on lines +936 to +938
// We can reenable this with Pyodide 0.29.0 and later.
// Module.API.config._makeSnapshot =
// IS_CREATING_SNAPSHOT && Module.API.version !== PyodideVersion.V0_26_0a2;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you just add a version check here instead of disabling it completely?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants