Skip to content

Commit 90ae483

Browse files
committed
feat: perf tests and fix: pin connections and streams
1 parent 875334a commit 90ae483

8 files changed

Lines changed: 169 additions & 30 deletions

File tree

lsquic.nimble

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ task format, "Format nim code using nph":
2121

2222
task test, "Run tests":
2323
when defined(windows):
24-
exec "nim c --mm:refc -d:nimDebugDlOpen -r --threads:on tests/test_connection.nim"
24+
exec "nim c --mm:refc -d:nimDebugDlOpen --threads:on tests/test_connection.nim"
2525
else:
26-
exec "nim c --mm:refc -r --threads:on tests/test_connection.nim"
26+
exec "nim c --mm:refc --threads:on tests/test_connection.nim"
27+
exec "./tests/test_connection --output-level=VERBOSE"

lsquic/connection.nim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ method incomingStream*(
9191
method incomingStream*(
9292
connection: IncomingConnection
9393
): Future[Stream] {.async: (raises: [CancelledError, ConnectionError]).} =
94+
if connection.isClosed:
95+
raise newException(ConnectionError, "connection is closed")
96+
9497
let closedFut = connection.closed.wait()
9598
let incomingFut = connection.quicConn.incomingStream()
9699
let raceFut = await race(closedFut, incomingFut)
@@ -108,6 +111,8 @@ method openStream*(
108111
method openStream*(
109112
connection: OutgoingConnection
110113
): Future[Stream] {.async: (raises: [CancelledError, ConnectionError]).} =
114+
if connection.isClosed:
115+
raise newException(ConnectionError, "connection is closed")
111116
let s = Stream.new()
112117
let created = connection.quicConn.addPendingStream(s)
113118
connection.quicContext.makeStream(connection.quicConn)

lsquic/connectionmanager.nim

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ proc localAddress*(
4646
connman.udp.localAddress()
4747

4848
proc startSending*(connman: ConnectionManager) =
49-
trace "Starting sending loop"
49+
debug "Starting sending loop"
5050

5151
proc send() {.async: (raises: [CancelledError]).} =
5252
try:
5353
let datagram = await connman.outgoing.get()
54+
if datagram.len == 0:
55+
# In windows, empty datagrams will make the peer stop receiving data
56+
return
5457
await connman.udp.sendTo(datagram.taddr, datagram.data)
5558
except TransportError as e:
56-
trace "Failed to send datagram", errorMsg = e.msg
59+
debug "Failed to send datagram", errorMsg = e.msg
5760

5861
connman.loop = asyncLoop(send)
5962

lsquic/context/client.nim

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import ../helpers/sequninit
99
proc onNewConn(
1010
stream_if_ctx: pointer, conn: ptr lsquic_conn_t
1111
): ptr lsquic_conn_ctx_t {.cdecl.} =
12-
trace "New connection established: client"
12+
debug "New connection established: client"
1313
let conn_ctx = lsquic_conn_get_ctx(conn)
1414
cast[ptr lsquic_conn_ctx_t](conn_ctx)
1515

1616
proc onHandshakeDone(
1717
conn: ptr lsquic_conn_t, status: enum_lsquic_hsk_status
1818
) {.cdecl.} =
19-
trace "Handshake done", status
19+
debug "Handshake done", status
2020
let conn_ctx = lsquic_conn_get_ctx(conn)
2121
if conn_ctx.isNil:
2222
debug "conn_ctx is nil in onHandshakeDone"
@@ -34,7 +34,7 @@ proc onHandshakeDone(
3434
quicClientConn.connectedFut.complete()
3535

3636
proc onConnClosed(conn: ptr lsquic_conn_t) {.cdecl.} =
37-
trace "Connection closed: client"
37+
debug "Connection closed: client"
3838
let conn_ctx = lsquic_conn_get_ctx(conn)
3939
if not conn_ctx.isNil:
4040
let quicClientConn = cast[QuicClientConn](conn_ctx)
@@ -52,12 +52,13 @@ proc onConnClosed(conn: ptr lsquic_conn_t) {.cdecl.} =
5252
)
5353
quicClientConn.cancelPending()
5454
quicClientConn.onClose()
55+
GC_unref(quicClientConn)
5556
lsquic_conn_set_ctx(conn, nil)
5657

5758
proc onNewStream(
5859
stream_if_ctx: pointer, stream: ptr lsquic_stream_t
5960
): ptr lsquic_stream_ctx_t {.cdecl.} =
60-
trace "New stream created: client"
61+
debug "New stream created: client"
6162
let conn = lsquic_stream_conn(stream)
6263
let conn_ctx = lsquic_conn_get_ctx(conn)
6364
if conn_ctx.isNil:
@@ -90,7 +91,7 @@ method dial*(
9091

9192
let quicClientConn =
9293
QuicClientConn(connectedFut: connectedFut, local: local, remote: remote)
93-
94+
GC_ref(quicClientConn) # Keep it pinned until on_conn_closed is called
9495
let conn = lsquic_engine_connect(
9596
ctx.engine,
9697
N_LSQVER,
@@ -163,5 +164,5 @@ proc new*(
163164
method makeStream*(
164165
ctx: ClientContext, quicConn: QuicConnection
165166
) {.gcsafe, raises: [].} =
166-
trace "Creating stream: client"
167+
debug "Creating stream: client"
167168
lsquic_conn_make_stream(quicConn.lsquicConn)

lsquic/context/server.nim

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ../helpers/[sequninit, transportaddr]
99
proc onNewConn(
1010
stream_if_ctx: pointer, conn: ptr lsquic_conn_t
1111
): ptr lsquic_conn_ctx_t {.cdecl.} =
12-
trace "New connection established: server"
12+
debug "New connection established: server"
1313
var local: ptr SockAddr
1414
var remote: ptr SockAddr
1515
discard lsquic_conn_get_sockaddr(conn, addr local, addr remote)
@@ -19,12 +19,13 @@ proc onNewConn(
1919
remote: remote.toTransportAddress(),
2020
lsquicConn: conn,
2121
)
22+
GC_ref(quicServerConn) # Keep it pinned until on_conn_closed is called
2223
let serverCtx = cast[ServerContext](stream_if_ctx)
2324
serverCtx.incoming.putNoWait(quicServerConn)
2425
cast[ptr lsquic_conn_ctx_t](quicServerConn)
2526

2627
proc onConnClosed(conn: ptr lsquic_conn_t) {.cdecl.} =
27-
trace "Connection closed: server"
28+
debug "Connection closed: server"
2829
let conn_ctx = lsquic_conn_get_ctx(conn)
2930
if not conn_ctx.isNil:
3031
let quicConn = cast[QuicConnection](conn_ctx)
@@ -34,7 +35,7 @@ proc onConnClosed(conn: ptr lsquic_conn_t) {.cdecl.} =
3435
proc onNewStream(
3536
stream_if_ctx: pointer, stream: ptr lsquic_stream_t
3637
): ptr lsquic_stream_ctx_t {.cdecl.} =
37-
trace "New stream created: server"
38+
debug "New stream created: server"
3839
let conn = lsquic_stream_conn(stream)
3940
let conn_ctx = lsquic_conn_get_ctx(conn)
4041
if conn_ctx.isNil:

lsquic/context/stream.nim

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ../[lsquic_ffi, stream]
44
import ../helpers/sequninit
55

66
proc onClose*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl.} =
7-
trace "Stream closed"
7+
debug "Stream closed"
88
if ctx.isNil:
99
debug "stream_ctx is nil onClose"
1010
return
@@ -13,7 +13,8 @@ proc onClose*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl
1313
if not streamCtx.closeWrite:
1414
streamCtx.isEof = true
1515
streamCtx.closed.fire()
16-
streamCtx.abortPendingWrites("stream closed")
16+
streamCtx.abortPendingWrites("stream closed 4")
17+
GC_unref(streamCtx)
1718

1819
type StreamReadContext = object
1920
stream: ptr lsquic_stream_t
@@ -40,12 +41,11 @@ proc onRead*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl.
4041
if nread < 0:
4142
error "could not read from stream", nread, streamId = lsquic_stream_id(stream)
4243
streamCtx.abort()
43-
44+
4445
if lsquic_stream_wantread(stream, 0) == -1:
4546
error "could not set stream wantread", streamId = lsquic_stream_id(stream)
4647
streamCtx.abort()
4748

48-
4949
proc onWrite*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl.} =
5050
trace "onWrite"
5151

@@ -55,6 +55,8 @@ proc onWrite*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl
5555

5656
let streamCtx = cast[Stream](ctx)
5757
if streamCtx.toWrite.len == 0:
58+
if not streamCtx.shouldClose.isNil and not streamCtx.shouldClose.finished:
59+
streamCtx.shouldClose.complete()
5860
if lsquic_stream_wantwrite(stream, 0) == -1:
5961
error "could not set stream wantwrite", streamId = lsquic_stream_id(stream)
6062
streamCtx.abort()
@@ -71,7 +73,6 @@ proc onWrite*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl
7173
let p = w.data[w.offset].addr
7274
let nAvail = (w.data.len - w.offset).csize_t
7375
let n: ssize_t = lsquic_stream_write(stream, p, nAvail)
74-
7576
if n > 0:
7677
w.offset += n.int
7778
if w.offset >= w.data.len:
@@ -82,12 +83,15 @@ proc onWrite*(stream: ptr lsquic_stream_t, ctx: ptr lsquic_stream_ctx_t) {.cdecl
8283
return
8384
streamCtx.toWrite.delete(0)
8485
elif n == 0:
86+
# Nothing to write
8587
break
8688
else:
8789
streamCtx.abortPendingWrites("write failed")
8890
return
8991

9092
if streamCtx.toWrite.len == 0:
93+
if not streamCtx.shouldClose.isNil and not streamCtx.shouldClose.finished:
94+
streamCtx.shouldClose.complete()
9195
if lsquic_stream_wantwrite(stream, 0) == -1:
9296
error "could not set stream wantwrite", streamId = lsquic_stream_id(stream)
9397
streamCtx.abort()

lsquic/stream.nim

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ type Stream* = ref object
1616
closed*: AsyncEvent # This is called when on_close callback is executed
1717
isEof*: bool # Received a FIN from remote
1818
toWrite*: seq[WriteTask]
19+
shouldClose*: Future[void].Raising([CancelledError])
1920

2021
proc new*(T: typedesc[Stream], quicStream: ptr lsquic_stream_t = nil): T =
21-
Stream(
22+
let s = Stream(
2223
quicStream: quicStream,
2324
incoming: newAsyncQueue[seq[byte]](),
2425
closed: newAsyncEvent(),
2526
)
27+
GC_ref(s) # Keep it pinned until stream_if.on_close is executed
28+
s
2629

2730
proc abortPendingWrites*(stream: Stream, reason: string = "") =
2831
for pendingWrite in stream.toWrite.mitems:
@@ -46,10 +49,14 @@ proc abort*(stream: Stream) =
4649
stream.abortPendingWrites("stream aborted")
4750
stream.closed.fire()
4851

49-
proc close*(stream: Stream) =
52+
proc close*(stream: Stream) {.async: (raises: [StreamError, CancelledError]).} =
5053
if stream.closeWrite:
5154
return
5255

56+
if stream.toWrite.len != 0:
57+
stream.shouldClose = Future[void].Raising([CancelledError]).init()
58+
await stream.shouldClose
59+
5360
# Closing only the write side
5461
let ret = lsquic_stream_shutdown(stream.quicStream, 1)
5562
if ret == 0:
@@ -78,7 +85,7 @@ proc read*(
7885
await incomingFut.cancelAndWait()
7986
stream.isEof = true
8087
stream.closeWrite = true
81-
raise newException(StreamError, "stream closed")
88+
raise newException(StreamError, "stream closed 1")
8289

8390
let incoming = await incomingFut
8491
if incoming.len == 0:
@@ -95,15 +102,16 @@ proc write*(
95102
stream: Stream, data: seq[byte]
96103
) {.async: (raises: [CancelledError, StreamError]).} =
97104
if stream.closeWrite:
98-
raise newException(StreamError, "stream is closed")
105+
raise newException(StreamError, "stream closed 3")
99106

100107
let closedFut = stream.closed.wait()
101108
let doneFut = Future[void].Raising([CancelledError, StreamError]).init()
102109
stream.toWrite.add(WriteTask(data: data, doneFut: doneFut))
103110
discard lsquic_stream_wantwrite(stream.quicStream, 1)
104111
let raceFut = await race(closedFut, doneFut)
105112
if raceFut == closedFut:
106-
doneFut.fail(newException(StreamError, "stream closed"))
113+
if not doneFut.finished:
114+
doneFut.fail(newException(StreamError, "stream closed 2"))
107115
stream.closeWrite = true
108116

109117
await doneFut

0 commit comments

Comments
 (0)