code wiki / (root) / nx_apistack_idempotency_candidate_t190.nx

nx_apistack_idempotency_candidate_t190.nx source

↩ module page · 153 lines · 6483 B

1// Retained idempotency evidence. Sequential replay is not an exactly-once transaction: 2// mutation and journal commit remain separate. Missing or malformed evidence is not NEW. 3import "nx_syscalls.nx" 4import "nx_fio.nx" 5const ID_NOT_FOUND:i64=0-1 6const ID_EVIDENCE_ERROR:i64=0-2 7const ID_MISSING:i64=0-3 8const ID_CAPACITY:i64=0-4 9const ID_RESOURCE:i64=0-5 10const ID_SIGNED_MAX:i64=9223372036854775807 11const ID_O_RDONLY:i64=0 12const ID_O_RDWR_APPEND_CREATE:i64=1090 13const ID_O_NONBLOCK:i64=2048 14const ID_O_NOFOLLOW:i64=131072 15const ID_O_CLOEXEC:i64=524288 16struct NxIdEvidence{ 17 buffer:*u8,bytes:i64,allocated:i64,result:*u8,result_bytes:i64,state:i64,system_code:i64 18} 19func id_evidence_init(r:*NxIdEvidence)->i64{ 20 r.buffer=0 as *u8;r.bytes=0;r.allocated=0;r.result=0 as *u8 21 r.result_bytes=0;r.state=ID_EVIDENCE_ERROR;r.system_code=0;return 0 22} 23func id_evidence_close(r:*NxIdEvidence)->i64{ 24 if (r.buffer as i64)>0{sys_munmap_direct(r.buffer,r.allocated)} 25 r.buffer=0 as *u8;r.result=0 as *u8;r.allocated=0;return 0 26} 27func id_field_valid(p:*u8,n:i64,key:i64)->i64{ 28 if n<0 || (p as i64)<=0{return 0} 29 if key==1&&n==0{return 0} 30 var i:i64=0;while i<n{ 31 if p[i]==0 as u8 || p[i]==9 as u8 || p[i]==10 as u8 || p[i]==13 as u8{return 0};i=i+1 32 };return 1 33} 34func id_read_evidence(path:*u8,key:*u8,key_n:i64,r:*NxIdEvidence)->i64{ 35 id_evidence_init(r) 36 if (path as i64)<=0 || id_field_valid(key,key_n,1)==0{return r.state} 37 let stat:*u8=sys_mmap_try(NX_STAT_X64_BYTES) 38 if (stat as i64)<=0{r.state=ID_RESOURCE;return r.state} 39 let fd:i64=__syscall(257,AT_FDCWD,path,ID_O_RDONLY|ID_O_NONBLOCK|ID_O_NOFOLLOW|ID_O_CLOEXEC,0,0,0) 40 if fd<0{ 41 sys_munmap_direct(stat,NX_STAT_X64_BYTES);r.system_code=fd 42 if fd==0-2{r.state=ID_MISSING};return r.state 43 } 44 var rc:i64=sys_flock(fd,SYS_LOCK_EX|SYS_LOCK_NB) 45 if rc==0{rc=sys_fstat_fd(fd,stat)} 46 var n:i64=0 47 if rc==0{ 48 let mode:i64=*((stat+NX_STAT_X64_MODE_OFFSET) as *i64) 49 n=*((stat+NX_STAT_X64_SIZE_OFFSET) as *i64) 50 if (mode&NX_STAT_TYPE_MASK)!=NX_STAT_REGULAR_FILE || n<0 || n==ID_SIGNED_MAX{rc=FIO_EINVAL} 51 } 52 if rc==0{ 53 r.allocated=n+1;r.buffer=sys_mmap_try(r.allocated) 54 if (r.buffer as i64)<=0{r.buffer=0 as *u8;r.allocated=0;r.state=ID_RESOURCE;rc=0-12} 55 } 56 var got:i64=0 57 while rc==0&&got<n{ 58 let z:i64=sys_read(fd,r.buffer+got,n-got) 59 if z==FIO_EINTR{continue} 60 if z<=0{rc=z;if z==0{rc=FIO_EIO}}else{got=got+z} 61 } 62 let unlock:i64=sys_flock(fd,SYS_LOCK_UN);let closed:i64=sys_close(fd) 63 sys_munmap_direct(stat,NX_STAT_X64_BYTES) 64 if rc==0&&unlock!=0{rc=unlock};if rc==0&&closed!=0{rc=closed} 65 if rc!=0{r.system_code=rc;id_evidence_close(r);return r.state} 66 r.bytes=n;r.buffer[n]=0 as u8 67 if n>0{if r.buffer[n-1]!=10 as u8{r.system_code=FIO_EBADMSG;return r.state}} 68 var start:i64=0;var found:i64=0;var chosen:i64=0;var chosen_n:i64=0 69 while start<n{ 70 var end:i64=start;while end<n&&r.buffer[end]!=10 as u8{end=end+1} 71 if end>start&&r.buffer[start]!=35 as u8{ 72 var tab:i64=0-1;var i:i64=start 73 while i<end{ 74 let c:u8=r.buffer[i] 75 if c==0 as u8||c==13 as u8{r.system_code=FIO_EBADMSG;return r.state} 76 if c==9 as u8{if tab>=0{r.system_code=FIO_EBADMSG;return r.state};tab=i} 77 i=i+1 78 } 79 if tab<=start{r.system_code=FIO_EBADMSG;return r.state} 80 if found==0&&tab-start==key_n{ 81 var same:i64=1;i=0;while i<key_n{if r.buffer[start+i]!=key[i]{same=0};i=i+1} 82 if same==1{found=1;chosen=tab+1;chosen_n=end-tab-1} 83 } 84 };start=end+1 85 } 86 if found==0{r.state=ID_NOT_FOUND;return r.state} 87 r.result=r.buffer+chosen;r.result_bytes=chosen_n;r.result[chosen_n]=0 as u8 88 r.state=chosen_n;return r.state 89} 90// Legacy caller-buffer API: success returns complete result length; -1 alone means unseen. 91// Other negative values require reconciliation; no truncated success or out-of-bounds terminator. 92func id_lookup(path:*u8,key:*u8,key_n:i64,out:*u8,cap:i64)->i64{ 93 if cap<=0||(out as i64)<=0{return ID_CAPACITY};out[0]=0 as u8 94 let r:*NxIdEvidence=sys_mmap_try(__size_of(NxIdEvidence)) as *NxIdEvidence 95 if (r as i64)<=0{return ID_RESOURCE} 96 var rc:i64=id_read_evidence(path,key,key_n,r) 97 if rc>=0{ 98 if rc>=cap{rc=ID_CAPACITY}else{ 99 var i:i64=0;while i<rc{out[i]=r.result[i];i=i+1};out[rc]=0 as u8 100 } 101 } 102 id_evidence_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEvidence));return rc 103} 104func id_seen(path:*u8,key:*u8,key_n:i64)->i64{ 105 let r:*NxIdEvidence=sys_mmap_try(__size_of(NxIdEvidence)) as *NxIdEvidence 106 if (r as i64)<=0{return ID_RESOURCE} 107 let rc:i64=id_read_evidence(path,key,key_n,r) 108 id_evidence_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEvidence)) 109 if rc>=0{return 1};if rc==ID_NOT_FOUND{return 0};return rc 110} 111// A lock protects the complete append against cooperating recorders. It does not cover 112// a caller's prior lookup or mutation, nor make a crashed mutation exactly-once. 113func id_record(path:*u8,key:*u8,key_n:i64,result:*u8,result_n:i64)->i64{ 114 if (path as i64)<=0||id_field_valid(key,key_n,1)==0||id_field_valid(result,result_n,0)==0{return FIO_EINVAL} 115 if key_n>ID_SIGNED_MAX-result_n-2{return ID_CAPACITY} 116 let bytes:i64=key_n+result_n+2 117 let row:*u8=sys_mmap_try(bytes) 118 let stat:*u8=sys_mmap_try(NX_STAT_X64_BYTES) 119 if (row as i64)<=0||(stat as i64)<=0{ 120 if (row as i64)>0{sys_munmap_direct(row,bytes)} 121 if (stat as i64)>0{sys_munmap_direct(stat,NX_STAT_X64_BYTES)} 122 return ID_RESOURCE 123 } 124 var i:i64=0;while i<key_n{row[i]=key[i];i=i+1};row[key_n]=9 as u8 125 i=0;while i<result_n{row[key_n+1+i]=result[i];i=i+1};row[bytes-1]=10 as u8 126 let fd:i64=__syscall(257,AT_FDCWD,path,ID_O_RDWR_APPEND_CREATE|ID_O_NONBLOCK|ID_O_NOFOLLOW|ID_O_CLOEXEC,MODE_0644,0,0) 127 var rc:i64=fd 128 if fd>=0{ 129 rc=sys_flock(fd,SYS_LOCK_EX) 130 if rc==0{rc=sys_fstat_fd(fd,stat)} 131 if rc==0{ 132 let mode:i64=*((stat+NX_STAT_X64_MODE_OFFSET) as *i64) 133 let n:i64=*((stat+NX_STAT_X64_SIZE_OFFSET) as *i64) 134 if (mode&NX_STAT_TYPE_MASK)!=NX_STAT_REGULAR_FILE||n<0{rc=FIO_EINVAL} 135 if rc==0&&n>0{ 136 if sys_lseek(fd,n-1,0)!=n-1{rc=FIO_EIO}else{ 137 let got:i64=sys_read(fd,stat,1);if got!=1||stat[0]!=10 as u8{rc=FIO_EBADMSG} 138 } 139 } 140 } 141 var written:i64=0 142 while rc==0&&written<bytes{ 143 let z:i64=sys_write(fd,row+written,bytes-written) 144 if z==FIO_EINTR{continue} 145 if z<=0{rc=z;if z==0{rc=FIO_EIO}}else{written=written+z} 146 } 147 if rc==0{rc=sys_fsync(fd)} 148 let unlock:i64=sys_flock(fd,SYS_LOCK_UN);let closed:i64=sys_close(fd) 149 if rc==0&&unlock!=0{rc=unlock};if rc==0&&closed!=0{rc=closed} 150 } 151 sys_munmap_direct(row,bytes);sys_munmap_direct(stat,NX_STAT_X64_BYTES) 152 if rc==0{return 1};return rc 153}