exceptions and resource cleanup

This commit is contained in:
Dmitriy Ryajov 2020-05-23 10:50:05 -06:00
parent d4bdb42046
commit d83ce4c932
4 changed files with 120 additions and 94 deletions

View File

@ -50,35 +50,24 @@ proc writeMsg*(conn: Connection,
trace "sending data over mplex", id,
msgType,
data = data.len
try:
var
left = data.len
offset = 0
while left > 0 or data.len == 0:
let
chunkSize = if left > MaxMsgSize: MaxMsgSize - 64 else: left
chunk = if chunkSize > 0 : data[offset..(offset + chunkSize - 1)] else: data
## write lenght prefixed
var buf = initVBuffer()
buf.writePBVarint(id shl 3 or ord(msgType).uint64)
buf.writePBVarint(chunkSize.uint64) # size should be always sent
buf.finish()
left = left - chunkSize
offset = offset + chunkSize
await conn.write(buf.buffer & chunk)
var
left = data.len
offset = 0
while left > 0 or data.len == 0:
let
chunkSize = if left > MaxMsgSize: MaxMsgSize - 64 else: left
chunk = if chunkSize > 0 : data[offset..(offset + chunkSize - 1)] else: data
## write lenght prefixed
var buf = initVBuffer()
buf.writePBVarint(id shl 3 or ord(msgType).uint64)
buf.writePBVarint(chunkSize.uint64) # size should be always sent
buf.finish()
left = left - chunkSize
offset = offset + chunkSize
await conn.write(buf.buffer & chunk)
if data.len == 0:
return
except LPStreamEOFError:
trace "Ignoring EOF while writing"
except CancelledError as exc:
raise exc
except CatchableError as exc:
# TODO these exceptions are ignored since it's likely that if writes are
# are failing, the underlying connection is already closed - this needs
# more cleanup though
debug "Could not write to connection", error = exc.name
trace "Could not write to connection - verbose", msg = exc.msg
if data.len == 0:
return
proc writeMsg*(conn: Connection,
id: uint64,

View File

@ -63,6 +63,8 @@ template withEOFExceptions(body: untyped): untyped =
except LPStreamIncompleteError as exc:
trace "incomplete message", exc = exc.msg
method reset*(s: LPChannel) {.base, async, gcsafe.}
proc newChannel*(id: uint64,
conn: Connection,
initiator: bool,
@ -81,17 +83,26 @@ proc newChannel*(id: uint64,
let chan = result
proc writeHandler(data: seq[byte]): Future[void] {.async, gcsafe.} =
if chan.isLazy and not(chan.isOpen):
await chan.open()
try:
if chan.isLazy and not(chan.isOpen):
await chan.open()
# writes should happen in sequence
trace "sending data", data = data.shortLog,
id = chan.id,
initiator = chan.initiator,
name = chan.name,
oid = chan.oid
# writes should happen in sequence
trace "sending data", data = data.shortLog,
id = chan.id,
initiator = chan.initiator,
name = chan.name,
oid = chan.oid
await conn.writeMsg(chan.id, chan.msgCode, data) # write header
try:
await conn.writeMsg(chan.id,
chan.msgCode,
data).wait(2.minutes) # write header
except AsyncTimeoutError:
trace "timeout writting channel, resetting"
asyncCheck chan.reset()
except CatchableError as exc:
trace "unable to write in bufferstream handler", exc = exc.msg
result.initBufferStream(writeHandler, size)
when chronicles.enabledLogLevel == LogLevel.TRACE:
@ -106,9 +117,9 @@ proc closeMessage(s: LPChannel) {.async.} =
withEOFExceptions:
withWriteLock(s.writeLock):
trace "sending close message", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
initiator = s.initiator,
name = s.name,
oid = s.oid
await s.conn.writeMsg(s.id, s.closeCode) # write close
@ -116,9 +127,9 @@ proc resetMessage(s: LPChannel) {.async.} =
withEOFExceptions:
withWriteLock(s.writeLock):
trace "sending reset message", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
initiator = s.initiator,
name = s.name,
oid = s.oid
await s.conn.writeMsg(s.id, s.resetCode) # write reset
@ -129,8 +140,8 @@ proc open*(s: LPChannel) {.async, gcsafe.} =
withEOFExceptions:
await s.conn.writeMsg(s.id, MessageType.New, s.name)
trace "oppened channel", oid = s.oid,
name = s.name,
initiator = s.initiator
name = s.name,
initiator = s.initiator
s.isOpen = true
proc closeRemote*(s: LPChannel) {.async.} =
@ -144,9 +155,9 @@ proc closeRemote*(s: LPChannel) {.async.} =
await s.dataReadEvent.wait()
s.dataReadEvent.clear()
# TODO: Not sure if this needs to be set here or bfore consuming
# the buffer
await s.close() # close local end
s.isEof = true # set EOF immediately to prevent further reads
# call to avoid leacks
await procCall BufferStream(s).close() # close parent bufferstream
trace "channel closed on EOF", id = s.id,
@ -161,33 +172,49 @@ method closed*(s: LPChannel): bool =
## header of the file
s.closedLocal
method close*(s: LPChannel) {.async, gcsafe.} =
if s.closedLocal:
return
trace "closing local lpchannel", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
# TODO: we should install a timer that on expire
# will make sure the channel did close by the remote
# so the hald-closed flow completed, if it didn't
# we should send a `reset` and move on.
await s.closeMessage()
s.closedLocal = true
if s.atEof: # already closed by remote close parent buffer imediately
await procCall BufferStream(s).close()
trace "lpchannel closed local", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
method reset*(s: LPChannel) {.base, async.} =
method reset*(s: LPChannel) {.base, async, gcsafe.} =
# we asyncCheck here because the other end
# might be dead already - reset is always
# optimistic
asyncCheck s.resetMessage()
# # because of the async check above,
# # give the message time to depart
# await sleepAsync(100.millis)
await procCall BufferStream(s).close()
s.isEof = true
s.closedLocal = true
method close*(s: LPChannel) {.async, gcsafe.} =
if s.closedLocal:
trace "channel already closed", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
return
proc closeRemote() {.async.} =
try:
trace "closing local lpchannel", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
# TODO: we should install a timer that on expire
# will make sure the channel did close by the remote
# so the hald-closed flow completed, if it didn't
# we should send a `reset` and move on.
await s.closeMessage().wait(2.minutes)
s.closedLocal = true
if s.atEof: # already closed by remote close parent buffer imediately
await procCall BufferStream(s).close()
except AsyncTimeoutError:
trace "close timeoud, reset channel"
asyncCheck s.reset() # reset on timeout
except CatchableError as exc:
trace "exception closing channel"
trace "lpchannel closed local", id = s.id,
initiator = s.initiator,
name = s.name,
oid = s.oid
asyncCheck closeRemote()

View File

@ -26,6 +26,7 @@ type
Mplex* = ref object of Muxer
remote: Table[uint64, LPChannel]
local: Table[uint64, LPChannel]
conns: seq[Connection]
handlerFuts: seq[Future[void]]
currentId*: uint64
maxChannels*: uint64
@ -93,17 +94,25 @@ method handle*(m: Mplex) {.async, gcsafe.} =
oid = m.oid
if not isNil(m.streamHandler):
let stream = newConnection(channel)
m.conns.add(stream)
stream.peerInfo = m.connection.peerInfo
var fut = newFuture[void]()
proc handler() {.async.} =
tryAndWarn "mplex channel handler":
await m.streamHandler(stream)
try:
try:
await m.streamHandler(stream)
trace "streamhandler ended", oid = stream.oid
finally:
if not(stream.closed):
await stream.close()
except CatchableError as exc:
trace "exception in stream handler", exc = exc.msg
finally:
m.conns.keepItIf(it != stream)
m.handlerFuts.keepItIf(it != fut)
fut = handler()
m.handlerFuts.add(fut)
fut.addCallback do(udata: pointer):
m.handlerFuts.keepItIf(it != fut)
of MessageType.MsgIn, MessageType.MsgOut:
trace "pushing data to channel", id = id,
@ -191,16 +200,16 @@ method close*(m: Mplex) {.async, gcsafe.} =
return
trace "closing mplex muxer", oid = m.oid
await all(
toSeq(m.remote.values).mapIt(it.reset()) &
toSeq(m.local.values).mapIt(it.reset()))
checkFutures(
await allFinished(
toSeq(m.remote.values).mapIt(it.reset()) &
toSeq(m.local.values).mapIt(it.reset())))
checkFutures(await allFinished(m.handlerFuts))
await all(m.conns.mapIt(it.close())) # dispose of channe's connections
await all(m.handlerFuts)
await m.connection.close()
m.remote.clear()
m.local.clear()
m.conns = @[]
m.handlerFuts = @[]
m.isClosed = true

View File

@ -46,22 +46,23 @@ proc newMuxerProvider*(creator: MuxerConstructor, codec: string): MuxerProvider
method init(c: MuxerProvider) =
proc handler(conn: Connection, proto: string) {.async, gcsafe, closure.} =
let
muxer = c.newMuxer(conn)
try:
let
muxer = c.newMuxer(conn)
if not isNil(c.streamHandler):
muxer.streamHandler = c.streamHandler
if not isNil(c.streamHandler):
muxer.streamHandler = c.streamHandler
var futs = newSeq[Future[void]]()
var futs = newSeq[Future[void]]()
futs &= muxer.handle()
futs &= muxer.handle()
# finally await both the futures
if not isNil(c.muxerHandler):
futs &= c.muxerHandler(muxer)
# finally await both the futures
if not isNil(c.muxerHandler):
futs &= c.muxerHandler(muxer)
# log and re-raise on errors
futs = await allFinished(futs)
checkFutures(futs)
# log and re-raise on errors
await all(futs)
except CatchableError as exc:
trace "exception in muxer handler", exc = exc.msg
c.handler = handler