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}