opensearch-project / opensearch-project/data-prepper
[BUG] KafkaSink.doOutput can return while holding reentrantLock, and calls prepareTopicAndSchema() on every batch
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 374
- Forks
- 354
- Avg merge
- 3d 18h
- Merged PRs (30d)
- 8
Description
Describe the bug
Two lifecycle problems in KafkaSink.doOutput, both found by inspection while
patching . Neither is fixed by that patch.
1. doOutput can return while holding the lock.
reentrantLock.lock();
if (records.isEmpty()) {
return; // never unlocks
}
try {
...
} catch (Exception e) {
LOG.error("Failed to setup the Kafka sink Plugin.", e);
throw new RuntimeException(e.getMessage());
}
reentrantLock.unlock();
The empty-records early return never unlocks. ReentrantLock is reentrant per
thread, so a sink whose doOutput is always invoked from the same thread merely
accumulates hold count and appears to work. A sink invoked from a different
thread than the one that leaked the hold blocks forever. doOutput is called
from Pipeline.publishToSinks via sinkExecutorService, so the calling thread
is not guaranteed to be stable.
The unlock() is also not in a finally, so the exception path leaks the hold
as well — including the RuntimeException rethrown two lines above it.
2. prepareTopicAndSchema() runs on every doOutput. Upstream's own comment
says as much:
// TODO: Looks like this call to prepareTopicAndSchema is unnecessary as it is
// done in createProducer().
prepareTopicAndSchema();
With create_topic: true this constructs a TopicService and an admin client
per batch, then closes them — the same create-per-call shape as , and
per-batch admin client churn against the broker.
To Reproduce
Steps to reproduce the behavior:
For the lock (code inspection; the concrete failure needs doOutput to be
dispatched across threads):
- Read
KafkaSink.doOutput— therecords.isEmpty()branch returns between
lock()andunlock(). - Configure a
kafkasink on a pipeline whose buffer read can return an empty
collection, so the early return is taken. - Have
doOutputsubsequently invoked from a different thread of
sinkExecutorServicethan the one that took the early return. - That call blocks indefinitely on
reentrantLock.lock().
For the admin client churn:
- Configure a
kafkasink withtopic.create_topic: true. - Send sustained traffic.
- Observe a
TopicServiceand KafkaAdminClientconstructed and closed once
per batch rather than once per sink.
Expected behavior
doOutput should release reentrantLock on every exit path — unlock() in a
finally, or lock() after the empty check. prepareTopicAndSchema() should
run once during initialisation, as the existing TODO suggests.
Screenshots
N/A.
Environment (please complete the following information):
- OS: Ubuntu 24.04
- Version: OpenSearch Data Prepper 2.16.0 (
docker.io/opensearchproject/data-prepper:2.16.0). Also verified present onmainat commit7222e0d0f. - Kafka:
docker.io/apache/kafka:4.3.1, KRaft, PLAINTEXT listener
Additional context
We have not observed the lock deadlock in production — our sinks appear to be
called from a stable thread, which masks it. Reporting it because the current
code is correct only by accident of the caller's threading, and #7131 moves
producer setup out of doOutput, which is a natural moment to also move
prepareTopicAndSchema() and tidy the lock.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read KafkaSink.doOutput first, then trace Pipeline.publishToSinks and sinkExecutorService to understand lifecycle and thread ownership. Inspect the initialization path around createProducer() and prepareTopicAndSchema(). Done means every doOutput exit path releases the lock and topic/schema preparation is not repeated for each batch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kafka
- Domain
- backend, stream-processing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100