code wiki / _hdl_build / nx_deploy_marker.nx

nx_deploy_marker.nx source

↩ module page · 55 lines · 3121 B

1// nx_deploy_marker.nx -- the SELF-SAFE deploy primitive (CAP-DEPLOY-MARKER). A deploy targeting a LIVE service -- 2// especially the mgmt API deploying ITSELF -- must NOT restart the process that is serving the deploy request (that 3// kills the response mid-flight and can wedge the control plane). Instead the deploy STAGES the new binary + REQUESTS 4// a restart via a per-service marker; the SUPERVISOR (nx_hostctl guard), on its next poll, CONSUMES the marker and 5// restarts the service. Decoupled restart = self-safe (the daemon never kills itself in-request) AND never-brick-safe 6// (the supervisor owns the atomic swap + can roll back). Consumed EXACTLY ONCE => no restart loop. 7// marker file <dir>/restart_<service>.req : "1" = restart pending, "0" = already consumed 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10 11// build "<dir>/restart_<service>.req" into out (NUL-terminated); returns length. 12func dm_marker_path(out: *u8, dir: *u8, service: *u8) -> i64 { 13 var o: i64 = 0 14 var i: i64 = 0; while dir[i] != (0 as u8) { out[o] = dir[i]; o = o + 1; i = i + 1 } 15 out[o] = 47 as u8; o = o + 1 // '/' 16 let pfx: *u8 = "restart_" as *u8; i = 0; while pfx[i] != (0 as u8) { out[o] = pfx[i]; o = o + 1; i = i + 1 } 17 i = 0; while service[i] != (0 as u8) { out[o] = service[i]; o = o + 1; i = i + 1 } 18 let sfx: *u8 = ".req" as *u8; i = 0; while sfx[i] != (0 as u8) { out[o] = sfx[i]; o = o + 1; i = i + 1 } 19 out[o] = 0 as u8 20 return o 21} 22 23// a deploy REQUESTS the supervisor restart `service` (after staging its new binary). Writes "1" to the marker. 24func dm_request_restart(dir: *u8, service: *u8) -> i64 { 25 let path: *u8 = sys_mmap(512); dm_marker_path(path, dir, service) 26 let fd: i64 = sys_openat_wr(path, 0x1a4) 27 if fd < 0 { return 0 - 1 } 28 sys_write(fd, "1\n" as *u8, 2); sys_close(fd) 29 return 1 30} 31 32// is a restart pending for `service`? (read-only; does not consume) -- for status surfaces. 33func dm_is_pending(dir: *u8, service: *u8) -> i64 { 34 let path: *u8 = sys_mmap(512); dm_marker_path(path, dir, service) 35 let fd: i64 = sys_openat_rd(path) 36 if fd < 0 { return 0 } 37 let buf: *u8 = sys_mmap(8); let nr: i64 = sys_read(fd, buf, 8); sys_close(fd) 38 if nr <= 0 { return 0 } 39 if buf[0] == (49 as u8) { return 1 } 40 return 0 41} 42 43// the SUPERVISOR checks + CONSUMES: if a restart is pending, mark it done and return 1 (=> perform the restart NOW). 44// Consumed exactly once -- a second call returns 0, so a single request never becomes a restart loop. 45func dm_check_and_consume(dir: *u8, service: *u8) -> i64 { 46 let path: *u8 = sys_mmap(512); dm_marker_path(path, dir, service) 47 let fd: i64 = sys_openat_rd(path) 48 if fd < 0 { return 0 } 49 let buf: *u8 = sys_mmap(8); let nr: i64 = sys_read(fd, buf, 8); sys_close(fd) 50 if nr <= 0 { return 0 } 51 if buf[0] != (49 as u8) { return 0 } // not "1" -> already consumed 52 let fd2: i64 = sys_openat_wr(path, 0x1a4) 53 if fd2 >= 0 { sys_write(fd2, "0\n" as *u8, 2); sys_close(fd2) } // consume (same length overwrite) 54 return 1 55}