Skip to content

fix: stop the kafka watch thread when the client is closed - #170

Open
TaurMorchant wants to merge 3 commits into
mainfrom
fix/maas-client-watch-thread-leak
Open

fix: stop the kafka watch thread when the client is closed#170
TaurMorchant wants to merge 3 commits into
mainfrom
fix/maas-client-watch-thread-leak

Conversation

@TaurMorchant

@TaurMorchant TaurMorchant commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Why

KafkaMaaSClientImpl.close() did not stop the watchTopicCreate thread while topic listeners were still registered.
The leaked thread kept polling the agent with no delay between attempts, which flooded the MockServer shared by
KafkaMaaSClientImplTest until it stopped answering. That failed unrelated tests in the class and blew
maas-client/prepare.log up to 569 MB, breaking two consecutive lts/26.3 release runs
(31105532757,
31109752591).

Details and log evidence: #169.

What

  • KafkaMaaSClientImpl tracks shutdown in a volatile boolean closed, checks it in the watch loop, and returns
    before the thread parks in wait().
  • close() sets the flag; the existing interrupt() stays, since it is what aborts an in-flight 60 s long poll.
  • New KafkaMaaSClientCloseTest pins the invariant: once close() returns, the watch thread is gone.

The interrupt flag alone cannot drive the exit. The retry branch in HttpExecution.sendAndReceive() calls
Thread.sleep(), and Thread.sleep() clears the flag when it throws, so the watch loop never sees it. The client log
from a failing run shows the thread carrying on right after the interrupt:

15:27:08.436 WARN  [watchTopicCreate] HttpExecution - Error execute http request: interrupted, Retry 1 of 30
15:27:08.448 DEBUG [watchTopicCreate] HttpExecution - Send request: POST .../watch-create   <- keeps polling

The test deliberately does not use MockServer. It runs its own com.sun.net.httpserver.HttpServer stub that answers
the watch endpoint slowly and successfully, so a regression shows up as a dead-or-alive assertion on the thread rather
than as collateral damage to whichever test happens to run next.

How to verify

mvn -B -ntp -f maas-client/client/pom.xml test

Result on this branch:

Tests run: 102, Failures: 0, Errors: 0, Skipped: 2
BUILD SUCCESS

KafkaMaaSClientCloseTest was run three times against main before the fix and failed all three times with the same
assertion, then three times after the fix and passed all three.

Across the whole module suite the log now contains 42 [watchTopicCreate] lines. The failing release run produced
roughly 7.6 million.

KafkaMaaSClientImpl.close() interrupted the watchTopicCreate thread, but
the thread only left its loop on an InterruptedException around wait().
While topic listeners were still registered, it sat in the inner polling
loop, which swallows every exception and retries with no delay, so the
thread outlived the client and kept hammering the agent.

In tests this leaked thread flooded the MockServer shared by
KafkaMaaSClientImplTest until it stopped responding, which failed
unrelated tests in the class and blew prepare.log up to 569 MB during
the monorepo release.

The interrupt flag alone cannot drive the exit: the retry branch in
HttpExecution calls Thread.sleep(), which clears the flag before the
watch loop sees it. Track shutdown in a dedicated volatile field
instead, and check it before the thread parks in wait().

KafkaMaaSClientCloseTest pins the invariant: once close() returns, the
watch thread is gone. It uses its own lightweight HTTP stub rather than
MockServer, so a regression cannot spill over into neighboring tests.
@TaurMorchant
TaurMorchant requested a review from lis0x90 as a code owner August 7, 2026 11:59
@github-actions github-actions Bot added the bug Something isn't working label Aug 7, 2026
JUnit builds a fresh instance per test method, so resetting the latches,
the flag and the thread reference in the setup method was dead code.
Initialize the fields inline instead.

Drop the cached thread pool: the stub serves one client, and a
user-supplied executor is not shut down by HttpServer.stop(), so it
leaked threads on every run.
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 7, 2026
@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Reliability Rating on New Code (required ≥ A)
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants