From ab8e25154b49191d6c9908eddb2dbdf002cac1cb Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 25 Aug 2023 09:54:16 -0700 Subject: [PATCH] Update multiple workerd/io files to idiomatic comment style (#1067) --- src/workerd/io/io-gate.h | 131 +++++++++++++-------------- src/workerd/io/limit-enforcer.h | 57 ++++++------ src/workerd/io/observer.h | 85 +++++++++--------- src/workerd/io/request-tracker.h | 11 ++- src/workerd/io/trace.c++ | 2 +- src/workerd/io/trace.h | 149 +++++++++++++++---------------- 6 files changed, 209 insertions(+), 226 deletions(-) diff --git a/src/workerd/io/io-gate.h b/src/workerd/io/io-gate.h index 840dc3a0764..290caf5add6 100644 --- a/src/workerd/io/io-gate.h +++ b/src/workerd/io/io-gate.h @@ -31,26 +31,26 @@ namespace workerd { using kj::uint; +// An InputGate blocks incoming events from being delivered to an actor while the lock is held. class InputGate { - // An InputGate blocks incoming events from being delivered to an actor while the lock is held. public: + // Hooks that can be used to customize InputGate behavior. + // + // Technically, everything implemented here could be accomplished by a class that wraps + // InputGate, but the part of the code that wants to implement these hooks (Worker::Actor) + // is far away from the part of the code that calls into the InputGate (ActorCache), and so + // it was more convenient to give Worker::Actor a way to inject behavior into InputGate which + // would kick in when ActorCache tried to use it. class Hooks { - // Hooks that can be used to customize InputGate behavior. - // - // Technically, everything implemented here could be accomplished by a class that wraps - // InputGate, but the part of the code that wants to implement these hooks (Worker::Actor) - // is far away from the part of the code that calls into the InputGate (ActorCache), and so - // it was more convenient to give Worker::Actor a way to inject behavior into InputGate which - // would kick in when ActorCache tried to use it. public: + // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but + // we don't want to depend on that class from here. virtual void inputGateLocked() {} virtual void inputGateReleased() {} virtual void inputGateWaiterAdded() {} virtual void inputGateWaiterRemoved() {} - // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but - // we don't want to depend on that class from here. static Hooks DEFAULT; }; @@ -60,19 +60,17 @@ class InputGate { class CriticalSection; + // A lock that blocks all new events from being delivered while it exists. class Lock { - // A lock that blocks all new events from being delivered while it exists. - public: KJ_DISALLOW_COPY(Lock); Lock(Lock&& other): gate(other.gate), cs(kj::mv(other.cs)) { other.gate = nullptr; } ~Lock() noexcept(false) { if (gate != nullptr) gate->releaseLock(); } - Lock addRef() { return Lock(*gate); } // Increments the lock's refcount, returning a duplicate `Lock`. All `Lock`s must be dropped // before the gate is unlocked. + Lock addRef() { return Lock(*gate); } - kj::Own startCriticalSection(); // Start a new critical section from this lock. After `wait()` has been called on the returned // critical section for the first time, no further Locks will be handed out by // InputGate::wait() until the CriticalSection has been dropped. @@ -80,17 +78,18 @@ class InputGate { // CriticalSections can be nested. If this Lock is itself part of a CriticalSection, the new // CriticalSection will be nested within it and the outer CriticalSection's wait() won't // produce a Lock again until the inner CriticalSection is dropped. + kj::Own startCriticalSection(); - kj::Maybe getCriticalSection(); // If this lock was taken in a CriticalSection, return it. + kj::Maybe getCriticalSection(); bool isFor(const InputGate& gate) const; inline bool operator==(const Lock& other) const { return gate == other.gate; } private: - InputGate* gate; // Becomes null on move. + InputGate* gate; kj::Maybe> cs; @@ -98,23 +97,23 @@ class InputGate { friend class InputGate; }; - kj::Promise wait(); // Wait until there are no `Lock`s, then create a new one and return it. + kj::Promise wait(); - kj::Promise onBroken(); // Rejects if and when calls to `wait()` become broken due to a failed critical section. The // actor should be shut down in this case. This promise never resolves, only rejects. + kj::Promise onBroken(); private: Hooks& hooks; - uint lockCount = 0; // How many instances of `Lock` currently exist? When this reaches zero, we'll release some // waiters. + uint lockCount = 0; - bool isCriticalSection = false; // CriticalSection inherits InputGate for implementation convenience (since much implementation // is shared). + bool isCriticalSection = false; struct Waiter { Waiter(kj::PromiseFulfiller& fulfiller, InputGate& gate, bool isChildWaiter); @@ -128,116 +127,114 @@ class InputGate { kj::List waiters; - kj::List waitingChildren; // Waiters representing CriticalSections that are ready to start. These take priority over other // waiters. + kj::List waitingChildren; + // A fulfiller for onBroken(), or an exception if already broken. kj::ForkedPromise brokenPromise; kj::OneOf>, kj::Exception> brokenState; - // A fulfiller for onBroken(), or an exception if already broken. void releaseLock(); - void setBroken(const kj::Exception& e); // Called when a critical section fails. All future waiters will throw this exception. + void setBroken(const kj::Exception& e); InputGate(Hooks& hooks, kj::PromiseFulfillerPair paf); }; +// A CriticalSection is a procedure that must not be interrupted by anything "external". +// While a CriticalSection is running, all events that were not initiated by the +// CriticalSection itself will be blocked from being delivered. +// +// The difference between a Lock and a CriticalSection is that a critical section may succeed +// or fail. A failed critical section permanently breaks the input gate. Locks, on the other +// hand, are simply released when dropped. +// +// A CriticalSection itself holds a Lock, which blocks the "parent scope" from continuing +// execution until the critical section is done. Meanwhile, the code running inside the critical +// section obtains nested Locks. These nested locks control concurrency of the operations +// initiated within the critical section in the same way that input locks normally do at the +// top-level scope. E.g., if a critical section initiates a storage read and a fetch() at the +// same time, the fetch() is prevented from returning until after the storage read has returned. class InputGate::CriticalSection: private InputGate, public kj::Refcounted { - // A CriticalSection is a procedure that must not be interrupted by anything "external". - // While a CriticalSection is running, all events that were not initiated by the - // CriticalSection itself will be blocked from being delivered. - // - // The difference between a Lock and a CriticalSection is that a critical section may succeed - // or fail. A failed critical section permanently breaks the input gate. Locks, on the other - // hand, are simply released when dropped. - // - // A CriticalSection itself holds a Lock, which blocks the "parent scope" from continuing - // execution until the critical section is done. Meanwhile, the code running inside the critical - // section obtains nested Locks. These nested locks control concurrency of the operations - // initiated within the critical section in the same way that input locks normally do at the - // top-level scope. E.g., if a critical section initiates a storage read and a fetch() at the - // same time, the fetch() is prevented from returning until after the storage read has returned. - public: CriticalSection(InputGate& parent); ~CriticalSection() noexcept(false); - kj::Promise wait(); // Wait for a nested lock in order to continue this CriticalSection. // // The first call to wait() begins the CriticalSection. After that wait completes, until the // CriticalSection is done and dropped, no other locks will be allowed on this InputGate, except // locks requested by calling wait() on this CriticalSection -- or one of its children. + kj::Promise wait(); - Lock succeeded(); // Call when the critical section has completed successfully. If this is not called before the // CriticalSection is dropped, then failed() is called implicitly. // // Returns the input lock that was held on the parent critical section. This can be used to // continue execution in the parent before any other input arrives. + Lock succeeded(); - void failed(const kj::Exception& e); // Call to indicate the CriticalSection has failed with the given exception. This immediately // breaks the InputGate. + void failed(const kj::Exception& e); private: enum State { - NOT_STARTED, // wait() hasn't been called. + NOT_STARTED, - INITIAL_WAIT, // wait() has been called once, and that wait hasn't finished yet. + INITIAL_WAIT, - RUNNING, // First lock has been obtained, waiting for success() or failed(). + RUNNING, - REPARENTED // success() or failed() has been called. + REPARENTED }; State state = NOT_STARTED; - kj::OneOf> parent; // Points to the parent scope, which may be another CriticalSection in the case of nesting. + kj::OneOf> parent; - kj::Maybe parentLock; // A lock in the parent scope. `parentLock` becomes non-null after the first lock is obtained, // and becomes null again when succeeded() is called. + kj::Maybe parentLock; friend class InputGate; - InputGate& parentAsInputGate(); // Return a reference for the parent scope, skipping any reparented CriticalSections + InputGate& parentAsInputGate(); }; +// An OutputGate blocks outgoing messages from an Actor until writes which they might depend on +// are confirmed. class OutputGate { - // An OutputGate blocks outgoing messages from an Actor until writes which they might depend on - // are confirmed. - public: + // Hooks that can be used to customize OutputGate behavior. + // + // Technically, everything implemented here could be accomplished by a class that wraps + // OutputGate, but the part of the code that wants to implement these hooks (Worker::Actor) + // is far away from the part of the code that calls into the OutputGate (ActorCache), and so + // it was more convenient to give Worker::Actor a way to inject behavior into OutputGate which + // would kick in when ActorCache tried to use it. class Hooks { - // Hooks that can be used to customize OutputGate behavior. - // - // Technically, everything implemented here could be accomplished by a class that wraps - // OutputGate, but the part of the code that wants to implement these hooks (Worker::Actor) - // is far away from the part of the code that calls into the OutputGate (ActorCache), and so - // it was more convenient to give Worker::Actor a way to inject behavior into OutputGate which - // would kick in when ActorCache tried to use it. - public: - virtual kj::Promise makeTimeoutPromise() { return kj::NEVER_DONE; } // Optionally make a promise which should be exclusiveJoin()ed with the lock promise to // implement a timeout. The returned promise should be something that throws an exception // after some timeout has expired. + virtual kj::Promise makeTimeoutPromise() { return kj::NEVER_DONE; } + + // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but + // we don't want to depend on that class from here. virtual void outputGateLocked() {} virtual void outputGateReleased() {} virtual void outputGateWaiterAdded() {} virtual void outputGateWaiterRemoved() {} - // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but - // we don't want to depend on that class from here. static Hooks DEFAULT; }; @@ -245,21 +242,21 @@ class OutputGate { OutputGate(Hooks& hooks = Hooks::DEFAULT); ~OutputGate() noexcept(false); - template - kj::Promise lockWhile(kj::Promise promise); // Block all future `wait()` calls until `promise` completes. Returns a wrapper around `promise`. // If `promise` rejects, the exception will propagate to all future `wait()`s. If the returned // promise is canceled before completion, all future `wait()`s will also throw. + template + kj::Promise lockWhile(kj::Promise promise); - kj::Promise wait(); // Wait until all preceding locks are released. The wait will not be affected by any future // call to `lockWhile()`. + kj::Promise wait(); - kj::Promise onBroken(); // Rejects if and when calls to `wait()` become broken due to a failed lockWhile(). The actor // should be shut down in this case. This promise never resolves, only rejects. // // This method can only be called once. + kj::Promise onBroken(); bool isBroken(); @@ -268,8 +265,8 @@ class OutputGate { kj::ForkedPromise pastLocksPromise; - kj::OneOf>, kj::Exception> brokenState; // A fulfiller for onBroken(), or an exception if already broken. + kj::OneOf>, kj::Exception> brokenState; void setBroken(const kj::Exception& e); diff --git a/src/workerd/io/limit-enforcer.h b/src/workerd/io/limit-enforcer.h index fd45ef89bf0..6acf0ab91e9 100644 --- a/src/workerd/io/limit-enforcer.h +++ b/src/workerd/io/limit-enforcer.h @@ -12,119 +12,116 @@ namespace workerd { struct ActorCacheSharedLruOptions; class IoContext; +// Interface for an object that enforces resource limits on an Isolate level. +// +// See also LimitEnforcer, which enforces on a per-request level. class IsolateLimitEnforcer { - // Interface for an object that enforces resource limits on an Isolate level. - // - // See also LimitEnforcer, which enforces on a per-request level. - public: - virtual v8::Isolate::CreateParams getCreateParams() = 0; // Get CreateParams to pass when constructing a new isolate. + virtual v8::Isolate::CreateParams getCreateParams() = 0; - virtual void customizeIsolate(v8::Isolate* isolate) = 0; // Further customize the isolate immediately after startup. + virtual void customizeIsolate(v8::Isolate* isolate) = 0; virtual ActorCacheSharedLruOptions getActorCacheLruOptions() = 0; - virtual kj::Own enterStartupJs( - jsg::Lock& lock, kj::Maybe& error) const = 0; // Like LimitEnforcer::enterJs(), but used to enforce limits on script startup. // // When the returned scope object is dropped, if a limit was exceeded, then `error` will be // filled in to indicate what happened, otherwise it is left null. + virtual kj::Own enterStartupJs( + jsg::Lock& lock, kj::Maybe& error) const = 0; + // Like enterStartupJs(), but used when compiling a dynamically-imported module. virtual kj::Own enterDynamicImportJs( jsg::Lock& lock, kj::Maybe& error) const = 0; - // Like enterStartupJs(), but used when compiling a dynamically-imported module. - virtual kj::Own enterLoggingJs( - jsg::Lock& lock, kj::Maybe& error) const = 0; // Like enterStartupJs(), but used to enforce tight limits in cases where we just intend // to log an error to the inspector or the like. + virtual kj::Own enterLoggingJs( + jsg::Lock& lock, kj::Maybe& error) const = 0; + // Like enterStartupJs(), but used when receiving commands via the inspector protocol. virtual kj::Own enterInspectorJs( jsg::Lock& lock, kj::Maybe& error) const = 0; - // Like enterStartupJs(), but used when receiving commands via the inspector protocol. - virtual void completedRequest(kj::StringPtr id) const = 0; // Notifies the enforcer that a request has been completed. The enforcer is more lenient about // limits if several requests have been completed, vs. if limits are broken right off the bat. + virtual void completedRequest(kj::StringPtr id) const = 0; - virtual bool exitJs(jsg::Lock& lock) const = 0; // Called whenever exiting JavaScript execution (i.e. releasing the isolate lock). The enforcer // may perform some resource usage checks at this time. // // Returns true if the isolate has exceeded limits and become condemned. + virtual bool exitJs(jsg::Lock& lock) const = 0; - virtual void reportMetrics(IsolateObserver& isolateMetrics) const = 0; // Report resource usage metrics to the given isolate metrics object. + virtual void reportMetrics(IsolateObserver& isolateMetrics) const = 0; }; +// Abstract interface that enforces resource limits on a IoContext. class LimitEnforcer { - // Abstract interface that enforces resource limits on a IoContext. - public: - virtual kj::Own enterJs(jsg::Lock& lock, IoContext& context) = 0; // Called just after taking the isolate lock, before executing JavaScript code, to enforce // limits on that code execution, particularly the CPU limit. The returned `Own` should // be dropped when JavaScript is done, before unlocking the isolate. + virtual kj::Own enterJs(jsg::Lock& lock, IoContext& context) = 0; - virtual void topUpActor() = 0; // Called on each new event delivered that should cause an actor's resource limits to be // "topped up". This method does nothing if the IoContext is not an actor. Note that this must // not be called while in a JS scope, i.e. when `enterJs()` has been called and the returned // object not yet dropped. - // + virtual void topUpActor() = 0; // TODO(cleanup): This is called in WebSocket when receiving a message, but should we do // something more generic like use a membrane to detect any incoming RPC call? - virtual void newSubrequest(bool isInHouse) = 0; // Called before starting a new subrequest. Throws a JSG exception if the limit has been // reached. // // `isInHouse` is true for types of subrequests which we need to be "in house" (i.e. to another // Cloudflare service, like Workers KV) and thus should not be subject to the same limits as // external subrequests. + virtual void newSubrequest(bool isInHouse) = 0; enum class KvOpType { GET, PUT, LIST, DELETE }; - virtual void newKvRequest(KvOpType op) = 0; // Called before starting a KV operation. Throws a JSG exception if the operation should be // blocked due to exceeding limits, such as the free tier daily operation limit. + virtual void newKvRequest(KvOpType op) = 0; - virtual void newAnalyticsEngineRequest() = 0; // Called before starting an attempt to write to the Analytics Engine. Throws // a JSG exception if the operation should be blocked due to exceeding limits. + virtual void newAnalyticsEngineRequest() = 0; - virtual kj::Promise limitDrain() = 0; // Applies a time limit to draining a request (i.e. waiting for `waitUntil()`s after the // response has been sent). Returns a promise that will resolve (without error) when the time // limit has expired. This should be joined with the drain task. // // This should not be called for actors, which are evicted when the supervisor decides to // evict them, not on a timeout basis. + virtual kj::Promise limitDrain() = 0; - virtual kj::Promise limitScheduled() = 0; // Like limitDrain() but applies a time limit to scheduled event processing. + virtual kj::Promise limitScheduled() = 0; - virtual size_t getBufferingLimit() = 0; // Gets a byte size limit to apply to operations that will buffer a possibly large amount of // data in C++ memory, such as reading an entire HTTP response into an `ArrayBuffer`. + virtual size_t getBufferingLimit() = 0; - virtual kj::Maybe getLimitsExceeded() = 0; // If a limit has been exceeded which prevents further JavaScript execution, such as the CPU or // memory limit, returns a request status code indicating which one. Returns null if no limits // are exceeded. + virtual kj::Maybe getLimitsExceeded() = 0; - virtual kj::Promise onLimitsExceeded() = 0; // Reutrns a promise that will reject if and when a limit is exceeded that prevents further // JavaScript execution, such as the CPU or memory limit. + virtual kj::Promise onLimitsExceeded() = 0; - virtual void requireLimitsNotExceeded() = 0; // Throws an exception if a limit has already been exceeded which prevents further JavaScript // execution, such as the CPU or memory limit. + virtual void requireLimitsNotExceeded() = 0; - virtual void reportMetrics(RequestObserver& requestMetrics) = 0; // Report resource usage metrics to the given request metrics object. + virtual void reportMetrics(RequestObserver& requestMetrics) = 0; }; } // namespace workerd diff --git a/src/workerd/io/observer.h b/src/workerd/io/observer.h index 8ce71322350..1f1826a357e 100644 --- a/src/workerd/io/observer.h +++ b/src/workerd/io/observer.h @@ -20,46 +20,45 @@ class WorkerInterface; class LimitEnforcer; class TimerChannel; +// Observes a specific request to a specific worker. Also observes outgoing subrequests. +// +// Observing anything is optional. Default implementations of all methods observe nothing. class RequestObserver: public kj::Refcounted { - // Observes a specific request to a specific worker. Also observes outgoing subrequests. - // - // Observing anything is optional. Default implementations of all methods observe nothing. - public: - virtual void delivered() {}; // Invoked when the request is actually delivered. // // If, for some reason, this is not invoked before the object is destroyed, this indicate that // the event was canceled for some reason before delivery. No JavaScript was invoked. In this // case, the request should not be billed. + virtual void delivered() {}; - virtual void jsDone() {} // Call when no more JavaScript will run on behalf of this request. Note that deferred proxying // may still be in progress. + virtual void jsDone() {} - virtual void setIsPrewarm() {} // Called to indicate this was a prewarm request. Normal request metrics won't be logged, but // the prewarm metric will be incremented. + virtual void setIsPrewarm() {} - virtual void reportFailure(const kj::Exception& e) {} // Report that the request failed with the given exception. This only needs to be called in // cases where the wrapper created with wrapWorkerInterface() wouldn't otherwise see the // exception, e.g. because it has been replaced with an HTTP error response or because it // occurred asynchronously. + virtual void reportFailure(const kj::Exception& e) {} - virtual WorkerInterface& wrapWorkerInterface(WorkerInterface& worker) { return worker; } // Wrap the given WorkerInterface with a version that collects metrics. This method may only be // called once, and only one method call may be made to the returned interface. // // The returned reference remains valid as long as the observer and `worker` both remain live. + virtual WorkerInterface& wrapWorkerInterface(WorkerInterface& worker) { return worker; } + // Wrap an HttpClient so that its usage is counted in the request's subrequest stats. virtual kj::Own wrapSubrequestClient(kj::Own client) { - // Wrap an HttpClient so that its usage is counted in the request's subrequest stats. return kj::mv(client); } + // Wrap an HttpClient so that its usage is counted in the request's actor subrequest count. virtual kj::Own wrapActorSubrequestClient(kj::Own client) { - // Wrap an HttpClient so that its usage is counted in the request's actor subrequest count. return kj::mv(client); } @@ -77,35 +76,34 @@ class IsolateObserver: public kj::AtomicRefcounted, public jsg::IsolateObserver public: virtual ~IsolateObserver() noexcept(false) { } - virtual void created() {}; // Called when Worker::Isolate is created. + virtual void created() {}; - virtual void evicted() {} // Called when the owning Worker::Script is being destroyed. The IsolateObserver may // live a while longer to handle deferred proxy requests. + virtual void evicted() {} virtual void teardownStarted() {} virtual void teardownLockAcquired() {} virtual void teardownFinished() {} + // Describes why a worker was started. enum class StartType: uint8_t { - // Describes why a worker was started. - - COLD, // Cold start with active request waiting. + COLD, - PREWARM, // Started due to prewarm hint (e.g. from TLS SNI); a real request is expected soon. + PREWARM, - PRELOAD // Started due to preload at process startup. + PRELOAD }; + // Created while parsing a script, to record related metrics. class Parse { - // Created while parsing a script, to record related metrics. public: - virtual void done() {} // Marks the ScriptReplica as finished parsing, which starts reporting of isolate metrics. + virtual void done() {} }; virtual kj::Own parse(StartType startType) const { @@ -115,14 +113,13 @@ class IsolateObserver: public kj::AtomicRefcounted, public jsg::IsolateObserver class LockTiming { public: - virtual void waitingForOtherIsolate(kj::StringPtr id) {} // Called by `Isolate::takeAsyncLock()` when it is blocked by a different isolate lock on the // same thread. + virtual void waitingForOtherIsolate(kj::StringPtr id) {} + // Call if this is an async lock attempt, before constructing LockRecord. virtual void reportAsyncInfo(uint currentLoad, bool threadWaitingSameLock, uint threadWaitingDifferentLockCount) {} - // Call if this is an async lock attempt, before constructing LockRecord. - // // TODO(cleanup): Should be able to get this data at `tryCreateLockTiming()` time. It'd be // easier if IsolateObserver were an AOP class, and thus had access to the real isolate. @@ -134,26 +131,25 @@ class IsolateObserver: public kj::AtomicRefcounted, public jsg::IsolateObserver virtual void gcEpilogue() {} }; - virtual kj::Maybe> tryCreateLockTiming( - kj::OneOf> parentOrRequest) const { return nullptr; } // Construct a LockTiming if config.reportScriptLockTiming is true, or if the // request (if any) is being traced. + virtual kj::Maybe> tryCreateLockTiming( + kj::OneOf> parentOrRequest) const { return nullptr; } + // Use like so: + // + // auto lockTiming = MetricsCollector::ScriptReplica::LockTiming::tryCreate(script, maybeRequest); + // MetricsCollector::ScriptReplica::LockRecord record(lockTiming); + // JsgWorkerIsolate::Lock lock(isolate); + // record.locked(); + // + // And `record()` will report the time spent waiting for the lock (including any asynchronous + // time you might insert between the construction of `lockTiming` and `LockRecord()`), plus + // the time spent holding the lock for the given ScriptReplica. + // + // This is a thin wrapper around LockTiming which efficiently handles the case where we don't + // want to track timing. class LockRecord { - // Use like so: - // - // auto lockTiming = MetricsCollector::ScriptReplica::LockTiming::tryCreate(script, maybeRequest); - // MetricsCollector::ScriptReplica::LockRecord record(lockTiming); - // JsgWorkerIsolate::Lock lock(isolate); - // record.locked(); - // - // And `record()` will report the time spent waiting for the lock (including any asynchronous - // time you might insert between the construction of `lockTiming` and `LockRecord()`), plus - // the time spent holding the lock for the given ScriptReplica. - // - // This is a thin wrapper around LockTiming which efficiently handles the case where we don't - // want to track timing. - public: explicit LockRecord(kj::Maybe> lockTimingParam) : lockTiming(kj::mv(lockTimingParam)) { @@ -169,16 +165,16 @@ class IsolateObserver: public kj::AtomicRefcounted, public jsg::IsolateObserver void gcEpilogue() { KJ_IF_MAYBE(l, lockTiming) l->get()->gcEpilogue(); } private: - kj::Maybe> lockTiming; // The presence of `lockTiming` determines whether or not we need to record timing data. If // we have no `lockTiming`, then this LockRecord wrapper is just a big nothingburger. + kj::Maybe> lockTiming; }; }; class WorkerObserver: public kj::AtomicRefcounted { public: + // Created while executing a script's global scope, to record related metrics. class Startup { - // Created while executing a script's global scope, to record related metrics. public: virtual void done() {} }; @@ -195,11 +191,10 @@ class WorkerObserver: public kj::AtomicRefcounted { class ActorObserver: public kj::Refcounted { public: + // Allows the observer to run in the background, periodically making observations. Owner must + // call this and store the promise. `limitEnforcer` is used to collect CPU usage metrics, it + // must remain valid as long as the loop is running. virtual kj::Promise flushLoop(TimerChannel& timer, LimitEnforcer& limitEnforcer) { - // Allows the observer to run in the background, periodically making observations. Owner must - // call this and store the promise. `limitEnforcer` is used to collect CPU usage metrics, it - // must remain valid as long as the loop is running. - return kj::NEVER_DONE; } diff --git a/src/workerd/io/request-tracker.h b/src/workerd/io/request-tracker.h index f84526966b7..7e3096b639c 100644 --- a/src/workerd/io/request-tracker.h +++ b/src/workerd/io/request-tracker.h @@ -10,11 +10,10 @@ namespace workerd { +// This class is used to track a number of associated requests so that some desired behavior +// is carried out once all requests have completed. `activeRequests` is incremented each time a +// new request is created, and then decremented once it completes. class RequestTracker final: public kj::Refcounted { - // This class is used to track a number of associated requests so that some desired behavior - // is carried out once all requests have completed. `activeRequests` is incremented each time a - // new request is created, and then decremented once it completes. - public: class Hooks { public: @@ -22,9 +21,9 @@ class RequestTracker final: public kj::Refcounted { virtual void inactive() = 0; }; + // An object that should be associated with (attached to) a request. class ActiveRequest { public: - // An object that should be associated with (attached to) a request. // On creation, if the parent RequestTracker has 0 active requests, we call the `active()` hook. // On destruction, if the RequestTracker has 0 active requests, we call the `inactive()` hook. // Otherwise, we just increment/decrement the count on creation/destruction respectively. @@ -41,12 +40,12 @@ class RequestTracker final: public kj::Refcounted { ~RequestTracker() noexcept(false); KJ_DISALLOW_COPY(RequestTracker); - ActiveRequest startRequest(); // Returns a new ActiveRequest, thereby bumping the count of active requests associated with the // RequestTracker. The ActiveRequest must be attached to the lifetime of the request such that we // destroy the ActiveRequest when the request is finished. On destruction, we decrement the count // of active requests associated with the RequestTracker, and if there are no more active requests // we call the `inactive()` hook. + ActiveRequest startRequest(); void shutdown() { // We want to prevent any hooks from running after this point. diff --git a/src/workerd/io/trace.c++ b/src/workerd/io/trace.c++ index 8cc78e8818b..0bf340e0384 100644 --- a/src/workerd/io/trace.c++ +++ b/src/workerd/io/trace.c++ @@ -10,10 +10,10 @@ namespace workerd { -static constexpr size_t MAX_TRACE_BYTES = 128 * 1024; // Approximately how much external data we allow in a trace before we start ignoring requests. We // want this number to be big enough to be useful for tracing, but small enough to make it hard to // DoS the C++ heap -- keeping in mind we can record a trace per handler run during a request. +static constexpr size_t MAX_TRACE_BYTES = 128 * 1024; namespace { diff --git a/src/workerd/io/trace.h b/src/workerd/io/trace.h index cd155cba799..a8831c48123 100644 --- a/src/workerd/io/trace.h +++ b/src/workerd/io/trace.h @@ -45,8 +45,9 @@ enum class PipelineLogLevel { // - Request builds a vector of results, while Tracer builds a tree. // TODO(cleanup) - worth separating into immutable Trace vs. mutable TraceBuilder? + +// Collects trace information about the handling of a worker/pipline fetch event. class Trace final : public kj::Refcounted { - // Collects trace information about the handling of a worker/pipline fetch event. public: explicit Trace(kj::Maybe stableId, kj::Maybe scriptName, kj::Maybe dispatchNamespace, kj::Array scriptTags); Trace(rpc::Trace::Reader reader); @@ -74,8 +75,8 @@ class Trace final : public kj::Refcounted { kj::HttpMethod method; kj::String url; + // TODO(perf): It might be more efficient to store some sort of parsed JSON result instead? kj::String cfJson; - // TODO(perf): It might be more efficient to store some sort of parsed JSON result instead? kj::Array
headers; void copyTo(rpc::Trace::FetchEventInfo::Builder builder); @@ -166,12 +167,12 @@ class Trace final : public kj::Refcounted { KJ_DISALLOW_COPY(Log); ~Log() noexcept(false) = default; - kj::Date timestamp; // Only as accurate as Worker's Date.now(), for Spectre mitigation. + kj::Date timestamp; LogLevel logLevel; - kj::String message; // TODO(soon): Just string for now. Eventually, capture serialized JS objects. + kj::String message; void copyTo(rpc::Trace::Log::Builder builder); }; @@ -184,21 +185,21 @@ class Trace final : public kj::Refcounted { KJ_DISALLOW_COPY(Exception); ~Exception() noexcept(false) = default; - kj::Date timestamp; // Only as accurate as Worker's Date.now(), for Spectre mitigation. + kj::Date timestamp; kj::String name; - kj::String message; // TODO(someday): record exception source, line/column number, stack trace? + kj::String message; void copyTo(rpc::Trace::Exception::Builder builder); }; - kj::Maybe stableId; // Empty for toplevel worker. + kj::Maybe stableId; - kj::Date eventTimestamp = kj::UNIX_EPOCH; // We treat the origin value as "unset". + kj::Date eventTimestamp = kj::UNIX_EPOCH; typedef kj::OneOf EventInfo; @@ -212,8 +213,8 @@ class Trace final : public kj::Refcounted { kj::Array scriptTags; kj::Vector logs; - kj::Vector exceptions; // A request's trace can have multiple exceptions due to separate request/waitUntil tasks. + kj::Vector exceptions; kj::Vector diagnosticChannelEvents; @@ -227,44 +228,42 @@ class Trace final : public kj::Refcounted { bool exceededLogLimit = false; bool exceededExceptionLimit = false; bool exceededDiagnosticChannelEventLimit = false; - size_t bytesUsed = 0; // Trace data is recorded outside of the JS heap. To avoid DoS, we keep an estimate of trace // data size, and we stop recording if too much is used. - + size_t bytesUsed = 0; // TODO(someday): Eventually, want to capture: customer-facing spans, metrics, user data - void copyTo(rpc::Trace::Builder builder); // Copy content from this trace into `builder`. + void copyTo(rpc::Trace::Builder builder); - void mergeFrom(rpc::Trace::Reader reader, PipelineLogLevel pipelineLogLevel); // Adds all content from `reader` to this `Trace`. (Typically this trace is empty before the // call.) Also applies filtering to the trace as if it were recorded with the given // pipelineLogLevel. + void mergeFrom(rpc::Trace::Reader reader, PipelineLogLevel pipelineLogLevel); }; // ======================================================================================= class WorkerTracer; +// A tracer which records traces for a set of stages. All traces for a pipeline's stages and +// possible subpipeline stages are recorded here, where they can be used to call a pipeline's +// trace worker. class PipelineTracer final : public kj::Refcounted { - // A tracer which records traces for a set of stages. All traces for a pipeline's stages and - // possible subpipeline stages are recorded here, where they can be used to call a pipeline's - // trace worker. - public: + // Creates a pipeline tracer (with a possible parent). explicit PipelineTracer(kj::Maybe> parentPipeline = nullptr) : parentTracer(kj::mv(parentPipeline)) {} - // Creates a pipeline tracer (with a possible parent). ~PipelineTracer() noexcept(false); KJ_DISALLOW_COPY_AND_MOVE(PipelineTracer); - kj::Promise>> onComplete(); // Returns a promise that fulfills when traces are complete. Only one such promise can // exist at a time. + kj::Promise>> onComplete(); + // Makes a tracer for a subpipeline. kj::Own makePipelineSubtracer() { - // Makes a tracer for a subpipeline. return kj::refcounted(kj::addRef(*this)); } @@ -284,20 +283,19 @@ class PipelineTracer final : public kj::Refcounted { friend class WorkerTracer; }; +// Records a worker stage's trace information into a Trace object. When all references to the +// Tracer are released, its Trace is considered complete and ready for submission. If the Trace to +// write to isn't provided (that already exists in a PipelineTracer), the trace must by extracted +// via extractTrace. class WorkerTracer final : public kj::Refcounted { - // Records a worker stage's trace information into a Trace object. When all references to the - // Tracer are released, its Trace is considered complete and ready for submission. If the Trace to - // write to isn't provided (that already exists in a PipelineTracer), the trace must by extracted - // via extractTrace. - public: explicit WorkerTracer(kj::Own parentPipeline, kj::Own trace, PipelineLogLevel pipelineLogLevel); explicit WorkerTracer(PipelineLogLevel pipelineLogLevel); KJ_DISALLOW_COPY_AND_MOVE(WorkerTracer); - void log(kj::Date timestamp, LogLevel logLevel, kj::String message); // Adds log line to trace. For Spectre, timestamp should only be as accurate as JS Date.now(). + void log(kj::Date timestamp, LogLevel logLevel, kj::String message); // TODO(soon): Eventually: //void setMetrics(...) // Or get from MetricsCollector::Request directly? @@ -307,12 +305,12 @@ class WorkerTracer final : public kj::Refcounted { void addDiagnosticChannelEvent(kj::Date timestamp, kj::String channel, kj::Array message); - void setEventInfo(kj::Date timestamp, Trace::EventInfo&&); // Adds info about the event that triggered the trace. Must not be called more than once. + void setEventInfo(kj::Date timestamp, Trace::EventInfo&&); - void setFetchResponseInfo(Trace::FetchResponseInfo&&); // Adds info about the response. Must not be called more than once, and only // after passing a FetchEventInfo to setEventInfo(). + void setFetchResponseInfo(Trace::FetchResponseInfo&&); void setOutcome(EventOutcome outcome); @@ -320,13 +318,13 @@ class WorkerTracer final : public kj::Refcounted { void setWallTime(kj::Duration wallTime); - void extractTrace(rpc::Trace::Builder builder); // Used only for a Trace in a process sandbox. Copies the content of this tracer's trace to the // builder. + void extractTrace(rpc::Trace::Builder builder); - void setTrace(rpc::Trace::Reader reader); // Sets the main trace of this Tracer to match the content of `reader`. This is used in the // parent process after receiving a trace from a process sandbox. + void setTrace(rpc::Trace::Reader reader); private: PipelineLogLevel pipelineLogLevel; @@ -339,9 +337,9 @@ class WorkerTracer final : public kj::Refcounted { // ======================================================================================= +// Helper function used when setting "truncated_script_id" tags. Truncates the scriptId to 10 +// characters. inline kj::String truncateScriptId(kj::StringPtr id) { - // Helper function used when setting "truncated_script_id" tags. Truncates the scriptId to 10 - // characters. auto truncatedId = id.slice(0, kj::min(id.size(), 10)); return kj::str(truncatedId); } @@ -383,30 +381,29 @@ struct Span { TagMap tags; kj::Vector logs; - static constexpr auto MAX_LOGS = 1023; - uint droppedLogs = 0; // We set an arbitrary (-ish) cap on log messages for safety. If we drop logs because of this, // we report how many in a final "dropped_logs" log. // // At the risk of being too clever, I chose a limit that is one below a power of two so that // we'll typically have space for one last element available for the "dropped_logs" log without // needing to grow the vector. + static constexpr auto MAX_LOGS = 1023; + uint droppedLogs = 0; explicit Span(kj::ConstString operationName, kj::Date startTime) : operationName(kj::mv(operationName)), startTime(startTime), endTime(startTime) {} }; +// An opaque token which can be used to create child spans of some parent. This is typically +// passed down from a caller to a callee when the caller wants to allow the callee to create +// spans for itself that show up as children of the caller's span, but the caller does not +// want to give the callee any other ability to modify the parent span. class SpanParent { - // An opaque token which can be used to create child spans of some parent. This is typically - // passed down from a caller to a callee when the caller wants to allow the callee to create - // spans for itself that show up as children of the caller's span, but the caller does not - // want to give the callee any other ability to modify the parent span. - public: SpanParent(SpanBuilder& builder); - SpanParent(decltype(nullptr)) {} // Make a SpanParent that causes children not to be reported anywhere. + SpanParent(decltype(nullptr)) {} SpanParent(kj::Maybe> observer): observer(kj::mv(observer)) {} @@ -416,21 +413,21 @@ class SpanParent { SpanParent addRef(); - SpanBuilder newChild(kj::ConstString operationName, - kj::Date startTime = kj::systemPreciseCalendarClock().now()); // Create a new child span. // // `operationName` should be a string literal with infinite lifetime. + SpanBuilder newChild(kj::ConstString operationName, + kj::Date startTime = kj::systemPreciseCalendarClock().now()); - bool isObserved() { return observer != nullptr; } // Useful to skip unnecessary code when not observed. + bool isObserved() { return observer != nullptr; } - kj::Maybe getObserver() { return observer; } // Get the underlying SpanObserver representing the parent span. // // This is needed in particular when making outbound network requests that must be annotated with // trace IDs in a way that is specific to the trace back-end being used. The caller must downcast // the `SpanObserver` to the expected observer type in order to extract the trace ID. + kj::Maybe getObserver() { return observer; } private: kj::Maybe> observer; @@ -438,17 +435,21 @@ class SpanParent { friend class SpanBuilder; }; +// Interface for writing a span. Essentially, this is a mutable interface to a `Span` object, +// given only to the code which is meant to create the span, whereas code that merely collects +// and reports spans gets the `Span` type. +// +// The reason we use a separate builder type rather than rely on constness is so that the methods +// can be no-ops when there is no observer, avoiding unnecessary allocations. To allow for this, +// SpanBuilder is designed to be write-only -- you cannot read back the content. Only the +// observer (if there is one) receives the content. class SpanBuilder { - // Interface for writing a span. Essentially, this is a mutable interface to a `Span` object, - // given only to the code which is meant to create the span, whereas code that merely collects - // and reports spans gets the `Span` type. - // - // The reason we use a separate builder type rather than rely on constness is so that the methods - // can be no-ops when there is no observer, avoiding unnecessary allocations. To allow for this, - // SpanBuilder is designed to be write-only -- you cannot read back the content. Only the - // observer (if there is one) receives the content. - public: + // Create a new top-level span that will report to the given observer. If the observer is null, + // no data is collected. + // + // `operationName` should be a string literal with infinite lifetime, or somehow otherwise be + // attached to the observer observing this span. explicit SpanBuilder(kj::Maybe> observer, kj::ConstString operationName, kj::Date startTime = kj::systemPreciseCalendarClock().now()) { if (observer != nullptr) { @@ -456,14 +457,9 @@ class SpanBuilder { span.emplace(kj::mv(operationName), startTime); } } - // Create a new top-level span that will report to the given observer. If the observer is null, - // no data is collected. - // - // `operationName` should be a string literal with infinite lifetime, or somehow otherwise be - // attached to the observer observing this span. - SpanBuilder(decltype(nullptr)) {} // Make a SpanBuilder that ignores all calls. (Useful if you want to assign it later.) + SpanBuilder(decltype(nullptr)) {} SpanBuilder(SpanBuilder&& other) = default; SpanBuilder& operator=(SpanBuilder&& other); // ends the existing span and starts a new one @@ -471,70 +467,69 @@ class SpanBuilder { ~SpanBuilder() noexcept(false); - void end(); // Finishes and submits the span. This is done implicitly by the destructor, but sometimes it's // useful to be able to submit early. The SpanBuilder ignores all further method calls after this // is invoked. + void end(); - bool isObserved() { return observer != nullptr; } // Useful to skip unnecessary code when not observed. + bool isObserved() { return observer != nullptr; } - kj::Maybe getObserver() { return observer; } // Get the underlying SpanObserver representing the span. // // This is needed in particular when making outbound network requests that must be annotated with // trace IDs in a way that is specific to the trace back-end being used. The caller must downcast // the `SpanObserver` to the expected observer type in order to extract the trace ID. + kj::Maybe getObserver() { return observer; } - SpanBuilder newChild(kj::ConstString operationName, - kj::Date startTime = kj::systemPreciseCalendarClock().now()); // Create a new child span. // // `operationName` should be a string literal with infinite lifetime. + SpanBuilder newChild(kj::ConstString operationName, + kj::Date startTime = kj::systemPreciseCalendarClock().now()); - void setOperationName(kj::ConstString operationName); // Change the operation name from what was specified at span creation. // // `operationName` should be a string literal with infinite lifetime. + void setOperationName(kj::ConstString operationName); using TagValue = Span::TagValue; - void setTag(kj::ConstString key, TagValue value); // `key` must point to memory that will remain valid all the way until this span's data is // serialized. + void setTag(kj::ConstString key, TagValue value); - void addLog(kj::Date timestamp, kj::ConstString key, TagValue value); // `key` must point to memory that will remain valid all the way until this span's data is // serialized. // // The differences between this and `setTag()` is that logs are timestamped and may have // duplicate keys. + void addLog(kj::Date timestamp, kj::ConstString key, TagValue value); private: kj::Maybe> observer; - kj::Maybe span; // The under-construction span, or null if the span has ended. + kj::Maybe span; friend class SpanParent; }; +// Abstract interface for observing trace spans reported by the runtime. Different +// implementations might support different tracing back-ends, e.g. Trace Workers, Jaeger, or +// whatever infrastrure you prefer to use for this. +// +// A new SpanObserver is created at the start of each Span. The observer is used to report the +// span data at the end of the span, as well as to construct child observers. class SpanObserver: public kj::Refcounted { - // Abstract interface for observing trace spans reported by the runtime. Different - // implementations might support different tracing back-ends, e.g. Trace Workers, Jaeger, or - // whatever infrastrure you prefer to use for this. - // - // A new SpanObserver is created at the start of each Span. The observer is used to report the - // span data at the end of the span, as well as to construct child observers. - public: - virtual kj::Own newChild() = 0; // Allocate a new child span. // // Note that children can be created long after a span has completed. + virtual kj::Own newChild() = 0; - virtual void report(const Span& span) = 0; // Report the span data. Called at the end of the span. // // This should always be called exactly once per observer. + virtual void report(const Span& span) = 0; }; inline SpanParent::SpanParent(SpanBuilder& builder)