12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182(** In-memory freelist.
Maps each [freed_at_txn_id] to the page-ids freed at that txn. [pop]
reuses the oldest-freed reusable page (lowest [freed_at_txn_id] among those
with [freed_at_txn_id < min_safe_txn_id]).
Keyed by txn lets both operations be O(log d) in the number of distinct
freeing txns (small and bounded), instead of O(n) in the page count:
- the non-reuse hot path (a long write txn whose own COW frees are never
yet safe) short-circuits on [min_binding] without scanning;
- the reuse path (allocating against a backlog of safely-freed pages, e.g.
inserting into a table seeded by an earlier committed txn) pops in
O(log d) instead of rescanning the whole growing list.
A flat per-page list made every [alloc] O(freelist size); during bulk
inserts that turned an n-row load into O(n^2) (#229). All operations are
pure — no mutation. *)moduleTxn_map=Map.Make(Int64)typet={by_txn:int32listTxn_map.t(** freed_at_txn_id -> page_ids freed then *);count:int(** total pages held (sum of list lengths) *)}letempty:t={by_txn=Txn_map.empty;count=0}letppfmtt=Format.fprintffmt"Freelist.t { entries = %d }"t.countletadd(t:t)~page_id~freed_at_txn_id:t=letcur=Option.value~default:[](Txn_map.find_optfreed_at_txn_idt.by_txn)in{by_txn=Txn_map.addfreed_at_txn_id(page_id::cur)t.by_txn;count=t.count+1};;(** Pop the reusable entry with the lowest [freed_at_txn_id] (oldest freed
first). A page is reusable iff [freed_at_txn_id < min_safe_txn_id].
O(log d) in the number of distinct freeing txns. *)letpop(t:t)~min_safe_txn_id:(int32*t)option=matchTxn_map.min_binding_optt.by_txnwith|Some(txn,pids)whenInt64.comparetxnmin_safe_txn_id<0->(matchpidswith|[]->(* Empty list under a key never persists (see below); treat as absent. *)None|pid::rest->letby_txn=ifrest=[]thenTxn_map.removetxnt.by_txnelseTxn_map.addtxnrestt.by_txninSome(pid,{by_txn;count=t.count-1}))|_->None(* empty, or the oldest free is not yet safe to reuse *);;(* Flatten to (page_id, freed_at_txn_id) pairs. Emits each txn's pages in
reverse of their internal (newest-first) order so that [of_list] — which
prepends via [add] — reconstructs the identical structure (round-trip
stable: pop order is preserved across to_list/of_list). *)letto_list(t:t):(int32*int64)list=Txn_map.fold(funtxnpidsacc->List.fold_left(funap->(p,txn)::a)accpids)t.by_txn[];;letof_list(l:(int32*int64)list):t=List.fold_left(funacc(page_id,freed_at_txn_id)->addacc~page_id~freed_at_txn_id)emptyl;;letsize(t:t):int=t.countletreusable_count(t:t)~min_safe_txn_id:int=Txn_map.fold(funtxnpidsacc->ifInt64.comparetxnmin_safe_txn_id<0thenacc+List.lengthpidselseacc)t.by_txn0;;[@@@ai_disclosure"ai-generated"][@@@ai_model"claude-opus-4-8"][@@@ai_provider"Anthropic"]