-
Notifications
You must be signed in to change notification settings - Fork 343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable FinalizationRegistry #3560
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
#include <workerd/jsg/jsg.h> | ||
#include <workerd/util/sentry.h> | ||
#include <workerd/util/uncaught-exception-source.h> | ||
#include <workerd/jsg/setup.h> | ||
|
||
#include <kj/debug.h> | ||
|
||
|
@@ -447,6 +448,7 @@ kj::Promise<void> IoContext::IncomingRequest::drain() { | |
// For non-actor requests, apply the configured soft timeout, typically 30 seconds. | ||
timeoutPromise = context->limitEnforcer->limitDrain(); | ||
} | ||
context->pumpMessageLoop(); | ||
return context->waitUntilTasks.onEmpty() | ||
.exclusiveJoin(kj::mv(timeoutPromise)) | ||
.exclusiveJoin(context->abortPromise.addBranch().then([] {}, [](kj::Exception&&) {})); | ||
|
@@ -466,6 +468,7 @@ kj::Promise<IoContext_IncomingRequest::FinishScheduledResult> IoContext::Incomin | |
// Mark ourselves so we know that we made a best effort attempt to wait for waitUntilTasks. | ||
KJ_ASSERT(context->incomingRequests.size() == 1); | ||
context->incomingRequests.front().waitedForWaitUntil = true; | ||
context->pumpMessageLoop(); | ||
|
||
auto timeoutPromise = context->limitEnforcer->limitScheduled().then( | ||
[] { return IoContext_IncomingRequest::FinishScheduledResult::TIMEOUT; }); | ||
|
@@ -1377,6 +1380,73 @@ kj::Promise<void> IoContext::startDeleteQueueSignalTask(IoContext* context) { | |
} | ||
} | ||
|
||
void IoContext::pumpMessageLoop() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a some duplication here with |
||
kj::Promise<Worker::AsyncLock> asyncLockPromise = nullptr; | ||
KJ_IF_SOME(a, actor) { | ||
asyncLockPromise = worker->takeAsyncLockWhenActorCacheReady(now(), a, getMetrics()); | ||
} else { | ||
asyncLockPromise = worker->takeAsyncLock(getMetrics()); | ||
} | ||
|
||
addWaitUntil(asyncLockPromise.then([](Worker::AsyncLock lock) { | ||
return lock; | ||
}).then([this](Worker::AsyncLock lock) mutable { | ||
KJ_REQUIRE(threadId == getThreadId(), "IoContext cannot switch threads"); | ||
IoContext* previousRequest = threadLocalRequest; | ||
KJ_DEFER(threadLocalRequest = previousRequest); | ||
threadLocalRequest = nullptr; | ||
|
||
worker->runInLockScope(lock, [&](Worker::Lock& workerLock) { | ||
workerLock.requireNoPermanentException(); | ||
|
||
auto limiterScope = limitEnforcer->enterJs(workerLock, *this); | ||
|
||
bool gotTermination = false; | ||
|
||
KJ_DEFER({ | ||
jsg::Lock& js = workerLock; | ||
if (gotTermination) { | ||
js.terminateExecution(); | ||
} | ||
}); | ||
|
||
v8::TryCatch tryCatch(workerLock.getIsolate()); | ||
try { | ||
jsg::Lock& js = workerLock; | ||
js.pumpMessageLoop(); | ||
} catch (const jsg::JsExceptionThrown&) { | ||
if (tryCatch.HasTerminated()) { | ||
gotTermination = true; | ||
limiterScope = nullptr; | ||
|
||
limitEnforcer->requireLimitsNotExceeded(); | ||
|
||
if (!abortFulfiller->isWaiting()) { | ||
KJ_FAIL_ASSERT("request terminated because it was aborted"); | ||
} | ||
|
||
// That should have thrown, so we shouldn't get here. | ||
KJ_FAIL_ASSERT("script terminated for unknown reasons"); | ||
} else { | ||
if (tryCatch.Message().IsEmpty()) { | ||
// Should never happen, but check for it because otherwise V8 will crash. | ||
KJ_LOG(ERROR, "tryCatch.Message() was empty even when not HasTerminated()??", | ||
kj::getStackTrace()); | ||
JSG_FAIL_REQUIRE(Error, "(JavaScript exception with no message)"); | ||
} else { | ||
auto jsException = tryCatch.Exception(); | ||
|
||
workerLock.logUncaughtException(UncaughtExceptionSource::INTERNAL, | ||
jsg::JsValue(jsException), jsg::JsMessage(tryCatch.Message())); | ||
|
||
jsg::throwTunneledException(workerLock.getIsolate(), jsException); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is copied over from |
||
} | ||
} | ||
} | ||
}); | ||
})); | ||
} | ||
|
||
// ====================================================================================== | ||
|
||
WarningAggregator::WarningAggregator(IoContext& context, EmitCallback emitter) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2276,6 +2276,8 @@ class ExternalMemoryAdjustment final { | |
// Isolate<TypeWrapper>::Lock. Usually this is only done in top-level code, and the Lock is | ||
// passed down to everyone else from there. See setup.h for details. | ||
|
||
class V8System; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should be exposing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree. |
||
|
||
class Lock { | ||
public: | ||
// The underlying V8 isolate, useful for directly calling V8 APIs. Hopefully, this is rarely | ||
|
@@ -2674,11 +2676,15 @@ class Lock { | |
|
||
void runMicrotasks(); | ||
void terminateExecution(); | ||
void pumpMessageLoop(); | ||
|
||
// Logs and reports the error to tail workers (if called within an request), | ||
// the inspector (if attached), or to KJ_LOG(Info). | ||
virtual void reportError(const JsValue& value) = 0; | ||
|
||
protected: | ||
virtual const V8System& getV8System() = 0; | ||
|
||
private: | ||
// Mark the jsg::Lock as being disallowed from being passed as a parameter into | ||
// a kj promise coroutine. Note that this only blocks directly passing the Lock | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -271,7 +271,10 @@ struct WorkerdApi::Impl final { | |
auto version = getPythonBundleName(pythonRelease); | ||
auto bundle = KJ_ASSERT_NONNULL( | ||
fetchPyodideBundle(pythonConfig, version), "Failed to get Pyodide bundle"); | ||
auto context = lock.newContext<api::ServiceWorkerGlobalScope>(lock.v8Isolate); | ||
jsg::NewContextOptions options{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we enable this for python workers unconditionally or behind a flag as well? I think pyodide already has its own dummy FinalizationRegistry implementation |
||
.enableWeakRef = features->getJsWeakRef() | ||
}; | ||
auto context = lock.newContext<api::ServiceWorkerGlobalScope>(options, lock.v8Isolate); | ||
v8::Context::Scope scope(context.getHandle(lock)); | ||
// Init emscripten synchronously, the python script will import setup-emscripten and | ||
// call setEmscriptenModele | ||
|
@@ -343,6 +346,7 @@ CompatibilityFlags::Reader WorkerdApi::getFeatureFlags() const { | |
jsg::JsContext<api::ServiceWorkerGlobalScope> WorkerdApi::newContext(jsg::Lock& lock) const { | ||
jsg::NewContextOptions options{ | ||
.newModuleRegistry = impl->tryGetModuleRegistry(), | ||
.enableWeakRef = getFeatureFlags().getJsWeakRef() | ||
}; | ||
return kj::downcast<JsgWorkerdIsolate::Lock>(lock).newContext<api::ServiceWorkerGlobalScope>( | ||
kj::mv(options), lock.v8Isolate); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a compatibility date as well (I think we should?)? How exactly do we decide what goes behind a date vs flag vs both?