I went looking for Pulsar’s riskiest code and found it all in one place. ManagedCursorTest.java in the managed-ledger module holds all 5 of the top hotspots in apache/pulsar, so this is a file-level problem, not a single-function one. Anyone adding a test or changing cursor behavior there is working in a file where every top-ranked function is a ‘fire’ quadrant function, all last changed 6 days ago, with activity-weighted risk scores between 50.57 and 53.61. I analyzed Pulsar at 46,369 functions, 1,752 of them critical, and I would start with testScan (score 53.61, fan-out 69) and replayRunsOfClosePositions (fan-out 84), because they pair the widest coupling with recent changes.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
testScan | managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java | 53.6 | 7 | 2 | 69 |
testConcurrentPropertyOperationsThreadSafety | managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java | 53.2 | 10 | 4 | 43 |
replayRunsOfClosePositions | managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java | 51.6 | 12 | 3 | 84 |
assertHasBacklogMatchesBacklogCountsAcrossGeneratedOperationSequence | managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java | 51.0 | 4 | 6 | 22 |
testReplayAtReadPositionMovesReadPositionForwardOnce | managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java | 50.6 | 3 | 1 | 33 |
46,369 functions analyzed
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×3Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Exit Heavy×2Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Complex Branching×1Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×1Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
The distribution first. Of 46,369 functions, 7,041 land in the fire quadrant and 39,328 in watch, with nothing in debt or ok. That split says less about Pulsar than about the snapshot: nearly everything here was touched recently enough to count as active. So the ranking among fire functions is what matters, and here one file dominates it.
What the five functions share is not high cyclomatic complexity. Their complexity values run from 3 to 12, which is moderate. Fan-out (22 to 84) combined with recent change is what pushes them up. These are test methods that set up a ledger, open a cursor, add entries, and assert on several outcomes in one body. That is the shape the god_function and long_function patterns describe, and it is what I would expect from tests that exercise a stateful component end to end.
I also want to be careful about what the history data does and does not say. All five functions share the same file-level signals: 2 total commits in the file’s history, a bug-fix rate of 100%, no bug-linked commits, no reverts, 2 distinct authors in the last 90 days, and zero review-comment density. With only 2 commits behind it, a 100% bug-fix rate is a thin sample, not a trend, and nothing here shows that any of these tests is defective. I treat it as a mild reason to read recent changes closely, not as evidence of problems.
Every function below was touched once in the last year, 6 days before the snapshot. These are not long-running churn magnets; they are recently added or recently edited, which fits a file that is still growing.
testScan — ManagedCursorTest.java
This is the top-ranked function, with an activity-weighted risk score of 53.61. It is a data-provider-driven test that exercises the cursor’s scan operation across several outcomes: full completion, scanning from a mid-point position, user interruption, abort on a max-entries limit, abort on a timeout, a user callback that throws, and deleted entries.
A fan-out of 69 is the headline. Cyclomatic complexity is only 7 and nesting depth is 2, so the control flow is easy to follow; the cost is that one method asserts on many distinct behaviors in sequence. The exit_heavy pattern shows in the early return when there are no entries, and the god_function and long_function patterns reflect the sheer number of scenarios packed together. When it fails partway through, the stack trace is the only way to tell which scenario broke.
My recommendation: split it by outcome. Scan-from-start, scan-from-position, interruption, abort limits, callback exceptions, and deleted-entry handling each become their own test sharing a small helper for ledger and cursor setup. The timeout case already has a batch-size guard that carries over easily to its own test. Each piece would then have a fan-out well under 20.
testConcurrentPropertyOperationsThreadSafety — ManagedCursorTest.java
With a score of 53.25, this is a concurrency stress test: it spins up a 100-thread pool, submits 10,000 operations that randomly put, remove, or read cursor properties, and checks that no inconsistent state or unexpected exceptions appear. Cyclomatic complexity is 10 and nesting depth is 4, which comes from a switch inside a try block inside a lambda, plus a separate wait loop with its own try and catch.
I would flag that the test mixes three jobs: orchestrating the executor, defining the randomized workload, and verifying results. Intermittent concurrency failures are the hardest to debug, and the extra nesting makes it harder to tell whether a failure came from the workload or from the harness. The workload creates a fresh Random per task and the failure path calls fail() from inside worker threads, so I would move the workload into a named method or small class and keep the test body to setup, submit, wait, and assert. Behavior stays identical, and nesting depth should drop to around 2.
replayRunsOfClosePositions — ManagedCursorTest.java
This one has the highest fan-out in the set at 84 and the highest cyclomatic complexity at 12. It is a private helper, not a test method itself, parameterized by whether entries are partly cached and whether cache eviction is by expected read count. It builds a two-ledger layout of 600 entries, selects a specific pattern of positions, optionally pre-populates the entry cache with chosen expected read counts, runs an asynchronous replay, and verifies the returned entries and cache state.
What concerns me is that the parameters change both the setup and the assertions, so several scenarios hide behind one body. The god_function and long_function patterns both apply. Callback boilerplate for the entry read and the replay also inflates the fan-out count without adding logic.
My recommendation: extract three pieces. One builds the ledger and positions, one populates the cache for the partly-cached variant, and one wraps the async read and replay into a future-returning helper. The callback adapters are the best candidates, since they appear twice and carry nothing scenario-specific. That should cut fan-out substantially and make the cache-state assertions easier to find.
assertHasBacklogMatchesBacklogCountsAcrossGeneratedOperationSequence — ManagedCursorTest.java
This is a seeded, randomized test helper that runs 120 steps of generated operations (add, delete, mark-delete, reset, reopen) and checks backlog consistency after each one. Cyclomatic complexity is only 4, but nesting depth is 6, the deepest in the set and the source of the deeply_nested pattern. The depth comes from branching on a random operation choice, with some branches containing further conditionals for the case where no readable position exists.
I would leave the design alone. It is sensible for catching state-machine bugs, and the seed in each failure message lets a run be reproduced. The problem is readability of the dispatch. The fallback of ‘if no readable position, add an entry instead’ repeats across several branches, and that repetition drives the depth. Extracting each operation into a small method that returns its description string, with the fallback handled once, would flatten the chain into a simple selection and bring nesting down to about 2 or 3.
testReplayAtReadPositionMovesReadPositionForwardOnce — ManagedCursorTest.java
The lowest-ranked of the five, with a score of 50.57 and the simplest control flow: cyclomatic complexity 3, nesting depth 1. It tests a specific race, where a replay compares a position against the read position at the same moment a normal read completes. To set that up, it defines an anonymous Position subclass whose comparison method moves the read position as a side effect.
Only the exit_heavy pattern is flagged, and the metrics are modest. I would not prioritize refactoring here. The point worth making is about maintenance: the test depends on the order in which the implementation calls the comparison method, so a change to how replay compares positions could break it for reasons unrelated to correctness. A short comment in the test explaining that dependency is enough.
What the concentration means
Two things stand out about this file. First, several other test methods in the file sit in the watch quadrant with scores near 47, just below the top five. The file looks broadly high-coupling, not just five outliers. Second, every one of these functions was last changed 6 days ago and the file has a short history of 2 commits, so the structure of this file is still being set. That is the cheapest time to establish conventions, such as scenario-per-test and shared setup helpers, before the file grows further.
My suggestion to anyone working here is to apply one rule to all five: when a test body covers several scenarios or one helper has a fan-out above roughly 40, split by scenario and move shared setup to named helpers. The change is mechanical and carries low risk to production code, since all five functions are test code.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
god_function | 4 |
long_function | 3 |
exit_heavy | 2 |
complex_branching | 1 |
deeply_nested | 1 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/apache/pulsar
cd pulsar
git checkout a996f4192d05c333944f64c1564e34dfb2187a41
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →