nx_checkpoint.nx source
↩ module page · 149 lines · 4498 B
1// nx_checkpoint.nx -- atomic high-water-mark file for resumable jobs.
2//
3// Persists a single "last completed id" via .tmp + renameat.
4//
5// genealogy_id: chandy_lamport_1985_snapshots + spark_checkpointing
6// lineage_id: resumable_state
7
8// nx_safety_envelope:
9// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
10// sil_target: SIL1
11// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
12// verdict: NOT_YET_EVALUATED
13
14import "nx_syscalls.nx"
15import "nx_tier.nx"
16import "nx_str.nx"
17import "nx_strconv.nx"
18
19const NX_CHECKPOINT_FRESH: nx_int = -1
20const NX_CHECKPOINT_WRITE_FAIL: nx_int = -2
21const NX_CHECKPOINT_RENAME_FAIL: nx_int = -3
22
23// renameat2 on RV64 Linux is syscall 276; the older renameat is 38.
24// Original value 82 was wrong — that's `sync()` on RV64, not rename —
25// which silently failed every atomic-write and made nx_checkpoint_set
26// fail at the rename step. Caught by the cold-cache smoke audit
27// 2026-05-15 once the fail-loud smoke lib exposed the silent rot.
28const SYS_RENAMEAT_LOCAL: nx_int = 276 // renameat2 (RV64)
29
30struct NxCheckpoint {
31 path_buf: *u8,
32 tmp_buf: *u8,
33 last_value: nx_int,
34 n_writes: nx_int,
35 n_write_fail: nx_int,
36}
37
38const NX_CHECKPOINT_STRUCT_BYTES: nx_size = 48
39const NX_CHECKPOINT_PATH_CAP: nx_size = 512
40
41func nx_ckpt_make_tmp(orig: *u8, out: *u8) -> nx_size {
42 nx_str_cpy(out, orig)
43 let n: nx_int = nx_str_len(out)
44 let sfx: *u8 = ".tmp" as *u8
45 var i: nx_int = 0
46 while sfx[i] != 0 {
47 out[n + i] = sfx[i]
48 i = i + 1
49 }
50 out[n + i] = 0
51 return (n + i) as nx_size
52}
53
54func nx_ckpt_parse_i64(buf: *u8, n: nx_int) -> nx_int {
55 if n <= 0 { return -1 }
56 var i: nx_int = 0
57 if buf[0] == 0x2D { i = 1 }
58 var v: nx_int = 0
59 var any: nx_int = 0
60 while i < n {
61 let c: nx_int = buf[i]
62 if c < 0x30 { i = n }
63 if c > 0x39 { i = n }
64 if i < n {
65 v = v * 10 + (c - 0x30)
66 any = 1
67 i = i + 1
68 }
69 }
70 if any == 0 { return -1 }
71 return v
72}
73
74func nx_checkpoint_open(path: *u8) -> *NxCheckpoint {
75 let raw: *u8 = sys_mmap(NX_CHECKPOINT_STRUCT_BYTES)
76 let cp: *NxCheckpoint = raw as *NxCheckpoint
77
78 let pbuf: *u8 = sys_mmap(NX_CHECKPOINT_PATH_CAP)
79 nx_str_cpy(pbuf, path)
80 let tbuf: *u8 = sys_mmap(NX_CHECKPOINT_PATH_CAP)
81 nx_ckpt_make_tmp(path, tbuf)
82 cp.path_buf = pbuf
83 cp.tmp_buf = tbuf
84 cp.last_value = NX_CHECKPOINT_FRESH
85 cp.n_writes = 0
86 cp.n_write_fail = 0
87
88 let len_p: *nx_size = (sys_mmap(NX_SIZEOF_NX_SIZE)) as *nx_size
89 len_p[0] = 0
90 let data: *u8 = sys_read_file(path, len_p)
91 if (data as nx_size) != 0 {
92 let n: nx_int = len_p[0]
93 let v: nx_int = nx_ckpt_parse_i64(data, n)
94 if v >= 0 { cp.last_value = v }
95 }
96
97 return cp
98}
99
100func nx_checkpoint_close(cp: *NxCheckpoint) -> nx_int {
101 cp.last_value = NX_CHECKPOINT_WRITE_FAIL
102 return 0
103}
104
105func nx_checkpoint_get(cp: *NxCheckpoint) -> nx_int {
106 return cp.last_value
107}
108
109func nx_checkpoint_set(cp: *NxCheckpoint, id: nx_int) -> nx_int {
110 let dbuf: *u8 = sys_mmap(NX_BUF_TINY)
111 let dlen: nx_int = nx_strconv_format_i64(id, dbuf)
112 dbuf[dlen] = 10
113 let total: nx_int = dlen + 1
114
115 let fd: nx_fd = __syscall(SYS_OPENAT, AT_FDCWD, cp.tmp_buf as i64, 0x241, 0x1A4, 0, 0)
116 if fd < 0 {
117 cp.n_write_fail = cp.n_write_fail + 1
118 return NX_CHECKPOINT_WRITE_FAIL
119 }
120 var w: nx_int = 0
121 while w < total {
122 let p: *u8 = ((dbuf as nx_size) + w) as *u8
123 let r: nx_int = sys_write(fd, p, total - w)
124 if r <= 0 {
125 sys_close(fd)
126 cp.n_write_fail = cp.n_write_fail + 1
127 return NX_CHECKPOINT_WRITE_FAIL
128 }
129 w = w + r
130 }
131 sys_close(fd)
132
133 let rc: nx_int = __syscall(SYS_RENAMEAT_LOCAL, AT_FDCWD, cp.tmp_buf as i64,
134 AT_FDCWD, cp.path_buf as i64, 0, 0)
135 if rc < 0 {
136 cp.n_write_fail = cp.n_write_fail + 1
137 return NX_CHECKPOINT_RENAME_FAIL
138 }
139 cp.last_value = id
140 cp.n_writes = cp.n_writes + 1
141 return 0
142}
143
144func nx_checkpoint_n_writes(cp: *NxCheckpoint) -> nx_int { return cp.n_writes }
145func nx_checkpoint_n_write_fail(cp: *NxCheckpoint) -> nx_int { return cp.n_write_fail }
146func nx_checkpoint_is_fresh(cp: *NxCheckpoint) -> nx_int {
147 if cp.last_value == NX_CHECKPOINT_FRESH { return 1 }
148 return 0
149}