`{.async: (raises).}` for `MultistreamSelect` (#1066)

This commit is contained in:
Etan Kissling 2024-03-12 21:05:53 +01:00 committed by GitHub
parent 49a92e5641
commit 48a3ac06ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 52 deletions

View File

@ -1,5 +1,5 @@
# Nim-LibP2P # Nim-LibP2P
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -48,12 +48,15 @@ template validateSuffix(str: string): untyped =
if str.endsWith("\n"): if str.endsWith("\n"):
str.removeSuffix("\n") str.removeSuffix("\n")
else: else:
raise newException(MultiStreamError, "MultistreamSelect failed, malformed message") raise (ref MultiStreamError)(msg:
"MultistreamSelect failed, malformed message")
proc select*(_: MultistreamSelect | type MultistreamSelect, proc select*(
_: MultistreamSelect | type MultistreamSelect,
conn: Connection, conn: Connection,
proto: seq[string]): proto: seq[string]
Future[string] {.async.} = ): Future[string] {.async: (raises: [
CancelledError, LPStreamError, MultiStreamError]).} =
trace "initiating handshake", conn, codec = Codec trace "initiating handshake", conn, codec = Codec
## select a remote protocol ## select a remote protocol
await conn.writeLp(Codec & "\n") # write handshake await conn.writeLp(Codec & "\n") # write handshake
@ -66,7 +69,7 @@ proc select*(_: MultistreamSelect | type MultistreamSelect,
if s != Codec: if s != Codec:
notice "handshake failed", conn, codec = s notice "handshake failed", conn, codec = s
raise newException(MultiStreamError, "MultistreamSelect handshake failed") raise (ref MultiStreamError)(msg: "MultistreamSelect handshake failed")
else: else:
trace "multistream handshake success", conn trace "multistream handshake success", conn
@ -98,19 +101,29 @@ proc select*(_: MultistreamSelect | type MultistreamSelect,
# No alternatives, fail # No alternatives, fail
return "" return ""
proc select*(_: MultistreamSelect | type MultistreamSelect, proc select*(
_: MultistreamSelect | type MultistreamSelect,
conn: Connection, conn: Connection,
proto: string): Future[bool] {.async.} = proto: string
): Future[bool] {.async: (raises: [
CancelledError, LPStreamError, MultiStreamError]).} =
if proto.len > 0: if proto.len > 0:
return (await MultistreamSelect.select(conn, @[proto])) == proto (await MultistreamSelect.select(conn, @[proto])) == proto
else: else:
return (await MultistreamSelect.select(conn, @[])) == Codec (await MultistreamSelect.select(conn, @[])) == Codec
proc select*(m: MultistreamSelect, conn: Connection): Future[bool] = proc select*(
m: MultistreamSelect,
conn: Connection
): Future[bool] {.async: (raises: [
CancelledError, LPStreamError, MultiStreamError], raw: true).} =
m.select(conn, "") m.select(conn, "")
proc list*(m: MultistreamSelect, proc list*(
conn: Connection): Future[seq[string]] {.async.} = m: MultistreamSelect,
conn: Connection
): Future[seq[string]] {.async: (raises: [
CancelledError, LPStreamError, MultiStreamError]).} =
## list remote protos requests on connection ## list remote protos requests on connection
if not await m.select(conn): if not await m.select(conn):
return return
@ -130,8 +143,9 @@ proc handle*(
conn: Connection, conn: Connection,
protos: seq[string], protos: seq[string],
matchers = newSeq[Matcher](), matchers = newSeq[Matcher](),
active: bool = false, active: bool = false
): Future[string] {.async.} = ): Future[string] {.async: (raises: [
CancelledError, LPStreamError, MultiStreamError]).} =
trace "Starting multistream negotiation", conn, handshaked = active trace "Starting multistream negotiation", conn, handshaked = active
var handshaked = active var handshaked = active
while not conn.atEof: while not conn.atEof:
@ -140,7 +154,7 @@ proc handle*(
if not handshaked and ms != Codec: if not handshaked and ms != Codec:
debug "expected handshake message", conn, instead=ms debug "expected handshake message", conn, instead=ms
raise newException(CatchableError, raise (ref MultiStreamError)(msg:
"MultistreamSelect handling failed, invalid first message") "MultistreamSelect handling failed, invalid first message")
trace "handle: got request", conn, ms trace "handle: got request", conn, ms
@ -172,13 +186,16 @@ proc handle*(
trace "no handlers", conn, protocol = ms trace "no handlers", conn, protocol = ms
await conn.writeLp(Na) await conn.writeLp(Na)
proc handle*(m: MultistreamSelect, conn: Connection, active: bool = false) {.async.} = proc handle*(
m: MultistreamSelect,
conn: Connection,
active: bool = false) {.async: (raises: [CancelledError]).} =
trace "Starting multistream handler", conn, handshaked = active trace "Starting multistream handler", conn, handshaked = active
var var
protos: seq[string] protos: seq[string]
matchers: seq[Matcher] matchers: seq[Matcher]
for h in m.handlers: for h in m.handlers:
if not isNil(h.match): if h.match != nil:
matchers.add(h.match) matchers.add(h.match)
for proto in h.protos: for proto in h.protos:
protos.add(proto) protos.add(proto)
@ -186,12 +203,13 @@ proc handle*(m: MultistreamSelect, conn: Connection, active: bool = false) {.asy
try: try:
let ms = await MultistreamSelect.handle(conn, protos, matchers, active) let ms = await MultistreamSelect.handle(conn, protos, matchers, active)
for h in m.handlers: for h in m.handlers:
if (not isNil(h.match) and h.match(ms)) or h.protos.contains(ms): if (h.match != nil and h.match(ms)) or h.protos.contains(ms):
trace "found handler", conn, protocol = ms trace "found handler", conn, protocol = ms
var protocolHolder = h var protocolHolder = h
let maxIncomingStreams = protocolHolder.protocol.maxIncomingStreams let maxIncomingStreams = protocolHolder.protocol.maxIncomingStreams
if protocolHolder.openedStreams.getOrDefault(conn.peerId) >= maxIncomingStreams: if protocolHolder.openedStreams.getOrDefault(conn.peerId) >=
maxIncomingStreams:
debug "Max streams for protocol reached, blocking new stream", debug "Max streams for protocol reached, blocking new stream",
conn, protocol = ms, maxIncomingStreams conn, protocol = ms, maxIncomingStreams
return return
@ -242,8 +260,32 @@ proc addHandler*(m: MultistreamSelect,
protocol: protocol, protocol: protocol,
match: matcher)) match: matcher))
proc start*(m: MultistreamSelect) {.async.} = proc start*(m: MultistreamSelect) {.async: (raises: [CancelledError]).} =
await allFutures(m.handlers.mapIt(it.protocol.start())) let
handlers = m.handlers
futs = handlers.mapIt(it.protocol.start())
try:
await allFutures(futs)
for fut in futs:
await fut
except CancelledError as exc:
var pending: seq[Future[void].Raising([])]
for i, fut in futs:
if not fut.finished:
pending.add noCancel fut.cancelAndWait()
elif fut.completed:
pending.add handlers[i].protocol.stop()
else:
static: doAssert typeof(fut).E is (CancelledError,)
await noCancel allFutures(pending)
raise exc
proc stop*(m: MultistreamSelect) {.async.} =
await allFutures(m.handlers.mapIt(it.protocol.stop())) proc stop*(m: MultistreamSelect) {.async: (raises: []).} =
# Nim 1.6.18: Using `mapIt` results in a seq of `.Raising([CancelledError])`
var futs = newSeqOfCap[Future[void].Raising([])](m.handlers.len)
for it in m.handlers:
futs.add it.protocol.stop()
await noCancel allFutures(futs)
for fut in futs:
await fut

View File

@ -1,5 +1,5 @@
# Nim-LibP2P # Nim-LibP2P
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -361,17 +361,25 @@ proc deletesReservation(r: Relay) {.async.} =
if n > r.rsvp[k]: if n > r.rsvp[k]:
r.rsvp.del(k) r.rsvp.del(k)
method start*(r: Relay) {.async.} = method start*(
r: Relay
): Future[void] {.async: (raises: [CancelledError], raw: true).} =
let fut = newFuture[void]()
fut.complete()
if not r.reservationLoop.isNil: if not r.reservationLoop.isNil:
warn "Starting relay twice" warn "Starting relay twice"
return return fut
r.reservationLoop = r.deletesReservation() r.reservationLoop = r.deletesReservation()
r.started = true r.started = true
fut
method stop*(r: Relay) {.async.} = method stop*(r: Relay): Future[void] {.async: (raises: [], raw: true).} =
let fut = newFuture[void]()
fut.complete()
if r.reservationLoop.isNil: if r.reservationLoop.isNil:
warn "Stopping relay without starting it" warn "Stopping relay without starting it"
return return fut
r.started = false r.started = false
r.reservationLoop.cancel() r.reservationLoop.cancel()
r.reservationLoop = nil r.reservationLoop = nil
fut

View File

@ -1,5 +1,5 @@
# Nim-LibP2P # Nim-LibP2P
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -31,8 +31,19 @@ type
maxIncomingStreams: Opt[int] maxIncomingStreams: Opt[int]
method init*(p: LPProtocol) {.base, gcsafe.} = discard method init*(p: LPProtocol) {.base, gcsafe.} = discard
method start*(p: LPProtocol) {.async, base.} = p.started = true
method stop*(p: LPProtocol) {.async, base.} = p.started = false method start*(
p: LPProtocol) {.async: (raises: [CancelledError], raw: true), base.} =
let fut = newFuture[void]()
fut.complete()
p.started = true
fut
method stop*(p: LPProtocol) {.async: (raises: [], raw: true), base.} =
let fut = newFuture[void]()
fut.complete()
p.started = false
fut
proc maxIncomingStreams*(p: LPProtocol): int = proc maxIncomingStreams*(p: LPProtocol): int =
p.maxIncomingStreams.get(DefaultMaxIncomingStreams) p.maxIncomingStreams.get(DefaultMaxIncomingStreams)

View File

@ -1,5 +1,5 @@
# Nim-LibP2P # Nim-LibP2P
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -701,30 +701,40 @@ proc maintainDirectPeers(g: GossipSub) {.async.} =
for id, addrs in g.parameters.directPeers: for id, addrs in g.parameters.directPeers:
await g.addDirectPeer(id, addrs) await g.addDirectPeer(id, addrs)
method start*(g: GossipSub) {.async.} = method start*(
g: GossipSub
): Future[void] {.async: (raises: [CancelledError], raw: true).} =
let fut = newFuture[void]()
fut.complete()
trace "gossipsub start" trace "gossipsub start"
if not g.heartbeatFut.isNil: if not g.heartbeatFut.isNil:
warn "Starting gossipsub twice" warn "Starting gossipsub twice"
return return fut
g.heartbeatFut = g.heartbeat() g.heartbeatFut = g.heartbeat()
g.scoringHeartbeatFut = g.scoringHeartbeat() g.scoringHeartbeatFut = g.scoringHeartbeat()
g.directPeersLoop = g.maintainDirectPeers() g.directPeersLoop = g.maintainDirectPeers()
g.started = true g.started = true
fut
method stop*(g: GossipSub): Future[void] {.async: (raises: [], raw: true).} =
let fut = newFuture[void]()
fut.complete()
method stop*(g: GossipSub) {.async.} =
trace "gossipsub stop" trace "gossipsub stop"
g.started = false g.started = false
if g.heartbeatFut.isNil: if g.heartbeatFut.isNil:
warn "Stopping gossipsub without starting it" warn "Stopping gossipsub without starting it"
return return fut
# stop heartbeat interval # stop heartbeat interval
g.directPeersLoop.cancel() g.directPeersLoop.cancel()
g.scoringHeartbeatFut.cancel() g.scoringHeartbeatFut.cancel()
g.heartbeatFut.cancel() g.heartbeatFut.cancel()
g.heartbeatFut = nil g.heartbeatFut = nil
fut
method initPubSub*(g: GossipSub) method initPubSub*(g: GossipSub)
{.raises: [InitializationError].} = {.raises: [InitializationError].} =

View File

@ -1,5 +1,5 @@
# Nim-LibP2P # Nim-LibP2P
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -678,17 +678,25 @@ proc deletesRegister(rdv: RendezVous) {.async.} =
libp2p_rendezvous_registered.set(int64(total)) libp2p_rendezvous_registered.set(int64(total))
libp2p_rendezvous_namespaces.set(int64(rdv.namespaces.len)) libp2p_rendezvous_namespaces.set(int64(rdv.namespaces.len))
method start*(rdv: RendezVous) {.async.} = method start*(
rdv: RendezVous
): Future[void] {.async: (raises: [CancelledError], raw: true).} =
let fut = newFuture[void]()
fut.complete()
if not rdv.registerDeletionLoop.isNil: if not rdv.registerDeletionLoop.isNil:
warn "Starting rendezvous twice" warn "Starting rendezvous twice"
return return fut
rdv.registerDeletionLoop = rdv.deletesRegister() rdv.registerDeletionLoop = rdv.deletesRegister()
rdv.started = true rdv.started = true
fut
method stop*(rdv: RendezVous) {.async.} = method stop*(rdv: RendezVous): Future[void] {.async: (raises: [], raw: true).} =
let fut = newFuture[void]()
fut.complete()
if rdv.registerDeletionLoop.isNil: if rdv.registerDeletionLoop.isNil:
warn "Stopping rendezvous without starting it" warn "Stopping rendezvous without starting it"
return return fut
rdv.started = false rdv.started = false
rdv.registerDeletionLoop.cancel() rdv.registerDeletionLoop.cancel()
rdv.registerDeletionLoop = nil rdv.registerDeletionLoop = nil
fut