diff --git a/src/workerd/server/server.c++ b/src/workerd/server/server.c++ index c59e004e3a6..f3df0555273 100644 --- a/src/workerd/server/server.c++ +++ b/src/workerd/server/server.c++ @@ -969,6 +969,38 @@ kj::Own Server::makeDiskDirectoryService( // ======================================================================================= +class Server::InspectorServiceIsolateRegistrar final { + // This class exists to update the InspectorService's table of isolates when a config + // has multiple services. The InspectorService exists on the stack of it's own thread and + // initializes state that is bound to the thread, e.g. a http server and an event loop. + // This class provides a small thread-safe interface to the InspectorService so : + // mappings can be added after the InspectorService has started. + // + // The CloudFlare devtools only show the first service in workerd configuration. This service + // is always contains a users code. However, in packaging user code wrangler may add + // additional services that also have code. If using Chrome devtools to inspect a workerd, + // instance all services are visible and can be debugged. + +public: + InspectorServiceIsolateRegistrar() {} + ~InspectorServiceIsolateRegistrar() noexcept(true); + + void registerIsolate(kj::StringPtr name, Worker::Isolate* isolate); + + KJ_DISALLOW_COPY_AND_MOVE(InspectorServiceIsolateRegistrar); +private: + void attach(const Server::InspectorService* anInspectorService) { + *inspectorService.lockExclusive() = anInspectorService; + } + + void detach() { + *inspectorService.lockExclusive() = nullptr; + } + + kj::MutexGuarded inspectorService; + friend class Server::InspectorService; +}; + class Server::InspectorService final: public kj::HttpService, public kj::HttpServerErrorHandler { // Implements the interface for the devtools inspector protocol. // @@ -977,12 +1009,26 @@ class Server::InspectorService final: public kj::HttpService, public kj::HttpSer public: InspectorService( kj::Timer& timer, - kj::HttpHeaderTable::Builder& headerTableBuilder) + kj::HttpHeaderTable::Builder& headerTableBuilder, + InspectorServiceIsolateRegistrar& registrar) : timer(timer), headerTable(headerTableBuilder.getFutureTable()), server(timer, headerTable, *this, kj::HttpServerSettings { .errorHandler = *this - }) {} + }), + registrar(registrar) { + registrar.attach(this); + } + + ~InspectorService() { + KJ_IF_MAYBE(r, registrar) { + r->detach(); + } + } + + void invalidateRegistrar() { + registrar = nullptr; + } kj::Promise handleApplicationError( kj::Exception exception, kj::Maybe response) override { @@ -1045,6 +1091,7 @@ public: } } + KJ_LOG(INFO, kj::str("Unknown worker session [", id, "]")); return response.sendError(404, "Unknown worker session", responseHeaders); } @@ -1142,10 +1189,26 @@ private: kj::HttpHeaderTable& headerTable; kj::HashMap> isolates; kj::HttpServer server; - - friend class Registration; + kj::Maybe registrar; }; +Server::InspectorServiceIsolateRegistrar::~InspectorServiceIsolateRegistrar() noexcept(true) { + auto lockedInspectorService = this->inspectorService.lockExclusive(); + if (lockedInspectorService != nullptr) { + auto is = const_cast(*lockedInspectorService); + is->invalidateRegistrar(); + } +} + +void Server::InspectorServiceIsolateRegistrar::registerIsolate(kj::StringPtr name, + Worker::Isolate* isolate) { + auto lockedInspectorService = this->inspectorService.lockExclusive(); + if (lockedInspectorService != nullptr) { + auto is = const_cast(*lockedInspectorService); + is->registerIsolate(name, isolate); + } +} + // ======================================================================================= class Server::WorkerService final: public Service, private kj::TaskSet::ErrorHandler, @@ -1964,7 +2027,7 @@ static kj::Maybe createBinding( "the schema?")); } -void startInspector(kj::StringPtr inspectorAddress, kj::StringPtr name, Worker::Isolate* isolate); +void startInspector(kj::StringPtr inspectorAddress, Server::InspectorServiceIsolateRegistrar& registrar); kj::Own Server::makeWorker(kj::StringPtr name, config::Worker::Reader conf, capnp::List::Reader extensions) { @@ -2068,8 +2131,8 @@ kj::Own Server::makeWorker(kj::StringPtr name, config::Worker:: // If we are using the inspector, we need to register the Worker::Isolate // with the inspector service. - KJ_IF_MAYBE(inspector, inspectorOverride) { - startInspector(*inspector, name, isolate.get()); + KJ_IF_MAYBE(isolateRegistrar, inspectorIsolateRegistrar) { + (*isolateRegistrar)->registerIsolate(name, isolate.get()); } auto script = isolate->newScript( @@ -2481,17 +2544,16 @@ void Server::startAlarmScheduler(config::Config::Reader config) { void startInspector(kj::StringPtr inspectorAddress, Server::InspectorServiceIsolateRegistrar& registrar) { // Configure and start the inspector socket. - kj::Thread thread([inspectorAddress, name, isolate](){ + kj::Thread thread([inspectorAddress, ®istrar](){ kj::AsyncIoContext io = kj::setupAsyncIo(); kj::HttpHeaderTable::Builder headerTableBuilder; // Create the special inspector service. - kj::Own inspectorService(kj::heap(io.provider->getTimer(), headerTableBuilder)); + auto inspectorService( + kj::heap(io.provider->getTimer(), headerTableBuilder, registrar)); auto ownHeaderTable = headerTableBuilder.build(); - inspectorService->registerIsolate(name, isolate); - // Configure and start the inspector socket. static constexpr uint DEFAULT_PORT = 9229; @@ -2577,6 +2639,14 @@ void Server::startServices(jsg::V8System& v8System, config::Config::Reader confi }); } + // If we are using the inspector, we need to register the Worker::Isolate + // with the inspector service. + KJ_IF_MAYBE(inspectorAddress, inspectorOverride) { + auto registrar = kj::heap(); + startInspector(*inspectorAddress, *registrar); + inspectorIsolateRegistrar = kj::mv(registrar); + } + // Second pass: Build services. for (auto serviceConf: config.getServices()) { kj::StringPtr name = serviceConf.getName(); diff --git a/src/workerd/server/server.h b/src/workerd/server/server.h index 134fa7dc5f2..68b7a7ae766 100644 --- a/src/workerd/server/server.h +++ b/src/workerd/server/server.h @@ -75,6 +75,7 @@ class Server: private kj::TaskSet::ErrorHandler { using ActorConfig = kj::OneOf; class InspectorService; + class InspectorServiceIsolateRegistrar; private: kj::Filesystem& fs; @@ -94,6 +95,7 @@ class Server: private kj::TaskSet::ErrorHandler { // code that parses strings from the config file. kj::Maybe inspectorOverride; + kj::Maybe> inspectorIsolateRegistrar; kj::Maybe> controlOverride; struct GlobalContext;