mplex fixes (#362)

* remove almost-empty types module
* lock when writing message (that's the only place the lock matters, and
only when the message is > max msg size)
* logging updates (log in consistent order, makes reading logs easier)
* raise EOF from readExactly only if no bytes have been read (to signal
that _no_ bytes were lost)
This commit is contained in:
Jacek Sieka 2020-09-14 10:19:54 +02:00 committed by GitHub
parent 96d4c44fec
commit 0db45462cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 60 additions and 82 deletions

View File

@ -22,7 +22,6 @@ import ../libp2p/[switch,
protocols/protocol, protocols/protocol,
muxers/muxer, muxers/muxer,
muxers/mplex/mplex, muxers/mplex/mplex,
muxers/mplex/types,
protocols/secure/secio, protocols/secure/secio,
protocols/secure/secure] protocols/secure/secure]

View File

@ -17,8 +17,7 @@ import ../libp2p/[switch,
protocols/secure/secure, protocols/secure/secure,
protocols/secure/secio, protocols/secure/secio,
muxers/muxer, muxers/muxer,
muxers/mplex/mplex, muxers/mplex/mplex]
muxers/mplex/types]
const ChatCodec = "/nim-libp2p/chat/1.0.0" const ChatCodec = "/nim-libp2p/chat/1.0.0"
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505" const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"

View File

@ -17,8 +17,7 @@ import ../libp2p/[switch,
protocols/secure/secure, protocols/secure/secure,
protocols/secure/secio, protocols/secure/secio,
muxers/muxer, muxers/muxer,
muxers/mplex/mplex, muxers/mplex/mplex]
muxers/mplex/types]
const ChatCodec = "/nim-libp2p/chat/1.0.0" const ChatCodec = "/nim-libp2p/chat/1.0.0"
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505" const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"

View File

@ -18,8 +18,7 @@ import ../libp2p/[switch, # manage transports, a single entry
protocols/secure/secure, # define the protocol of secure connection protocols/secure/secure, # define the protocol of secure connection
protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS
muxers/muxer, # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection muxers/muxer, # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection
muxers/mplex/mplex, # implement stream multiplexing muxers/mplex/mplex] # define some contants and message types for stream multiplexing
muxers/mplex/types] # define some contants and message types for stream multiplexing
const ChatCodec = "/nim-libp2p/chat/1.0.0" const ChatCodec = "/nim-libp2p/chat/1.0.0"
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505" const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"

View File

@ -9,8 +9,7 @@
import chronos import chronos
import nimcrypto/utils, chronicles, stew/byteutils import nimcrypto/utils, chronicles, stew/byteutils
import types, import ../../stream/connection,
../../stream/connection,
../../utility, ../../utility,
../../varint, ../../varint,
../../vbuffer ../../vbuffer
@ -19,6 +18,15 @@ logScope:
topics = "mplexcoder" topics = "mplexcoder"
type type
MessageType* {.pure.} = enum
New,
MsgIn,
MsgOut,
CloseIn,
CloseOut,
ResetIn,
ResetOut
Msg* = tuple Msg* = tuple
id: uint64 id: uint64
msgType: MessageType msgType: MessageType
@ -26,6 +34,9 @@ type
InvalidMplexMsgType = object of CatchableError InvalidMplexMsgType = object of CatchableError
# https://github.com/libp2p/specs/tree/master/mplex#writing-to-a-stream
const MaxMsgSize* = 1 shl 20 # 1mb
proc newInvalidMplexMsgType*(): ref InvalidMplexMsgType = proc newInvalidMplexMsgType*(): ref InvalidMplexMsgType =
newException(InvalidMplexMsgType, "invalid message type") newException(InvalidMplexMsgType, "invalid message type")

View File

@ -9,8 +9,7 @@
import std/[oids, strformat] import std/[oids, strformat]
import chronos, chronicles, metrics import chronos, chronicles, metrics
import types, import ./coder,
coder,
../muxer, ../muxer,
nimcrypto/utils, nimcrypto/utils,
../../stream/[bufferstream, connection, streamseq], ../../stream/[bufferstream, connection, streamseq],
@ -76,8 +75,7 @@ func shortLog*(s: LPChannel): auto =
chronicles.formatIt(LPChannel): shortLog(it) chronicles.formatIt(LPChannel): shortLog(it)
proc closeMessage(s: LPChannel) {.async.} = proc closeMessage(s: LPChannel) {.async.} =
## send close message - this will not raise ## send close message
## on EOF or Closed
withWriteLock(s.writeLock): withWriteLock(s.writeLock):
trace "sending close message", s trace "sending close message", s
@ -94,13 +92,13 @@ proc resetMessage(s: LPChannel) {.async.} =
# need to re-raise CancelledError. # need to re-raise CancelledError.
debug "Unexpected cancellation while resetting channel", s debug "Unexpected cancellation while resetting channel", s
except LPStreamEOFError as exc: except LPStreamEOFError as exc:
trace "muxed connection EOF", s, exc = exc.msg trace "muxed connection EOF", s, msg = exc.msg
except LPStreamClosedError as exc: except LPStreamClosedError as exc:
trace "muxed connection closed", s, exc = exc.msg trace "muxed connection closed", s, msg = exc.msg
except LPStreamIncompleteError as exc: except LPStreamIncompleteError as exc:
trace "incomplete message", s, exc = exc.msg trace "incomplete message", s, msg = exc.msg
except CatchableError as exc: except CatchableError as exc:
debug "Unhandled exception leak", s, exc = exc.msg debug "Unhandled exception leak", s, msg = exc.msg
proc open*(s: LPChannel) {.async, gcsafe.} = proc open*(s: LPChannel) {.async, gcsafe.} =
await s.conn.writeMsg(s.id, MessageType.New, s.name) await s.conn.writeMsg(s.id, MessageType.New, s.name)
@ -115,7 +113,7 @@ proc closeRemote*(s: LPChannel) {.async.} =
except CancelledError as exc: except CancelledError as exc:
raise exc raise exc
except CatchableError as exc: except CatchableError as exc:
trace "exception closing remote channel", s, exc = exc.msg trace "exception closing remote channel", s, msg = exc.msg
trace "Closed remote", s trace "Closed remote", s
@ -141,7 +139,7 @@ method reset*(s: LPChannel) {.base, async, gcsafe.} =
trace "channel already closed or reset", s trace "channel already closed or reset", s
return return
trace "Resetting channel", s trace "Resetting channel", s, len = s.len
# First, make sure any new calls to `readOnce` and `pushTo` will fail - there # First, make sure any new calls to `readOnce` and `pushTo` will fail - there
# may already be such calls in the event queue # may already be such calls in the event queue
@ -174,7 +172,7 @@ method close*(s: LPChannel) {.async, gcsafe.} =
trace "Already closed", s trace "Already closed", s
return return
trace "Closing channel", s trace "Closing channel", s, len = s.len
proc closeInternal() {.async.} = proc closeInternal() {.async.} =
try: try:
@ -189,7 +187,7 @@ method close*(s: LPChannel) {.async, gcsafe.} =
except LPStreamClosedError, LPStreamEOFError: except LPStreamClosedError, LPStreamEOFError:
trace "Connection already closed", s trace "Connection already closed", s
except CatchableError as exc: # Shouldn't happen? except CatchableError as exc: # Shouldn't happen?
debug "Exception closing channel", s, exc = exc.msg warn "Exception closing channel", s, msg = exc.msg
await s.reset() await s.reset()
trace "Closed channel", s trace "Closed channel", s
@ -221,10 +219,11 @@ method write*(s: LPChannel, msg: seq[byte]): Future[void] {.async.} =
# writes should happen in sequence # writes should happen in sequence
trace "write msg", len = msg.len trace "write msg", len = msg.len
await s.conn.writeMsg(s.id, s.msgCode, msg) withWriteLock(s.writeLock):
await s.conn.writeMsg(s.id, s.msgCode, msg)
s.activity = true s.activity = true
except CatchableError as exc: except CatchableError as exc:
trace "exception in lpchannel write handler", s, exc = exc.msg trace "exception in lpchannel write handler", s, msg = exc.msg
await s.conn.close() await s.conn.close()
raise exc raise exc

View File

@ -14,18 +14,20 @@ import ../muxer,
../../stream/bufferstream, ../../stream/bufferstream,
../../utility, ../../utility,
../../peerinfo, ../../peerinfo,
coder, ./coder,
types, ./lpchannel
lpchannel
export muxer export muxer
logScope: logScope:
topics = "mplex" topics = "mplex"
const MplexCodec* = "/mplex/6.7.0"
const const
MaxChannelCount = 200 MaxChannelCount = 200
when defined(libp2p_expensive_metrics): when defined(libp2p_expensive_metrics):
declareGauge(libp2p_mplex_channels, declareGauge(libp2p_mplex_channels,
"mplex channels", labels = ["initiator", "peer"]) "mplex channels", labels = ["initiator", "peer"])
@ -58,19 +60,17 @@ proc cleanupChann(m: Mplex, chann: LPChannel) {.async, inline.} =
try: try:
await chann.join() await chann.join()
m.channels[chann.initiator].del(chann.id) m.channels[chann.initiator].del(chann.id)
debug "cleaned up channel", m, chann trace "cleaned up channel", m, chann
when defined(libp2p_expensive_metrics): when defined(libp2p_expensive_metrics):
libp2p_mplex_channels.set( libp2p_mplex_channels.set(
m.channels[chann.initiator].len.int64, m.channels[chann.initiator].len.int64,
labelValues = [$chann.initiator, $m.connection.peerInfo.peerId]) labelValues = [$chann.initiator, $m.connection.peerInfo.peerId])
except CancelledError:
# This is top-level procedure which will work as separate task, so it
# do not need to propogate CancelledError.
debug "Unexpected cancellation in mplex channel cleanup",
m, chann
except CatchableError as exc: except CatchableError as exc:
debug "error cleaning up mplex channel", exc = exc.msg, m, chann # This is top-level procedure which will work as separate task, so it
# do not need to propogate CancelledError, and no other exceptions should
# happen here
warn "Error cleaning up mplex channel", m, chann, msg = exc.msg
proc newStreamInternal*(m: Mplex, proc newStreamInternal*(m: Mplex,
initiator: bool = true, initiator: bool = true,
@ -99,7 +99,7 @@ proc newStreamInternal*(m: Mplex,
result.peerInfo = m.connection.peerInfo result.peerInfo = m.connection.peerInfo
result.observedAddr = m.connection.observedAddr result.observedAddr = m.connection.observedAddr
trace "Creating new channel", id, initiator, name, m, channel = result trace "Creating new channel", m, channel = result, id, initiator, name
m.channels[initiator][id] = result m.channels[initiator][id] = result
@ -118,14 +118,10 @@ proc handleStream(m: Mplex, chann: LPChannel) {.async.} =
await m.streamHandler(chann) await m.streamHandler(chann)
trace "finished handling stream", m, chann trace "finished handling stream", m, chann
doAssert(chann.closed, "connection not closed by handler!") doAssert(chann.closed, "connection not closed by handler!")
except CancelledError: except CatchableError as exc:
trace "Unexpected cancellation in stream handler", m, chann
await chann.reset()
# This is top-level procedure which will work as separate task, so it # This is top-level procedure which will work as separate task, so it
# do not need to propogate CancelledError. # do not need to propogate CancelledError.
except CatchableError as exc: trace "Exception in mplex stream handler", m, chann, msg = exc.msg
trace "Exception in mplex stream handler",
exc = exc.msg, m, chann
await chann.reset() await chann.reset()
method handle*(m: Mplex) {.async, gcsafe.} = method handle*(m: Mplex) {.async, gcsafe.} =
@ -162,6 +158,8 @@ method handle*(m: Mplex) {.async, gcsafe.} =
let name = string.fromBytes(data) let name = string.fromBytes(data)
m.newStreamInternal(false, id, name, timeout = m.outChannTimeout) m.newStreamInternal(false, id, name, timeout = m.outChannTimeout)
trace "Processing channel message", m, channel, data = data.shortLog
case msgType: case msgType:
of MessageType.New: of MessageType.New:
trace "created channel", m, channel trace "created channel", m, channel
@ -177,9 +175,9 @@ method handle*(m: Mplex) {.async, gcsafe.} =
allowed = MaxMsgSize, channel allowed = MaxMsgSize, channel
raise newLPStreamLimitError() raise newLPStreamLimitError()
trace "pushing data to channel", m, channel trace "pushing data to channel", m, channel, len = data.len
await channel.pushTo(data) await channel.pushTo(data)
trace "pushed data to channel", m, channel trace "pushed data to channel", m, channel, len = data.len
of MessageType.CloseIn, MessageType.CloseOut: of MessageType.CloseIn, MessageType.CloseOut:
await channel.closeRemote() await channel.closeRemote()
@ -190,9 +188,9 @@ method handle*(m: Mplex) {.async, gcsafe.} =
# there no way for this procedure to be cancelled implicitely. # there no way for this procedure to be cancelled implicitely.
debug "Unexpected cancellation in mplex handler", m debug "Unexpected cancellation in mplex handler", m
except LPStreamEOFError as exc: except LPStreamEOFError as exc:
trace "Stream EOF", msg = exc.msg, m trace "Stream EOF", m, msg = exc.msg
except CatchableError as exc: except CatchableError as exc:
warn "Unexpected exception in mplex read loop", msg = exc.msg, m warn "Unexpected exception in mplex read loop", m, msg = exc.msg
finally: finally:
await m.close() await m.close()
trace "Stopped mplex handler", m trace "Stopped mplex handler", m

View File

@ -1,27 +0,0 @@
## Nim-LibP2P
## Copyright (c) 2019 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
import chronos
# https://github.com/libp2p/specs/tree/master/mplex#writing-to-a-stream
const MaxMsgSize* = 1 shl 20 # 1mb
const MplexCodec* = "/mplex/6.7.0"
const MaxReadWriteTime* = 5.seconds
type
MplexNoSuchChannel* = object of CatchableError
MessageType* {.pure.} = enum
New,
MsgIn,
MsgOut,
CloseIn,
CloseOut,
ResetIn,
ResetOut

View File

@ -2,7 +2,7 @@ import
options, tables, chronos, bearssl, options, tables, chronos, bearssl,
switch, peerid, peerinfo, stream/connection, multiaddress, switch, peerid, peerinfo, stream/connection, multiaddress,
crypto/crypto, transports/[transport, tcptransport], crypto/crypto, transports/[transport, tcptransport],
muxers/[muxer, mplex/mplex, mplex/types], muxers/[muxer, mplex/mplex],
protocols/[identify, secure/secure] protocols/[identify, secure/secure]
import import

View File

@ -112,6 +112,9 @@ proc readExactly*(s: LPStream,
if s.atEof: if s.atEof:
raise newLPStreamEOFError() raise newLPStreamEOFError()
if nbytes == 0:
return
logScope: logScope:
s s
nbytes = nbytes nbytes = nbytes
@ -122,13 +125,14 @@ proc readExactly*(s: LPStream,
while read < nbytes and not(s.atEof()): while read < nbytes and not(s.atEof()):
read += await s.readOnce(addr pbuffer[read], nbytes - read) read += await s.readOnce(addr pbuffer[read], nbytes - read)
if read == 0:
doAssert s.atEof()
trace "couldn't read all bytes, stream EOF", s, nbytes, read
raise newLPStreamEOFError()
if read < nbytes: if read < nbytes:
if s.atEof: trace "couldn't read all bytes, incomplete data", s, nbytes, read
trace "couldn't read all bytes, stream EOF", expected = nbytes, read raise newLPStreamIncompleteError()
raise newLPStreamEOFError()
else:
trace "couldn't read all bytes, incomplete data", expected = nbytes, read
raise newLPStreamIncompleteError()
proc readLine*(s: LPStream, proc readLine*(s: LPStream,
limit = 0, limit = 0,

View File

@ -95,7 +95,7 @@ suite "BufferStream":
try: try:
await readFut await readFut
except LPStreamEOFError: except LPStreamIncompleteError:
result = true result = true
check: check:

View File

@ -19,7 +19,6 @@ import ../libp2p/[daemon/daemonapi,
crypto/crypto, crypto/crypto,
muxers/mplex/mplex, muxers/mplex/mplex,
muxers/muxer, muxers/muxer,
muxers/mplex/types,
protocols/protocol, protocols/protocol,
protocols/identify, protocols/identify,
transports/transport, transports/transport,

View File

@ -8,7 +8,6 @@ import ../libp2p/[errors,
multiaddress, multiaddress,
muxers/mplex/mplex, muxers/mplex/mplex,
muxers/mplex/coder, muxers/mplex/coder,
muxers/mplex/types,
muxers/mplex/lpchannel, muxers/mplex/lpchannel,
vbuffer, vbuffer,
varint] varint]

View File

@ -27,7 +27,6 @@ import ../libp2p/[switch,
protocols/protocol, protocols/protocol,
muxers/muxer, muxers/muxer,
muxers/mplex/mplex, muxers/mplex/mplex,
muxers/mplex/types,
protocols/secure/noise, protocols/secure/noise,
protocols/secure/secure] protocols/secure/secure]
import ./helpers import ./helpers