https://bz.apache.org/bugzilla/show_bug.cgi?id=70305

            Bug ID: 70305
           Summary: FFM OpenSSLEngine creates an upcall stub per engine,
                    causing per-connection hidden-class generation and GC
                    degradation
           Product: Tomcat 10
           Version: 10.1.61
          Hardware: All
                OS: All
            Status: NEW
          Severity: normal
          Priority: P2
         Component: Connectors
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: ------

Created attachment 40243
  --> https://bz.apache.org/bugzilla/attachment.cgi?id=40243&action=edit
Test class to reproduce

The OpenSSLEngine constructor allocates a new upcall stub for the info callback
on every engine, i.e. on every TLS connection:

    engineArena = Arena.ofAuto();
    ...
    SSL_set_info_callback(ssl, SSL_set_info_callback$cb.allocate(new
InfoCallback(), engineArena));

Each stub wraps its own bound MethodHandle. The JDK's upcall path invokes that
handle through an exact invoker, so after a handle has been called
MethodHandle.CUSTOMIZE_THRESHOLD (127) times, the JVM compiles a LambdaForm
customized for that specific handle instance, which defines a new hidden class.
OpenSSL calls the info callback many times per connection (every handshake
state
change and alert), so long-lived or busy connections cross the threshold, and
each one leaves behind its own hidden class. Those classes can only be
reclaimed
by class unloading after the engine's auto arena is collected.

Under sustained traffic this produces continuous class churn. 

Standalone reproducer:
Attached is a program that mirrors what OpenSSLEngine does: one upcall stub per
"engine", each called N times from native code. Passing "shared" uses one stub
for all engines instead.

  javac UpcallInvoke.java
  java --enable-native-access=ALL-UNNAMED UpcallInvoke per 200
  java --enable-native-access=ALL-UNNAMED UpcallInvoke shared 200

Results on JDK 25:

  stub per engine, 2000 engines x 10 callbacks:  classes loaded=85
  stub per engine, 2000 engines x 100 callbacks: classes loaded=85
  stub per engine, 2000 engines x 200 callbacks: classes loaded=4086
  shared stub,     2000 engines x 200 callbacks: classes loaded=12

Possible fix:
InfoCallback holds no per-engine state: it looks up the EngineState by the SSL*
address in the static `states` map. A single stub can therefore serve every
engine. For example, allocate it once in a static field (Arena.global()), or
once
per OpenSSLContext in contextArena alongside the existing context-level
callbacks, and pass it to SSL_set_info_callback in the constructor.

-- 
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to