Skip to content

Commit

Permalink
[Backport] Fixes callbacks being dropped on Linux/macOS/BSD. (#15012)
Browse files Browse the repository at this point in the history
Fixes #15003.

This is a serious bug which occurs when data cannot be read/sent
immediately and there are a bunch of other read/write events
pending. What happens is that the new events are dropped which
results in the case of the reported bug resulted in some data not
being sent (!).

(cherry picked from commit 1e3a0ef)
  • Loading branch information
dom96 authored and narimiran committed Jul 21, 2020
1 parent 28bc815 commit ddcbc1a
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
7 changes: 6 additions & 1 deletion lib/pure/asyncdispatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1211,14 +1211,19 @@ else:
let newLength = max(len(curList), InitCallbackListSize)
var newList = newSeqOfCap[Callback](newLength)

var eventsExtinguished = false
for cb in curList:
if eventsExtinguished:
newList.add(cb)
continue
if not cb(fd):
# Callback wants to be called again.
newList.add(cb)
# This callback has returned with EAGAIN, so we don't need to
# call any other callbacks as they are all waiting for the same event
# on the same fd.
break
# We do need to ensure they are called again though.
eventsExtinguished = true

withData(selector, fd.int, fdData) do:
# Descriptor is still present in the queue.
Expand Down
67 changes: 67 additions & 0 deletions tests/async/tasynceagain.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
discard """
disabled: "windows"
exitcode: 0
"""
# AsyncSocketBug.nim
# Jens Alfke (@snej) -- 16 July 2020
# Demonstrates data loss by Nim's AsyncSocket.
# Just run it, and it will raise an assertion failure within a minute.

import asyncdispatch, asyncnet, strformat, strutils, sugar

const FrameSize = 9999 # Exact size not important, but larger sizes fail quicker

proc runServer() {.async.} =
# Server side:
var server = newAsyncSocket()
server.bindAddr(Port(9001))
server.listen()
let client = await server.accept()
echo "Server got client connection"
var lastN = 0
while true:
let frame = await client.recv(FrameSize)
assert frame.len == FrameSize
let n = frame[0..<6].parseInt()
echo "RCVD #", n, ": ", frame[0..80], "..."
if n != lastN + 1:
echo &"******** ERROR: Server received #{n}, but last was #{lastN}!"
assert n == lastN + 1
lastN = n
await sleepAsync 100


proc main() {.async.} =
asyncCheck runServer()

# Client side:
let socket = newAsyncSocket(buffered = false)
await socket.connect("localhost", Port(9001))
echo "Client socket connected"

var sentCount = 0
var completedCount = 0

while sentCount < 2000:
sentCount += 1
let n = sentCount

var message = &"{n:06} This is message #{n} of ∞. Please stay tuned for more. "
#echo ">>> ", message
while message.len < FrameSize:
message = message & message
let frame = message[0..<FrameSize]

capture n:
socket.send(frame).addCallback proc(f: Future[void]) =
# Callback when the send completes:
assert not f.failed
echo "SENT #", n
if n != completedCount + 1:
echo &"******** ERROR: Client completed #{n}, but last completed was #{completedCount}!"
# If this assert is enabled, it will trigger earlier than the server-side assert above:
assert n == completedCount + 1
completedCount = n
await sleepAsync 1

waitFor main()

0 comments on commit ddcbc1a

Please sign in to comment.