spawnhandshake.mx raw
1 //go:build linux || darwin
2
3 package runtime
4
5 import "unsafe"
6
7 // Handshake timeout parameters. 100 retries × 50ms = 5 second total.
8 const (
9 handshakeRetries = 100
10 handshakeSleepMs = 50
11 )
12
13 // SpawnHandshake performs a protocol hash exchange over fd immediately after
14 // a cross-binary spawn. Each side sends its hashes then reads the peer's.
15 //
16 // Protocol: each hash is sent as a length-prefixed line: uint8(len) + bytes.
17 // The peer sends the same number of hashes in the same type order.
18 //
19 // On hash mismatch or timeout, returns an error string. Returns "" on success.
20 func SpawnHandshake(fd int32, localHashes []string) string {
21 // Send all local hashes first.
22 for _, h := range localHashes {
23 if !spawnHandshakeWrite(fd, h) {
24 return "spawn handshake: write failed (child may have crashed)"
25 }
26 }
27
28 // Read back one hash per type from the peer.
29 for i, local := range localHashes {
30 remote, ok := spawnHandshakeRead(fd)
31 if !ok {
32 return "spawn handshake: timeout after 5s (child may have crashed)"
33 }
34 if remote != local {
35 return "spawn handshake: protocol version mismatch on type " +
36 itoa(i) + ": expected " + local + ", got " + remote +
37 "; run `moxie install` to update"
38 }
39 }
40 return ""
41 }
42
43 // spawnHandshakeWrite sends a single hash string as uint8(len) + bytes.
44 func spawnHandshakeWrite(fd int32, s string) bool {
45 n := len(s)
46 if n > 255 {
47 n = 255
48 }
49 var hdr [1]byte
50 hdr[0] = byte(n)
51 if moxie_write(fd, unsafe.Pointer(&hdr[0]), 1) != 1 {
52 return false
53 }
54 if n == 0 {
55 return true
56 }
57 written := int32(0)
58 for written < int32(n) {
59 r := moxie_write(fd, unsafe.Pointer(uintptr(unsafe.Pointer(&([]byte(s))[0]))+uintptr(written)), int32(n)-written)
60 if r <= 0 {
61 return false
62 }
63 written += r
64 }
65 return true
66 }
67
68 // spawnHandshakeRead reads a single hash string with retry-and-yield timeout.
69 // Returns ("", false) on timeout.
70 func spawnHandshakeRead(fd int32) (string, bool) {
71 // Read the length byte with retries.
72 var hdr [1]byte
73 retries := int32(0)
74 for {
75 n := moxie_read(fd, unsafe.Pointer(&hdr[0]), 1)
76 if n == 1 {
77 break
78 }
79 if n == 0 {
80 return "", false // EOF
81 }
82 retries++
83 if retries >= handshakeRetries {
84 return "", false
85 }
86 sleepTicks(nanosecondsToTicks(int64(handshakeSleepMs) * 1_000_000))
87 }
88
89 length := int32(hdr[0])
90 if length == 0 {
91 return "", true
92 }
93
94 buf := []byte{:length}
95 got := int32(0)
96 for got < length {
97 n := moxie_read(fd, unsafe.Pointer(uintptr(unsafe.Pointer(&buf[0]))+uintptr(got)), length-got)
98 if n <= 0 {
99 return "", false
100 }
101 got += n
102 }
103 return string(buf), true
104 }
105
106 // itoa converts a small integer to its decimal string representation.
107 func itoa(n int) string {
108 if n == 0 {
109 return "0"
110 }
111 var buf [10]byte
112 i := 10
113 for n > 0 {
114 i--
115 buf[i] = byte('0' + n%10)
116 n /= 10
117 }
118 return string(buf[i:])
119 }
120