8000 fix(graalvm): pre-generate WebSessionAvailable mixin to fix native image crash by kawasima · Pull Request #154 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

fix(graalvm): pre-generate WebSessionAvailable mixin to fix native image crash - #154

Merged
kawasima merged 3 commits into
developfrom
feature/graalvm-session-mixin-fix
Apr 4, 2026
Merged

kawasima merged 3 commits into
developfrom
feature/graalvm-session-mixin-fix

Conversation

@kawasima
@kawasima kawasima commented Apr 4, 2026
Copy link
Copy Markdown
Collaborator

Summary

  • SessionMiddleware calls MixinUtils.mixin(request, WebSessionAvailable.class) at runtime, which internally calls Proxy.newProxyInstance() — forbidden in GraalVM native image
  • Root cause: NativeApplicationFactory.buildApp() did not include SessionMiddleware, so WebSessionAvailable was never pre-generated as a $Mixin subclass at build time
  • Fix: add SessionMiddleware to the build-time middleware stack so WebApplication.buildRequestFactory() includes WebSessionAvailable in MixinUtils.createFactory(), making the mixin() call a noop at runtime

Changes

  • NativeApplicationFactory: merged duplicate buildApp() / create() into a single buildApp(ComponentInjector) to eliminate double maintenance; buildApp() (no-arg) and create() both delegate to it
  • SessionController: new demo endpoint GET /session — per-session visit counter, demonstrates session usage in native image
  • KotowariFeatureTest: new test verifying that WebSessionAvailable is included in the pre-generated $Mixin class when SessionMiddleware is in the stack

Test plan

  • mvn test -pl kotowari-graalvm -am -DexcludedGroups=integration passes (11 tests including new buildRequestFactory_pregeneratesMixinForSessionMiddleware)
  • (if GraalVM available) mvn -pl kotowari-graalvm-example -Pnative package builds without error
  • curl -c /tmp/s.txt http://localhost:3000/session → visits=1
  • curl -b /tmp/s.txt -c /tmp/s.txt http://localhost:3000/session → visits=2

Closes #148

🤖 Generated with Claude Code

… support

Add SessionMiddleware to NativeApplicationFactory.buildApp() so that
WebSessionAvailable is included in the $Mixin subclass generated at
build time. Without this, SessionMiddleware.handle() falls through to
Proxy.newProxyInstance() at runtime, which is forbidden in native image.

- Merge buildApp()/create() into a single buildApp(ComponentInjector)
  to eliminate duplicate middleware stack definitions (closes #148)
- Add GET /session endpoint with SessionController (visit counter demo)
- Add KotowariFeatureTest to verify WebSessionAvailable is pre-generated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a GraalVM native-image runtime crash caused by SessionMiddleware invoking MixinUtils.mixin(...) (which uses Proxy.newProxyInstance()) by ensuring the required WebSessionAvailable mixin class is pre-generated at build time.

Changes:

  • Add SessionMiddleware to the kotowari-graalvm-example middleware stack so WebApplication.createRequest() pre-generates the WebSessionAvailable $Mixin class.
  • Refactor NativeApplicationFactory to centralize stack construction in buildApp(ComponentInjector) and have both buildApp() and create() delegate to it.
  • Add a /session demo endpoint and a unit test asserting WebSessionAvailable mixin pre-generation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
kotowari-graalvm/src/test/java/kotowari/graalvm/KotowariFeatureTest.java Adds a test verifying mixin pre-generation for WebSessionAvailable when SessionMiddleware is used.
kotowari-graalvm-example/src/main/java/kotowari/example/graalvm/NativeApplicationFactory.java Ensures SessionMiddleware is included in the build-time middleware stack and consolidates app creation logic.
kotowari-graalvm-example/src/main/java/kotowari/example/graalvm/controller/SessionController.java Adds a simple per-session visit counter endpoint for native-image validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +237 to +250
app.createRequest();

// One of the generated classes must implement WebSessionAvailable
boolean found = false;
for (String className : MixinUtils.generatedClassBytes.keySet()) {
Class<?> cls = Class.forName(className);
if (WebSessionAvailable.class.isAssignableFrom(cls)) {
found = true;
break;
}
}
assertThat(found)
.as("Expected a $Mixin class implementing WebSessionAvailable to be pre-generated")
.isTrue();
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test scans the global MixinUtils.generatedClassBytes map and calls Class.forName on every entry. This can become brittle if other tests populate the map (false positives) or if any generated entry isn’t loadable in this test’s class loader. Consider asserting against the specific request returned by app.createRequest(): verify it is instanceof WebSessionAvailable, and then assert generatedClassBytes containsKey(request.getClass().getName()) (or only scan newly-added keys).

Suggested change
app.createRequest();
// One of the generated classes must implement WebSessionAvailable
boolean found = false;
for (String className : MixinUtils.generatedClassBytes.keySet()) {
Class<?> cls = Class.forName(className);
if (WebSessionAvailable.class.isAssignableFrom(cls)) {
found = true;
break;
}
}
assertThat(found)
.as("Expected a $Mixin class implementing WebSessionAvailable to be pre-generated")
.isTrue();
HttpRequest request = app.createRequest();
assertThat(request)
.as("Expected app.createRequest() to return a request implementing WebSessionAvailable")
.isInstanceOf(WebSessionAvailable.class);
assertThat(MixinUtils.generatedClassBytes)
.as("Expected the generated request mixin class to be pre-generated")
.containsKey(request.getClass().getName());

Copilot uses AI. Check for mistakes.
Routes routes = routes();

if (injector != null) {
// Register routes for KotowariFeature build-time discovery (runtime only)
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is contradictory: the code runs only when injector != null (runtime), but it says it’s for “KotowariFeature build-time discovery”. Either update the wording to reflect the actual behavior, or (if the intent really is build-time discovery via RouteRegistry.get()) move RouteRegistry.register(routes) out of the injector != null conditional.

Suggested change
// Register routes for KotowariFeature build-time discovery (runtime only)
// Register routes in the runtime registry when a real injector is available.

Copilot uses AI. Check for mistakes.
kawasima and others added 2 commits April 4, 2026 12:55
- KotowariFeatureTest: assert createRequest() result is instanceof
  WebSessionAvailable directly, instead of scanning generatedClassBytes
  (more focused, less fragile against static map state)
- NativeApplicationFactory: fix misleading comment on RouteRegistry.register()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop the MixinUtils.generatedClassBytes.containsKey() assertion from
KotowariFeatureTest — the instanceof check on the return value of
createRequest() is sufficient and does not depend on internal state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kawasima
kawasima merged commit e84c748 into develop Apr 4, 2026
@kawasima
kawasima deleted the feature/graalvm-session-mixin-fix branch April 4, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0