Skip to content

Commit

Permalink
Add support for debugging multiple workers
Browse files Browse the repository at this point in the history
If there are multiple service workers in a config, expose them to
devtools. This is largely just for Chrome devtools since wrangler
does not expect to generate more than one worker per config that
might need to be debugged.

Test: manual using chrome devtools and https://bitbucket.cfdata.org/users/bcoll/repos/workerd-bus-error-10/browse
  • Loading branch information
ohodson committed Jul 5, 2023
1 parent c57a978 commit dea77d4
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 13 deletions.
86 changes: 73 additions & 13 deletions src/workerd/server/server.c++
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,27 @@ kj::Own<Server::Service> Server::makeDiskDirectoryService(

// =======================================================================================

class Server::InspectorServiceIsolateRegistrar final {
public:
InspectorServiceIsolateRegistrar() {}
~InspectorServiceIsolateRegistrar();

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<const InspectorService*> inspectorService;
friend class Server::InspectorService;
};

class Server::InspectorService final: public kj::HttpService, public kj::HttpServerErrorHandler {
// Implements the interface for the devtools inspector protocol.
//
Expand All @@ -972,12 +993,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& theRegistrar)
: timer(timer),
headerTable(headerTableBuilder.getFutureTable()),
server(timer, headerTable, *this, kj::HttpServerSettings {
.errorHandler = *this
}) {}
}),
registrar(theRegistrar) {
theRegistrar.attach(this);
}

~InspectorService() {
KJ_IF_MAYBE(r, registrar) {
r->detach();
}
}

void invalidateRegistrar() {
registrar = nullptr;
}

kj::Promise<void> handleApplicationError(
kj::Exception exception, kj::Maybe<kj::HttpService::Response&> response) override {
Expand Down Expand Up @@ -1040,6 +1075,7 @@ public:
}
}

KJ_LOG(INFO, kj::str("Unknown worker session [", id, "]"));
return response.sendError(404, "Unknown worker session", responseHeaders);
}

Expand Down Expand Up @@ -1137,10 +1173,26 @@ private:
kj::HttpHeaderTable& headerTable;
kj::HashMap<kj::String, kj::Own<const Worker::Isolate::WeakIsolateRef>> isolates;
kj::HttpServer server;

friend class Registration;
kj::Maybe<InspectorServiceIsolateRegistrar&> registrar;
};

Server::InspectorServiceIsolateRegistrar::~InspectorServiceIsolateRegistrar() {
auto lockedInspectorService = this->inspectorService.lockExclusive();
if (lockedInspectorService != nullptr) {
auto is = const_cast<InspectorService*>(*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<InspectorService*>(*lockedInspectorService);
is->registerIsolate(name, isolate);
}
}

// =======================================================================================

class Server::WorkerService final: public Service, private kj::TaskSet::ErrorHandler,
Expand Down Expand Up @@ -1908,7 +1960,7 @@ static kj::Maybe<WorkerdApiIsolate::Global> 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::Service> Server::makeWorker(kj::StringPtr name, config::Worker::Reader conf,
capnp::List<config::Extension>::Reader extensions) {
Expand Down Expand Up @@ -2011,8 +2063,8 @@ kj::Own<Server::Service> 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(
Expand Down Expand Up @@ -2401,7 +2453,7 @@ kj::Promise<void> Server::run(jsg::V8System& v8System, config::Config::Reader co

auto listenPromise = listenOnSockets(config, headerTableBuilder, forkedDrainWhen);

// We should have registered all headers synchronously. This is important becaues we want to
// We should have registered all headers synchronously. This is important because we want to
// be able to start handling requests as soon as the services are available, even if some other
// services take longer to get ready.
auto ownHeaderTable = headerTableBuilder.build();
Expand All @@ -2420,22 +2472,22 @@ void Server::startAlarmScheduler(config::Config::Reader config) {
.attach(kj::mv(vfs));
}

void startInspector(kj::StringPtr inspectorAddress, kj::StringPtr name, Worker::Isolate* isolate) {
void startInspector(kj::StringPtr inspectorAddress,
Server::InspectorServiceIsolateRegistrar& registrar) {
// ---------------------------------------------------------------------------
// Configure inspector.

// Configure and start the inspector socket.
kj::Thread thread([inspectorAddress, name, isolate](){
kj::Thread thread([inspectorAddress, &registrar](){
kj::AsyncIoContext io = kj::setupAsyncIo();

kj::HttpHeaderTable::Builder headerTableBuilder;

// Create the special inspector service.
kj::Own<Server::InspectorService> inspectorService(kj::heap<Server::InspectorService>(io.provider->getTimer(), headerTableBuilder));
auto inspectorService(
kj::heap<Server::InspectorService>(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;

Expand Down Expand Up @@ -2521,6 +2573,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<InspectorServiceIsolateRegistrar>();
startInspector(*inspectorAddress, *registrar);
inspectorIsolateRegistrar = kj::mv(registrar);
}

// Second pass: Build services.
for (auto serviceConf: config.getServices()) {
kj::StringPtr name = serviceConf.getName();
Expand Down
2 changes: 2 additions & 0 deletions src/workerd/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Server: private kj::TaskSet::ErrorHandler {
using ActorConfig = kj::OneOf<Durable, Ephemeral>;

class InspectorService;
class InspectorServiceIsolateRegistrar;

private:
kj::Filesystem& fs;
Expand All @@ -94,6 +95,7 @@ class Server: private kj::TaskSet::ErrorHandler {
// code that parses strings from the config file.

kj::Maybe<kj::String> inspectorOverride;
kj::Maybe<kj::Own<InspectorServiceIsolateRegistrar>> inspectorIsolateRegistrar;
kj::Maybe<kj::Own<kj::FdOutputStream>> controlOverride;

struct GlobalContext;
Expand Down

0 comments on commit dea77d4

Please sign in to comment.