Skip to content
Merged
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
32 changes: 32 additions & 0 deletions .ai/wheels/cross-engine-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ plugin.onPluginLoad(context);

**Why**: Adobe's application scope is implemented differently from a regular CFML struct. Function members get lost or throw errors during serialization.

### Application Scope Unreliable During onApplicationEnd() Teardown (Adobe CF 2023)

On Adobe CF 2023, `onApplicationEnd()` fires synchronously during `applicationStop()` teardown (triggered by a `?reload` restart or idle-timeout reclaim). Inside that teardown the live `application` scope is no longer reliable — bare `application.wo` can resolve against a stale/torn-down scope and land on a Java `String[]`, throwing `Element wo is undefined in a Java object of type class [Ljava.lang.String;` and erroring the whole site until a CF service restart.

The only dependable reference at shutdown is the passed-in `arguments.applicationScope` (already used by the `$wheelsBrowserLauncher` cleanup in the same handler). Route all `onApplicationEnd()` calls through it and guard with `StructKeyExists` so a partially reclaimed scope degrades to a no-op instead of a hard error. Lucee 6/7 and BoxLang are unaffected; this only manifests on Adobe CF during real teardown (issue #3379).

```cfm
// WRONG — bare application.wo breaks during Adobe CF applicationStop() teardown
public void function onApplicationEnd(struct ApplicationScope) {
application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}

// RIGHT — use the passed-in scope, the only reliable reference at shutdown
public void function onApplicationEnd(struct ApplicationScope) {
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}
```

**Existing apps**: apply this same change to `public/Application.cfc` — the CLI template (`wheels new`) and the demo app were fixed in Wheels 4.x (#3380).

### Closure `this` Captures Declaring Scope

CFML closures bind `this` to the component where they are DEFINED, not where they are ASSIGNED. This trips up test code that dynamically adds methods.
Expand Down
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,34 @@ The framework must run on Lucee 5/6/7, Adobe CF 2018/2021/2023/2025, and BoxLang
FileWrite(path, payload);
```

19. **The `application` scope is unreliable inside `onApplicationEnd()` on Adobe CF 2023.** During `applicationStop()` teardown (triggered by a `?reload` restart or idle-timeout reclaim), bare `application.wo` can resolve against a stale/torn-down scope and land on a Java `String[]`, throwing `Element wo is undefined in a Java object of type class [Ljava.lang.String;` and erroring the whole site until a CF service restart. The only dependable reference at shutdown is the passed-in `arguments.applicationScope`. Always route `onApplicationEnd()` through it and guard with `StructKeyExists` so a partially reclaimed scope degrades to a no-op (#3379). Lucee 6/7 and BoxLang are unaffected; only Adobe CF exhibits this during real teardown.

```cfm
// WRONG — bare application.wo breaks on Adobe CF during applicationStop() teardown
public void function onApplicationEnd(struct ApplicationScope) {
application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}

// RIGHT — route through the passed-in scope and guard before dereferencing
public void function onApplicationEnd(struct ApplicationScope) {
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}
```

The CLI template (`wheels new`) and the demo app were fixed in #3380. **Existing apps must apply the same change to their `public/Application.cfc`.**

Verify Adobe CF fixes locally before pushing — don't iterate via CI:
```bash
curl -s "http://localhost:62023/wheels/core/tests?db=mysql&format=json" | \
Expand Down
1 change: 1 addition & 0 deletions changelog.d/3379-onapplicationend-scope-guard.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- The application template's `onApplicationEnd()` handler now invokes the Wheels global through the passed-in `arguments.applicationScope.wo` (guarded with `StructKeyExists`) instead of the live `application.wo` scope. On Adobe ColdFusion 2023 the `application` scope is unreliable during `applicationStop()` teardown, so bare `application.wo` could resolve to a stale Java `String[]` and throw `Element wo is undefined in a Java object of type class [Ljava.lang.String;`, erroring the whole site until a CF service restart. The same fix is applied to the repo's demo app and the bundled example apps; existing apps should apply the same edit to their `public/Application.cfc` (#3379)
24 changes: 20 additions & 4 deletions cli/lucli/templates/app/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,26 @@ component output="false" {
}
}

application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
// Run the framework's onApplicationEnd event through the Wheels global.
// During applicationStop() teardown on Adobe CF 2023 the LIVE `application`
// scope is unreliable — bare `application.wo` can resolve against a
// stale/torn-down scope and land on a Java String[], throwing "Element wo
// is undefined in a Java object of type class [Ljava.lang.String;" and
// erroring the whole site until a CF service restart (issue #3379). The
// passed-in arguments.applicationScope is the only dependable reference at
// shutdown (it is what the $wheelsBrowserLauncher cleanup above uses), so
// route the call through it and guard so a partially reclaimed scope
// degrades to a no-op instead of a hard error.
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}

public void function onSessionStart() {
Expand Down
20 changes: 16 additions & 4 deletions examples/starter-app/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,22 @@ component output="false" {
}

public void function onApplicationEnd( struct ApplicationScope ) {
application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
// During applicationStop() teardown on Adobe CF 2023 the LIVE `application`
// scope is unreliable — bare `application.wo` can resolve against a
// stale/torn-down scope and land on a Java String[], throwing "Element wo
// is undefined in a Java object of type class [Ljava.lang.String;" (issue
// #3379). The passed-in arguments.applicationScope is the only dependable
// reference at shutdown, so route the call through it and guard it.
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}

public void function onSessionStart() {
Expand Down
20 changes: 16 additions & 4 deletions examples/tweet/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,22 @@ component output="false" {
}

public void function onApplicationEnd( struct ApplicationScope ) {
application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
// During applicationStop() teardown on Adobe CF 2023 the LIVE `application`
// scope is unreliable — bare `application.wo` can resolve against a
// stale/torn-down scope and land on a Java String[], throwing "Element wo
// is undefined in a Java object of type class [Ljava.lang.String;" (issue
// #3379). The passed-in arguments.applicationScope is the only dependable
// reference at shutdown, so route the call through it and guard it.
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}

public void function onSessionStart() {
Expand Down
24 changes: 20 additions & 4 deletions public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,26 @@ component output="false" {
}
}

application.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
// Run the framework's onApplicationEnd event through the Wheels global.
// During applicationStop() teardown on Adobe CF 2023 the LIVE `application`
// scope is unreliable — bare `application.wo` can resolve against a
// stale/torn-down scope and land on a Java String[], throwing "Element wo
// is undefined in a Java object of type class [Ljava.lang.String;" and
// erroring the whole site until a CF service restart (issue #3379). The
// passed-in arguments.applicationScope is the only dependable reference at
// shutdown (it is what the $wheelsBrowserLauncher cleanup above uses), so
// route the call through it and guard so a partially reclaimed scope
// degrades to a no-op instead of a hard error.
if (
StructKeyExists(arguments.applicationScope, "wo")
&& StructKeyExists(arguments.applicationScope, "wheels")
&& StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {
arguments.applicationScope.wo.$include(
template = "../../#arguments.applicationScope.wheels.eventPath#/onapplicationend.cfm",
argumentCollection = arguments
);
}
}

public void function onSessionStart() {
Expand Down
Loading
Loading