File
Blob: src/workerd/io/limit-enforcer.h
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #pragma once |
| 6 | |
| 7 | #include <workerd/io/outcome.capnp.h> |
| 8 | #include <workerd/io/tracked-wasm-instance.h> |
| 9 | |
| 10 | #include <v8-isolate.h> |
| 11 | |
| 12 | #include <kj/async.h> // For Promise |
| 13 | #include <kj/debug.h> // For KJ_REQUIRE |
| 14 | #include <kj/memory.h> // for Own |
| 15 | #include <kj/one-of.h> // for OneOf |
| 16 | #include <kj/time.h> // for Duration |
| 17 | |
| 18 | namespace workerd { |
| 19 | class IsolateObserver; |
| 20 | class RequestObserver; |
| 21 | |
| 22 | struct ActorCacheSharedLruOptions; |
| 23 | class IoContext; |
| 24 | |
| 25 | namespace jsg { |
| 26 | class Lock; |
| 27 | } // namespace jsg |
| 28 | |
| 29 | static constexpr size_t DEFAULT_MAX_PBKDF2_ITERATIONS = 100'000; |
| 30 | |
| 31 | // Interface for an object that enforces resource limits on an Isolate level. |
| 32 | // |
| 33 | // See also LimitEnforcer, which enforces on a per-request level. |
| 34 | class IsolateLimitEnforcer: public kj::Refcounted { |
| 35 | public: |
| 36 | // Get CreateParams to pass when constructing a new isolate. |
| 37 | virtual v8::Isolate::CreateParams getCreateParams() = 0; |
| 38 | |
| 39 | // Further customize the isolate immediately after startup. |
| 40 | virtual void customizeIsolate(v8::Isolate* isolate) = 0; |
| 41 | |
| 42 | virtual ActorCacheSharedLruOptions getActorCacheLruOptions() = 0; |
| 43 | |
| 44 | // Like LimitEnforcer::enterJs(), but used to enforce limits on script startup. |
| 45 | // |
| 46 | // When the returned scope object is dropped, if a limit was exceeded, then `error` will be |
| 47 | // filled in to indicate what happened, otherwise it is left null. |
| 48 | virtual kj::Own<void> enterStartupJs( |
| 49 | jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0; |
| 50 | |
| 51 | // used to enforce limits on Python script startup. |
| 52 | virtual kj::Own<void> enterStartupPython( |
| 53 | jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0; |
| 54 | |
| 55 | // Like enterStartupJs(), but used when compiling a dynamically-imported module. |
| 56 | virtual kj::Own<void> enterDynamicImportJs( |
| 57 | jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0; |
| 58 | |
| 59 | // Like enterStartupJs(), but used to enforce tight limits in cases where we just intend |
| 60 | // to log an error to the inspector or the like. |
| 61 | virtual kj::Own<void> enterLoggingJs( |
| 62 | jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0; |
| 63 | |
| 64 | // Like enterStartupJs(), but used when receiving commands via the inspector protocol. |
| 65 | virtual kj::Own<void> enterInspectorJs( |
| 66 | jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0; |
| 67 | |
| 68 | // Notifies the enforcer that a request has been completed. The enforcer is more lenient about |
| 69 | // limits if several requests have been completed, vs. if limits are broken right off the bat. |
| 70 | virtual void completedRequest(kj::StringPtr id) const = 0; |
| 71 | |
| 72 | // Called whenever exiting JavaScript execution (i.e. releasing the isolate lock). The enforcer |
| 73 | // may perform some resource usage checks at this time. |
| 74 | // |
| 75 | // Returns true if the isolate has exceeded limits and become condemned. |
| 76 | virtual bool exitJs(jsg::Lock& lock) const = 0; |
| 77 | |
| 78 | // Report resource usage metrics to the given isolate metrics object. |
| 79 | virtual void reportMetrics(IsolateObserver& isolateMetrics) const = 0; |
| 80 | |
| 81 | // Called when performing a crypto key derivation function (like pbkdf2) to determine if |
| 82 | // if the requested number of iterations is acceptable. If kj::none is returned, the |
| 83 | // number of iterations requested is acceptable. If a number is returned, the requested |
| 84 | // iterations is unacceptable and the return value specifies the maximum. |
| 85 | virtual kj::Maybe<size_t> checkPbkdfIterations(jsg::Lock& js, size_t iterations) const { |
| 86 | // By default, historically we've limited this to 100,000 iterations max. We'll set |
| 87 | // that as the default for now. To set a default of no-limit, this would be changed |
| 88 | // to return kj::none. Note, this current default limit is *WAY* below the recommended |
| 89 | // minimum iterations for pbkdf2. |
| 90 | // TODO(maybe): We might consider emitting a warning if the number of iterations is |
| 91 | // too low to be safe. |
| 92 | if (iterations > DEFAULT_MAX_PBKDF2_ITERATIONS) return DEFAULT_MAX_PBKDF2_ITERATIONS; |
| 93 | return kj::none; |
| 94 | } |
| 95 | |
| 96 | // Called when a Blob is being created to determine the maximum allowed size of the Blob. |
| 97 | virtual size_t getBlobSizeLimit() const { |
| 98 | return 128 * 1024 * 1024; // 128 MB |
| 99 | } |
| 100 | |
| 101 | virtual bool hasExcessivelyExceededHeapLimit() const = 0; |
| 102 | |
| 103 | // Returns the TrackedWasmInstanceList for this isolate. Subclasses own the list and provide |
| 104 | // it here. The returned object provides lock-guarded mutation methods and a read-only accessor |
| 105 | // for signal-handler use. |
| 106 | virtual const TrackedWasmInstanceList& getTrackedWasmInstances() const = 0; |
| 107 | |
| 108 | // Inserts a custom mark event named `name` into this isolate's perf event data stream. At |
| 109 | // present, this is only implemented internally. Call this function from various APIs to be able |
| 110 | // to correlate perf event data with usage of those APIs. |
| 111 | // |
| 112 | // TODO(cleanup): This isn't strictly related to limit enforcement, so it's a bit odd here. It's |
| 113 | // observability-related. However, our internal perf event observability is fairly tightly |
| 114 | // coupled with our CPU time limiting system, so adding this function here is a path of least |
| 115 | // resistance. |
| 116 | virtual void markPerfEvent(kj::LiteralStringConst name) const {}; |
| 117 | }; |
| 118 | |
| 119 | // Abstract interface that enforces resource limits on a IoContext. |
| 120 | class LimitEnforcer { |
| 121 | public: |
| 122 | // Called just after taking the isolate lock, before executing JavaScript code, to enforce |
| 123 | // limits on that code execution, particularly the CPU limit. The returned `Own<void>` should |
| 124 | // be dropped when JavaScript is done, before unlocking the isolate. |
| 125 | virtual kj::Own<void> enterJs(jsg::Lock& lock, IoContext& context) = 0; |
| 126 | |
| 127 | // Called on each new event delivered that should cause an actor's resource limits to be |
| 128 | // "topped up". This method does nothing if the IoContext is not an actor. Note that this must |
| 129 | // not be called while in a JS scope, i.e. when `enterJs()` has been called and the returned |
| 130 | // object not yet dropped. |
| 131 | virtual void topUpActor() = 0; |
| 132 | // TODO(cleanup): This is called in WebSocket and JsRpcTargetBase when receiving an event, but |
| 133 | // should we do something more generic like use a membrane to detect any incoming RPC call? |
| 134 | |
| 135 | // Called before starting a new subrequest. Throws a JSG exception if the limit has been |
| 136 | // reached. |
| 137 | // |
| 138 | // `isInHouse` is true for types of subrequests which we need to be "in house" (i.e. to another |
| 139 | // Cloudflare service, like Workers KV) and thus should not be subject to the same limits as |
| 140 | // external subrequests. |
| 141 | virtual void newSubrequest(bool isInHouse) = 0; |
| 142 | |
| 143 | enum class KvOpType { GET, GET_WITH, PUT, LIST, DELETE, GET_BULK }; |
| 144 | // Called before starting a KV operation. Throws a JSG exception if the operation should be |
| 145 | // blocked due to exceeding limits, such as the free tier daily operation limit. |
| 146 | virtual void newKvRequest(KvOpType op) = 0; |
| 147 | |
| 148 | // Called before starting an attempt to write to the Analytics Engine. Throws |
| 149 | // a JSG exception if the operation should be blocked due to exceeding limits. |
| 150 | virtual void newAnalyticsEngineRequest() = 0; |
| 151 | |
| 152 | // Applies a time limit to draining a request (i.e. waiting for `waitUntil()`s after the |
| 153 | // response has been sent). Returns a promise that will resolve (without error) when the time |
| 154 | // limit has expired. This should be joined with the drain task. |
| 155 | // |
| 156 | // This should not be called for actors, which are evicted when the supervisor decides to |
| 157 | // evict them, not on a timeout basis. |
| 158 | virtual kj::Promise<void> limitDrain() = 0; |
| 159 | |
| 160 | // Like limitDrain() but applies a time limit to scheduled event processing. |
| 161 | virtual kj::Promise<void> limitScheduled() = 0; |
| 162 | |
| 163 | // Like limitDrain() and limitScheduled() but applies a time limit to alarm event processing. |
| 164 | virtual kj::Duration getAlarmLimit() = 0; |
| 165 | |
| 166 | // Gets a byte size limit to apply to operations that will buffer a possibly large amount of |
| 167 | // data in C++ memory, such as reading an entire HTTP response into an `ArrayBuffer`. |
| 168 | virtual size_t getBufferingLimit() = 0; |
| 169 | |
| 170 | // If a limit has been exceeded which prevents further JavaScript execution, such as the CPU or |
| 171 | // memory limit, returns a request status code indicating which one. Returns null if no limits |
| 172 | // are exceeded. |
| 173 | virtual kj::Maybe<EventOutcome> getLimitsExceeded() = 0; |
| 174 | |
| 175 | // Returns a promise that will reject if and when a limit is exceeded that prevents further |
| 176 | // JavaScript execution, such as the CPU or memory limit. |
| 177 | virtual kj::Promise<void> onLimitsExceeded() = 0; |
| 178 | // Sets a callback to call when the cpu limit is nearly exceeded. The callback must be signal safe |
| 179 | // and cannot take the isolate lock. |
| 180 | virtual void setCpuLimitNearlyExceededCallback(kj::Function<void(void)>) = 0; |
| 181 | |
| 182 | // Throws an exception if a limit has already been exceeded which prevents further JavaScript |
| 183 | // execution, such as the CPU or memory limit. |
| 184 | virtual void requireLimitsNotExceeded() = 0; |
| 185 | |
| 186 | // Report resource usage metrics to the given request metrics object. |
| 187 | virtual void reportMetrics(RequestObserver& requestMetrics) = 0; |
| 188 | |
| 189 | // Only used downstream for internal metrics. |
| 190 | virtual kj::Duration consumeTimeElapsedForPeriodicLogging() = 0; |
| 191 | |
| 192 | // Only used for internal metrics. |
| 193 | virtual size_t getSqliteMemoryUsage() const = 0; |
| 194 | }; |
| 195 | |
| 196 | } // namespace workerd |