Skip to content

feat: @ExternalType adapter generator for provided-style extensions#831

Open
jbachorik wants to merge 14 commits intojb/configurationsfrom
jb/external-type-adapter
Open

feat: @ExternalType adapter generator for provided-style extensions#831
jbachorik wants to merge 14 commits intojb/configurationsfrom
jb/external-type-adapter

Conversation

@jbachorik
Copy link
Copy Markdown
Collaborator

@jbachorik jbachorik commented Apr 24, 2026

Stacked on top of #791. This PR targets the jb/configurations branch so it shows only the incremental diff. Once #791 merges, this can be rebased onto develop.

Rationale

Provided-style extension impls (introduced in #791) currently reach into application types via hand-written MethodHandleCache adapters. That works but has three ergonomic costs:

  • String method names. mh.findVirtual(cls, "jobId", int.class) is not refactor-safe and IDE tooling can't check it.
  • Eager-resolution foot-gun. static final MethodHandle fields fail extension init if the target class isn't yet visible on TCCL.
  • Boilerplate per call site. Every reflective access expands into 5+ lines of try/catch, type wiring, and cache plumbing.

Solution

@ExternalType("com.example.app.Real") on an interface + a build-time annotation processor → generated $Ext adapter with typed static dispatchers, per-method lazy volatile MethodHandle resolution, and no cache lookup on the warm path.

Quick Start

Declare the interface in the extension's api source set:

@ExternalType("org.apache.spark.scheduler.SparkListenerJobStart")
public interface JobStart {
  int jobId();
  long time();
}

Use the generated adapter from the impl:

int id = JobStart$Ext.jobId(event);
long ts = JobStart$Ext.time(event);

The org.openjdk.btrace.extension Gradle plugin auto-registers the processor on the api source set — no extra wiring needed.

See the migrated btrace-spark example (btrace-extensions/examples/btrace-spark/src/impl/java/.../SparkApiImpl.java) for a before/after in a real extension.

Scope

In v1:

  • Virtual + static method adapters (@ExternalType.Static for statics)
  • Primitive and reference return / parameter types
  • Lazy resolution via receiver defining loader (virtual) or TCCL (static)
  • Generic types erased to raw forms for MethodType literals
  • Compile-time validation (non-interface targets, empty value → clear error)

Not in v1 (use ClassLoadingUtil / MethodHandleCache directly):

  • Field access
  • Constructors
  • instanceof / checkcast on external types
  • Chained @ExternalType references
  • Non-public method visibility

Test Plan

  • Unit: annotation retention + @Static targeting (ExternalTypeAnnotationTest)
  • Unit: processor detects annotated interfaces (ExternalTypeProcessorTest.generatesAdapterForAnnotatedInterface)
  • Unit: method dispatcher signatures (generatedAdapterContainsDispatchersForEachMethod)
  • Unit: virtual dispatcher shape (virtualDispatcherUsesLazyMethodHandle)
  • Unit: static dispatcher uses TCCL (staticDispatcherUsesTccl)
  • Runtime smoke: compile + load + invoke generated adapter (generatedAdapterInvokesRealMethod)
  • Unit: validation errors for invalid targets (rejectsAnnotationOnClass, rejectsEmptyValue)
  • Unit: parameterized types produce raw type literals (generatedAdapterHandlesParameterizedTypes)
  • Integration: in-tree Spark example migrated from MethodHandleCache to @ExternalType — builds end-to-end through the Gradle plugin

🤖 Generated with Claude Code


This change is Reviewable

jbachorik and others added 14 commits April 23, 2026 08:32
- integration-tests/.../RuntimeTest.java:802 — remove duplicate 1s
  Thread.sleep after the flush sleep (second block had no independent
  purpose and only added dead time).
- docs/README.md — normalize Markdown links to the actual on-disk
  PascalCase filenames (GettingStarted, OnelinerGuide, QuickReference,
  BTraceTutorial, Troubleshooting, FAQ) so they resolve on
  case-sensitive filesystems; fix the "pattern-1-method-entrye xit-timing"
  anchor typo to "pattern-1-method-entryexit-timing".
- btrace-client/.../Client.java — fail-fast with IllegalArgumentException
  when pass-through system-property values (btrace.feature.manifestLibs,
  btrace.system.appendJar, btrace.allowExternalLibs, btrace.test.skipLibs)
  contain ',' rather than letting the comma-delimited agentArgs silently
  split the value into stray args.

Co-Authored-By: muse <muse@noreply>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cessor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…apter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ethods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cation

Add RunnableResult + compileAndLoad() to CompileTestHarness so compiled
classes can be loaded and invoked; add generatedAdapterInvokesRealMethod
test that exercises the full findVirtual + MethodHandle.invoke pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… api source set

Wire btrace-extension-processor as an automatic apiAnnotationProcessor dependency
in BTraceExtensionPlugin so extension authors get adapter generation for free.
In-tree builds use the sibling project reference; external consumers resolve the
published Maven artifact by version.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atestSupported

Apply Types#erasure() to return and parameter types in buildSpec() so that
parameterized types like List<String> produce raw-type literals (List.class)
in generated adapter source instead of the invalid List<String>.class form.
Replace @SupportedSourceVersion(RELEASE_8) with getSupportedSourceVersion()
returning SourceVersion.latestSupported() to eliminate the javac warning on
JDK 11+. Adds a test covering both parameterized-type cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant