mirror of
https://github.com/zap-proto/nim.git
synced 2026-10-30 15:04:34 +00:00
Rename the capnp/ module set to zap/ and the capnpc compiler to zapc; rename all .capnp schemas and fixtures to .zap; update every import, include guard (isInZap), generated-code emission, exception type (ZapFormatError), and the macro-generated zapUnpackStructImpl/ zapPackStructImpl identifiers to the ZAP brand. Zero capnp/Cap'n Proto references remain in source or build files; upstream MIT attribution (Sandstorm, Michał Zieliński) is preserved. Raise the nimble nim floor from the fictional 0.12.0 to the locked, verified 0.15.2 (matches nimenv.cfg/build.sh; code carries fixes through Nim 0.18). Rewrite TODO.md as honest, source-anchored known-limitations (two-word intersegment pointers, nested/named-union codegen, RPC interface inheritance) instead of a bare wishlist.
384 lines
13 KiB
Nim
384 lines
13 KiB
Nim
## Implements the core of the RPC mechanism.
|
|
import caprpc/common, caprpc/util, caprpc/rpcschema, tables, collections/weaktable, collections/weakref, reactor, zap, collections/iface, caprpc/util, collections/iterate, collections, typetraits
|
|
|
|
type
|
|
RpcSystem* = ref object
|
|
network: VatNetwork
|
|
myBootstrap: CapServer # my bootstrap interface
|
|
connections: TableRef[VatId, VatConnection]
|
|
|
|
QuestionId = uint32
|
|
AnswerId = uint32
|
|
ExportId = uint32
|
|
ImportId = uint32
|
|
|
|
VatConnection = ref object
|
|
system: RpcSystem
|
|
connected: bool
|
|
vatId: VatId
|
|
vatConn: Connection
|
|
questions: QuestionTable[QuestionId, Question]
|
|
answers: Table[AnswerId, Answer]
|
|
exports: QuestionTable[ExportId, Export]
|
|
imports: WeakValueTable[ImportId, RemoteCapObj]
|
|
|
|
Answer = ref object
|
|
result: Future[AnyPointer]
|
|
|
|
Export = ref object
|
|
refCount: int64
|
|
cap: CapServer
|
|
|
|
Question = ref object
|
|
id: QuestionId
|
|
returnCallback: (proc(ret: Return): Future[void])
|
|
|
|
RemoteCapObj = object
|
|
vatConnection: VatConnection
|
|
importId: ImportId
|
|
refCount: int64
|
|
|
|
RemoteCap = WeakRefable[RemoteCapObj]
|
|
|
|
proc call(cap: RemoteCap, ifaceId: uint64, methodId: uint64, payload: AnyPointer): Future[AnyPointer] {.async.}
|
|
proc makePayload(self: VatConnection, payload: AnyPointer): Payload
|
|
|
|
proc newRpcSystem*(network: VatNetwork, myBootstrap: CapServer=nothingImplemented): RpcSystem =
|
|
return RpcSystem(
|
|
network: network,
|
|
myBootstrap: myBootstrap,
|
|
connections: newTable[VatId, VatConnection]())
|
|
|
|
proc send(self: VatConnection, msg: Message): Future[void] =
|
|
when defined(caprpcTraceMessages):
|
|
when defined(caprpcShowSendTrace):
|
|
stderr.writeLine(getStackTrace())
|
|
stderr.writeLine("\x1B[91msend\x1B[0m ", msg.pprint)
|
|
return self.vatConn.output.send(msg)
|
|
|
|
proc finishQuestion(self: VatConnection, questionId: QuestionId): Future[void] {.async.} =
|
|
await self.send(Message(
|
|
kind: MessageKind.finish,
|
|
finish: Finish(releaseResultCaps: false, questionId: questionId)
|
|
))
|
|
self.questions.del(questionId)
|
|
|
|
proc newQuestion(self: VatConnection): Question =
|
|
let q = Question()
|
|
q.id = self.questions.putNext(q)
|
|
return q
|
|
|
|
proc catchInternalError[T](f: Future[T], self: VatConnection) =
|
|
f.ignore # TODO
|
|
|
|
proc traceLifetime(args: varargs[string, `$`]) =
|
|
when defined(caprpcTraceLifetime):
|
|
stderr.writeLine(args)
|
|
|
|
proc releaseCap(cap: ref RemoteCapObj) {.cdecl.} =
|
|
traceLifetime("release ", cap.importId)
|
|
|
|
proc doRelease() =
|
|
cap.vatConnection.send(Message(
|
|
kind: MessageKind.release,
|
|
release: Release(
|
|
referenceCount: cap.refCount.uint32,
|
|
id: cap.importId
|
|
)
|
|
)).catchInternalError(cap.vatConnection)
|
|
|
|
doRelease()
|
|
|
|
proc getImportedCap(self: VatConnection, id: ImportId): CapServer =
|
|
var cap: RemoteCap
|
|
if id in self.imports:
|
|
cap = self.imports[id]
|
|
else:
|
|
cap = self.imports.addKey(id, (ref RemoteCapObj)(
|
|
vatConnection: self,
|
|
importId: id
|
|
), freeCallback=releaseCap)
|
|
|
|
cap.obj.refCount += 1
|
|
return cap.asCapServer
|
|
|
|
proc capFromDescriptor(self: VatConnection, descriptor: CapDescriptor): CapServer =
|
|
case descriptor.kind:
|
|
of CapDescriptorKind.none:
|
|
return nothingImplemented
|
|
of CapDescriptorKind.senderHosted:
|
|
return self.getImportedCap(descriptor.senderHosted)
|
|
of CapDescriptorKind.senderPromise:
|
|
raise newException(system.Exception, "promise not implemented")
|
|
of CapDescriptorKind.receiverHosted:
|
|
let id = descriptor.receiverHosted
|
|
if id notin self.exports:
|
|
raise newException(system.Exception, "bad export ID")
|
|
return self.exports[id].cap
|
|
of CapDescriptorKind.receiverAnswer:
|
|
return nothingImplemented
|
|
of CapDescriptorKind.thirdPartyHosted:
|
|
return nothingImplemented
|
|
|
|
proc unpackPayload(self: VatConnection, payload: Payload): AnyPointer =
|
|
let caps = payload.capTable.map(x => self.capFromDescriptor(x)).toSeq
|
|
payload.content.setCapGetter(proc(val: RawCapValue): CapServer =
|
|
case val.kind:
|
|
of rawCapNumber:
|
|
let id = val.number
|
|
if id == -1: return nullCap
|
|
if id < 0 or id >= caps.len:
|
|
raise newException(system.Exception, "invalid capability")
|
|
return caps[id]
|
|
else:
|
|
raise newException(system.Exception, "invalid capability type"))
|
|
return payload.content
|
|
|
|
proc getFutureForQuestion(self: VatConnection, question: Question): Future[AnyPointer] =
|
|
let completer = newCompleter[AnyPointer]()
|
|
|
|
proc cb(ret: Return): Future[void] {.async.} =
|
|
if ret == nil:
|
|
completer.completeError(newException(CaprpcException, "disconnected"))
|
|
return
|
|
|
|
case ret.kind:
|
|
of ReturnKind.results:
|
|
completer.complete(self.unpackPayload(ret.results))
|
|
of ReturnKind.exception:
|
|
completer.completeError((ref CaprpcException)(
|
|
exceptionMsg: ret.exception, msg: "server: " & nilToEmpty(ret.exception.reason)))
|
|
else:
|
|
stderr.writeLine("unsupported return: ", ret.kind)
|
|
|
|
await self.finishQuestion(ret.answerId)
|
|
|
|
question.returnCallback = cb
|
|
|
|
return completer.getFuture
|
|
|
|
proc resolveTransform(item: AnyPointer, ops: seq[PromisedAnswer_Op]): AnyPointer =
|
|
var item = item
|
|
|
|
for op in ops:
|
|
case op.kind:
|
|
of PromisedAnswer_OpKind.noop:
|
|
discard
|
|
of PromisedAnswer_OpKind.getPointerField:
|
|
item = item.getPointerField(op.getPointerField.int)
|
|
|
|
return item
|
|
|
|
proc getTargetCap(self: VatConnection, target: MessageTarget): Future[CapServer] {.async.} =
|
|
case target.kind:
|
|
of MessageTargetKind.importedCap:
|
|
let id = target.importedCap
|
|
if id notin self.exports:
|
|
asyncRaise "bad export ID"
|
|
return self.exports[id].cap
|
|
of MessageTargetKind.promisedAnswer:
|
|
let promise = target.promisedAnswer
|
|
if promise.questionId notin self.answers:
|
|
asyncRaise "invalid question id"
|
|
return self.answers[promise.questionId].result.then(
|
|
x => x.resolveTransform(promise.transform).castAs(CapServer))
|
|
|
|
proc respondToCall(self: VatConnection, msg: Message, questionId: uint32, value: Result[AnyPointer]) {.async.} =
|
|
var ret: Return
|
|
|
|
if value.isSuccess:
|
|
ret = Return(kind: ReturnKind.results,
|
|
results: self.makePayload(value.get),
|
|
answerId: questionId)
|
|
else:
|
|
when defined(caprpcPrintExceptions):
|
|
value.error.printError
|
|
ret = Return(kind: ReturnKind.exception,
|
|
exception: rpcschema.Exception(reason: $value),
|
|
answerId: questionId)
|
|
|
|
await self.send(Message(kind: MessageKind.`return`, `return`: ret))
|
|
|
|
proc processMessage(self: VatConnection, msg: Message) {.async.} =
|
|
case msg.kind:
|
|
of MessageKind.unimplemented:
|
|
stderr.writeLine("peer sent 'unimplemented' message")
|
|
of MessageKind.abort:
|
|
discard
|
|
|
|
of {MessageKind.call, MessageKind.bootstrap}:
|
|
let questionId = if msg.kind == MessageKind.bootstrap:
|
|
msg.bootstrap.questionId
|
|
else:
|
|
msg.call.questionId
|
|
|
|
if questionId in self.answers:
|
|
asyncRaise "question id reused"
|
|
|
|
let callResult =
|
|
if msg.kind == MessageKind.bootstrap:
|
|
now(just(self.system.myBootstrap.toAnyPointer))
|
|
else:
|
|
self.getTargetCap(msg.call.target).then(
|
|
target => target.call(msg.call.interfaceId, msg.call.methodId, self.unpackPayload(msg.call.params)))
|
|
|
|
callResult.onSuccessOrError(
|
|
proc(r: Result[AnyPointer]) = self.respondToCall(msg, questionId, r).catchInternalError(self))
|
|
|
|
self.answers[questionId] = Answer(result: callResult)
|
|
|
|
of MessageKind.`return`:
|
|
let questionId = msg.`return`.answerId
|
|
if msg.`return`.releaseParamCaps: stderr.writeLine("releaseParamCaps unsupported") # TODO
|
|
if questionId notin self.questions:
|
|
stderr.writeLine("peer sent return to invalid question")
|
|
else:
|
|
await self.questions[questionId].returnCallback(msg.`return`)
|
|
|
|
of MessageKind.finish:
|
|
# TODO: handle releaseResultCaps
|
|
if msg.finish.releaseResultCaps:
|
|
discard # answers[msg.finish.questionId].then(x => self.releasePtr(x)).ignore
|
|
self.answers.del(msg.finish.questionId)
|
|
|
|
of MessageKind.resolve:
|
|
discard
|
|
of MessageKind.release:
|
|
traceLifetime("remote asks to release " & $msg.release.id)
|
|
|
|
if msg.release.id notin self.exports:
|
|
asyncRaise "bad release export id"
|
|
|
|
let exportObj = self.exports[msg.release.id]
|
|
exportObj.refCount -= msg.release.referenceCount.int64
|
|
if exportObj.refCount == 0:
|
|
when defined(caprpcTraceLifetime):
|
|
let cap = self.exports[msg.release.id].cap
|
|
stderr.writeLine("releasing export " & $msg.release.id & " refcnt after: " & $(getRefcount(cap.getImpl)-2) &
|
|
" cap: " & cap.pprint)
|
|
|
|
del self.exports, msg.release.id
|
|
of MessageKind.obsoleteSave:
|
|
discard
|
|
of MessageKind.obsoleteDelete:
|
|
discard
|
|
of MessageKind.provide:
|
|
discard
|
|
of MessageKind.accept:
|
|
discard
|
|
of MessageKind.join:
|
|
discard
|
|
of MessageKind.disembargo:
|
|
discard
|
|
|
|
proc start(self: VatConnection) {.async.} =
|
|
asyncFor msg in self.vatConn.input:
|
|
when defined(caprpcTraceMessages):
|
|
stderr.writeLine("\x1B[92mrecv\x1B[0m ", msg.pprint)
|
|
await self.processMessage(msg)
|
|
|
|
proc cleanup(self: VatConnection) =
|
|
self.connected = false
|
|
|
|
for k, question in self.questions:
|
|
question.returnCallback(nil).ignore
|
|
|
|
self.system.connections.del self.vatId
|
|
|
|
# may help breaking circular references
|
|
self.exports = initQuestionTable[ExportId, Export]()
|
|
self.answers = initTable[AnswerId, Answer]()
|
|
|
|
proc getConnection(self: RpcSystem, vatId: VatId): VatConnection =
|
|
if vatId in self.connections:
|
|
return self.connections[vatId]
|
|
|
|
let rpcConn = VatConnection()
|
|
self.connections[vatId] = rpcConn
|
|
rpcConn.connected = true
|
|
rpcConn.system = self
|
|
rpcConn.vatId = vatId
|
|
rpcConn.vatConn = self.network.connect(vatId)
|
|
rpcConn.questions = initQuestionTable[QuestionId, Question]()
|
|
rpcConn.answers = initTable[AnswerId, Answer]()
|
|
rpcConn.imports = newWeakValueTable[ImportId, RemoteCapObj]()
|
|
rpcConn.exports = initQuestionTable[ExportId, Export]()
|
|
when defined(caprpcTraceMessages):
|
|
stderr.writeLine "creating new connection"
|
|
|
|
# TODO: close connection on error
|
|
rpcConn.start().onSuccessOrError(
|
|
proc(r: Result[void]) =
|
|
when defined(caprpcTraceMessages):
|
|
stderr.writeLine "RPC connection closed " & ($r)
|
|
rpcConn.cleanup
|
|
)
|
|
return rpcConn
|
|
|
|
proc initConnection*(self: RpcSystem, vatId: VatId) =
|
|
## Initialize connecting to vatId without bootstrapping. To be used by VatNetwork.
|
|
discard self.getConnection(vatId)
|
|
|
|
proc bootstrap*(self: RpcSystem, vatId: VatId): Future[AnyPointer] {.async.} =
|
|
let conn = self.getConnection(vatId)
|
|
let question = conn.newQuestion()
|
|
|
|
# queue the message
|
|
await conn.send(Message(
|
|
kind: MessageKind.bootstrap,
|
|
bootstrap: Bootstrap(
|
|
questionId: question.id,
|
|
deprecatedObjectId: nil
|
|
)
|
|
))
|
|
|
|
return (await conn.getFutureForQuestion(question))
|
|
|
|
proc exportCap(self: VatConnection, cap: CapServer): CapDescriptor =
|
|
# TODO: no need to always export
|
|
if cap.getImpl of RemoteCap:
|
|
let remoteCap = cap.getImpl.RemoteCap
|
|
if remoteCap.obj.vatConnection == self:
|
|
# TODO: refcount?
|
|
return CapDescriptor(kind: CapDescriptorKind.receiverHosted, receiverHosted: remoteCap.obj.importId)
|
|
|
|
let exportId = self.exports.putNext(Export(refCount: 1, cap: cap))
|
|
return CapDescriptor(kind: CapDescriptorKind.senderHosted, senderHosted: exportId)
|
|
|
|
proc makePayload(self: VatConnection, payload: AnyPointer): Payload =
|
|
var capTable: seq[CapDescriptor] = @[]
|
|
|
|
proc capToIndex(cap: CapServer): RawCapValue =
|
|
if cap.isNullCap:
|
|
return RawCapValue(kind: rawCapNumber, number: -1)
|
|
capTable.add(self.exportCap(cap))
|
|
return RawCapValue(kind: rawCapNumber, number: capTable.len - 1)
|
|
|
|
let newPayload = payload.packNow(capToIndex)
|
|
return Payload(content: newPayload, capTable: capTable) # TODO: caps
|
|
|
|
# RemoteCap impl
|
|
|
|
proc call(cap: RemoteCap, ifaceId: uint64, methodId: uint64, payload: AnyPointer): Future[AnyPointer] {.async.} =
|
|
let conn = cap.obj.vatConnection
|
|
|
|
if not conn.connected:
|
|
asyncRaise "object doesn't exist anymore (not connected)"
|
|
|
|
let question = conn.newQuestion()
|
|
|
|
await conn.send(Message(
|
|
kind: MessageKind.call,
|
|
call: Call(
|
|
questionId: question.id,
|
|
interfaceId: ifaceId,
|
|
target: MessageTarget(kind: MessageTargetKind.importedCap,
|
|
importedCap: cap.obj.importId),
|
|
methodId: methodId.uint16,
|
|
params: conn.makePayload(payload),
|
|
sendResultsTo: Call_sendResultsTo(kind: Call_sendResultsToKind.caller),
|
|
)
|
|
))
|
|
|
|
return (await conn.getFutureForQuestion(question))
|