From df3bd77384d882b0e53fd20b100b258c9d948b71 Mon Sep 17 00:00:00 2001 From: gmega Date: Tue, 29 Aug 2023 17:30:08 -0300 Subject: [PATCH] add basic peer lifecycle --- swarmsim/engine/peer.nim | 43 ++++++++++++++++++++++++++++++++++ swarmsim/engine/protocol.nim | 8 +++++++ swarmsim/engine/types.nim | 6 +++++ tests/codex/tblockexchange.nim | 7 +++++- tests/engine/tnetwork.nim | 1 + tests/engine/tpeer.nim | 36 +++++++++++++++++++++++++++- tests/helpers/inbox.nim | 14 +++++++++++ 7 files changed, 113 insertions(+), 2 deletions(-) diff --git a/swarmsim/engine/peer.nim b/swarmsim/engine/peer.nim index 618263d..d47acb5 100644 --- a/swarmsim/engine/peer.nim +++ b/swarmsim/engine/peer.nim @@ -2,6 +2,7 @@ import std/options import std/random import std/hashes import sequtils +import sugar import ./types import ./message @@ -15,6 +16,33 @@ export protocol export eventdrivenengine export Peer +type + PeerLifecycleChange* = ref object of SchedulableEvent + peer: Peer + network: Network + event: LifecycleEventType + +method atScheduledTime*(self: PeerLifecycleChange, + engine: EventDrivenEngine): void = + + let peer = self.peer + let oldState = peer.up + + # XXX We're somewhat lax with state machine transitions and will allow + # self-transitions... + let newState = case self.event: + of started, up: + true + of down: + false + + peer.up = newState + + # ... but self-transitions do not get reported downstream. + if oldState != newState: + self.peer.protocols.values.toSeq.apply(p => + p.onLifecycleEventType(self.peer, self.event, self.network)) + proc getProtocol*(self: Peer, id: string): Option[Protocol] = if self.protocols.hasKey(id): return self.protocols[id].some @@ -34,6 +62,21 @@ proc deliver*(self: Peer, message: Message, engine: EventDrivenEngine, self.deliverForType(message.messageType, message, engine, network) self.deliverForType(Message.allMessages, message, engine, network) +proc scheduleLifecycleChange*(self: Peer, event: LifecycleEventType, + network: Network, time: uint64): void = + network.engine.schedule(PeerLifecycleChange( + peer: self, + network: network, + event: event, + time: time + )) + +proc startAt*(self: Peer, network: Network, time: uint64): void = + self.scheduleLifecycleChange(started, network, time) + +proc start*(self: Peer, network: Network): void = + self.startAt(network, network.engine.currentTime) + proc initPeer*(self: Peer, protocols: seq[Protocol], peerId: Option[int] = none(int)): Peer = diff --git a/swarmsim/engine/protocol.nim b/swarmsim/engine/protocol.nim index 7d01dd9..8672c41 100644 --- a/swarmsim/engine/protocol.nim +++ b/swarmsim/engine/protocol.nim @@ -14,3 +14,11 @@ method deliver*( network: Network ): void {.base.} = raise newException(CatchableError, "Method without implementation override") + +method onLifecycleEventType*( + self: Protocol, + peer: Peer, + event: LifecycleEventType, + network: Network +): void {.base.} = + discard diff --git a/swarmsim/engine/types.nim b/swarmsim/engine/types.nim index 9dbfcc4..630366e 100644 --- a/swarmsim/engine/types.nim +++ b/swarmsim/engine/types.nim @@ -37,8 +37,14 @@ type messageTypes*: seq[string] type + LifecycleEventType* = enum + started + up + down + Peer* = ref object of RootObj peerId*: int + up*: bool = false protocols*: Table[string, Protocol] dispatch*: MultiTable[string, Protocol] diff --git a/tests/codex/tblockexchange.nim b/tests/codex/tblockexchange.nim index 04b3927..8b8fa0f 100644 --- a/tests/codex/tblockexchange.nim +++ b/tests/codex/tblockexchange.nim @@ -9,9 +9,11 @@ import ../helpers/testpeer suite "block exchange": - test "should respond to want-block message with a list of the blocks it has": + setup: let engine = EventDrivenEngine() let network = Network(engine: engine) + + test "should respond to want-have message with a list of the blocks it has": let sender = TestPeer.new(network) let bex = BlockExchangeProtocol.new() @@ -41,3 +43,6 @@ suite "block exchange": let response = Have(sender.inbox.messages[0]) check(response.haves == toIntSet([0, 1, 4])) + + + diff --git a/tests/engine/tnetwork.nim b/tests/engine/tnetwork.nim index 7289513..612b116 100644 --- a/tests/engine/tnetwork.nim +++ b/tests/engine/tnetwork.nim @@ -10,6 +10,7 @@ import ../helpers/inbox import ../helpers/types suite "network": + test "should dispatch message to the correct peer": let engine = EventDrivenEngine() diff --git a/tests/engine/tpeer.nim b/tests/engine/tpeer.nim index 64d0b02..ee27713 100644 --- a/tests/engine/tpeer.nim +++ b/tests/engine/tpeer.nim @@ -9,6 +9,8 @@ import swarmsim/engine/message import ../helpers/inbox import ../helpers/types +import pretty + # We need this here as otherwise for some reason the nim compiler trips. proc `$`*(m: Message): string = repr m @@ -79,7 +81,6 @@ suite "peer": check(i1.messages == @[m1, m2]) - test "should deliver all message types when listening to Message.allMessages": let i1 = Inbox(protocolId: "protocol1", messageTypes: @[Message.allMessages]) @@ -95,3 +96,36 @@ suite "peer": check(i1.messages == @[m1, m2, m3]) + test "should be up after start": + let i1 = Inbox(protocolId: "lifecycleListener", messageTypes: @[]) + let peer = Peer.new(protocols = @[Protocol i1]) + + peer.startAt(network, 5) + + check(not peer.up) + + engine.run() + + check((repr i1.events) == (repr @[ + LifecycleEvent(event: started, time: 5.uint64)])) + + check(peer.up) + + test "should not be up after going down": + let i1 = Inbox(protocolId: "lifecycleListener", messageTypes: @[]) + + let peer = Peer.new(protocols = @[Protocol i1]) + + peer.start(network) + peer.scheduleLifecycleChange( + event = down, + network = network, + time = 10 + ) + + engine.run() + + check((repr i1.events) == (repr @[ + LifecycleEvent(event: started, time: 0), + LifecycleEvent(event: down, time: 10) + ])) diff --git a/tests/helpers/inbox.nim b/tests/helpers/inbox.nim index d25cccf..deefcbc 100644 --- a/tests/helpers/inbox.nim +++ b/tests/helpers/inbox.nim @@ -9,6 +9,11 @@ withTypeId: Inbox* = ref object of Protocol protocolId*: string messages*: seq[Message] + events*: seq[LifecycleEvent] + + LifecycleEvent* = ref object of RootObj + event*: LifecycleEventType + time*: uint64 method deliver*( self: Inbox, @@ -20,7 +25,16 @@ method deliver*( method `protocolId`*(self: Inbox): string = self.protocolId +method onLifecycleEventType*( + self: Inbox, + peer: Peer, + event: LifecycleEventType, + network: Network +) = + self.events.add(LifecycleEvent(event: event, time: network.engine.currentTime)) + export Message +export LifecycleEvent export peer export protocol export network