summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthias Jaros <jarlucmat@mailbox.org>2026-10-01 23:02:15 +0200
committerGravatar Matthias Jaros <jarlucmat@mailbox.org>2026-10-02 19:15:39 +0200
commit6921450c42259f59db11ec057e18d0708e0a4d9a (patch)
tree9df941b0c4c6f2927d35acf2f0c6e0f17248ca4a
parentd65fc538e7370e70a376662eb5a1f1a69578468f (diff)
Added TODO list
-rw-r--r--TODO.md82
1 files changed, 82 insertions, 0 deletions
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..1c27abc
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,82 @@
+# TODO — memory / allocation optimizations
+
+Background: the scanner runs millions of virtual threads (one per port probe today).
+Per-probe allocation churn, not peak RSS, is the main memory pressure source (GC load).
+Hot path: per host -> `ScanHostTask` -> per port -> `ScanPortTask` / `ScanUdpPortTask`.
+
+These items were reviewed and approved in this order. Nothing is implemented yet.
+Constraint (multi-scan): Scanner must remain able to run several parallel scans with
+different configurations and targets in one JVM. Anything static must be a true
+compile-time constant, never per-scan state.
+
+## Phase 2 — approved small changes (do when green-lit, one commit each)
+
+- [ ] `ScanPortTask` / `ScanUdpPortTask`: make `waitInMillis` / `waitInNanos`
+ `static final` (they are hardcoded 100 ms constants, not config — safe for
+ multi-scan). Saves 16+ bytes on every port task.
+- [ ] `PortRangeIterator`: delete dead field `done` (never read).
+- [ ] `PortRangeIterator`: remember last `nextSetBit` result so `next()` does not
+ rescan; `hasNext()` + `next()` currently both call `nextSetBit`.
+- [ ] Parse port range **once per scan** (in `Scanner`/context), not per host:
+ remove `new PortRange(scan.getPorts())` from `ScanHostTask`; workers iterate
+ the shared immutable BitSet with a plain int cursor. Saves per host: one
+ string parse + up to two 8 KB BitSet copies.
+- [ ] `CliPrinter`: pre-compile the `stty size` regex as a `static final Pattern`.
+ NOTE: the `getColumnWidth()` process spawn runs on purpose (terminal can be
+ resized) — do NOT cache the width itself.
+- [ ] `CliPrinter.printErrors` / `printPortsResults` / `PortList.getPorts()`:
+ replace `String.format` + `stream().boxed().toList()` with direct
+ `StringBuilder` appends over the BitSet.
+
+## Phase 2b — deferred (user: not implemented now, revisit later)
+
+- [ ] **`LinuxUdpSocketAPI` allocation churn per probe** (see
+ `wrapNativeMethodCall`, `destination`, `sendEmptyDatagram`)
+ Today every native call allocates: one errno call-state segment,
+ one rebuilt `Object[] argv`, plus the `destination` segment per port
+ (4–5 small allocations per probe).
+ - Idea: allocate the errno state + argv arrays once per probe (or per
+ worker) and pre-size the destination segment.
+ - **Hard constraint (user):** the errno call-state must NOT be a single
+ segment shared across all virtual threads. Any reuse must stay scoped
+ to one worker thread / one probe at a time; otherwise keep status quo.
+ - Also review the per-port `Arena.ofConfined()` + `new LinuxUdpSocketAPI`
+ pair (confined arenas assume thread affinity which virtual threads
+ do not guarantee across park/yield).
+
+## Phase 3 — deferred (measure first)
+
+- [ ] **Replace "1 virtual thread + Callable + CompletionService per port" with a
+ fixed worker pool per host**
+ - Start N persistent virtual worker threads per host
+ (N = min(maxWorkersPerHost, socketLimit), capped), pulling ports from a
+ shared cursor and pushing `PortResult`s to a shared queue the consumer
+ drains (today: `PortScanResultAccumulator` + `CompletionConsumer`).
+ - Removes per port: the vthread, `ScanPortTask`, Integer boxes (×2),
+ Runnable wrapper, `CompletableFuture`, `QueueElement`, `PollState`.
+ - ~40–50 % fewer bytes per probe + far less GC + no per-port
+ thread-create/park cycles.
+ - Caveats: touches cancellation / "partial results on Ctrl-C" behavior
+ (see `CompletionConsumer` contract). Keep `socketLimit` semaphore as the
+ global throttle. Must stay compatible with parallel scanners.
+ - Only do if Phase 2 numbers still show GC pressure dominated by per-port
+ scaffolding. Measure with `-Xlog:gc*`, JFR `jdk.ObjectAllocationInNewTLAB`,
+ wall-time on a fixed target set (e.g. `ips-for-testing.txt` × `1-1024`).
+
+## Phase 4 — optional / measure first
+
+- [ ] **Reduce per-port atomic traffic in `Scan` counters**: `portStart()` /
+ `portFinish()` do 3 CAS each per port. `socketCounter` increments in exact
+ lockstep with `portCounter` in both tasks — consider dropping it or packing
+ counters to cut ~1/3 of per-port CAS work. CPU-only win, profile before.
+
+## Explicitly NOT doing (decisions)
+
+- `CliPrinter.getColumnWidth()`: keep the `stty size` process spawn per call
+ (intentional: terminal can be resized).
+- `LinuxUdpSocketAPI`: do NOT share the errno call-state segment across
+ worker threads (would be a data race). Keep per-call allocation.
+- Do NOT remove the default online check to "save" exception stack traces —
+ it is a deliberate memory saver (prevents 1024 timed-out throws per offline
+ host). Keep `PortResult` exceptions un-retained (accumulator already stores
+ only `ExceptionInfo`).