mirror of https://github.com/status-im/nim-eth.git
Implement fast resend logic (#466)
This commit is contained in:
parent
9a7b1afe9b
commit
7afd44d33e
|
@ -51,7 +51,7 @@ proc currentFreeBytes*(t: SendBufferTracker): uint32 =
|
||||||
else:
|
else:
|
||||||
return maxSend - t.currentWindow
|
return maxSend - t.currentWindow
|
||||||
|
|
||||||
proc checkWaiters(t: SendBufferTracker) =
|
proc notifyWaiters*(t: SendBufferTracker) =
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(t.waiters):
|
while i < len(t.waiters):
|
||||||
let freeSpace = t.currentFreeBytes()
|
let freeSpace = t.currentFreeBytes()
|
||||||
|
@ -68,18 +68,16 @@ proc checkWaiters(t: SendBufferTracker) =
|
||||||
|
|
||||||
proc updateMaxRemote*(t: SendBufferTracker, newRemoteWindow: uint32) =
|
proc updateMaxRemote*(t: SendBufferTracker, newRemoteWindow: uint32) =
|
||||||
t.maxRemoteWindow = newRemoteWindow
|
t.maxRemoteWindow = newRemoteWindow
|
||||||
t.checkWaiters()
|
t.notifyWaiters()
|
||||||
|
|
||||||
proc updateMaxWindowSize*(t: SendBufferTracker, newRemoteWindow: uint32, maxWindow: uint32) =
|
proc updateMaxWindowSize*(t: SendBufferTracker, newRemoteWindow: uint32, maxWindow: uint32) =
|
||||||
t.maxRemoteWindow = newRemoteWindow
|
t.maxRemoteWindow = newRemoteWindow
|
||||||
t.maxWindow = maxWindow
|
t.maxWindow = maxWindow
|
||||||
t.checkWaiters()
|
t.notifyWaiters()
|
||||||
|
|
||||||
proc decreaseCurrentWindow*(t: SendBufferTracker, value: uint32, notifyWaiters: bool) =
|
proc decreaseCurrentWindow*(t: SendBufferTracker, value: uint32) =
|
||||||
doAssert(t.currentWindow >= value)
|
doAssert(t.currentWindow >= value)
|
||||||
t.currentWindow = t.currentWindow - value
|
t.currentWindow = t.currentWindow - value
|
||||||
if (notifyWaiters):
|
|
||||||
t.checkWaiters()
|
|
||||||
|
|
||||||
proc reserveNBytesWait*(t: SendBufferTracker, n: uint32): Future[void] =
|
proc reserveNBytesWait*(t: SendBufferTracker, n: uint32): Future[void] =
|
||||||
let fut = newFuture[void]("SendBufferTracker.reserveNBytesWait")
|
let fut = newFuture[void]("SendBufferTracker.reserveNBytesWait")
|
||||||
|
@ -99,4 +97,7 @@ proc reserveNBytes*(t: SendBufferTracker, n: uint32): bool =
|
||||||
else:
|
else:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc forceReserveNBytes*(t: SendBufferTracker, n: uint32) =
|
||||||
|
t.currentWindow = t.currentWindow + n
|
||||||
|
|
||||||
proc currentBytesInFlight*(t: SendBufferTracker): uint32 = t.currentWindow
|
proc currentBytesInFlight*(t: SendBufferTracker): uint32 = t.currentWindow
|
||||||
|
|
|
@ -200,6 +200,14 @@ type
|
||||||
# indicator if we're in slow-start (exponential growth) phase
|
# indicator if we're in slow-start (exponential growth) phase
|
||||||
slowStart: bool
|
slowStart: bool
|
||||||
|
|
||||||
|
# indiciator if we're in fast time out mode i.e we will resent
|
||||||
|
# oldest packet un-acket in case of newer packet arriving
|
||||||
|
fastTimeout: bool
|
||||||
|
|
||||||
|
# Sequence number of the next packet we are allowed to fast-resend. This is
|
||||||
|
# necessary to make sure we only fast resend once per packet
|
||||||
|
fastResendSeqNr: uint16
|
||||||
|
|
||||||
#the slow-start threshold, in bytes
|
#the slow-start threshold, in bytes
|
||||||
slowStartTreshold: uint32
|
slowStartTreshold: uint32
|
||||||
|
|
||||||
|
@ -391,7 +399,7 @@ proc markAllPacketAsLost(s: UtpSocket) =
|
||||||
# lack of waiters notification in case of timeout effectivly means that
|
# lack of waiters notification in case of timeout effectivly means that
|
||||||
# we do not allow any new bytes to enter snd buffer in case of new free space
|
# we do not allow any new bytes to enter snd buffer in case of new free space
|
||||||
# due to timeout.
|
# due to timeout.
|
||||||
s.sendBufferTracker.decreaseCurrentWindow(packetPayloadLength, notifyWaiters = false)
|
s.sendBufferTracker.decreaseCurrentWindow(packetPayloadLength)
|
||||||
|
|
||||||
inc i
|
inc i
|
||||||
|
|
||||||
|
@ -406,6 +414,20 @@ proc shouldDisconnectFromFailedRemote(socket: UtpSocket): bool =
|
||||||
(socket.state == SynSent and socket.retransmitCount >= 2) or
|
(socket.state == SynSent and socket.retransmitCount >= 2) or
|
||||||
(socket.retransmitCount >= socket.socketConfig.dataResendsBeforeFailure)
|
(socket.retransmitCount >= socket.socketConfig.dataResendsBeforeFailure)
|
||||||
|
|
||||||
|
# Forces asynchronous re-send of packet waiting in outgoing buffer
|
||||||
|
proc forceResendPacket(socket: UtpSocket, pkSeqNr: uint16) =
|
||||||
|
doAssert(
|
||||||
|
socket.outBuffer.get(pkSeqNr).isSome(),
|
||||||
|
"Force resend should be called only on packet still in outgoing buffer"
|
||||||
|
)
|
||||||
|
if socket.outBuffer[pkSeqNr].needResend:
|
||||||
|
# if needResend is set to true it means that packet payload was already
|
||||||
|
# removed from the bytes window and need to be re-added.
|
||||||
|
socket.sendBufferTracker.forceReserveNBytes(socket.outBuffer[pkSeqNr].payloadLength)
|
||||||
|
|
||||||
|
let data = socket.setSend(socket.outBuffer[pkSeqNr])
|
||||||
|
discard socket.sendData(data)
|
||||||
|
|
||||||
proc checkTimeouts(socket: UtpSocket) {.async.} =
|
proc checkTimeouts(socket: UtpSocket) {.async.} =
|
||||||
let currentTime = getMonoTimestamp().moment
|
let currentTime = getMonoTimestamp().moment
|
||||||
# flush all packets which needs to be re-send
|
# flush all packets which needs to be re-send
|
||||||
|
@ -493,30 +515,19 @@ proc checkTimeouts(socket: UtpSocket) {.async.} =
|
||||||
|
|
||||||
# resend oldest packet if there are some packets in flight
|
# resend oldest packet if there are some packets in flight
|
||||||
if (socket.curWindowPackets > 0):
|
if (socket.curWindowPackets > 0):
|
||||||
inc socket.retransmitCount
|
|
||||||
let oldestPacketSeqNr = socket.seqNr - socket.curWindowPackets
|
let oldestPacketSeqNr = socket.seqNr - socket.curWindowPackets
|
||||||
# TODO add handling of fast timeout
|
|
||||||
|
inc socket.retransmitCount
|
||||||
doAssert(
|
socket.fastTimeout = true
|
||||||
socket.outBuffer.get(oldestPacketSeqNr).isSome(),
|
|
||||||
"oldest packet should always be available when there is data in flight"
|
debug "Resending oldest packet",
|
||||||
)
|
pkSeqNr = oldestPacketSeqNr,
|
||||||
|
retransmitCount = socket.retransmitCount,
|
||||||
let payloadLength = socket.outBuffer[oldestPacketSeqNr].payloadLength
|
curWindowPackets = socket.curWindowPackets
|
||||||
if (socket.sendBufferTracker.reserveNBytes(payloadLength)):
|
|
||||||
debug "Resending oldest packet in outBuffer",
|
|
||||||
seqNr = oldestPacketSeqNr,
|
|
||||||
curWindowPackets = socket.curWindowPackets
|
|
||||||
|
|
||||||
let dataToSend = socket.setSend(socket.outBuffer[oldestPacketSeqNr])
|
|
||||||
await socket.sendData(dataToSend)
|
|
||||||
else:
|
|
||||||
# TODO Logs added here to check if we need to check for spcae in send buffer
|
|
||||||
# reference impl does not do it.
|
|
||||||
debug "Should resend oldest packet in outBuffer but there is no place for more bytes in send buffer",
|
|
||||||
seqNr = oldestPacketSeqNr,
|
|
||||||
curWindowPackets = socket.curWindowPackets
|
|
||||||
|
|
||||||
|
# Oldest packet should always be present, so it is safe to call force
|
||||||
|
# resend
|
||||||
|
socket.forceResendPacket(oldestPacketSeqNr)
|
||||||
|
|
||||||
# TODO add sending keep alives when necessary
|
# TODO add sending keep alives when necessary
|
||||||
|
|
||||||
|
@ -684,6 +695,8 @@ proc new[A](
|
||||||
zeroWindowTimer: currentTime + cfg.remoteWindowResetTimeout,
|
zeroWindowTimer: currentTime + cfg.remoteWindowResetTimeout,
|
||||||
socketKey: UtpSocketKey.init(to, rcvId),
|
socketKey: UtpSocketKey.init(to, rcvId),
|
||||||
slowStart: true,
|
slowStart: true,
|
||||||
|
fastTimeout: false,
|
||||||
|
fastResendSeqNr: initialSeqNr,
|
||||||
slowStartTreshold: cfg.optSndBuffer,
|
slowStartTreshold: cfg.optSndBuffer,
|
||||||
ourHistogram: DelayHistogram.init(currentTime),
|
ourHistogram: DelayHistogram.init(currentTime),
|
||||||
remoteHistogram: DelayHistogram.init(currentTime),
|
remoteHistogram: DelayHistogram.init(currentTime),
|
||||||
|
@ -860,7 +873,10 @@ proc ackPacket(socket: UtpSocket, seqNr: uint16, currentTime: Moment): AckResult
|
||||||
# been considered timed-out, and is not included in
|
# been considered timed-out, and is not included in
|
||||||
# the cur_window anymore
|
# the cur_window anymore
|
||||||
if (not packet.needResend):
|
if (not packet.needResend):
|
||||||
socket.sendBufferTracker.decreaseCurrentWindow(packet.payloadLength, notifyWaiters = true)
|
socket.sendBufferTracker.decreaseCurrentWindow(packet.payloadLength)
|
||||||
|
|
||||||
|
# we notify all waiters that there is possibly new space in send buffer
|
||||||
|
socket.sendBufferTracker.notifyWaiters()
|
||||||
|
|
||||||
socket.retransmitCount = 0
|
socket.retransmitCount = 0
|
||||||
PacketAcked
|
PacketAcked
|
||||||
|
@ -1212,6 +1228,10 @@ proc processPacket*(socket: UtpSocket, p: Packet) {.async.} =
|
||||||
# but in theory remote could stil write some data on this socket (or even its own fin)
|
# but in theory remote could stil write some data on this socket (or even its own fin)
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
|
|
||||||
|
# Update fast resend counter to avoid resending old packet twice
|
||||||
|
if wrapCompareLess(socket.fastResendSeqNr, pkAckNr + 1):
|
||||||
|
socket.fastResendSeqNr = pkAckNr + 1
|
||||||
|
|
||||||
socket.ackPackets(acks, timestampInfo.moment)
|
socket.ackPackets(acks, timestampInfo.moment)
|
||||||
|
|
||||||
# packets in front may have been acked by selective ack, decrease window until we hit
|
# packets in front may have been acked by selective ack, decrease window until we hit
|
||||||
|
@ -1221,6 +1241,32 @@ proc processPacket*(socket: UtpSocket, p: Packet) {.async.} =
|
||||||
debug "Packet in front hase been acked by selective ack. Decrese window",
|
debug "Packet in front hase been acked by selective ack. Decrese window",
|
||||||
windowPackets = socket.curWindowPackets
|
windowPackets = socket.curWindowPackets
|
||||||
|
|
||||||
|
# fast timeout
|
||||||
|
if socket.fastTimeout:
|
||||||
|
let oldestOutstandingPktSeqNr = socket.seqNr - socket.curWindowPackets
|
||||||
|
|
||||||
|
debug "Hit fast timeout re-send",
|
||||||
|
curWindowPackets = socket.curWindowPackets,
|
||||||
|
oldesPkSeqNr = oldestOutstandingPktSeqNr,
|
||||||
|
fastResendSeqNr = socket.fastResendSeqNr
|
||||||
|
|
||||||
|
|
||||||
|
if oldestOutstandingPktSeqNr != socket.fastResendSeqNr:
|
||||||
|
# fastResendSeqNr do not point to oldest unacked packet, we probably already resent
|
||||||
|
# packet that timed-out. Leave fast timeout mode
|
||||||
|
socket.fastTimeout = false
|
||||||
|
else:
|
||||||
|
let shouldReSendPacket = socket.outBuffer.exists(oldestOutstandingPktSeqNr, (p: OutgoingPacket) => p.transmissions > 0)
|
||||||
|
if shouldReSendPacket:
|
||||||
|
debug "Packet fast timeout resend",
|
||||||
|
pkSeqNr = oldestOutstandingPktSeqNr
|
||||||
|
|
||||||
|
inc socket.fastResendSeqNr
|
||||||
|
|
||||||
|
# Is is safe to call force resend as we already checked shouldReSendPacket
|
||||||
|
# condition
|
||||||
|
socket.forceResendPacket(oldestOutstandingPktSeqNr)
|
||||||
|
|
||||||
if (p.eack.isSome()):
|
if (p.eack.isSome()):
|
||||||
socket.selectiveAckPackets(pkAckNr, p.eack.unsafeGet(), timestampInfo.moment)
|
socket.selectiveAckPackets(pkAckNr, p.eack.unsafeGet(), timestampInfo.moment)
|
||||||
|
|
||||||
|
|
|
@ -1331,6 +1331,8 @@ procSuite "Utp socket unit test":
|
||||||
|
|
||||||
await outgoingSocket.processPacket(dataP1)
|
await outgoingSocket.processPacket(dataP1)
|
||||||
|
|
||||||
|
let fastResend = await q.get()
|
||||||
|
|
||||||
let ack = await q.get()
|
let ack = await q.get()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
@ -1345,3 +1347,69 @@ procSuite "Utp socket unit test":
|
||||||
thirdSend.header.ackNr > secondSend.header.ackNr
|
thirdSend.header.ackNr > secondSend.header.ackNr
|
||||||
|
|
||||||
await outgoingSocket.destroyWait()
|
await outgoingSocket.destroyWait()
|
||||||
|
|
||||||
|
asyncTest "Should support fast timeout ":
|
||||||
|
let q = newAsyncQueue[Packet]()
|
||||||
|
let initialRemoteSeq = 10'u16
|
||||||
|
|
||||||
|
# small writes to make sure it will be 3 different packets
|
||||||
|
let dataToWrite1 = @[1'u8]
|
||||||
|
let dataToWrite2 = @[1'u8]
|
||||||
|
let dataToWrite3 = @[1'u8]
|
||||||
|
|
||||||
|
|
||||||
|
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
|
||||||
|
|
||||||
|
let writeRes1 = await outgoingSocket.write(dataToWrite1)
|
||||||
|
let writeRes2 = await outgoingSocket.write(dataToWrite2)
|
||||||
|
let writeRes3 = await outgoingSocket.write(dataToWrite3)
|
||||||
|
|
||||||
|
check:
|
||||||
|
writeRes1.isOk()
|
||||||
|
writeRes2.isOk()
|
||||||
|
writeRes3.isOk()
|
||||||
|
|
||||||
|
# drain queue of all sent packets
|
||||||
|
let sent1 = await q.get()
|
||||||
|
let sent2 = await q.get()
|
||||||
|
let sent3 = await q.get()
|
||||||
|
|
||||||
|
# wait for first timeout. Socket will enter fast timeout mode
|
||||||
|
let reSent1 = await q.get()
|
||||||
|
|
||||||
|
check:
|
||||||
|
# check that re-sent packet is the oldest one
|
||||||
|
reSent1.payload == sent1.payload
|
||||||
|
reSent1.header.seqNr == sent1.header.seqNr
|
||||||
|
|
||||||
|
# ack which will ack our re-sent packet
|
||||||
|
let responseAck =
|
||||||
|
ackPacket(
|
||||||
|
initialRemoteSeq,
|
||||||
|
initialPacket.header.connectionId,
|
||||||
|
reSent1.header.seqNr,
|
||||||
|
testBufferSize,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
await outgoingSocket.processPacket(responseAck)
|
||||||
|
|
||||||
|
let fastResentPacket = await q.get()
|
||||||
|
|
||||||
|
check:
|
||||||
|
# second packet is now oldest unacked packet so it should be the one which
|
||||||
|
# is send during fast resend
|
||||||
|
fastResentPacket.payload == sent2.payload
|
||||||
|
fastResentPacket.header.seqNr == sent2.header.seqNr
|
||||||
|
|
||||||
|
# duplicate ack, processing it should not fast-resend any packet
|
||||||
|
await outgoingSocket.processPacket(responseAck)
|
||||||
|
|
||||||
|
let resent3 = await q.get()
|
||||||
|
|
||||||
|
check:
|
||||||
|
# in next timeout cycle packet nr3 is the only one waiting for re-send
|
||||||
|
resent3.payload == sent3.payload
|
||||||
|
resent3.header.seqNr == sent3.header.seqNr
|
||||||
|
|
||||||
|
await outgoingSocket.destroyWait()
|
||||||
|
|
Loading…
Reference in New Issue