code wiki / (root) / nx_avail_map.nx

nx_avail_map.nx source

↩ module page · 154 lines · 7129 B

1// nx_avail_map.nx -- shared cross-peer piece-AVAILABILITY map for rarest-first selection. 2// 3// module: nishi-core.torrent.avail_map 4// depends: nx_peerwire.nx (nx_pw_bitfield_has -- the peer-has filter), 5// nx_piece_manager.nx (nx_pm_rarest -- the CANONICAL rarest-first tie-break; 6// wiring it here un-orphans the smart layer, DRY: one picker), 7// nx_syscalls.nx (transitive: mmap/openat/lseek/read/write/close/FLOCK). 8// capability: SWARM_INTELLIGENCE 9// 10// WHY this organ exists: 11// The download worker forks one child PER PEER, so each child only knows ITS OWN peer's 12// bitfield -- there is no in-process view of how rare each piece is across the swarm, which 13// is exactly the input rarest-first needs. This is that missing cross-PROCESS view: a small 14// on-disk array of per-piece peer counts that every child contributes to and reads back to 15// pick the rarest piece IT can actually serve. Replacing the worker's linear stride scan with 16// this is the Phase-2 "smart layer" rung (operator's rebuild order: reliability -> smart). 17// 18// CORRECTNESS vs HEURISTIC (deliberate split): 19// The counts are a SELECTION heuristic, never a data-integrity invariant -- every received 20// piece is still SHA1-verified against the metadata before it touches disk. So: 21// * increments (read-modify-write) ARE locked (flock LOCK_EX) -- the proven framed-append 22// floor idiom; concurrent unlocked RMW silently LOSES counts (gate proves this), which 23// would bias selection, so we pay the lock on the (rare, once-per-peer) contribution path. 24// * reads (am_load / am_pick_rarest) are LOCK-FREE -- a momentarily-stale or slightly-torn 25// count only nudges the ordering, never corrupts data, and the pick is on the hot path. 26// 27// FILE FORMAT: `npc` bytes; byte p = saturating count (0..255) of pool peers advertising piece p. 28// One byte/piece keeps even a 10,000-piece torrent's map at 10 KB. 29 30import "nx_peerwire.nx" 31import "nx_piece_manager.nx" 32 33const AM_SAT: i64 = 255 // per-piece count ceiling (1 byte storage) 34 35// open the avail file O_RDWR|O_CREAT, NO truncate (mirrors the worker's tg_open_rw idiom exactly). 36func am_open(path: *u8) -> i64 { return __syscall(SYS_OPENAT, AT_FDCWD, path as i64, 0x42, 0x1a4, 0, 0) } 37 38// zero the first `npc` bytes -- called ONCE by the parent on metadata-resolve so availability is 39// rebuilt from the LIVE swarm each session (stale counts from a prior run are meaningless). mmap 40// is kernel-zero-filled, so we just write npc zero bytes over any prior content. 41func am_init(path: *u8, npc: i64) -> i64 { 42 let fd: i64 = am_open(path) 43 if fd < 0 { return 0 - 1 } 44 let z: *u8 = sys_mmap(npc + 16) 45 sys_lseek(fd, 0, 0) 46 sys_write(fd, z, npc) 47 sys_close(fd) 48 return 0 49} 50 51// contribute one peer's whole bitfield to the shared counts (LOCKED read-modify-write). For each 52// piece the peer HAS, increment its saturating count. ONE lock acquisition per call -- a child 53// calls this once, when it learns the peer's BITFIELD. Returns how many pieces this peer added. 54func am_add_bitfield(path: *u8, peerbits: *u8, byte_len: i64, npc: i64) -> i64 { 55 let fd: i64 = am_open(path) 56 if fd < 0 { return 0 } 57 sys_flock(fd, SYS_LOCK_EX) 58 let buf: *u8 = sys_mmap(npc + 16) 59 sys_lseek(fd, 0, 0); sys_read(fd, buf, npc) 60 var added: i64 = 0; var p: i64 = 0 61 while p < npc { 62 if nx_pw_bitfield_has(peerbits, byte_len, p) == 1 { 63 let c: i64 = buf[p] as i64 64 if c < AM_SAT { buf[p] = (c + 1) as u8 } 65 added = added + 1 66 } 67 p = p + 1 68 } 69 sys_lseek(fd, 0, 0); sys_write(fd, buf, npc) 70 sys_flock(fd, SYS_LOCK_UN) 71 sys_close(fd) 72 return added 73} 74 75// UNLOCKED twin of am_add_bitfield -- EXISTS ONLY as the gate's negative control (it demonstrates 76// that the lock is load-bearing: concurrent RMW without it loses increments). NEVER call this from 77// the live worker. Kept beside its locked sibling so the two stay byte-identical except the lock. 78func am_add_bitfield_unlocked(path: *u8, peerbits: *u8, byte_len: i64, npc: i64) -> i64 { 79 let fd: i64 = am_open(path) 80 if fd < 0 { return 0 } 81 let buf: *u8 = sys_mmap(npc + 16) 82 sys_lseek(fd, 0, 0); sys_read(fd, buf, npc) 83 var added: i64 = 0; var p: i64 = 0 84 while p < npc { 85 if nx_pw_bitfield_has(peerbits, byte_len, p) == 1 { 86 let c: i64 = buf[p] as i64 87 if c < AM_SAT { buf[p] = (c + 1) as u8 } 88 added = added + 1 89 } 90 p = p + 1 91 } 92 sys_lseek(fd, 0, 0); sys_write(fd, buf, npc) 93 sys_close(fd) 94 return added 95} 96 97// incremental single-piece bump for a peer's HAVE message after its initial bitfield (LOCKED). 98func am_add_have(path: *u8, piece_idx: i64) -> i64 { 99 let fd: i64 = am_open(path) 100 if fd < 0 { return 0 - 1 } 101 sys_flock(fd, SYS_LOCK_EX) 102 let b: *u8 = sys_mmap(16) 103 sys_lseek(fd, piece_idx, 0); let r: i64 = sys_read(fd, b, 1) 104 var c: i64 = 0 105 if r == 1 { c = b[0] as i64 } 106 if c < AM_SAT { b[0] = (c + 1) as u8 } 107 sys_lseek(fd, piece_idx, 0); sys_write(fd, b, 1) 108 sys_flock(fd, SYS_LOCK_UN) 109 sys_close(fd) 110 return 0 111} 112 113// snapshot the byte counts into a caller-owned i64 array for the canonical picker (LOCK-FREE read; 114// a heuristic ordering, never a data path). Bytes past EOF / missing file read as 0. Returns npc. 115func am_load(path: *u8, out_counts: *i64, npc: i64) -> i64 { 116 let fd: i64 = am_open(path) 117 if fd < 0 { 118 var z: i64 = 0; while z < npc { out_counts[z] = 0; z = z + 1 } 119 return 0 120 } 121 let buf: *u8 = sys_mmap(npc + 16) 122 sys_lseek(fd, 0, 0); let r: i64 = sys_read(fd, buf, npc); sys_close(fd) 123 var p: i64 = 0 124 while p < npc { 125 if p < r { out_counts[p] = buf[p] as i64 } else { out_counts[p] = 0 } 126 p = p + 1 127 } 128 return npc 129} 130 131// RAREST-FIRST pick restricted to pieces THIS peer can actually serve. 132// Builds an "effective-have" array -- a piece is effectively-had (i.e. SKIP it) if we already 133// have it OR this peer lacks it -- and hands av + eff to the canonical nx_pm_rarest, so the 134// swarm-wide rarest tie-break lives in exactly ONE place (DRY; this is the call that un-orphans 135// the smart layer). `havebits`==0 means the peer sent no bitfield (fast-ext have-all seeder): 136// treat it as has-everything. Caller passes scratch buffers `av`/`eff` (each >= npc i64) to 137// avoid per-pick mmap churn. Returns the rarest needed+serveable piece index, or -1 if none 138// (caller then falls back to its linear scan -- so this can only improve, never regress). 139func am_pick_rarest(path: *u8, donebits: *u8, peerbits: *u8, byte_len: i64, havebits: i64, npc: i64, av: *i64, eff: *i64) -> i64 { 140 am_load(path, av, npc) 141 var p: i64 = 0 142 while p < npc { 143 var skip: i64 = 0 144 if (donebits[p] as i64) == 1 { skip = 1 } 145 else { 146 if havebits == 1 { 147 if nx_pw_bitfield_has(peerbits, byte_len, p) == 0 { skip = 1 } 148 } 149 } 150 eff[p] = skip 151 p = p + 1 152 } 153 return nx_pm_rarest(av, eff, npc) 154}