Skip to content
File

Blob: src/workerd/io/limit-enforcer.h

cpp197 lines
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 
18namespace workerd {
19class IsolateObserver;
20class RequestObserver;
21 
22struct ActorCacheSharedLruOptions;
23class IoContext;
24 
25namespace jsg {
26class Lock;
27} // namespace jsg
28 
29static 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.
34class 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.
120class 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