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
1 change: 1 addition & 0 deletions src/cloudflare/internal/workers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export class RpcTarget {}
export class ServiceStub {}

export function waitUntil(promise: Promise<unknown>): void;
export function abortIsolate(reason?: string): never;
1 change: 1 addition & 0 deletions src/cloudflare/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,4 @@ export const exports = new Proxy(
);

export const waitUntil = entrypoints.waitUntil.bind(entrypoints);
export const abortIsolate = entrypoints.abortIsolate.bind(entrypoints);
11 changes: 11 additions & 0 deletions src/workerd/api/workers-module.c++
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,15 @@ void EntrypointsModule::waitUntil(kj::Promise<void> promise) {
IoContext::current().addWaitUntil(kj::mv(promise));
}

void EntrypointsModule::abortIsolate(jsg::Optional<kj::String> reason) {
auto& context = IoContext::current();

// Signal the runtime to swap the worker for future requests and abort the current IoContext.
context.abortIsolate(kj::mv(reason));

// Immediately terminate V8 execution so no further JS runs in this request.
// This raises an uncatchable exception in V8, causing the request to fail immediately.
jsg::Lock::from(v8::Isolate::GetCurrent()).terminateExecutionNow();
}

} // namespace workerd::api
6 changes: 6 additions & 0 deletions src/workerd/api/workers-module.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class EntrypointsModule: public jsg::Object {

void waitUntil(kj::Promise<void> promise);

// Throws away the current JS isolate and recreates the worker from scratch. The current
// request continues running on the old isolate; subsequent requests will use a fresh
// isolate with re-executed top-level module code and fresh global state.
void abortIsolate(jsg::Optional<kj::String> reason);

JSG_RESOURCE_TYPE(EntrypointsModule) {
JSG_NESTED_TYPE(WorkerEntrypoint);
JSG_NESTED_TYPE(WorkflowEntrypoint);
Expand All @@ -90,6 +95,7 @@ class EntrypointsModule: public jsg::Object {
JSG_NESTED_TYPE_NAMED(Fetcher, ServiceStub);

JSG_METHOD(waitUntil);
JSG_METHOD(abortIsolate);
}
};

Expand Down
7 changes: 7 additions & 0 deletions src/workerd/io/io-channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ class IoChannelFactory {
KJ_UNIMPLEMENTED("Only implemented by single-tenant workerd runtime");
}

// Signals that the current worker's JS isolate should be thrown away and recreated from scratch.
// The current request continues running on the old isolate; subsequent requests will use a fresh
// isolate with re-executed top-level module code and fresh global state.
virtual void abortIsolate(kj::Maybe<kj::StringPtr> reason) {
JSG_FAIL_REQUIRE(Error, "abortIsolate() is not supported by this runtime.");
}

// Use a dynamic Worker loader binding to obtain an Worker by name. If name is null, or if the named Worker doesn't already exist, the callback will be called to fetch the source code from which the Worker should be created.
virtual kj::Own<WorkerStubChannel> loadIsolate(uint loaderChannel,
kj::Maybe<kj::String> name,
Expand Down
18 changes: 18 additions & 0 deletions src/workerd/io/io-context.c++
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,24 @@ void IoContext::abort(kj::Exception&& e) {
abortFulfiller->reject(kj::mv(e));
}

void IoContext::abortIsolate(kj::Maybe<kj::String> reason) {
// Build the error message, including the reason if provided.
auto message = [&]() -> kj::String {
KJ_IF_SOME(r, reason) {
return kj::str("abortIsolate(): ", r);
} else {
return kj::str("abortIsolate() was called.");
}
}();

// Tell the IoChannelFactory to swap the worker for future requests and abort ALL in-flight
// requests on the isolate. Pass the reason so all aborted requests see the same message.
getIoChannelFactory().abortIsolate(message.asPtr());

// Abort the current IoContext so this request fails.
abort(JSG_KJ_EXCEPTION(FAILED, Error, message));
}

void IoContext::abortWhen(kj::Promise<void> promise) {
// Unlike addTask(), abortWhen() always uses `tasks`, even in actors, because we do not want
// these tasks to block hibernation.
Expand Down
5 changes: 5 additions & 0 deletions src/workerd/io/io-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,11 @@ class IoContext final: public kj::Refcounted, private kj::TaskSet::ErrorHandler
getIoChannelFactory().abortAllActors(reason);
}

// Signals that the current worker's JS isolate should be thrown away and recreated from scratch.
// This immediately aborts the current IoContext and terminates V8 execution, causing the current
// request to fail. Subsequent requests will use a freshly created worker.
void abortIsolate(kj::Maybe<kj::String> reason = kj::none);

// Get an HttpClient to use for Cache API subrequests.
kj::Own<CacheClient> getCacheClient();

Expand Down
Loading
Loading