diff --git a/src/workerd/io/worker-entrypoint.c++ b/src/workerd/io/worker-entrypoint.c++ index 00a822234d8..78a25827030 100644 --- a/src/workerd/io/worker-entrypoint.c++ +++ b/src/workerd/io/worker-entrypoint.c++ @@ -8,6 +8,7 @@ #include #include #include +#include namespace workerd { @@ -181,7 +182,14 @@ kj::Promise WorkerEntrypoint::request( failOpenClient = context.getHttpClientNoChecks(IoContext::NEXT_CLIENT_CHANNEL, false, kj::mv(cfBlobJson)); } - waitUntilTasks.add(incomingRequest->drain().attach(kj::mv(incomingRequest))); + auto promise = incomingRequest->drain().attach(kj::mv(incomingRequest)); + if (isPredictableModeForTest()) { + promise = promise.then([worker = kj::atomicAddRef(context.getWorker())]() { + auto lock = worker->getIsolate().getApiIsolate().lock(); + lock->requestGcForTesting(); + }); + } + waitUntilTasks.add(kj::mv(promise)); })).then([this]() -> kj::Promise { // Now that the IoContext is dropped (unless it had waitUntil()s), we can finish proxying // without pinning it or the isolate into memory. @@ -349,12 +357,22 @@ kj::Promise WorkerEntrypoint::runScheduled( lock.getExportedHandler(entrypointName, context.getActor())); })); - return incomingRequest->finishScheduled().then([&context](bool completed) mutable { + auto promise = incomingRequest->finishScheduled().then([&context](bool completed) mutable { return WorkerInterface::ScheduledResult { .retry = context.shouldRetryScheduled(), .outcome = completed ? context.waitUntilStatus() : EventOutcome::EXCEEDED_CPU }; }).attach(kj::mv(incomingRequest)); + + if (isPredictableModeForTest()) { + promise = promise.then([worker = kj::atomicAddRef(context.getWorker())](auto res) { + auto lock = worker->getIsolate().getApiIsolate().lock(); + lock->requestGcForTesting(); + return res; + }); + } + + return promise; } kj::Promise WorkerEntrypoint::runAlarm( @@ -368,7 +386,7 @@ kj::Promise WorkerEntrypoint::runAlarm( //alarm() should only work with actors auto& actor = KJ_REQUIRE_NONNULL(context.getActor()); - return actor.dedupAlarm(scheduledTime, + auto promise = actor.dedupAlarm(scheduledTime, [this,&context,scheduledTime,incomingRequest = kj::mv(incomingRequest)]() mutable -> kj::Promise { incomingRequest->delivered(); @@ -395,6 +413,16 @@ kj::Promise WorkerEntrypoint::runAlarm( })); }); }); + + if (isPredictableModeForTest()) { + promise = promise.then([worker = kj::atomicAddRef(context.getWorker())](auto res) { + auto lock = worker->getIsolate().getApiIsolate().lock(); + lock->requestGcForTesting(); + return res; + }); + } + + return promise; } kj::Promise @@ -403,7 +431,18 @@ kj::Promise "customEvent() can only be called once")); this->incomingRequest = nullptr; - return event->run(kj::mv(incomingRequest), entrypointName).attach(kj::mv(event)); + auto& context = incomingRequest->getContext(); + auto worker = kj::atomicAddRef(context.getWorker()); + auto promise = event->run(kj::mv(incomingRequest), entrypointName).attach(kj::mv(event)); + + if (isPredictableModeForTest()) { + promise = promise.then([worker = kj::mv(worker)](auto res) { + auto lock = worker->getIsolate().getApiIsolate().lock(); + lock->requestGcForTesting(); + return res; + }); + } + return promise; } } // namespace workerd diff --git a/src/workerd/jsg/jsg.c++ b/src/workerd/jsg/jsg.c++ index e5dacc57bcd..2ca84d5a9a7 100644 --- a/src/workerd/jsg/jsg.c++ +++ b/src/workerd/jsg/jsg.c++ @@ -5,6 +5,7 @@ #include "jsg.h" #include "setup.h" #include +#include namespace workerd::jsg { @@ -149,6 +150,15 @@ void Lock::setLoggerCallback(kj::Function&& logger) { IsolateBase::from(v8Isolate).setLoggerCallback({}, kj::mv(logger)); } +void Lock::requestGcForTesting() const { + if (!isPredictableModeForTest()) { + KJ_LOG(ERROR, "Test GC used while not in a test"); + return; + } + v8Isolate->RequestGarbageCollectionForTesting( + v8::Isolate::GarbageCollectionType::kFullGarbageCollection); +} + Name Lock::newSymbol(kj::StringPtr symbol) { return Name(*this, v8::Symbol::New(v8Isolate, v8StrIntern(v8Isolate, symbol))); } diff --git a/src/workerd/jsg/jsg.h b/src/workerd/jsg/jsg.h index 56ddeca68e0..fe0c42c72d8 100644 --- a/src/workerd/jsg/jsg.h +++ b/src/workerd/jsg/jsg.h @@ -1906,6 +1906,7 @@ class Lock { // --------------------------------------------------------------------------- // Name/Symbol stuff + Name newSymbol(kj::StringPtr symbol); // Creates a Name encapsulating a new unique v8::Symbol. @@ -1970,6 +1971,14 @@ class Lock { using Logger = void(Lock&, kj::StringPtr); void setLoggerCallback(kj::Function&& logger); + // --------------------------------------------------------------------------- + // Misc. Stuff + + void requestGcForTesting() const; + // Sends an immediate request for full GC, this function is to ONLY be used in testing, otherwise + // it will throw. If a need for a minor GC is needed look at the call in jsg.c++ and the + // implementation in setup.c++. Use responsibly. + private: friend class IsolateBase; template