whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF

This commit is contained in:
russell@unturf.com 2026-03-27 22:18:31 -04:00
parent e4ee168b1e
commit 2e4f7807d5
401 changed files with 3914 additions and 114 deletions

View file

@ -0,0 +1,74 @@
# pulsar-0005 — PartialRoundRobinMessageRouterImpl CopyOnWriteArrayList.contains O(N×L) → HashSet O(N)
## Classification
| Field | Value |
|-------------|-------|
| CWE | CWE-407 Inefficient Algorithmic Complexity |
| Severity | MEDIUM |
| Component | `pulsar-client/src/main/java/org/apache/pulsar/client/impl/customroute/PartialRoundRobinMessageRouterImpl.java` |
| Introduced | PartialRoundRobin routing support |
| Status | PATCHED (unit test PASS) |
## Defect
In `getOrCreatePartialList()`, when the partial list needs to grow, a stream over all N partition
indices filters out those already present in `partialList`, a `CopyOnWriteArrayList<Integer>`:
```java
// DEFECTIVE — PartialRoundRobinMessageRouterImpl.java:73-74
} else if (partialList.size() < numPartitionsLimit && partialList.size() < metadata.numPartitions()) {
partialList.addAll(IntStream.range(0, metadata.numPartitions()).boxed()
.filter(e -> !partialList.contains(e)) // O(L) per element
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
```
`CopyOnWriteArrayList.contains()` is O(L) where L = `partialList.size()` (up to `numPartitionsLimit`).
The stream visits N elements (N = `metadata.numPartitions()`).
Total: **O(N × L)**.
For a topic with N=1 000 partitions and L=500 already selected, this performs 500 000 comparisons
instead of 1 000.
Additionally, `partialList` is a `CopyOnWriteArrayList`, which copies the backing array on every
`add()` during the `addAll()` batch, adding O(L²) write overhead if `addAll()` is not atomic.
In practice `addAll()` is a single copy, but `contains()` remains O(L).
## Fix
Build a `HashSet<Integer>` snapshot of `partialList` before the filter, then use the set for
O(1) membership tests:
```java
// FIXED
Set<Integer> existing = new HashSet<>(partialList);
partialList.addAll(IntStream.range(0, metadata.numPartitions()).boxed()
.filter(e -> !existing.contains(e)) // O(1) per element
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.shuffle(list);
return list.stream();
})).limit(numPartitionsLimit - partialList.size()).collect(Collectors.toList()));
```
The snapshot is built once in O(L) and all N filter calls are O(1), giving O(N + L) total.
## Complexity
| Scenario | Before | After | Ratio |
|----------|--------|-------|-------|
| N=100, L=50 | O(5 000) | O(150) | 33× |
| N=500, L=250 | O(125 000) | O(750) | 167× |
| N=1 000, L=500 | O(500 000) | O(1 500) | 333× |
## Speedup (measured)
See unit test `PulsarPartialRoundRobinRouterTest.java`.
Measured ratio ≥ 5× at N=500 (SLOW CopyOnWriteArrayList vs FAST HashSet snapshot).
## Affected File
```
pulsar-client/src/main/java/org/apache/pulsar/client/impl/customroute/PartialRoundRobinMessageRouterImpl.java
Line 35: partialList = new CopyOnWriteArrayList<>() — fine for reads, O(L) contains
Line 74: .filter(e -> !partialList.contains(e)) — O(N × L), should snapshot to HashSet first
```

View file

@ -0,0 +1,21 @@
# pulsar deeper scan — CWE-407 triage
## Scan date: 2026-03-27
## Areas scanned
| Path | Result |
|------|--------|
| `pulsar-broker/src/main/java/org/apache/pulsar/broker/service/` | CLEAN — consumerList.contains guarded by consumerSet (HashSet), membership check deferred to Set |
| `pulsar-broker/src/main/java/org/apache/pulsar/broker/resourcegroup/ResourceUsageTopicTransportManager.java` | CLEAN — tenantList.contains and nsList.contains are one-shot checks at startup, not in a loop |
| `pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/` | CLEAN — no ArrayList.contains in loops found |
| `pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/` | CLEAN |
| `pulsar-broker/src/main/java/org/apache/pulsar/broker/namespace/` | CLEAN |
| `pulsar-client/src/main/java/org/apache/pulsar/client/impl/customroute/PartialRoundRobinMessageRouterImpl.java` | **pulsar-0005** — CopyOnWriteArrayList.contains in O(N) stream filter |
| `pulsar-client/src/main/java/` (rest) | CLEAN — sharedResources.contains is on a small enum-backed list |
| `managed-ledger/src/main/java/` | CLEAN — String.contains (substring match), not collection membership |
| `pulsar-common/src/main/java/` | CLEAN |
## Defect found
- **pulsar-0005**: `PartialRoundRobinMessageRouterImpl.java` CopyOnWriteArrayList.contains O(N×L) — patched, 3/3 PASS, 18×71× measured