Skip to content

fix: exclude jdk.jfr classes from transformation to prevent JFR reentrancy corruption#821

Open
korniltsev-grafanista-yolo-vibecoder239 wants to merge 1 commit intoalibaba:masterfrom
korniltsev-grafanista-yolo-vibecoder239:fix/exclude-jfr-from-transform
Open

fix: exclude jdk.jfr classes from transformation to prevent JFR reentrancy corruption#821
korniltsev-grafanista-yolo-vibecoder239 wants to merge 1 commit intoalibaba:masterfrom
korniltsev-grafanista-yolo-vibecoder239:fix/exclude-jfr-from-transform

Conversation

@korniltsev-grafanista-yolo-vibecoder239
Copy link
Copy Markdown

@korniltsev-grafanista-yolo-vibecoder239 korniltsev-grafanista-yolo-vibecoder239 commented Apr 1, 2026

Summary

  • Exclude jdk.jfr package (and sub-packages) from TtlTransformer class transformation
  • Applied to both v3 (ttl-agent) and v2-compatible modules

Problem

When JFR (Java Flight Recorder) is active, TtlTransformer can cause reentrancy corruption in JFR's EventWriter, producing corrupted .jfr files with invalid event sizes.

The reentrancy chain:

  1. FileOutputStream.write() triggers a FileWriteEvent JFR event
  2. EventWriter.endEvent() calls Bits.putInt() (large-size path, events >= 128 bytes)
  3. Loading jdk.jfr.internal.Bits triggers ClassFileTransformer.transform()
  4. TtlTransformer intercepts, Javassist introspects the class via getDeclaredMethod()
  5. Javassist throws NotFoundException ("run(..) is not found in jdk.jfr.internal.Bits")
  6. The exception + object allocation trigger nested JFR events (JavaExceptionThrow, ObjectAllocationOutsideTLAB)
  7. These nested events write into the same thread-local EventWriter buffer that is still finalizing the outer FileWriteEvent
  8. The outer event's size header gets corrupted → invalid .jfr file

Stack trace from corrupted JFR file

The following jdk.JavaExceptionThrow event was found embedded inside a corrupted event, showing TTL's transformer being invoked during JFR event writing:

jdk.JavaExceptionThrow

  stackTrace:
    [0] java/lang/Throwable.<init>:286
    [1] java/lang/Exception.<init>:67
    [2] com/alibaba/ttl/threadpool/agent/internal/javassist/NotFoundException.<init>:27
    [3] com/alibaba/ttl/threadpool/agent/internal/javassist/CtClassType.getDeclaredMethod:1376
    [4] com/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.doTransform:45
    [5] com/alibaba/ttl/threadpool/agent/TtlTransformer.transform:80
    [6] java/lang/instrument/ClassFileTransformer.transform:257
    [7] sun/instrument/TransformerManager.transform:188
    [8] sun/instrument/InstrumentationImpl.transform:594
    [9] jdk/jfr/internal/event/EventWriter.endEvent:262
    [10] jdk/internal/event/FileWriteEvent.commit:4294967295
    [11] jdk/internal/event/FileWriteEvent.offer:69
    [12] java/io/FileOutputStream.traceWriteBytes:314
    [13] java/io/FileOutputStream.write:349
    [14] sun/nio/cs/StreamEncoder.writeBytes:220
    [15] sun/nio/cs/StreamEncoder.implClose:340
    [16] sun/nio/cs/StreamEncoder.close:159
    [17] java/io/OutputStreamWriter.close:253
    [18] <REDACTED>
    [19] <REDACTED>
    [20] <REDACTED>
    [21] <REDACTED>
    [22] <REDACTED>
    [23] <REDACTED>
    [24] com/alibaba/ttl/TtlRunnable.run:60
    [25] java/util/concurrent/Executors$RunnableAdapter.call:545
    [26] java/util/concurrent/FutureTask.runAndReset:369
    [27] java/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask.run:310
    [28] java/util/concurrent/ThreadPoolExecutor.runWorker:1090
    [29] java/util/concurrent/ThreadPoolExecutor$Worker.run:614
    [30] java/lang/Thread.runWith:1487
    [31] java/lang/Thread.run:1474

  message: "run(..) is not found in jdk.jfr.internal.Bits"
  thrownClass: com/alibaba/ttl/threadpool/agent/internal/javassist/NotFoundException

After this event, an ObjectAllocationOutsideTLAB event is also embedded in the remaining bytes, followed by corruption:

pos=2989693 size=9223372036854775808 err=invalid event size 9223372036854775808 at position 2989693

Fix

Add jdk.jfr to the existing package exclusion list in TtlTransformer.transform(), alongside the existing com.alibaba.ttl and java.lang exclusions.

This is safe because TTL only transforms executor/thread-pool classes — JFR classes would never be transformed anyway. The filter simply avoids the Javassist introspection side effects that trigger the reentrancy.

Test plan

  • Existing tests should pass (no behavioral change for non-JFR classes)
  • Verified with a JFR recording that previously produced corrupted events — corruption no longer occurs with this fix

…rancy corruption

During JFR event writing (e.g. FileWriteEvent), EventWriter.endEvent() may
trigger class loading of jdk.jfr.internal.Bits on the large-size path.
TtlTransformer intercepts this class loading, and Javassist introspection
side effects (NotFoundException, object allocations) produce nested JFR
events that corrupt the in-progress event buffer, resulting in corrupted
JFR files with invalid event sizes.

Adding jdk.jfr to the package exclusion list prevents this reentrancy.
TTL would never transform JFR classes anyway (they are not executors or
thread pools), so this is a safe no-op filter.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 1, 2026

CLA assistant check
All committers have signed the CLA.

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