nx_id_epoch_prepare_gate_t293.nx source
↩ module page · 164 lines · 11475 B
1// nx_id_epoch_prepare_gate_t293.nx -- Tests id epoch preparation and resolution under various crash and failure scenarios.
2import "nx_apistack_idempotency_epoch_candidate_t293.nx"
3import "nx_gate_verdict.nx"
4import "nx_fsops_lib.nx"
5func eg_write(p:*u8,b:*u8,n:i64)->i64{
6 let fd:i64=sys_openat_wr(p,MODE_0600);if fd<0{return fd}
7 let r:*NxFileWriteResult=sys_mmap_try(__size_of(NxFileWriteResult)) as *NxFileWriteResult
8 let rc:i64=fio_write_sync_fd(fd,b,n,r);sys_munmap_direct(r as *u8,__size_of(NxFileWriteResult));return rc
9}
10func eg_run(root:*u8,s:*NxIdEpochSpec)->i64{
11 let r:*NxIdEpochPrepared=sys_mmap_try(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared
12 let state:i64=id_epoch_prepare(root,s,r);ie_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared))
13 if state==ID_EP_PREPARED_SYNCED{return 0}
14 if state==ID_EP_BUSY{return 2};if state==ID_EP_INCOMPLETE{return 3};return 4
15}
16
17// These child crash fixtures reproduce persistence states, not injected production fsync errors.
18func eg_death_case(root:*u8,s:*NxIdEpochSpec,phase:i64,c:*i64)->i64{
19 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared
20 if ie_build(root,s,r)!=0{return 1}
21 let child:i64=sys_fork()
22 if child==0{
23 if sys_mkdir(r.directory,ID_EP_PRIVATE_MODE)!=0{sys_exit(2)}
24 if phase>0{if ie_create_file(r.ledger,"",0)!=0{sys_exit(3)}}
25 if phase>1{
26 let fd:i64=sys_openat_exclusive(r.manifest,MODE_0600);if fd<0{sys_exit(4)}
27 var extent:i64=r.definition_bytes
28 if phase==2{extent=extent-1}
29 if sys_write(fd,r.definition,extent)!=extent{sys_exit(5)}
30 // Intentionally die with open unsynced definition: no helper completion was acknowledged.
31 }
32 nx_kill(osp_selfpid(),9);sys_exit(6)
33 }
34 let status:*i64=sys_mmap(8) as *i64
35 let waited:i64=sys_wait4(child,status,0)
36 gv_kv("crash_phase",phase);gv_kv("crash_signal",wait_term_signal(status[0]))
37 gv_check("controlled child died after selected persistence phase",child>0&&waited==child&&wait_term_signal(status[0])==9,c)
38 ie_close(r)
39 let resolved:i64=id_epoch_resolve(root,s,r)
40 if phase<3{gv_check("partial crash state cannot resolve prepared",resolved==ID_EP_INCOMPLETE,c)}
41 else{gv_check("complete unsynced bytes are observed only not active",resolved==ID_EP_PREPARED_OBSERVED,c)}
42 ie_close(r)
43 let replay:i64=id_epoch_prepare(root,s,r)
44 if phase<3{gv_check("partial crash replay stays incomplete no overwrite",replay==ID_EP_INCOMPLETE,c)}
45 else{gv_check("explicit replay revalidates and resyncs complete bytes",replay==ID_EP_PREPARED_SYNCED&&r.created==0,c)}
46 ie_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared));sys_munmap_direct(status as *u8,8);return 0
47}
48func eg_failures(root:*u8,s:*NxIdEpochSpec,c:*i64)->i64{
49 s.epoch="crash-mkdir";eg_death_case(root,s,0,c)
50 s.epoch="crash-ledger";eg_death_case(root,s,1,c)
51 s.epoch="crash-partial-definition";eg_death_case(root,s,2,c)
52 s.epoch="crash-complete-unsynced";eg_death_case(root,s,3,c)
53 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared
54 let status:*i64=sys_mmap(8) as *i64
55 let limit:*i64=sys_mmap(16) as *i64;limit[0]=0;limit[1]=0
56 s.epoch="allocation-refusal"
57 let child:i64=sys_fork()
58 if child==0{
59 if nx_prlimit(0,RLIMIT_AS,limit as *u8,0 as *u8)!=0{sys_exit(2)}
60 let rc:i64=id_epoch_prepare(root,s,r)
61 var good:i64=0;if rc==ID_RESOURCE&&r.system_code==-12{good=1}
62 ie_close(r);if good==1{sys_exit(0)};sys_exit(3)
63 }
64 let waited:i64=sys_wait4(child,status,0)
65 gv_check("actual child address-space exhaustion returns resource refusal",child>0&&waited==child&&wait_status_rc(status[0])==0,c)
66 gv_check("allocation failure did not create epoch",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r)
67 // Adversarial namespace replacement demonstrates the unresolved full-path lock gap.
68 s.epoch="ancestor-replacement"
69 let prepared:i64=id_epoch_prepare(root,s,r)
70 let held:i64=ie_open_directory(r.directory);let locked:i64=sys_flock(held,SYS_LOCK_EX|SYS_LOCK_NB)
71 var movedlen:i64=0;let moved:*u8=ie_path(root,"replaced-directory-retained",&movedlen)
72 let renamed:i64=sys_renameat(r.directory,moved);let made:i64=sys_mkdir(r.directory,ID_EP_PRIVATE_MODE)
73 let ledger:i64=ie_create_file(r.ledger,"",0);let manifest:i64=ie_create_file(r.manifest,r.definition,r.definition_bytes)
74 let observed:i64=ie_verify_files(r)
75 let other:i64=ie_open_directory(r.directory);let secondlock:i64=sys_flock(other,SYS_LOCK_EX|SYS_LOCK_NB)
76 gv_check("existing full-path read can cross held-directory identity: defect reproduced",prepared==ID_EP_PREPARED_SYNCED&&locked==0&&renamed==0&&made==0&&ledger==0&&manifest==0&&observed==ID_EP_PREPARED_OBSERVED&&secondlock==0,c)
77 sys_flock(other,SYS_LOCK_UN);sys_close(other);sys_flock(held,SYS_LOCK_UN);sys_close(held)
78 ie_close(r);sys_munmap_direct(moved,movedlen);sys_munmap_direct(limit as *u8,16);sys_munmap_direct(status as *u8,8);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared))
79 return 0
80}
81
82func main()->i64{
83 let c:*i64=gv_ctr();gv_head("EXPLICIT EPOCH PREPARATION ONLY")
84 var ln:i64=0;let legacy:*u8=sys_read_file("../knowledge/status/fswrite_idem.jrnl",&ln)
85 if (legacy as i64)<=0||ln<=0{return 2}
86 let root:*u8=sys_mmap(160);let prefix:*u8="knowledge/gates/epoch-prepare-t293-"
87 let pn:i64=ie_len(prefix);ie_copy(root,0,prefix,pn);let end:i64=nxi_buf(root,pn,osp_selfpid());root[end]=0 as u8
88 gv_check("fresh isolated epoch registry root",sys_mkdir(root,ID_EP_PRIVATE_MODE)==0,c)
89 let snaplen:*i64=sys_mmap(8) as *i64;let snapshot:*u8=ie_path(root,"legacy-snapshot",snaplen)
90 gv_check("actual legacy snapshot retained without modifying original",eg_write(snapshot,legacy,ln)==0,c)
91 let sha:*u8=sys_mmap(ID_EP_HASH_HEX+1);gv_check("native predecessor digest computed",ie_hash_hex(legacy,ln,sha)==0,c)
92 gv_kv("predecessor_snapshot_bytes",ln);gv_puts("predecessor_snapshot_sha256=");gv_puts(sha);gv_puts("\nfixture_root=");gv_puts(root);gv_puts("\n")
93 let s:*NxIdEpochSpec=sys_mmap(__size_of(NxIdEpochSpec)) as *NxIdEpochSpec
94 s.domain="fswrite";s.epoch="new-work-test";s.creation="create-request-1";s.creation_sha=sha
95 s.predecessor="legacy";s.snapshot_path=snapshot;s.snapshot_sha=sha;s.snapshot_bytes=ln
96 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared
97 gv_check("null output boundary refuses",id_epoch_prepare(root,s,0 as *NxIdEpochPrepared)==ID_EP_INVALID,c)
98 gv_check("null specification boundary refuses",id_epoch_resolve(root,0 as *NxIdEpochSpec,r)==ID_EP_INVALID,c);ie_close(r)
99 gv_check("resolve absent epoch does not create it",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r)
100 gv_check("explicit preparation completes synced files",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED&&r.created==1,c);ie_close(r)
101 gv_check("same creation identity replays existing prepared definition",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED&&r.created==0,c);ie_close(r)
102 gv_check("resolve observes prepared without claiming active execution",id_epoch_resolve(root,s,r)==ID_EP_PREPARED_OBSERVED,c);ie_close(r)
103 s.creation="changed-request"
104 gv_check("changed creation identity cannot reuse epoch",id_epoch_prepare(root,s,r)<0,c);ie_close(r)
105 s.creation="create-request-1";s.predecessor="changed-predecessor"
106 gv_check("changed predecessor cannot resolve same epoch",id_epoch_resolve(root,s,r)<0,c);ie_close(r)
107 s.predecessor="legacy";s.domain="other-domain"
108 gv_check("different domain cannot alias epoch",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r)
109 s.domain="fswrite";s.snapshot_bytes=ln-1
110 gv_check("wrong predecessor extent refuses creation",id_epoch_prepare(root,s,r)==ID_EP_CONFLICT,c);ie_close(r);s.snapshot_bytes=ln
111 s.creation_sha="xyz"
112 gv_check("malformed creation digest refused",id_epoch_resolve(root,s,r)==ID_EP_INVALID,c);ie_close(r);s.creation_sha=sha
113 s.epoch="orphan"
114 ie_build(root,s,r);sys_mkdir(r.directory,ID_EP_PRIVATE_MODE);ie_close(r)
115 gv_check("orphan namespace is not silently initialized",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r)
116 s.epoch="new-work-test"
117 id_epoch_resolve(root,s,r)
118 let held:i64=sys_openat_directory(r.directory);sys_flock(held,SYS_LOCK_EX|SYS_LOCK_NB)
119 let child:i64=sys_fork()
120 if child==0{
121 // Drop inherited lock reference before independently trying to resolve.
122 sys_close(held)
123 let z:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared
124 let got:i64=id_epoch_resolve(root,s,z);ie_close(z);if got==ID_EP_BUSY{sys_exit(0)};sys_exit(1)
125 }
126 let status:*i64=sys_mmap(8) as *i64;let waited:i64=sys_wait4(child,status,0)
127 gv_check("other process resolve refuses held preparation lock",child>0&&waited==child&&wait_status_rc(status[0])==0,c)
128 sys_flock(held,SYS_LOCK_UN);sys_close(held);ie_close(r)
129 s.epoch="race"
130 let first:i64=sys_fork();if first==0{sys_exit(eg_run(root,s))}
131 let second:i64=sys_fork();if second==0{sys_exit(eg_run(root,s))}
132 var a:i64=9;var b:i64=9
133 if first>0{if sys_wait4(first,status,0)==first{a=wait_status_rc(status[0])}}
134 if second>0{if sys_wait4(second,status,0)==second{b=wait_status_rc(status[0])}}
135 gv_kv("first_creator_exit",a);gv_kv("second_creator_exit",b)
136 gv_check("concurrent create has a completed owner and no invalid success",((a==0||b==0)&&(a==0||a==2||a==3)&&(b==0||b==2||b==3)),c)
137 gv_check("concurrent result has exact expected definition",id_epoch_resolve(root,s,r)==ID_EP_PREPARED_OBSERVED,c);ie_close(r)
138 s.epoch="different-creation-hash";s.creation_sha="0000000000000000000000000000000000000000000000000000000000000000"
139 gv_check("valid independent creation hash can prepare",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED,c);ie_close(r)
140 s.creation_sha=sha
141 gv_check("creation fingerprint change cannot resolve",id_epoch_resolve(root,s,r)<0,c);ie_close(r)
142 s.epoch="wrong-snapshot";s.snapshot_sha="0000000000000000000000000000000000000000000000000000000000000000"
143 gv_check("incorrect predecessor digest refuses before creation",id_epoch_prepare(root,s,r)==ID_EP_CONFLICT,c);ie_close(r);s.snapshot_sha=sha
144 s.epoch="missing-ledger"
145 id_epoch_prepare(root,s,r)
146 let movedlen:*i64=sys_mmap(8) as *i64;let moved:*u8=ie_path(r.directory,"ledger.retained",movedlen)
147 sys_renameat(r.ledger,moved);ie_close(r)
148 gv_check("missing prepared ledger does not initialize on resolve",id_epoch_resolve(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r)
149 gv_check("missing prepared ledger does not initialize on prepare replay",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r)
150 s.epoch="symlink"
151 ie_build(root,s,r);sys_symlinkat(".",r.directory);ie_close(r)
152 let symlink_state:i64=id_epoch_resolve(root,s,r)
153 gv_check("namespace symlink is refused without following it",symlink_state==ID_EP_INCOMPLETE&&(r.system_code==-20||r.system_code==-40),c);ie_close(r)
154 s.epoch="new-work-test";id_epoch_resolve(root,s,r)
155 eg_write(r.manifest,"bad",3);ie_close(r)
156 gv_check("malformed definition refuses resolve",id_epoch_resolve(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r)
157 gv_check("malformed definition is not overwritten by prepare",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r)
158 let ev:*NxIdEvidence=sys_mmap(__size_of(NxIdEvidence)) as *NxIdEvidence
159 let legacy_state:i64=id_read_evidence(snapshot,"unseen-proof-key",16,ev)
160 gv_check("legacy malformed ambiguity remains refusal",legacy_state==ID_EVIDENCE_ERROR&&ev.system_code==FIO_EBADMSG,c)
161 id_evidence_close(ev)
162 eg_failures(root,s,c)
163 return gv_verdict("epoch-prepare",c,"Isolated PREPARED only; no activation, admission, writer routing, live mutation or exactly-once claim")
164}