# Evaluation of Parallel Transaction Cancellation in Besu
**PR under test:** [https://github.com/hyperledger/besu/pull/9360](https://github.com/hyperledger/besu/pull/9360)
## TL;DR
Across ~18k blocks there’s plenty of ±<10% noise. When focusing on slower blocks and on blocks involving **known heavy contracts**, the cancellation fix shows **neutral to mildly positive** impact, including a **–38%** outlier on a >2s block and a **–30%** win on another contract. No systematic regressions were observed in slow-block cuts.
---
## What’s being tested (context)
Besu executes blocks in **parallel and serial** concurrently; whichever finishes first “wins.” When serial catches up first, the parallel path should be cancelled. This PR fixes tracking of parallel `CompletableFuture`s so the losing parallel work can be **cancelled promptly**.
**Hypothesis:** Correct cancellation reduces wasted work on heavy, state-touching blocks without hurting others. The effect may be **data dependent**: not every transaction from the same contract is heavy.
---
## Methodology (brief)
* Two runs on identical chain data:
* **Control:** current behavior
* **Test:** PR #9360 (parallel-tx cancellation fix)
* Parsed per-block execution time from logs (`Xms exec` / `Ys exec`), normalized to **milliseconds**, and matched by **block number**.
* Reported per-block deltas:
**Δ(ms)** and **Δ(%) = (test − control)/control × 100**; flagged when |Δ%| ≥ 10%.
* Filters:
* `--min-exec-millis` to focus on slow blocks.
* `--blocks-csv` to restrict to **specific contracts** known to often produce slow blocks:
* `MCT-XEN_Batch_Minter` (0x2f84…0479)
* `MCT_MXENFT_Token` (0x0000…f20b)
* `CoinTool_XEN_Batch_Minter` (0x0de8…f628)
* `XEN_Minter` (0xc3c7…c226f)
> Sign convention: **Δ% > 0** = test slower; **Δ% < 0** = test faster.
---
## Results
### A) All blocks (no CSV filter)
| Filter | Compared | Flagged (≥10%) | Mean Δ% | Median Δ% |
| ------------------------ | -------: | -------------: | ---------: | ---------: |
| none | 17,937 | 8,414 | **+2.17%** | **+1.90%** |
| `--min-exec-millis 1000` | 165 | 11 | **−0.64%** | **−0.32%** |
| `--min-exec-millis 2000` | 9 | 1 | **−3.58%** | **+0.79%** |
**Notable ≥2s example**
```
Block Control(ms) Test(ms) Δ(ms) Δ(%) Flag
-----------------------------------------------------------
23647133 2386.0 1478.0 -908.0 -38.1 ***
```
### B) Contract-focused cuts (each CSV run separately)
**MCT-XEN Batch Minter ([0x2f84…0479](https://etherscan.io/address/0x2f848984984d6c3c036174ce627703edaf780479))**
| Filter | Compared | Flagged | Mean Δ% | Median Δ% |
| ------------------------ | -------: | ------: | ---------: | ---------: |
| none | 32 | 7 | **−2.84%** | **−1.86%** |
| `--min-exec-millis 1000` | 10 | 0 | **−0.88%** | **−0.39%** |
| `--min-exec-millis 2000` | 3 | 0 | **+0.85%** | **+1.90%** |
**MCT_MXENFT Token ([0x0000…f20b](https://etherscan.io/address/0x0000000000771a79d0fc7f3b7fe270eb4498f20b))**
| Filter | Compared | Flagged | Mean Δ% | Median Δ% |
| ------------------------ | -------: | ------: | ---------: | ---------: |
| none | 382 | 163 | **−0.39%** | **+0.57%** |
| `--min-exec-millis 1000` | 3 | 0 | **+0.33%** | **−0.80%** |
**CoinTool XEN Batch Minter ([0x0de8…f628](https://etherscan.io/address/0x0de8bf93da2f7eecb3d9169422413a9bef4ef628))**
| Filter | Compared | Flagged | Mean Δ% | Median Δ% |
| ------------------------ | -------: | ------: | ---------: | ---------: |
| none | 2,783 | 1,285 | **+1.36%** | **+2.13%** |
| `--min-exec-millis 2000` | 4 | 1 | **−8.68%** | **+0.03%** |
**XEN Minter ([0xc3c7…c226f](https://etherscan.io/address/0xc3c7b049678d84081dfd0ba21a6c7fdcc31c226f))**
| Filter | Compared | Flagged | Mean Δ% | Median Δ% |
| ------------------------ | -------: | ------: | ----------: | ----------: |
| none | 116 | 63 | **−3.28%** | **−2.14%** |
| `--min-exec-millis 1000` | 1 | 1 | **−30.22%** | **−30.22%** |
> The additional contract reinforces the trend: **neutral to mildly positive overall**, with **material wins** on some slow blocks (e.g., ~–30%).
---
## Interpretation
* **Noise dominates short blocks** (small positive median when unfiltered).
* **Slow-block focus shows benefit**: filtering to ≥1–2s shifts means negative (faster).
* **Per-contract**: effects are **data dependent**—not all txs from a “heavy” contract are heavy every time. Still, we see solid wins (–38%, –30%) on some slow blocks, consistent with the idea that **stopping the losing parallel work** helps the serial winner finish earlier.
---
## Could cancellation harm performance?
Potential downsides of cancelling Java `CompletableFuture`-based work (in general, and how they might show up in Besu):
* **Cancellation ≠ immediate stop:** `CompletableFuture.cancel()` marks the stage cancelled, but **doesn’t forcibly stop** underlying work unless the code cooperatively checks for cancellation or responds to interruption. If low-level calls (e.g., DB/IO) aren’t interruptible, some **work still runs**, muting the benefit.
* **Extra signalling & exception paths:** Cancellation completes futures **exceptionally** (`CancellationException`) and propagates through dependent stages. If this happens frequently, you pay for **additional atomic state changes** and **exceptional-control-flow overhead** (especially if stack traces are logged).
* **ForkJoin/Executor churn:** Launching many tasks that are then cancelled can create **queue churn** and **lost locality** (warm caches discarded), with small overheads that add up if the cancel rate is high.
* **Locks & resources:** If a task is cancelled while holding **locks** or native resources (e.g., RocksDB iterators), incorrect cleanup can cause **longer lock holds**, **contention**, or **leaks**. Cooperative checks should happen at **safe points** (between critical sections) and always release resources.
* **GC pressure:** Aborted partial results and short-lived objects can increase **allocation churn**, nudging GC overhead upward in some phases.
* **Interruption handling pitfalls:** If interruption is used, **interrupt status leaks** or swallowed interrupts can lead to subtle bugs. Besu code must handle `Thread.interrupted()` carefully and keep invariants intact on early exit.
**Why we likely don’t see regressions here:**
The observed distributions are near neutral, with improvements concentrated in the slow tail, suggesting cancellation is generally **helpful or harmless** in this workload. Any overhead from signalling/cascade appears **smaller than** the saved work on slow blocks.
**What to validate next (low effort, high signal):**
* Emit counters/timers for: *parallel started / cancelled / actually aborted early*, and *who won (serial vs parallel)* per block.
* Sample **CPU utilization** and **GC logs** to ensure cancellation reduces CPU time when serial wins.
* Add a **percentile view (P50/P90/P99)** for blocks ≥1s and ≥2s, plus histograms of Δ%.
---
## Repro (commands)
```bash
# All blocks
./compare_besu_exec.py control-besu.log test-cancellation-besu.log
# Slow blocks
./compare_besu_exec.py control-besu.log test-cancellation-besu.log --min-exec-millis 1000
./compare_besu_exec.py control-besu.log test-cancellation-besu.log --min-exec-millis 2000
# Contract-specific (each CSV run separately)
./compare_besu_exec.py control-besu.log test-cancellation-besu.log \
--blocks-csv 0x2f848984984d6c3c036174ce627703edaf780479_MCT-XEN_Batch_Minter_export-transaction-list-1761523431099.csv
./compare_besu_exec.py control-besu.log test-cancellation-besu.log \
--blocks-csv 0x0000000000771a79d0fc7f3b7fe270eb4498f20b_MCT_MXENFT_Token_export-transaction-list.csv
./compare_besu_exec.py control-besu.log test-cancellation-besu.log \
--blocks-csv export-0x0de8bf93da2f7eecb3d9169422413a9bef4ef628_CoinTool_XEN_Batch_Minter.csv
./compare_besu_exec.py control-besu.log test-cancellation-besu.log \
--blocks-csv export-0xc3c7b049678d84081dfd0ba21a6c7fdcc31c226f_XEN_Minter.csv
# Optional filters
--min-exec-millis <ms> # e.g., 1000 or 2000
--threshold 10 # flag when |Δ%| ≥ 10
--csv results.csv # write detailed rows
```
---