xushan12138 opened a new issue, #4797:
URL: https://github.com/apache/bookkeeper/issues/4797

   **BUG REPORT**
   
   ***Describe the bug***
   
   The `BookKeeper` client constructor in
   
`bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java`
   allocates several heavy resources sequentially (worker pools, schedulers,
   event loop group, request timer, bookie client, metadata driver, ledger
   manager, bookie info reader, etc.). If any step after the early allocations
   throws — for example `MetadataClientDriver` initialization fails, or
   `LedgerManagerFactory#newLedgerManager()` throws — the already-created
   resources are NOT released, because the constructor never returns an
   instance and `close()` is therefore unreachable to the caller.
   
   The leaked resources include:
   - `scheduler` (`OrderedScheduler`) — 1 leaked thread
   - `highPriorityTaskExecutor` (`OrderedScheduler`) — 1 leaked thread
   - `mainWorkerPool` (`OrderedExecutor`) — `numWorkerThreads` leaked threads
   - `bookieClient` — leaked Netty channels / sockets to bookies
   - `metadataDriver` — leaked ZK / metadata-store session
   - `requestTimer` (when `ownTimer == true`) — 1 leaked `HashedWheelTimer` 
thread
   - `eventLoopGroup` (when `ownEventLoopGroup == true`) — leaked Netty I/O 
threads
   - `bookieInfoScheduler` (when disk-weight placement enabled) — 1 leaked 
thread
   
   For long-running processes that retry client creation on transient metadata
   failures (e.g. ZK timeouts at startup), every failed `build()` accumulates
   threads / sockets / file descriptors until OOM or FD exhaustion.
   
   ***To Reproduce***
   
   Steps to reproduce the behavior:
   
   1. Start a BookKeeper cluster normally.
   2. Configure a client with a metadata service URI that will reject the
      connection AFTER the worker pools and bookie client have been constructed
      (e.g. point `metadataServiceUri` at an unreachable ZK address such as
      `zk+null://127.0.0.1:1/ledgers`, or at a ZK ensemble that accepts TCP
      but rejects `getLedgerManagerFactory`).
   3. In a loop, call:
   `java try { BookKeeper.forConfig(conf).build(); } catch (Exception ignore) { 
/* expected */ }`
   100 iterations.
   5. Run `jstack <pid>` and observe that   `BookKeeperClientScheduler`, 
`BookKeeperClientWorker-*`,
      `BookKeeperHighPriorityThread` (and on disk-weight setups
      `BKClientMetaDataPollScheduler-*`) thread counts — and bookie / metadata
      sockets — grow monotonically with every failed `build()`.
   
   ***Expected behavior***
   
   If the constructor throws, all resources that were already created inside it
   must be released before the exception propagates to the caller. After a
   failed `build()`, the JVM should retain zero client-owned threads and zero
   client-owned sockets attributable to that attempt.
   
   **Proposed fix.**
   
   1. Wrap the constructor body in `try { ... } finally { ... }`, using a
      local `boolean initialized = false` set to `true` only after the last
      initialization step succeeds. In `finally`, call `close()` when
      `!initialized`.
   2. Make `close()` safe on a partially initialized instance by null-guarding
      every field dereference. The existing `closed` flag plus `closeLock`
      already make `close()` idempotent, so this also hardens the double-close
      path.


-- 
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]

Reply via email to