Skip to content

Commit d3c5a1a

Browse files
committed
fix: Pass ctx.props to default handlers
1 parent a0b8d14 commit d3c5a1a

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

src/workerd/api/global-scope.h

+26
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,32 @@ struct ExportedHandler {
382382
jsg::Optional<jsg::Ref<ExecutionContext>> getCtx() {
383383
return ctx.map([&](jsg::Ref<ExecutionContext>& p) { return p.addRef(); });
384384
}
385+
386+
private:
387+
template <typename T>
388+
static jsg::LenientOptional<T> mapAddRef(jsg::Lock& js, jsg::LenientOptional<T>& function) {
389+
return function.map([&](T& a) { return a.addRef(js); });
390+
}
391+
392+
public:
393+
ExportedHandler clone(jsg::Lock& js) {
394+
return ExportedHandler{
395+
.fetch{mapAddRef(js, fetch)},
396+
.tail{mapAddRef(js, tail)},
397+
.trace{mapAddRef(js, trace)},
398+
.tailStream{mapAddRef(js, tailStream)},
399+
.scheduled{mapAddRef(js, scheduled)},
400+
.alarm{mapAddRef(js, alarm)},
401+
.test{mapAddRef(js, test)},
402+
.webSocketMessage{mapAddRef(js, webSocketMessage)},
403+
.webSocketClose{mapAddRef(js, webSocketClose)},
404+
.webSocketError{mapAddRef(js, webSocketError)},
405+
.self{js.v8Isolate, v8::Object::New(js.v8Isolate)},
406+
.env{env.addRef(js)},
407+
.ctx{getCtx()},
408+
.missingSuperclass = missingSuperclass,
409+
};
410+
}
385411
};
386412

387413
// An approximation of Node.js setImmediate `Immediate` object.

src/workerd/io/compatibility-date.capnp

+6
Original file line numberDiff line numberDiff line change
@@ -692,4 +692,10 @@ struct CompatibilityFlags @0x8f8c1b68151b6cef {
692692
$experimental
693693
$neededByFl;
694694
# Enables cache settings specified request in fetch api cf object to override cache rules. (only for user owned or grey-clouded sites)
695+
696+
uniqueCtxPerInvocation @73: Bool
697+
$compatEnableFlag("unique_ctx_per_invocation")
698+
$compatDisableFlag("nonclass_entrypoint_reuses_ctx_accros_invocations")
699+
$compatEnableDate("2025-02-24");
700+
# Creates a unique ExportedHandler for each call to `export default` allows a unique ctx per invocation
695701
}

src/workerd/io/worker.c++

+9-5
Original file line numberDiff line numberDiff line change
@@ -1665,11 +1665,9 @@ Worker::Worker(kj::Own<const Script> scriptParam,
16651665
KJ_SWITCH_ONEOF(handler.value) {
16661666
KJ_CASE_ONEOF(obj, api::ExportedHandler) {
16671667
obj.env = lock.v8Ref(bindingsScope.As<v8::Value>());
1668-
// TODO(cleanup): Unfortunately, for non-class-based handlers, we have
1669-
// always created only a single `ctx` object and reused it for all
1670-
// requests. This is weird and obviously wrong but changing it probably
1671-
// requires a compat flag. Until then, connection properties will not be
1672-
// available for non-class handlers.
1668+
// Historically, non-class-based handlers reused the same ctx object for all requests.
1669+
// This was an accident, but some Workers depend on it.
1670+
// Newer worker with the unique_ctx_per_invocation will allocate a new ctx for every request.
16731671
obj.ctx = jsg::alloc<api::ExecutionContext>(lock);
16741672

16751673
impl->namedHandlers.insert(kj::mv(handler.name), kj::mv(obj));
@@ -1971,6 +1969,12 @@ kj::Maybe<kj::Own<api::ExportedHandler>> Worker::Lock::getExportedHandler(
19711969

19721970
kj::StringPtr n = name.orDefault("default"_kj);
19731971
KJ_IF_SOME(h, worker.impl->namedHandlers.find(n)) {
1972+
jsg::Lock& js = *this;
1973+
if (FeatureFlags::get(js).getUniqueCtxPerInvocation()) {
1974+
api::ExportedHandler constructedHandler = h.clone(js);
1975+
constructedHandler.ctx = jsg::alloc<api::ExecutionContext>(js, props.toJs(js));
1976+
return kj::heap(kj::mv(constructedHandler));
1977+
}
19741978
return fakeOwn(h);
19751979
} else KJ_IF_SOME(cls, worker.impl->statelessClasses.find(n)) {
19761980
jsg::Lock& js = *this;

0 commit comments

Comments
 (0)