Translate wopQueryPathInfo, wopQueryMissing

This commit is contained in:
Emery Hemingway 2023-06-10 11:37:15 +01:00
parent 6bdd8fafad
commit 9a0d2a22ec
6 changed files with 407 additions and 389 deletions

View File

@ -2,21 +2,47 @@
An actor for interacting with the [Nix](https://nixos.org/) daemon via the [Syndicated Actor Model](https://syndicate-lang.org/).
See [protocol.prs](./protocol.prs) for the Syndicate protocol [schema](https://preserves.dev/preserves-schema.html).
*This is only a proof-of-concept and is not yet useful.*
## Example configuration
A demo script for the [Syndicate server](https://git.syndicate-lang.org/syndicate-lang/syndicate-rs), see https://synit.org/book/operation/scripting.html
```
? <nixspace ?nixspace> $nixspace [
? <instantiate "let pkgs = import <nixpkgs> {}; in pkgs.hello" { } ?drv> [
? <realise $drv ?outputs> [ ]
? <realise $drv ?outputs> [
$log ! <log "-" { "hello": $outputs }>
]
]
? <eval "3 * 4" {} _> []
? <eval "builtins.getEnv \"PATH\"" {impure: ""} _> []
? <eval "3 * 4" {} ?result> [
$log ! <log "-" { "nix eval 3 * 4": $result }>
]
? <eval "builtins.getEnv \"PATH\"" {impure: ""} ?result> [
$log ! <log "-" { "nix impure path": $result }>
]
? <missing ["/nix/store/p7fnjrbvmpwl192ir8p2ixfym68j7sgv-invidious-unstable-2023-05-08"] _ ?subs _ ?dlSize ?narSize> [
$log ! <log "-" { invidious-unstable-2023-05-08: {
substitutes: $subs
downloadSize: $dlSize
narSize: $narSize
} }>
]
? <path-info "/nix/store/jhgh02lyizd1kyl71brvc01ygsmgi40a-tzdata-2023c"
?deriver ?narHash _ _ ?narSize _ ?sigs _> [
$log ! <log "-" { tzdata-2023c: {
deriver: $deriver
narHash: $narHash
narSize: $narSize
sigs: $sigs
} }>
? ?any [
$log ! <log "-" { nix: $any }>
]
$config [
@ -24,10 +50,12 @@ An actor for interacting with the [Nix](https://nixos.org/) daemon via the [Synd
? <service-object <daemon nix_actor> ?cap> [
$cap {
dataspace: $nixspace
daemon-socket: "/nix/var/nix/daemon-socket/socket"
listen-socket: "/tmp/translator.worker.nix.socket"
}
]
<daemon nix_actor {
argv: "/usr/local/nix_actor"
argv: "/bin/nix_actor"
protocol: application/syndicate
}>
]

View File

@ -1,4 +1,4 @@
version = "20230607"
version = "20230610"
author = "Emery Hemingway"
description = "Syndicated Nix Actor"
license = "Unlicense"

View File

@ -22,8 +22,9 @@ ActionStop = <stop @id int> .
ActionResult = <result @id int @type int @fields Fields> .
; TODO: why not make target a singleton?
Missing = <missing @targets #{string} @willBuild #{string} @willSubstitute #{string} @unknown #{string} @downloadSize int @narSize int> .
Missing = <missing @targets [string ...] @willBuild #{string} @willSubstitute #{string} @unknown #{string} @downloadSize int @narSize int> .
; TODO keep a few critical fields and move the rest into a dictionary
PathInfo = <path-info
@path string
@deriver string

View File

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, httpclient, json, os, osproc, parseutils, strutils, tables]
import std/[asyncdispatch, httpclient, json, osproc, parseutils, strutils, tables]
import preserves, preserves/jsonhooks
import syndicate
from syndicate/protocols/dataspace import Observe
@ -76,7 +76,7 @@ proc eval(eval: Eval): Value =
var js = parseJson(execOutput)
result = js.toPreserve
proc bootNixFacet(ds: Ref; turn: var Turn): Facet =
proc bootNixFacet(turn: var Turn; ds: Ref): Facet =
# let store = openStore()
result = inFacet(turn) do (turn: var Turn):
@ -110,18 +110,22 @@ proc bootNixFacet(ds: Ref; turn: var Turn): Facet =
type
RefArgs {.preservesDictionary.} = object
dataspace: Ref
SocketArgs {.preservesDictionary.} = object
ClientSideArgs {.preservesDictionary.} = object
`listen-socket`: string
DaemonSideArgs {.preservesDictionary.} = object
`daemon-socket`: string
proc bootNixActor(root: Ref; turn: var Turn) =
connectStdio(root, turn)
during(turn, root, ?RefArgs) do (ds: Ref):
discard bootNixFacet(ds, turn)
during(turn, root, ?SocketArgs) do (path: string):
removeFile(path)
asyncCheck(turn, emulateSocket(path))
do:
removeFile(path)
discard bootNixFacet(turn, ds)
during(turn, root, ?ClientSideArgs) do (socketPath: string):
bootClientSide(turn.facet, ds, socketPath)
during(turn, root, ?DaemonSideArgs) do (socketPath: string):
bootDaemonSide(turn, ds, socketPath)
initNix() # Nix lib isn't actually being used but it's nice to know that it links.
runActor("main", bootNixActor)

View File

@ -13,7 +13,7 @@ type
`outputs`*: seq[string]
Missing* {.preservesRecord: "missing".} = object
`targets`*: HashSet[string]
`targets`*: seq[string]
`willBuild`*: HashSet[string]
`willSubstitute`*: HashSet[string]
`unknown`*: HashSet[string]

View File

@ -1,11 +1,12 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asyncnet, os, sets, strtabs, strutils]
import std/[algorithm, asyncdispatch, asyncnet, os, sets, strtabs, strutils]
from std/nativesockets import AF_INET, AF_UNIX, SOCK_STREAM, Protocol
import preserves
import ./protocol, ./store
import preserves, syndicate
from syndicate/protocols/dataspace import Observe
import ./protocol
{.pragma: workerProtocol, importc, header: "worker-protocol.hh".}
@ -15,7 +16,7 @@ proc `$`(w: Word): string = toHex(w)
const
WORKER_MAGIC_1 = 0x6E697863
WORKER_MAGIC_2 = 0x6478696F
PROTOCOL_VERSION = 256 or 35
PROTOCOL_VERSION = 0x100 or 35
STDERR_NEXT = 0x6F6C6d67
STDERR_READ = 0x64617461
@ -65,400 +66,384 @@ const
type
ProtocolError = object of IOError
Version = uint16
StringSeq = seq[string]
StringSet = HashSet[string]
Session = ref object
client, daemon: AsyncSocket
socket: AsyncSocket
buffer: seq[Word]
version: Version
Observe = dataspace.Observe[Ref]
func major(version: Version): uint16 = version and 0xff00
func minor(version: Version): uint16 = version and 0x00ff
proc close(session: Session) =
close(session.socket)
reset(session.buffer)
proc send(session: Session; words: varargs[Word]): Future[void] =
if session.buffer.len < words.len:
session.buffer.setLen(words.len)
for i, word in words: session.buffer[i] = word
send(session.socket, addr session.buffer[0], words.len shl 3)
proc send(session: Session; s: string): Future[void] =
let wordCount = 1 + ((s.len + 7) shr 3)
if session.buffer.len < wordCount: setLen(session.buffer, wordCount)
session.buffer[0] = Word s.len
if s != "":
session.buffer[pred wordCount] = 0x00
copyMem(addr session.buffer[1], unsafeAddr s[0], s.len)
send(session.socket, addr session.buffer[0], wordCount shl 3)
proc send(session: Session; ss: StringSeq|StringSet): Future[void] =
## Send a set of strings. The set is sent as a contiguous buffer.
session.buffer[0] = Word ss.len
var off = 1
for s in ss:
let
stringWordLen = (s.len + 7) shr 3
bufferWordLen = off+1+stringWordLen
if session.buffer.len < bufferWordLen:
setLen(session.buffer, bufferWordLen)
session.buffer[off] = Word s.len
session.buffer[off+stringWordLen] = 0 # clear the aligning bits
inc(off)
copyMem(addr session.buffer[off], unsafeAddr s[0], s.len)
inc(off, stringWordLen)
send(session.socket, addr session.buffer[0], off shl 3)
proc recvWord(sock: AsyncSocket): Future[Word] {.async.} =
var w: Word
let n = await recvInto(sock, addr w, sizeof(Word))
if n != sizeof(Word): raise newException(ProtocolError, "short read")
return w
proc recvWord(session: Session): Future[Word] =
recvWord(session.socket)
proc discardWords(session: Session; n: int): Future[void] {.async.} =
if session.buffer.len < n: setLen(session.buffer, n)
let byteCount = n shl 3
let n = await recvInto(session.socket, addr session.buffer[0], byteCount)
if n != byteCount:
raise newException(ProtocolError, "short read")
proc recvString(socket: AsyncSocket): Future[string] {.async.} =
let stringLen = int (await recvWord(socket))
if stringLen > 0:
var s = newString((stringLen + 7) and (not 7))
let n = await recvInto(socket, addr s[0], s.len)
if n != s.len:
raise newException(ProtocolError, "short read")
setLen(s, stringLen)
return s
return ""
proc recvString(session: Session): Future[string] =
recvString(session.socket)
proc recvStringSeq(session: Session): Future[StringSeq] {.async.} =
let count = int(await recvWord(session.socket))
var strings = newSeq[string](count)
for i in 0..<count: strings[i] = await recvString(session)
return strings
proc recvStringSet(session: Session): Future[StringSet] {.async.} =
let count = int(await recvWord(session.socket))
var strings = initHashSet[string](count)
for i in 0..<count: incl(strings, await recvString(session))
return strings
proc recvError(session: Session) {.async.} =
discard #[typ]# await recvString(session)
discard #[lvl]# await recvWord(session)
discard #[name]# await recvString(session)
discard #[msg]# await recvString(session)
discard #[havePos]# await recvWord(session)
let nrTraces = await recvWord(session)
for i in 1..nrTraces:
discard #[havPos]# await recvWord(session)
discard #[msg]# await recvString(session)
proc recvFields(session: Session) {.async.} =
let count = await recvWord(session)
for i in 0..<count:
let typ = await recvWord(session)
case typ
of 0: discard await recvWord(session)
of 1: discard await recvString(session)
else: raiseAssert "unknown field type " & $typ
proc recvWork(session: Session) {.async.} =
while true:
let word = await recvWord(session)
case word
of STDERR_WRITE:
discard await recvString(session)
of STDERR_READ:
await send(session, "")
of STDERR_ERROR:
await recvError(session)
of STDERR_NEXT:
discard await recvString(session)
of STDERR_START_ACTIVITY:
discard await recvWord(session) # id
discard await recvWord(session) # level
discard await recvWord(session) # type
discard await recvString(session) # text
await recvFields(session) # fields
discard await recvWord(session) # parent
of STDERR_STOP_ACTIVITY:
discard await recvWord(session) # id
of STDERR_RESULT:
var act: ActionResult
discard await recvWord(session) # id
discard await recvWord(session) # type
await recvFields(session) # fields
of STDERR_LAST:
break
else:
raise newException(ProtocolError, "unknown work verb " & $word)
proc daemonSocketPath: string =
getEnv(
"NIX_DAEMON_SOCKET_PATH",
"/nix/var/nix/daemon-socket/socket")
proc send(session: Session; sock: AsyncSocket; words: varargs[Word]): Future[void] =
for i, word in words: session.buffer[i] = word
send(sock, addr session.buffer[0], words.len shl 3)
proc newSession(socket: AsyncSocket): Session =
Session(socket: socket, buffer: newSeq[Word](512))
proc send(session: Session; sock: AsyncSocket; s: string): Future[void] =
let wordCount = (s.len + 7) shr 3
if wordCount > session.buffer.len: setLen(session.buffer, wordCount)
session.buffer[0] = Word s.len
if wordCount > 0:
session.buffer[wordCount] = 0x00
copyMem(addr session.buffer[1], unsafeAddr s[0], s.len)
send(sock, addr session.buffer[0], (1 + wordCount) shl 3)
proc newSession(): Session =
newSession(newAsyncSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false))
proc recvWord(sock: AsyncSocket): Future[Word] {.async.} =
var w: Word
let n = await recvInto(sock, addr w, sizeof(Word))
if n != sizeof(Word): raise newException(ProtocolError, "short read of word")
return w
proc send(session: Session; miss: Missing) {.async.} =
await send(session, STDERR_LAST)
await send(session, miss.willBuild)
await send(session, miss.willSubstitute)
await send(session, miss.unknown)
await send(session, Word miss.downloadSize)
await send(session, Word miss.narSize)
proc passWord(a, b: AsyncSocket): Future[Word] {.async.} =
var w = await recvWord(a)
await send(b, addr w, sizeof(Word))
return w
proc send(session: Session; info: PathInfo) {.async.} =
await send(session, STDERR_LAST)
await send(session, 1)
if info.path != "":
await send(session, info.path)
await send(session, info.deriver)
await send(session, info.narHash)
await send(session, info.references)
await send(session, Word info.registrationTime)
await send(session, Word info.narSize)
await send(session, Word info.ultimate)
await send(session, info.sigs)
await send(session, info.ca)
proc recvString(sock: AsyncSocket): Future[string] {.async.} =
let w = await recvWord(sock)
let stringLen = int w
var s: string
if stringLen > 0:
s.setLen((stringLen + 7) and (not 7))
let n = await recvInto(sock, addr s[0], s.len)
if n != s.len:
raise newException(ProtocolError, "short string read")
setLen(s, stringLen)
return s
proc serveClient(facet: Facet; ds: Ref; session: Session) {.async.} =
block:
let clientMagic = await recvWord(session)
if clientMagic != WORKER_MAGIC_1:
raise newException(ProtocolError, "invalid protocol magic")
await send(session, WORKER_MAGIC_2, PROTOCOL_VERSION)
let clientVersion = Version(await recvWord(session))
if clientVersion < 0x1_21:
raise newException(ProtocolError, "obsolete protocol version")
assert clientVersion.minor >= 14
discard await(recvWord(session))
# obsolete CPU affinity
assert clientVersion.minor >= 11
discard await(recvWord(session))
# obsolete reserveSpace
assert clientVersion.minor >= 33
await send(session, "0.0.0")
await send(session, STDERR_LAST)
while not session.socket.isClosed:
let wop = await recvWord(session.socket)
case wop
proc passString(session: Session; a, b: AsyncSocket): Future[string] {.async.} =
var s = await recvString(a)
await send(session, b, s)
return s
of wopQueryPathInfo:
let
path = await recvString(session)
pat = inject(?PathInfo, { 0: ?path })
await send(session, STDERR_NEXT)
await send(session, $pat)
run(facet) do (turn: var Turn):
onPublish(turn, ds, pat) do (
deriver: string,
narHash: string,
references: StringSet,
registrationTime: BiggestInt,
narSize: BiggestInt,
ultimate: bool,
sigs: StringSet,
ca: string
):
var info = PathInfo(
deriver: deriver,
narHash: narHash,
references: references,
registrationTime: registrationTime,
narSize: narSize,
ultimate: ultimate,
sigs: sigs,
ca: ca,
)
asyncCheck(turn, send(session, info))
proc passStringSeq(session: Session; a, b: AsyncSocket): Future[seq[string]] {.async.} =
let count = int(await passWord(a, b))
var strings = newSeq[string](count)
for i in 0..<count: strings[i] = await passString(session, a, b)
return strings
of wopQueryMissing:
var targets = toPreserve(await recvStringSeq(session))
sort(targets.sequence)
# would prefer to use a set but that doesn't translate into a pattern
let pat = inject(?Missing, { 0: ?targets })
# TODO send the pattern to the client as a log line
await send(session, STDERR_NEXT)
await send(session, $pat)
run(facet) do (turn: var Turn):
onPublish(turn, ds, pat) do (
willBuild: StringSet,
willSubstitute: StringSet,
unknown: StringSet,
downloadSize: BiggestInt,
narSize: BiggestInt
):
let miss = Missing(
willBuild: willBuild,
willSubstitute: willSubstitute,
unknown: unknown,
downloadSize: downloadSize,
narSize: narSize,
)
asyncCheck(turn, send(session, miss))
proc passStringSet(session: Session; a, b: AsyncSocket): Future[HashSet[string]] {.async.} =
let count = int(await passWord(a, b))
var strings = initHashSet[string](count)
for i in 0..<count: incl(strings, await passString(session, a, b))
return strings
proc passStringMap(session: Session; a, b: AsyncSocket): Future[StringTableRef] {.async.} =
var table = newStringTable(modeCaseSensitive)
let n = await passWord(a, b)
for i in 1..n:
var
key = await passString(session, a, b)
val = await passString(session, a, b)
table[key] = val
return table
proc passClientWord(session: Session): Future[Word] =
passWord(session.client, session.daemon)
proc passDaemonWord(session: Session): Future[Word] =
passWord(session.daemon, session.client)
proc passClientString(session: Session): Future[string] =
passString(session, session.client, session.daemon)
proc passDaemonString(session: Session): Future[string] =
passString(session, session.daemon, session.client)
proc passClientStringSeq(session: Session): Future[seq[string]] =
passStringSeq(session, session.client, session.daemon)
proc passDaemonStringSeq(session: Session): Future[seq[string]] =
passStringSeq(session, session.daemon, session.client)
proc passClientStringSet(session: Session): Future[HashSet[string]] =
passStringSet(session, session.client, session.daemon)
proc passDaemonStringSet(session: Session): Future[HashSet[string]] =
passStringSet(session, session.daemon, session.client)
proc passClientStringMap(session: Session): Future[StringTableRef] =
passStringMap(session, session.client, session.daemon)
proc passDaemonStringMap(session: Session): Future[StringTableRef] =
passStringMap(session, session.daemon, session.client)
type ValidPathInfo = object
path: string
deriver: string
narHash: string
references: HashSet[string]
registrationTime, narSize: BiggestInt
ultimate: bool
sigs: HashSet[string]
ca: string
proc passDaemonValidPathInfo(session: Session; includePath: bool): Future[PathInfo] {.async.} =
var info: PathInfo
if includePath:
info.path = await passDaemonString(session)
info.deriver = await passDaemonString(session)
info.narHash = await passDaemonString(session)
info.references = await passDaemonStringSet(session)
info.registrationTime = BiggestInt(await passDaemonWord(session))
info.narSize = BiggestInt(await passDaemonWord(session))
assert session.version.minor >= 16
info.ultimate = (await passDaemonWord(session)) != 0
info.sigs = await passDaemonStringSet(session)
info.ca = await passDaemonString(session)
return info
proc passChunks(session: Session; a, b: AsyncSocket): Future[int] {.async.} =
var total: int
while true:
let chunkLen = int(await passWord(a, b))
if chunkLen == 0:
break
else:
let wordLen = (chunkLen + 7) shr 3
if session.buffer.len < wordLen: setLen(session.buffer, wordLen)
let recvLen = await recvInto(a, addr session.buffer[0], chunkLen)
# each chunk must be recved contiguously
if recvLen != chunkLen:
raise newException(ProtocolError, "invalid chunk read")
await send(b, addr session.buffer[0], recvLen)
inc(total, recvLen)
return total
proc passClientChunks(session: Session): Future[int] =
passChunks(session, session.client, session.daemon)
proc passErrorDaemonError(session: Session) {.async.} =
let
typ = await passDaemonString(session)
assert typ == "Error"
let
lvl = await passDaemonWord(session)
name = await passDaemonString(session)
msg = passDaemonString(session)
havePos = await passDaemonWord(session)
assert havePos == 0
let
nrTraces = await passDaemonWord(session)
for i in 1..nrTraces:
let havPos = await passDaemonWord(session)
assert havPos == 0
let msg = await passDaemonString(session)
proc passDaemonFields(session: Session): Future[Fields] {.async.} =
let count = await passDaemonWord(session)
var fields = newSeq[Field](count)
for i in 0..<count:
let typ = await passDaemonWord(session)
case typ
of 0:
let num = await passDaemonWord(session)
fields[i] = Field(orKind: FieldKind.int, int: int num)
of 1:
let str = await passDaemonString(session)
fields[i] = Field(orKind: FieldKind.string, string: str)
else:
raiseAssert "unknown field type " & $typ
return fields
proc passWork(session: Session) {.async.} =
while true:
let word = await passDaemonWord(session)
case word
of STDERR_WRITE:
discard await passDaemonString(session)
of STDERR_READ:
discard await passClientString(session)
of STDERR_ERROR:
assert session.version.minor >= 26
await passErrorDaemonError(session)
of STDERR_NEXT:
let s = await passDaemonString(session)
of STDERR_START_ACTIVITY:
var act: ActionStart
act.id = BiggestInt(await passDaemonWord(session))
act.level = BiggestInt(await passDaemonWord(session))
act.`type` = BiggestInt(await passDaemonWord(session))
act.text = await passDaemonString(session)
act.fields = await passDaemonFields(session)
act.parent = BiggestInt(await passDaemonWord(session))
of STDERR_STOP_ACTIVITY:
var act: ActionStop
act.id = BiggestInt(await passDaemonWord(session))
of STDERR_RESULT:
var act: ActionResult
act.id = BiggestInt(await passDaemonWord(session))
act.`type` = BiggestInt(await passDaemonWord(session))
act.fields = await passDaemonFields(session)
of STDERR_LAST:
break
of wopSetOptions:
await discardWords(session, 12)
# 01 keepFailed
# 02 keepGoing
# 03 tryFallback
# 04 verbosity
# 05 maxBuildJobs
# 06 maxSilentTime
# 07 useBuildHook
# 08 verboseBuild
# 09 logType
# 10 printBuildTrace
# 11 buildCores
# 12 useSubstitutes
let overridePairCount = await recvWord(session)
for _ in 1..overridePairCount:
discard await (recvString(session))
discard await (recvString(session))
await send(session, STDERR_LAST)
# all options from the client are ingored
else:
raise newException(ProtocolError, "unknown work verb " & $word)
let msg = "unhandled worker op " & $wop.int
await send(session, STDERR_NEXT)
await send(session, msg)
await send(session, STDERR_LAST)
close(session.socket)
#[
proc fromClient(miss: var Missing; socket: AsyncSocket) {.async.} =
result.targets = await passClientStringSet(session)
proc serveClientSide*(facet: Facet; ds: Ref; listener: AsyncSocket) {.async.} =
while not listener.isClosed:
let
client = await accept(listener)
fut = serveClient(facet, ds, newSession(client))
addCallback(fut) do ():
if not client.isClosed:
close(client)
proc fromDaemon(miss: var Missing; socket: AsyncSocket) {.async.} =
miss.willBuild = await passDaemonStringSet(session)
miss.willSubstitute = await passDaemonStringSet(session)
miss.unknown = await passDaemonStringSet(session)
miss.downloadSize = BiggestInt await passDaemonWord(session)
miss.narSize = BiggestInt await passDaemonWord(session)
]#
proc loop(session: Session) {.async.} =
var chunksTotal: int
try:
while not session.client.isClosed:
let wop = await passClientWord(session)
case wop
of wopIsValidPath:
let path = await passClientString(session)
stderr.writeLine "wopIsValidPath ", path
await passWork(session)
let word = await passDaemonWord(session)
of wopAddToStore:
assert session.version.minor >= 25
let
name = await passClientString(session)
caMethod = await passClientString(session)
refs = await passClientStringSet(session)
repairBool = await passClientWord(session)
stderr.writeLine "wopAddToStore ", name
let n = await passClientChunks(session)
inc(chunksTotal, n)
await passWork(session)
let info = await passDaemonValidPathInfo(session, true)
of wopAddTempRoot:
let path = await passClientString(session)
stderr.writeLine "wopAddTempRoot ", path
await passWork(session)
discard await passDaemonWord(session)
of wopAddIndirectRoot:
let path = await passClientString(session)
stderr.writeLine "wopAddIndirectRoot ", path
await passWork(session)
discard await passDaemonWord(session)
of wopSetOptions:
discard passClientWord(session) # keepFailed
discard passClientWord(session) # keepGoing
discard passClientWord(session) # tryFallback
discard passClientWord(session) # verbosity
discard passClientWord(session) # maxBuildJobs
discard passClientWord(session) # maxSilentTime
discard passClientWord(session) # useBuildHook
discard passClientWord(session) # verboseBuild
discard passClientWord(session) # logType
discard passClientWord(session) # printBuildTrace
discard passClientWord(session) # buildCores
discard passClientWord(session) # useSubstitutes
assert session.version.minor >= 12
let overrides = await passClientStringMap(session)
await passWork(session)
of wopQueryPathInfo:
assert session.version >= 17
let path = await passClientString(session)
stderr.writeLine "wopQueryPathInfo ", path
await passWork(session)
let valid = await passDaemonWord(session)
if valid != 0:
var info = await passDaemonValidPathInfo(session, false)
info.path = path
stderr.writeLine "wopQueryPathInfo ", $info
of wopQueryMissing:
assert session.version >= 30
var miss: Missing
miss.targets = await passClientStringSet(session)
await passWork(session)
miss.willBuild = await passDaemonStringSet(session)
miss.willSubstitute = await passDaemonStringSet(session)
miss.unknown = await passDaemonStringSet(session)
miss.downloadSize = BiggestInt await passDaemonWord(session)
miss.narSize = BiggestInt await passDaemonWord(session)
stderr.writeLine "wopQueryMissing ", $miss
of wopBuildPathsWithResults:
assert session.version >= 34
let
drvs = await passClientStringSeq(session)
buildMode = await passClientWord(session)
stderr.writeLine "wopBuildPathsWithResults drvs ", $drvs
await passWork(session)
let count = await passDaemonWord(session)
for _ in 1..count:
let
path = await passDaemonString(session)
status = await passDaemonWord(session)
errorMsg = await passDaemonString(session)
timesBUild = await passDaemonWord(session)
isNonDeterministic = await passDaemonWord(session)
startTime = await passDaemonWord(session)
stopTime = await passDaemonWord(session)
outputs = await passDaemonStringMap(session)
else:
stderr.writeLine "unknown worker op ", wop.int
break
except ProtocolError as err:
stderr.writeLine "connection terminated"
stderr.writeLine "chunk bytes transfered: ", formatSize(chunksTotal)
finally:
close(session.daemon)
close(session.client)
proc handshake(listener: AsyncSocket): Future[Session] {.async.} =
## Take the next connection from `listener` and return a `Session`.
let session = Session(buffer: newSeq[Word](1024)) # 8KiB
session.client = await listener.accept()
session.daemon = newAsyncSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false)
await connectUnix(session.daemon, daemonSocketPath())
let clientMagic = await passClientWord(session)
if clientMagic != WORKER_MAGIC_1:
raise newException(ProtocolError, "invalid protocol magic")
let daemonMagic = await passDaemonWord(session)
let daemonVersion = await passDaemonWord(session)
session.version = Version(await passClientWord(session))
if session.version < 0x1_0a:
raise newException(ProtocolError, "obsolete protocol version")
assert session.version.minor >= 14
discard await(passClientWord(session))
# obsolete CPU affinity
assert session.version.minor >= 11
discard await(passClientWord(session))
# obsolete reserveSpace
assert session.version.minor >= 33
let daemonVersionString = await passDaemonString(session)
assert daemonVersionString == $store.nixVersion
await passWork(session)
return session
proc emulateSocket*(path: string) {.async, gcsafe.} =
proc bootClientSide*(facet: Facet; ds: Ref; socketPath: string) =
let listener = newAsyncSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false)
bindUnix(listener, path)
onStop(facet) do (turn: var Turn):
close(listener)
removeFile(socketPath)
removeFile(socketPath)
bindUnix(listener, socketPath)
listen(listener)
stderr.writeLine "listening on ", path
while not listener.isClosed:
try:
let session = await handshake(listener)
assert not session.isNil
asyncCheck loop(session)
except ProtocolError as err:
stderr.writeLine "failed to service client, ", err.msg
asyncCheck(facet, serveClientSide(facet, ds, listener))
when isMainModule:
const path = "/tmp/worker.nix.socket"
if fileExists(path): removeFile(path)
try: waitFor emulateSocket(path)
finally: removeFile(path)
proc connectDaemon(session: Session; socketPath: string) {.async.} =
await connectUnix(session.socket, socketPath)
await send(session, WORKER_MAGIC_1)
let daemonMagic = await recvWord(session)
if daemonMagic != WORKER_MAGIC_2:
raise newException(ProtocolError, "bad magic from daemon")
let daemonVersion = await recvWord(session)
session.version = min(Version daemonVersion, PROTOCOL_VERSION)
await send(session, Word session.version)
await send(session, 0) # CPU affinity
await send(session, 0) # reserve space
if session.version.minor >= 33:
discard await recvString(session) # version
if session.version.minor >= 35:
discard await recvWord(session) # remoteTrustsUs
await recvWork(session)
proc queryMissing(session: Session; targets: StringSeq): Future[Missing] {.async.} =
var miss = Missing(targets: targets)
await send(session, wopQueryMissing)
await send(session, miss.targets)
await recvWork(session)
miss.willBuild = await recvStringSet(session)
miss.willSubstitute = await recvStringSet(session)
miss.unknown = await recvStringSet(session)
miss.downloadSize = BiggestInt await recvWord(session)
miss.narSize = BiggestInt await recvWord(session)
return miss
proc queryPathInfo(session: Session; path: string): Future[PathInfo] {.async.} =
var info = PathInfo(path: path)
await send(session, wopQueryPathInfo)
await send(session, info.path)
await recvWork(session)
let valid = await recvWord(session)
if valid != 0:
info.deriver = await recvString(session)
info.narHash = await recvString(session)
info.references = await recvStringSet(session)
info.registrationTime = BiggestInt await recvWord(session)
info.narSize = BiggestInt await recvWord(session)
info.ultimate = (await recvWord(session)) != 0
info.sigs = await recvStringSet(session)
info.ca = await recvString(session)
return info
proc bootDaemonSide*(turn: var Turn; ds: Ref; socketPath: string) =
during(turn, ds, ?Observe(pattern: !Missing) ?? {0: grab()}) do (a: Preserve[Ref]):
# cannot use `grabLit` here because an array is a compound
let
session = newSession()
fut = connectDaemon(session, socketPath)
addCallback(fut, turn) do (turn: var Turn):
read(fut)
var targets: StringSeq
doAssert targets.fromPreserve(unpackLiterals(a))
# unpack <arr [<lit " …">]>
let missFut = queryMissing(session, targets)
addCallback(missFut, turn) do (turn: var Turn):
var miss = read(missFut)
discard publish(turn, ds, miss)
do:
close(session)
during(turn, ds, ?Observe(pattern: !PathInfo) ?? {0: grabLit()}) do (path: string):
let
session = newSession()
fut = connectDaemon(session, socketPath)
addCallback(fut, turn) do (turn: var Turn):
read(fut)
let infoFut = queryPathInfo(session, path)
addCallback(infoFut, turn) do (turn: var Turn):
var info = read(infoFut)
discard publish(turn, ds, info)
do:
close(session)