From 9272eae0fc67bae72ca26545c6da00188ff4b11e Mon Sep 17 00:00:00 2001 From: Dan Lapid Date: Thu, 17 Oct 2024 15:58:58 +0000 Subject: [PATCH] Instantiate Emscripten Runtime for python workers earlier. --- src/workerd/api/pyodide/pyodide.c++ | 2 +- src/workerd/api/pyodide/pyodide.h | 6 +- src/workerd/server/workerd-api.c++ | 206 ++++++++++++++-------------- 3 files changed, 110 insertions(+), 104 deletions(-) diff --git a/src/workerd/api/pyodide/pyodide.c++ b/src/workerd/api/pyodide/pyodide.c++ index cbeeed5bf99..3d9493464f2 100644 --- a/src/workerd/api/pyodide/pyodide.c++ +++ b/src/workerd/api/pyodide/pyodide.c++ @@ -490,7 +490,7 @@ jsg::JsValue SetupEmscripten::getModule(jsg::Lock& js) { } void SetupEmscripten::visitForGc(jsg::GcVisitor& visitor) { - visitor.visit(emscriptenRuntime.emscriptenRuntime); + visitor.visit(const_cast(emscriptenRuntime).emscriptenRuntime); } bool hasPythonModules(capnp::List::Reader modules) { diff --git a/src/workerd/api/pyodide/pyodide.h b/src/workerd/api/pyodide/pyodide.h index b4b419664c7..05ddcc9ecab 100644 --- a/src/workerd/api/pyodide/pyodide.h +++ b/src/workerd/api/pyodide/pyodide.h @@ -411,8 +411,8 @@ class SimplePythonLimiter: public jsg::Object { class SetupEmscripten: public jsg::Object { public: - SetupEmscripten(EmscriptenRuntime emscriptenRuntime) - : emscriptenRuntime(kj::mv(emscriptenRuntime)) {}; + SetupEmscripten(const EmscriptenRuntime& emscriptenRuntime) + : emscriptenRuntime(emscriptenRuntime) {}; jsg::JsValue getModule(jsg::Lock& js); @@ -421,7 +421,7 @@ class SetupEmscripten: public jsg::Object { } private: - EmscriptenRuntime emscriptenRuntime; + const EmscriptenRuntime& emscriptenRuntime; void visitForGc(jsg::GcVisitor& visitor); }; diff --git a/src/workerd/server/workerd-api.c++ b/src/workerd/server/workerd-api.c++ index ceb1ca79cce..abbd234b632 100644 --- a/src/workerd/server/workerd-api.c++ +++ b/src/workerd/server/workerd-api.c++ @@ -129,6 +129,92 @@ static const PythonConfig defaultConfig{ }; } // namespace +namespace { +kj::Path getPyodideBundleFileName(kj::StringPtr version) { + return kj::Path(kj::str("pyodide_", version, ".capnp.bin")); +} + +kj::Maybe> getPyodideBundleFile( + const kj::Maybe>& maybeDir, kj::StringPtr version) { + KJ_IF_SOME(dir, maybeDir) { + kj::Path filename = getPyodideBundleFileName(version); + auto file = dir->tryOpenFile(filename); + + return file; + } + + return kj::none; +} + +void writePyodideBundleFileToDisk(const kj::Maybe>& maybeDir, + kj::StringPtr version, + kj::ArrayPtr bytes) { + KJ_IF_SOME(dir, maybeDir) { + kj::Path filename = getPyodideBundleFileName(version); + auto replacer = dir->replaceFile(filename, kj::WriteMode::CREATE | kj::WriteMode::MODIFY); + + replacer->get().writeAll(bytes); + replacer->commit(); + } +} + +kj::Maybe fetchPyodideBundle( + const api::pyodide::PythonConfig& pyConfig, kj::StringPtr version) { + if (pyConfig.pyodideBundleManager.getPyodideBundle(version) != kj::none) { + return pyConfig.pyodideBundleManager.getPyodideBundle(version); + } + + auto maybePyodideBundleFile = getPyodideBundleFile(pyConfig.pyodideDiskCacheRoot, version); + KJ_IF_SOME(pyodideBundleFile, maybePyodideBundleFile) { + auto body = pyodideBundleFile->readAllBytes(); + pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body)); + return pyConfig.pyodideBundleManager.getPyodideBundle(version); + } + + if (version == "dev") { + // the "dev" version is special and indicates we're using the tip-of-tree version built for testing + // so we shouldn't fetch it from the internet, only check for its existence in the disk cache + return kj::none; + } + + { + KJ_LOG(INFO, "Loading Pyodide package from internet..."); + kj::Thread([&]() { + kj::AsyncIoContext io = kj::setupAsyncIo(); + kj::HttpHeaderTable table; + + kj::TlsContext::Options options; + options.useSystemTrustStore = true; + + kj::Own tls = kj::heap(kj::mv(options)); + auto& network = io.provider->getNetwork(); + auto tlsNetwork = tls->wrapNetwork(network); + auto& timer = io.provider->getTimer(); + + auto client = kj::newHttpClient(timer, table, network, *tlsNetwork); + + kj::HttpHeaders headers(table); + + kj::String url = + kj::str("https://pyodide.runtime-playground.workers.dev/pyodide-capnp-bin/pyodide_", + version, ".capnp.bin"); + + auto req = client->request(kj::HttpMethod::GET, url.asPtr(), headers); + + auto res = req.response.wait(io.waitScope); + auto body = res.body->readAllBytes().wait(io.waitScope); + + writePyodideBundleFileToDisk(pyConfig.pyodideDiskCacheRoot, version, body); + + pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body)); + }); + } + + KJ_LOG(INFO, "Loaded Pyodide package from internet"); + return pyConfig.pyodideBundleManager.getPyodideBundle(version); +} +} // namespace + struct WorkerdApi::Impl final { kj::Own features; kj::Maybe> maybeOwnedModuleRegistry; @@ -137,6 +223,7 @@ struct WorkerdApi::Impl final { JsgWorkerdIsolate jsgIsolate; api::MemoryCacheProvider& memoryCacheProvider; const PythonConfig& pythonConfig; + kj::Maybe maybeEmscriptenRuntime; class Configuration { public: @@ -175,8 +262,20 @@ struct WorkerdApi::Impl final { limitEnforcer->getCreateParams()), memoryCacheProvider(memoryCacheProvider), pythonConfig(pythonConfig) { - jsgIsolate.runInLockScope( - [&](JsgWorkerdIsolate::Lock& lock) { limitEnforcer->customizeIsolate(lock.v8Isolate); }); + jsgIsolate.runInLockScope([&](JsgWorkerdIsolate::Lock& lock) { + limitEnforcer->customizeIsolate(lock.v8Isolate); + if (features->getPythonWorkers()) { + auto pythonRelease = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(*features)); + auto version = getPythonBundleName(pythonRelease); + auto bundle = KJ_ASSERT_NONNULL( + fetchPyodideBundle(pythonConfig, version), "Failed to get Pyodide bundle"); + auto context = lock.newContext(lock.v8Isolate); + v8::Context::Scope scope(context.getHandle(lock)); + // Init emscripten synchronously, the python script will import setup-emscripten and + // call setEmscriptenModele + maybeEmscriptenRuntime = api::pyodide::EmscriptenRuntime::initialize(lock, true, bundle); + } + }); } static v8::Local compileTextGlobal( @@ -437,92 +536,6 @@ kj::Maybe WorkerdApi::tryCompileModule(jsg::Loc KJ_UNREACHABLE; } -namespace { -kj::Path getPyodideBundleFileName(kj::StringPtr version) { - return kj::Path(kj::str("pyodide_", version, ".capnp.bin")); -} - -kj::Maybe> getPyodideBundleFile( - const kj::Maybe>& maybeDir, kj::StringPtr version) { - KJ_IF_SOME(dir, maybeDir) { - kj::Path filename = getPyodideBundleFileName(version); - auto file = dir->tryOpenFile(filename); - - return file; - } - - return kj::none; -} - -void writePyodideBundleFileToDisk(const kj::Maybe>& maybeDir, - kj::StringPtr version, - kj::ArrayPtr bytes) { - KJ_IF_SOME(dir, maybeDir) { - kj::Path filename = getPyodideBundleFileName(version); - auto replacer = dir->replaceFile(filename, kj::WriteMode::CREATE | kj::WriteMode::MODIFY); - - replacer->get().writeAll(bytes); - replacer->commit(); - } -} - -kj::Maybe fetchPyodideBundle( - const api::pyodide::PythonConfig& pyConfig, kj::StringPtr version) { - if (pyConfig.pyodideBundleManager.getPyodideBundle(version) != kj::none) { - return pyConfig.pyodideBundleManager.getPyodideBundle(version); - } - - auto maybePyodideBundleFile = getPyodideBundleFile(pyConfig.pyodideDiskCacheRoot, version); - KJ_IF_SOME(pyodideBundleFile, maybePyodideBundleFile) { - auto body = pyodideBundleFile->readAllBytes(); - pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body)); - return pyConfig.pyodideBundleManager.getPyodideBundle(version); - } - - if (version == "dev") { - // the "dev" version is special and indicates we're using the tip-of-tree version built for testing - // so we shouldn't fetch it from the internet, only check for its existence in the disk cache - return kj::none; - } - - { - KJ_LOG(INFO, "Loading Pyodide package from internet..."); - kj::Thread([&]() { - kj::AsyncIoContext io = kj::setupAsyncIo(); - kj::HttpHeaderTable table; - - kj::TlsContext::Options options; - options.useSystemTrustStore = true; - - kj::Own tls = kj::heap(kj::mv(options)); - auto& network = io.provider->getNetwork(); - auto tlsNetwork = tls->wrapNetwork(network); - auto& timer = io.provider->getTimer(); - - auto client = kj::newHttpClient(timer, table, network, *tlsNetwork); - - kj::HttpHeaders headers(table); - - kj::String url = - kj::str("https://pyodide.runtime-playground.workers.dev/pyodide-capnp-bin/pyodide_", - version, ".capnp.bin"); - - auto req = client->request(kj::HttpMethod::GET, url.asPtr(), headers); - - auto res = req.response.wait(io.waitScope); - auto body = res.body->readAllBytes().wait(io.waitScope); - - writePyodideBundleFileToDisk(pyConfig.pyodideDiskCacheRoot, version, body); - - pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body)); - }); - } - - KJ_LOG(INFO, "Loaded Pyodide package from internet"); - return pyConfig.pyodideBundleManager.getPyodideBundle(version); -} -} // namespace - void WorkerdApi::compileModules(jsg::Lock& lockParam, config::Worker::Reader conf, Worker::ValidationErrorReporter& errorReporter, @@ -537,22 +550,15 @@ void WorkerdApi::compileModules(jsg::Lock& lockParam, if (hasPythonModules(confModules)) { KJ_REQUIRE(featureFlags.getPythonWorkers(), "The python_workers compatibility flag is required to use Python."); + // Inject SetupEmscripten module + modules->addBuiltinModule("internal:setup-emscripten", + jsg::alloc(KJ_ASSERT_NONNULL(impl->maybeEmscriptenRuntime)), + workerd::jsg::ModuleRegistry::Type::INTERNAL); + auto pythonRelease = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags)); auto version = getPythonBundleName(pythonRelease); auto bundle = KJ_ASSERT_NONNULL( fetchPyodideBundle(impl->pythonConfig, version), "Failed to get Pyodide bundle"); - // Inject SetupEmscripten module - { - auto& lock = kj::downcast(lockParam); - auto context = lock.newContext({}, lock.v8Isolate); - v8::Context::Scope scope(context.getHandle(lock)); - // Init emscripten synchronously, the python script will import setup-emscripten and - // call setEmscriptenModele - auto emscriptenRuntime = api::pyodide::EmscriptenRuntime::initialize(lock, true, bundle); - modules->addBuiltinModule("internal:setup-emscripten", - jsg::alloc(kj::mv(emscriptenRuntime)), - workerd::jsg::ModuleRegistry::Type::INTERNAL); - } // Inject Pyodide bundle modules->addBuiltinBundle(bundle, kj::none);