nix_actor/src/nix_actor/clients.nim

173 lines
6.0 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asyncnet, os, sets, strutils, tables]
from std/algorithm import sort
import eris
import preserves, syndicate
import ./protocol, ./sockets
proc sendNext(client: Session; msg: string) {.async.} =
await send(client, STDERR_NEXT)
await send(client, msg)
proc sendWorkEnd(client: Session): Future[void] =
send(client, STDERR_LAST)
proc send(client: Session; miss: Missing) {.async.} =
await sendWorkEnd(client)
await send(client, miss.willBuild)
await send(client, miss.willSubstitute)
await send(client, miss.unknown)
await send(client, Word miss.downloadSize)
await send(client, Word miss.narSize)
proc send(client: Session; info: LegacyPathAttrs) {.async.} =
await send(client, info.deriver)
await send(client, info.narHash)
await send(client, info.references)
await send(client, Word info.registrationTime)
await send(client, Word info.narSize)
await send(client, Word info.ultimate)
await send(client, info.sigs)
await send(client, info.ca)
proc sendValidInfo(client: Session; info: LegacyPathAttrs) {.async.} =
await sendWorkEnd(client)
await send(client, 1) # valid
await send(client, info)
proc completeAddToStore(client: Session; path: string; info: LegacyPathAttrs) {.async.} =
await sendWorkEnd(client)
await send(client, path)
await send(client, info)
proc serveClient(facet: Facet; ds: Ref; store: ErisStore; client: Session) {.async.} =
block:
let clientMagic = await recvWord(client)
if clientMagic != WORKER_MAGIC_1:
raise newException(ProtocolError, "invalid protocol magic")
await send(client, WORKER_MAGIC_2, PROTOCOL_VERSION)
let clientVersion = Version(await recvWord(client))
if clientVersion < 0x1_21:
raise newException(ProtocolError, "obsolete protocol version")
assert clientVersion.minor >= 14
discard await(recvWord(client))
# obsolete CPU affinity
assert clientVersion.minor >= 11
discard await(recvWord(client))
# obsolete reserveSpace
assert clientVersion.minor >= 33
await send(client, "0.0.0")
await sendWorkEnd(client)
while not client.socket.isClosed:
let wop = await recvWord(client.socket)
case wop
of wopAddToStore:
let
name = await recvString(client)
caMethod = await recvString(client)
var storeRefs = await recvStringSeq(client)
sort(storeRefs) # sets not valid for patterns so use a sorted list
discard await recvWord(client) # repair, not implemented
let cap = await ingestChunks(client, store)
await sendNext(client, $cap & " " & name)
let attrsPat = inject(?AddToStoreAttrs, {
"name".toSymbol(Ref): ?name,
"ca-method".toSymbol(Ref): ?caMethod.toSymbol,
"references".toSymbol(Ref): ?storeRefs,
"eris".toSymbol(Ref): ?cap.bytes,
})
# bind AddToStoreAttrs and override with some literal values
let pat = PathInfo ? { 0: grab(), 1: attrsPat }
run(facet) do (turn: var Turn):
onPublish(turn, ds, pat) do (path: string, ca: string, deriver: string, narHash: string, narSize: BiggestInt, regTime: BiggestInt, sigs: StringSet, ultimate: bool):
asyncCheck(turn, completeAddToStore(client, path, LegacyPathAttrs(
ca: ca,
deriver: deriver,
narHash: narHash,
narSize: narSize,
references: storeRefs,
registrationTime: regTime,
sigs: sigs,
ultimate: ultimate,
)))
of wopQueryPathInfo:
let
path = await recvString(client)
pat = PathInfo ? { 0: ?path, 1: grab() }
run(facet) do (turn: var Turn):
onPublish(turn, ds, pat) do (info: LegacyPathAttrs):
asyncCheck(turn, sendValidInfo(client, info))
of wopQueryMissing:
var targets = toPreserve(await recvStringSeq(client))
sort(targets.sequence)
# would prefer to use a set but that doesn't translate into a pattern
let pat = inject(?Missing, { 0: ?targets })
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(client, miss))
of wopSetOptions:
await discardWords(client, 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(client)
for _ in 1..overridePairCount:
discard await (recvString(client))
discard await (recvString(client))
await sendWorkEnd(client)
# all options from the client are ingored
else:
let msg = "unhandled worker op " & $wop.int
await sendNext(client, msg)
await sendWorkEnd(client)
close(client.socket)
proc serveClientSide(facet: Facet; ds: Ref; store: ErisStore; listener: AsyncSocket) {.async.} =
while not listener.isClosed:
let
client = await accept(listener)
fut = serveClient(facet, ds, store, newSession(client))
addCallback(fut) do ():
if not client.isClosed:
close(client)
proc bootClientSide*(turn: var Turn; ds: Ref; store: ErisStore; socketPath: string) =
let listener = newUnixSocket()
onStop(turn.facet) do (turn: var Turn):
close(listener)
removeFile(socketPath)
removeFile(socketPath)
bindUnix(listener, socketPath)
listen(listener)
asyncCheck(turn, serveClientSide(turn.facet, ds, store, listener))