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 14, 2023
1 parent 0038a1b commit a4d477b
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
95 changes: 83 additions & 12 deletions src/workerd/server/server.c++
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,38 @@ kj::Own<Server::Service> 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 <name>:<isolate>
// 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<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 @@ -973,12 +1005,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<void> handleApplicationError(
kj::Exception exception, kj::Maybe<kj::HttpService::Response&> response) override {
Expand Down Expand Up @@ -1041,6 +1087,7 @@ public:
}
}

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

Expand Down Expand Up @@ -1138,10 +1185,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() noexcept(true) {
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 @@ -1909,7 +1972,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 @@ -2013,8 +2076,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 @@ -2422,22 +2485,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 @@ -2523,6 +2586,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 a4d477b

Please sign in to comment.