3pacccccc opened a new pull request, #23481:
URL: https://github.com/apache/kafka/pull/23481
## Problem
`Utils.loadClass()` uses the thread context class loader (TCCL) whenever
it is non-null. If that loader cannot find a configured class, the
lookup fails even when Kafka's defining class loader can load it.
For example, this string-valued producer configuration:
```properties
key.serializer=org.apache.kafka.common.serialization.StringSerializer
```
can fail before the producer connects to a broker:
```text
ConfigException: Invalid value
org.apache.kafka.common.serialization.StringSerializer for configuration
key.serializer: Class
org.apache.kafka.common.serialization.StringSerializer
could not be found.
```
## Real-world trigger
The issue was observed when the first `KafkaTemplate.send()` call ran in
an asynchronous completion callback:
```java
CompletableFuture
.supplyAsync(() -> handleMessage(message), applicationExecutor)
.whenCompleteAsync((result, exception) ->
kafkaTemplate.send(topic, key, serialize(result)))
.exceptionally(exception -> null);
```
The first stage uses an explicit application executor. However,
`whenCompleteAsync()` has no executor argument, so its callback uses the
default asynchronous execution facility, normally
`ForkJoinPool.commonPool()`.
The producer is created lazily by `KafkaTemplate`. If the callback makes
the first send, Kafka parses the serializer class name on a common-pool
worker. In a Spring Boot executable JAR, that worker's TCCL may be
unable to see classes loaded from `BOOT-INF/lib`, while Kafka's defining
class loader can load `StringSerializer`.
The resulting path is:
```text
CompletableFuture.whenCompleteAsync
-> ForkJoinPool.commonPool worker
-> KafkaTemplate.send
-> DefaultKafkaProducerFactory.createProducer
-> KafkaProducer constructor
-> ConfigDef.parseType(Type.CLASS)
-> Utils.loadClass
-> ClassNotFoundException
```
The trailing `exceptionally()` may consume the exception, but it does
not cause the class-loading failure.
The original failure was observed with `kafka-clients` 3.0.0. The
class-loading behavior and the minimal reproduction are also present on
Kafka trunk at commit `56baca2b65251318a17154034a638b6f37654c26`.
## Minimal reproduction
The regression is isolated without Spring Boot or a broker:
1. Confirm that Kafka's defining class loader can load
`StringSerializer`.
2. Install a non-null TCCL that deliberately rejects only the
`StringSerializer` class name.
3. Parse that class name through `ConfigDef.parseType(..., Type.CLASS)`.
4. Restore the original TCCL in a `finally` block.
```java
ClassLoader original = Thread.currentThread().getContextClassLoader();
String className = StringSerializer.class.getName();
ClassLoader rejectingLoader = new ClassLoader(original) { @Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (className.equals(name)) { throw new
ClassNotFoundException(name); } return
super.loadClass(name); } };
try {
Thread.currentThread().setContextClassLoader(rejectingLoader);
ConfigDef.parseType("key.serializer", className,
ConfigDef.Type.CLASS);
} finally {
Thread.currentThread().setContextClassLoader(original);
}
```
Before this change, step 3 throws `ConfigException`, wrapping a
`ClassNotFoundException`. The class remains loadable through
`Utils.class.getClassLoader()`.
## Change
Update `Utils.loadClass()` to:
1. Try the TCCL first, preserving its precedence and alias support.
2. If the initial lookup throws `ClassNotFoundException`, retry with
Kafka's defining class loader when it is non-null and different.
3. Initialize the resolved class using the loader that found it.
If both lookups fail, the second exception retains the first as a
suppressed exception. Initialization, linkage, and type-check failures
do not trigger the fallback.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]