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