Files
five/hbrdd
CharlesKWON 12fcb8d249 fix(dbf): Layer 6 — EOF marker max-merge + disable append batching in shared
Closes two more multi-session correctness bugs surfaced by the
post-Layer-5 stress harness. Combined with Layer 5's panic-free
result, three-worker concurrency now sits around 80% pass with
zero Go-level crashes; higher worker counts trade reliability
for throughput against the inherent single-file-multi-writer
limit of the DBF format.

1. EOF marker write at Close (max-merge with disk)

   `Close()` writes the EOF marker `0x1A` at
   `header.HeaderLen + a.recCount * RecordLen`, computed from
   our LOCAL recCount. A peer Append between our last refresh
   (under the append-intent lock at Append-time) and Close-time
   may have bumped the disk recCount above ours. Writing EOF
   at our stale offset overwrites byte 0 of the peer's record
   — flipping the delete-flag from ' ' (RecordActive) to 0x1A.
   The field bytes survive, but downstream code that depends on
   byte 0's exact value misclassifies the record.

   Fix mirrors updateHeader's max-merge (Layer 3a): in shared
   mode, re-read the disk header right before computing
   EOFOffset and use max(disk.RecCount, local). Cheap (~1 stat-
   sized read per Close) and the eventual close-fd is already
   the serial bottleneck of any meaningful churn.

2. Append-batching disabled in shared mode

   The appendBuf optimisation accumulates several consecutive
   APPENDs into a single WriteAt at flushRecord time. In single-
   process EXCLUSIVE mode that's a clean throughput win. In
   shared mode, though, a peer SELECT can open the file while
   our slots N..N+M are buffered but still on-disk only as
   reserved-but-zero bytes. The peer iterates 1..recCount and
   ReadAts zeros at offsets [N..N+M), treating the records as
   garbage / empty markers.

   Skip the batch path when `a.shared`: each Append writes its
   record straight through via flushRecord on the next state
   change. EXCLUSIVE single-process flows are unaffected.

Observed stress numbers (3 trials × 30 runs each, average):

  pre-Layer-1 baseline:   ~60% / panics
  +Layer 1+2:             80% / 50% / panic
  +Layer 4a/4b:           75-90% / 50-80% / panic
  +Layer 5 (mmap-gen):    ~73% / ~67% / ~33% / NO PANICS
  +THIS (EOF + no-batch): ~83% / ~50% / ~22% / NO PANICS

The remaining flake at 5+ concurrent writers reflects the
fundamental constraint of FiveSql2's DBF model: no table-level
write lock, no MVCC. PostgreSQL solves this with snapshot
isolation; the equivalent for FiveSql2 would need a
write-ahead log or per-table writer mutex. Tracked as a
post-1.0 R&D direction.

For typical pgserver use — many read clients, few write
clients — the current correctness is production-acceptable.
The pgserver Phase 7 integration suite (3/3 in the basic
psql harness + 3/3 in the auth/TLS harness) remains 6/6 green
because each suite uses one connection at a time.

All six release gates green:
  go test ./...               ✓
  FiveSql2 SQL:1999 43/43     ✓
  Harbour compat 56/56        ✓
  std.ch 17/17                ✓
  FRB 7/7                     ✓
  pgserver integration 6/6    ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:03:56 +09:00
..