Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/workerd/io/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ wd_cc_library(
hdrs = ["io-gate.h"],
visibility = ["//visibility:public"],
deps = [
":trace",
"@capnp-cpp//src/kj",
"@capnp-cpp//src/kj:kj-async",
],
Expand Down
10 changes: 10 additions & 0 deletions src/workerd/io/io-gate.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// to the application are still being flushed to disk. If the flush fails, these messages will
// never be sent, so that the rest of the world cannot observe a prematurely-confirmed write.

#include <workerd/io/trace.h>

#include <kj/async.h>
#include <kj/list.h>
#include <kj/one-of.h>
Expand Down Expand Up @@ -50,6 +52,10 @@ class InputGate {
virtual void inputGateWaiterRemoved() {}

static const Hooks DEFAULT;

protected:
kj::Maybe<SpanBuilder> inputGateWaitSpan;
kj::Maybe<SpanBuilder> inputGateHoldSpan;
};

// Hooks has no member variables, so const_cast is acceptable.
Expand Down Expand Up @@ -245,6 +251,10 @@ class OutputGate {
virtual void outputGateWaiterRemoved() {}

static const Hooks DEFAULT;

protected:
kj::Maybe<SpanBuilder> outputGateWaitSpan;
kj::Maybe<SpanBuilder> outputGateHoldSpan;
};

// Hooks has no member variables, so const_cast is acceptable.
Expand Down
21 changes: 21 additions & 0 deletions src/workerd/io/worker.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://opensource.org/licenses/Apache-2.0

#include "actor-cache.h"
#include "workerd/io/io-gate.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "workerd/io/io-gate.h"
#include <workerd/io/io-gate.h>


#include <workerd/api/actor-state.h>
#include <workerd/api/global-scope.h>
Expand Down Expand Up @@ -3441,15 +3442,25 @@ struct Worker::Actor::Impl {

void inputGateLocked() override {
metrics.inputGateLocked();
if (IoContext::hasCurrent()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should always have an active IoContext shouldn't we?
So

    auto& context = IoContext::current();
    auto inputGateHoldSpan = context.makeTraceSpan("actor_input_gate_hold"_kjc);

Should be better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my testing, we don't have IoContext when we invoke the input gate or hold them. I am trying to see if I can use the parent's context.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would really limit the effectiveness and reliability of this PR.

I don't understand input gates well enough but I would love to learn more about them to understand how to best approach this.
If we need to instead iterate over all of the actor's current inflight requests and open spans in all of them we could do that instead of relying on the IoContext

auto& ioContext = IoContext::current();
inputGateHoldSpan = ioContext.makeTraceSpan("actor_input_gate_hold"_kjc);
}
}
void inputGateReleased() override {
metrics.inputGateReleased();
inputGateHoldSpan = kj::none;
}
void inputGateWaiterAdded() override {
metrics.inputGateWaiterAdded();
if (IoContext::hasCurrent()) {
auto& ioContext = IoContext::current();
inputGateWaitSpan = ioContext.makeTraceSpan("actor_input_gate_wait"_kjc);
}
}
void inputGateWaiterRemoved() override {
metrics.inputGateWaiterRemoved();
inputGateWaitSpan = kj::none;
}
// Implements InputGate::Hooks.

Expand All @@ -3469,15 +3480,25 @@ struct Worker::Actor::Impl {

void outputGateLocked() override {
metrics.outputGateLocked();
if (IoContext::hasCurrent()) {
auto& ioContext = IoContext::current();
outputGateHoldSpan = ioContext.makeTraceSpan("actor_output_gate_hold"_kjc);
}
}
void outputGateReleased() override {
metrics.outputGateReleased();
outputGateHoldSpan = kj::none;
}
void outputGateWaiterAdded() override {
metrics.outputGateWaiterAdded();
if (IoContext::hasCurrent()) {
auto& ioContext = IoContext::current();
outputGateWaitSpan = ioContext.makeTraceSpan("actor_output_gate_wait"_kjc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is naively overwriting any existing span, but it's possible for an OutputGate to have multiple waiters. I'll look more closely in a minute, but I'd expect us to want to attach a span to each (traced) waiter

Copy link
Member

@a-robinson a-robinson Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd think we could hold these WaitSpan variables in the InputGate::Waiter for InputGates, and for OutputGates we could attach a span on the branch promise returned from OutputGate::wait (similar to the defer for hooks.outputGateWaiterRemoved())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with one request and this works, but or input gates the IoContext is not set at the point where the gates are held/waited, so have to use the parent's IoContext or pass in a parent span. I am working on that change now, will mostly switch to same model for outpute gate too

}
}
void outputGateWaiterRemoved() override {
metrics.outputGateWaiterRemoved();
outputGateWaitSpan = kj::none;
}

// Implements ActorCache::Hooks
Expand Down
Loading