Add uTP over discv5 test and small uTP performance improvements (#663)

- Add a multiple sockets use test for uTP over discv5
- Use assign2 for the biggest consumer of genericAssignAux in uTP
- Avoid calling exists on the growable buffer when there is no
place in the socket window.
This commit is contained in:
Kim De Mey 2024-01-18 12:07:03 +01:00 committed by GitHub
parent d4927593a1
commit 974a995b21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 108 additions and 16 deletions

View File

@ -1,4 +1,4 @@
# Copyright (c) 2021-2023 Status Research & Development GmbH # Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -9,6 +9,8 @@
import import
std/[options, math] std/[options, math]
from stew/assign2 import assign
export options export options
# Buffer implementation similar to the one in the reference implementation. # Buffer implementation similar to the one in the reference implementation.
@ -32,7 +34,7 @@ proc init*[A](T: type GrowableCircularBuffer[A], size: uint32 = 16): T =
) )
proc get*[A](buff: GrowableCircularBuffer[A], i: uint32): Option[A] = proc get*[A](buff: GrowableCircularBuffer[A], i: uint32): Option[A] =
buff.items[i and buff.mask] assign(result, buff.items[i and buff.mask])
proc putImpl[A](buff: var GrowableCircularBuffer[A], i: uint32, elem: Option[A]) = proc putImpl[A](buff: var GrowableCircularBuffer[A], i: uint32, elem: Option[A]) =
buff.items[i and buff.mask] = elem buff.items[i and buff.mask] = elem

View File

@ -1,4 +1,4 @@
# Copyright (c) 2021-2023 Status Research & Development GmbH # Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -481,6 +481,10 @@ proc resetSendTimeout(socket: UtpSocket) =
socket.rtoTimeout = getMonoTimestamp().moment + socket.retransmitTimeout socket.rtoTimeout = getMonoTimestamp().moment + socket.retransmitTimeout
proc flushPackets(socket: UtpSocket) = proc flushPackets(socket: UtpSocket) =
if (socket.freeWindowBytes() == 0):
trace "No place in send window, not flushing"
return
let oldestOutgoingPacketSeqNr = socket.seqNr - socket.curWindowPackets let oldestOutgoingPacketSeqNr = socket.seqNr - socket.curWindowPackets
var i: uint16 = oldestOutgoingPacketSeqNr var i: uint16 = oldestOutgoingPacketSeqNr
while i != socket.seqNr: while i != socket.seqNr:

View File

@ -14,7 +14,7 @@ import
type TestObj = object type TestObj = object
foo: string foo: string
suite "Utp ring buffer": suite "uTP ring buffer":
test "Empty buffer": test "Empty buffer":
let buff = GrowableCircularBuffer[int].init(size = 4) let buff = GrowableCircularBuffer[int].init(size = 4)
check: check:

View File

@ -11,8 +11,7 @@ import
unittest2, unittest2,
../../eth/utp/clock_drift_calculator ../../eth/utp/clock_drift_calculator
suite "Clock drift calculator": suite "uTP clock drift calculator":
test "Initial clock drift should be 0": test "Initial clock drift should be 0":
let currentTime = Moment.now() let currentTime = Moment.now()
let calculator = ClockDriftCalculator.init(currentTime) let calculator = ClockDriftCalculator.init(currentTime)

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020-2021 Status Research & Development GmbH # Copyright (c) 2020-2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -7,7 +7,7 @@
{.used.} {.used.}
import import
std/options, std/[options, sequtils],
chronos, chronos,
stew/shims/net, stew/byteutils, stew/shims/net, stew/byteutils,
testutils/unittests, testutils/unittests,
@ -19,7 +19,7 @@ import
../p2p/discv5_test_helper, ../p2p/discv5_test_helper,
../stubloglevel ../stubloglevel
procSuite "Utp protocol over discovery v5 tests": procSuite "uTP over discovery v5 protocol":
let rng = newRng() let rng = newRng()
let utpProtId = "test-utp".toBytes() let utpProtId = "test-utp".toBytes()
@ -230,3 +230,90 @@ procSuite "Utp protocol over discovery v5 tests":
await serverSocket.destroyWait() await serverSocket.destroyWait()
await node1.closeWait() await node1.closeWait()
await node2.closeWait() await node2.closeWait()
asyncTest "Data transfer over multiple sockets":
const
amountOfTransfers = 25
dataToSend: seq[byte] = repeat(byte 0xA0, 1_000_000)
var readFutures: seq[Future[void]]
proc readAndCheck(
socket: UtpSocket[NodeAddress],
): Future[void] {.async.} =
let readData = await socket.read()
check:
readData == dataToSend
socket.atEof()
proc handleIncomingConnection(
server: UtpRouter[NodeAddress],
client: UtpSocket[NodeAddress]
): Future[void] =
readFutures.add(client.readAndCheck())
var fut = newFuture[void]("test.AcceptConnectionCallback")
fut.complete()
return fut
proc handleIncomingConnectionDummy(
server: UtpRouter[NodeAddress],
client: UtpSocket[NodeAddress]
): Future[void] =
var fut = newFuture[void]("test.AcceptConnectionCallback")
fut.complete()
return fut
let
address1 = localAddress(20302)
address2 = localAddress(20303)
node1 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), address1)
node2 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), address2)
utp1 = UtpDiscv5Protocol.new(
node1, utpProtId, handleIncomingConnectionDummy)
utp2 {.used.} = UtpDiscv5Protocol.new(
node2, utpProtId, handleIncomingConnection)
# nodes must have session between each other
check:
(await node1.ping(node2.localNode)).isOk()
proc connectSendAndCheck(
utpProto: UtpDiscv5Protocol,
address: NodeAddress
): Future[void] {.async.} =
let socketRes = await utpProto.connectTo(address)
check:
socketRes.isOk()
let socket = socketRes.value()
let dataSend = await socket.write(dataToSend)
check:
dataSend.isOk()
dataSend.value() == dataToSend.len()
await socket.closeWait()
let t0 = Moment.now()
for i in 0..<amountOfTransfers:
asyncSpawn utp1.connectSendAndCheck(
NodeAddress.init(node2.localNode.id, address2))
while readFutures.len() < amountOfTransfers:
await sleepAsync(milliseconds(100))
await allFutures(readFutures)
let elapsed = Moment.now() - t0
await utp1.shutdownWait()
await utp2.shutdownWait()
let megabitsSent = amountOfTransfers * dataToSend.len() * 8 / 1_000_000
let seconds = float(elapsed.nanoseconds) / 1_000_000_000
let throughput = megabitsSent / seconds
echo ""
echo "Sent ", amountOfTransfers, " asynchronous uTP transfers in ", seconds,
" seconds, payload throughput: ", throughput, " Mbit/s"

View File

@ -12,7 +12,7 @@ import
../../eth/utp/packets, ../../eth/utp/packets,
../../eth/keys ../../eth/keys
suite "uTP Packet Encoding": suite "uTP packet encoding":
test "Encode/decode SYN packet": test "Encode/decode SYN packet":
let let
synPacket = synPacket(5, 10, 20) synPacket = synPacket(5, 10, 20)

View File

@ -130,7 +130,7 @@ proc close(s: TwoClientsServerScenario) {.async.} =
await s.utp2.shutdownWait() await s.utp2.shutdownWait()
await s.utp3.shutdownWait() await s.utp3.shutdownWait()
procSuite "uTP over UDP protocol tests": procSuite "uTP over UDP protocol":
let rng = newRng() let rng = newRng()
asyncTest "Connect to remote host: test connection callback": asyncTest "Connect to remote host: test connection callback":

View File

@ -51,7 +51,7 @@ proc getServerSocket(
else: else:
return some(srvSocket) return some(srvSocket)
procSuite "Utp protocol over udp tests with loss and delays": procSuite "uTP over UDP protocol with loss and delays":
let rng = newRng() let rng = newRng()
proc sendBuilder(maxDelay: int, packetDropRate: int): SendCallbackBuilder = proc sendBuilder(maxDelay: int, packetDropRate: int): SendCallbackBuilder =

View File

@ -25,7 +25,7 @@ proc hash*(x: UtpSocketKey[int]): Hash =
type type
TestError* = object of CatchableError TestError* = object of CatchableError
procSuite "Utp router unit tests": procSuite "uTP router unit":
let rng = newRng() let rng = newRng()
let testSender = 1 let testSender = 1
let testSender2 = 2 let testSender2 = 2

View File

@ -17,7 +17,7 @@ import
../../eth/keys, ../../eth/keys,
../stubloglevel ../stubloglevel
procSuite "uTP socket tests": procSuite "uTP socket":
let let
rng = newRng() rng = newRng()
testAddress = initTAddress("127.0.0.1", 9079) testAddress = initTAddress("127.0.0.1", 9079)

View File

@ -18,7 +18,7 @@ import
../../eth/keys, ../../eth/keys,
../stubloglevel ../stubloglevel
procSuite "Utp socket selective acks unit test": procSuite "uTP socket selective acks":
let rng = newRng() let rng = newRng()
let testAddress = initTAddress("127.0.0.1", 9079) let testAddress = initTAddress("127.0.0.1", 9079)
let defaultBufferSize = 1024'u32 let defaultBufferSize = 1024'u32

View File

@ -13,7 +13,7 @@ import
../../eth/utp/packets, ../../eth/utp/packets,
../../eth/keys ../../eth/keys
suite "Utp packets test vectors": suite "uTP packets test vectors":
test "SYN packet": test "SYN packet":
let synPacket = Packet( let synPacket = Packet(
header: PacketHeaderV1( header: PacketHeaderV1(