Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/pyodide/internal/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ export const CHECK_RNG_STATE = !!COMPATIBILITY_FLAGS.python_check_rng_state;

export const setCpuLimitNearlyExceededCallback =
MetadataReader.setCpuLimitNearlyExceededCallback.bind(MetadataReader);

export const condemnIsolate: (reason: string) => never =
MetadataReader.condemnIsolate.bind(MetadataReader);
7 changes: 7 additions & 0 deletions src/pyodide/internal/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import {
LEGACY_VENDOR_PATH,
setCpuLimitNearlyExceededCallback,
condemnIsolate,
} from 'pyodide-internal:metadata';

/**
Expand Down Expand Up @@ -241,6 +242,12 @@ export function loadPyodide(
);
Module.compileModuleFromReadOnlyFS = compileModuleFromReadOnlyFS;
Module.API.config.jsglobals = globalThis;

// Set up the fatal error handler to condemn the isolate when Pyodide
// encounters an unrecoverable error.
Module.API.on_fatal = (error: any): void => {
condemnIsolate(`${error}`);
};
if (isWorkerd) {
Module.API.config.indexURL = indexURL;
Module.API.config.resolveLockFilePromise!(lockfile);
Expand Down
3 changes: 3 additions & 0 deletions src/pyodide/types/emscripten.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ interface API {
serializeHiwireState(serializer: (obj: any) => any): SnapshotConfig;
pyVersionTuple: [number, number, number];
scheduleCallback: (callback: () => void, timeout: number) => void;
// Callback invoked when Pyodide encounters a fatal error. Setting this allows
// the runtime to handle fatal errors (e.g., by condemning the isolate).
on_fatal?: (error: any) => void;
}

interface LDSO {
Expand Down
1 change: 1 addition & 0 deletions src/pyodide/types/runtime-generated/metadata.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare namespace MetadataReader {
sig_clock: number,
sig_flag: number
) => void;
const condemnIsolate: (reason: string) => never;
const constructor: {
getBaselineSnapshotImports(): string[];
};
Expand Down
18 changes: 18 additions & 0 deletions src/workerd/api/pyodide/pyodide.c++
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <workerd/api/pyodide/setup-emscripten.h>
#include <workerd/io/compatibility-date.h>
#include <workerd/io/features.h>
#include <workerd/io/io-context.h>
#include <workerd/util/strings.h>

#include <pyodide/generated/pyodide_extra.capnp.h>
Expand Down Expand Up @@ -102,6 +103,23 @@ void PyodideMetadataReader::setCpuLimitNearlyExceededCallback(
});
}

[[noreturn]] void PyodideMetadataReader::condemnIsolate(jsg::Lock& js, kj::String reason) {
// Condemn the isolate due to a fatal Pyodide error. This marks the isolate as condemned
// so that future requests will be routed to a new isolate, aborts the current request context,
// and terminates JavaScript execution.
auto description = kj::str("Pyodide fatal error: ", reason);
kj::Exception error(kj::Exception::Type::FAILED, __FILE__, __LINE__, kj::mv(description));

// Condemn the isolate so future requests get a fresh one
Worker::Isolate::from(js).getLimitEnforcer().condemn();
Copy link
Contributor

Choose a reason for hiding this comment

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

As I've mentioned in upstream PR, I think we want a custom method for this.


// Abort the current request context
IoContext::current().abort(kj::cp(error));

// Terminate JavaScript execution immediately
js.terminateExecutionNow();
}

kj::Array<kj::String> PythonModuleInfo::getPythonFileContents() {
auto builder = kj::Vector<kj::String>(names.size());
for (auto i: kj::zeroTo(names.size())) {
Expand Down
5 changes: 5 additions & 0 deletions src/workerd/api/pyodide/pyodide.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ class PyodideMetadataReader: public jsg::Object {
void setCpuLimitNearlyExceededCallback(
jsg::Lock& js, kj::Array<kj::byte> wasm_memory, int sig_clock, int sig_flag);

// Condemns the isolate by aborting the IoContext and terminating JavaScript execution.
// This should be called when Pyodide encounters a fatal error that cannot be recovered from.
[[noreturn]] void condemnIsolate(jsg::Lock& js, kj::String reason);

// Similar to Cloudflare::::getCompatibilityFlags in global-scope.c++, but the key difference is
// that it returns experimental flags even if `experimental` is not enabled. This avoids a gotcha
// where an experimental compat flag is enabled in our C++ code, but not in our JS code.
Expand Down Expand Up @@ -274,6 +278,7 @@ class PyodideMetadataReader: public jsg::Object {
JSG_METHOD(getCompatibilityFlags);
JSG_STATIC_METHOD(getBaselineSnapshotImports);
JSG_METHOD(setCpuLimitNearlyExceededCallback);
JSG_METHOD(condemnIsolate);
}

void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
Expand Down
6 changes: 6 additions & 0 deletions src/workerd/io/limit-enforcer.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ class IsolateLimitEnforcer: public kj::Refcounted {

virtual bool hasExcessivelyExceededHeapLimit() const = 0;

// Condemns the isolate due to a fatal error. Requests being handled by condemned isolates
// should be able to complete any in-flight requests (within a grace period) but should not
// accept any new requests. The default implementation is a no-op; subclasses may override
// to implement actual condemnation behavior.
virtual void condemn() const {}

// Inserts a custom mark event named `name` into this isolate's perf event data stream. At
// present, this is only implemented internally. Call this function from various APIs to be able
// to correlate perf event data with usage of those APIs.
Expand Down
Loading