Skip to content

Commit 47b9309

Browse files
committed
mitogen: Factor MessageHeader class out of Message
1 parent ec212a1 commit 47b9309

File tree

4 files changed

+124
-38
lines changed

4 files changed

+124
-38
lines changed

mitogen/core.py

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import linecache
4545
import logging
4646
import os
47+
import operator
4748
import pickle as py_pickle
4849
import pstats
4950
import signal
@@ -767,6 +768,54 @@ def find_class(self, module, func):
767768
_Unpickler = pickle.Unpickler
768769

769770

771+
class MessageError(Error): pass
772+
class MessageMagicError(MessageError): pass
773+
class MessageSizeError(MessageError): pass
774+
775+
776+
class MessageHeader(tuple):
777+
__slots__ = ()
778+
_struct = struct.Struct('>hLLLLLL')
779+
MAGIC = 0x4d49 # b'MI'
780+
SIZE = _struct.size
781+
782+
def __new__(cls, magic, dst, src, auth, handle, reply_to, data_size):
783+
args = (magic, dst, src, auth, handle, reply_to, data_size)
784+
return tuple.__new__(cls, args)
785+
786+
magic = property(operator.itemgetter(0))
787+
dst = property(operator.itemgetter(1))
788+
src = property(operator.itemgetter(2))
789+
auth = property(operator.itemgetter(3))
790+
handle = property(operator.itemgetter(4))
791+
reply_to = property(operator.itemgetter(5))
792+
data_size = property(operator.itemgetter(6))
793+
794+
@classmethod
795+
def unpack(cls, buffer, max_message_size):
796+
self = cls(*cls._struct.unpack(buffer))
797+
if self.magic != cls.MAGIC:
798+
raise MessageMagicError(
799+
'Expected magic %x, got %x' % (cls.MAGIC, self.magic),
800+
)
801+
if self.data_size > max_message_size:
802+
raise MessageSizeError(
803+
'Maximum size exceeded (got %d, max %d)'
804+
% (self.data_size, max_message_size),
805+
)
806+
return self
807+
808+
def pack(self):
809+
return self._struct.pack(*self)
810+
811+
def __repr__(self):
812+
return '%s.%s(magic=%d, dst=%d, src=%d, auth=%d, handle=%d, reply_to=%d, data_size=%d)' % (
813+
self.__class__.__module__, self.__class__.__name__,
814+
self.magic, self.dst, self.src, self.auth, self.handle,
815+
self.reply_to, self.data_size,
816+
)
817+
818+
770819
class Message(object):
771820
"""
772821
Messages are the fundamental unit of communication, comprising fields from
@@ -810,10 +859,6 @@ class Message(object):
810859
#: the :class:`mitogen.select.Select` interface. Defaults to :data:`None`.
811860
receiver = None
812861

813-
HEADER_FMT = '>hLLLLLL'
814-
HEADER_LEN = struct.calcsize(HEADER_FMT)
815-
HEADER_MAGIC = 0x4d49 # 'MI'
816-
817862
def __init__(self, **kwargs):
818863
"""
819864
Construct a message from from the supplied `kwargs`. :attr:`src_id` and
@@ -825,12 +870,11 @@ def __init__(self, **kwargs):
825870
assert isinstance(self.data, BytesType), 'Message data is not Bytes'
826871

827872
def pack(self):
828-
return (
829-
struct.pack(self.HEADER_FMT, self.HEADER_MAGIC, self.dst_id,
830-
self.src_id, self.auth_id, self.handle,
831-
self.reply_to or 0, len(self.data))
832-
+ self.data
873+
hdr = MessageHeader(
874+
MessageHeader.MAGIC, self.dst_id, self.src_id, self.auth_id,
875+
self.handle, self.reply_to or 0, len(self.data),
833876
)
877+
return hdr.pack() + self.data
834878

835879
def _unpickle_context(self, context_id, name):
836880
return _unpickle_context(context_id, name, router=self.router)
@@ -2138,37 +2182,32 @@ def on_receive(self, broker, buf):
21382182
)
21392183

21402184
def _receive_one(self, broker):
2141-
if self._input_buf_len < Message.HEADER_LEN:
2185+
if self._input_buf_len < MessageHeader.SIZE:
21422186
return False
21432187

2144-
msg = Message()
2145-
msg.router = self._router
2146-
(magic, msg.dst_id, msg.src_id, msg.auth_id,
2147-
msg.handle, msg.reply_to, msg_len) = struct.unpack(
2148-
Message.HEADER_FMT,
2149-
self._input_buf[0][:Message.HEADER_LEN],
2150-
)
2151-
2152-
if magic != Message.HEADER_MAGIC:
2188+
try:
2189+
hdr = MessageHeader.unpack(
2190+
self._input_buf[0][:MessageHeader.SIZE],
2191+
self._router.max_message_size,
2192+
)
2193+
except MessageMagicError:
21532194
LOG.error(self.corrupt_msg, self.stream.name, self._input_buf[0][:2048])
21542195
self.stream.on_disconnect(broker)
21552196
return False
2156-
2157-
if msg_len > self._router.max_message_size:
2158-
LOG.error('%r: Maximum message size exceeded (got %d, max %d)',
2159-
self, msg_len, self._router.max_message_size)
2197+
except MessageSizeError as exc:
2198+
LOG.error('%r: %s', self, exc)
21602199
self.stream.on_disconnect(broker)
21612200
return False
21622201

2163-
total_len = msg_len + Message.HEADER_LEN
2202+
total_len = MessageHeader.SIZE + hdr.data_size
21642203
if self._input_buf_len < total_len:
21652204
_vv and IOLOG.debug(
21662205
'%r: Input too short (want %d, got %d)',
2167-
self, msg_len, self._input_buf_len - Message.HEADER_LEN
2206+
self, hdr.data_size, self._input_buf_len - MessageHeader.SIZE
21682207
)
21692208
return False
21702209

2171-
start = Message.HEADER_LEN
2210+
start = MessageHeader.SIZE
21722211
prev_start = start
21732212
remain = total_len
21742213
bits = []
@@ -2180,6 +2219,11 @@ def _receive_one(self, broker):
21802219
prev_start = start
21812220
start = 0
21822221

2222+
msg = Message(
2223+
dst_id=hdr.dst, src_id=hdr.src, auth_id=hdr.auth,
2224+
handle=hdr.handle, reply_to=hdr.reply_to,
2225+
)
2226+
msg.router = self._router
21832227
msg.data = b('').join(bits)
21842228
self._input_buf.appendleft(buf[prev_start+len(bit):])
21852229
self._input_buf_len -= total_len

mitogen/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ def on_shutdown(self):
981981
# The IO loop pumps 128KiB chunks. An ideal message is a multiple of this,
982982
# odd-sized messages waste one tiny write() per message on the trailer.
983983
# Therefore subtract 10 bytes pickle overhead + 24 bytes header.
984-
IO_SIZE = mitogen.core.CHUNK_SIZE - (mitogen.core.Message.HEADER_LEN + (
984+
IO_SIZE = mitogen.core.CHUNK_SIZE - (mitogen.core.MessageHeader.SIZE + (
985985
len(
986986
mitogen.core.Message.pickled(
987987
mitogen.core.Blob(b(' ') * mitogen.core.CHUNK_SIZE)

tests/message_test.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,57 @@
1111
from mitogen.core import b
1212

1313

14+
class MessageHeaderTest(testlib.TestCase):
15+
def test_attributes(self):
16+
hdr = mitogen.core.MessageHeader(1, 2, 3, 4, 5, 6, 7)
17+
self.assertEqual(hdr.magic, 1)
18+
self.assertEqual(hdr.dst, 2)
19+
self.assertEqual(hdr.src, 3)
20+
self.assertEqual(hdr.auth, 4)
21+
self.assertEqual(hdr.handle, 5)
22+
self.assertEqual(hdr.reply_to, 6)
23+
self.assertEqual(hdr.data_size, 7)
24+
25+
def test_unpack(self):
26+
hdr1 = mitogen.core.MessageHeader(0x4d49, 2, 3, 4, 5, 6, 7)
27+
hdr2 = mitogen.core.MessageHeader.unpack(
28+
b'MI\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0\x05\0\0\0\x06\0\0\0\x07',
29+
max_message_size=100,
30+
)
31+
self.assertEqual(hdr1, hdr2)
32+
33+
self.assertRaises(
34+
mitogen.core.MessageMagicError,
35+
mitogen.core.MessageHeader.unpack,
36+
b'AB\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0\x05\0\0\0\x06\0\0\0\x07',
37+
max_message_size=100,
38+
)
39+
40+
self.assertRaises(
41+
mitogen.core.MessageSizeError,
42+
mitogen.core.MessageHeader.unpack,
43+
b'MI\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0\x05\0\0\0\x06\0\0\0\x07',
44+
max_message_size=6,
45+
)
46+
47+
def test_pack(self):
48+
hdr = mitogen.core.MessageHeader(
49+
mitogen.core.MessageHeader.MAGIC, 2, 3, 4, 5, 6, 7,
50+
)
51+
self.assertEqual(
52+
hdr.pack(),
53+
b'MI\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0\x05\0\0\0\x06\0\0\0\x07',
54+
)
55+
self.assertEqual(len(hdr.pack()), mitogen.core.MessageHeader.SIZE)
56+
57+
def test_repr(self):
58+
hdr = mitogen.core.MessageHeader(1, 2, 3, 4, 5, 6, 7)
59+
self.assertEqual(
60+
repr(hdr),
61+
'mitogen.core.MessageHeader(magic=1, dst=2, src=3, auth=4, handle=5, reply_to=6, data_size=7)',
62+
)
63+
64+
1465
class ConstructorTest(testlib.TestCase):
1566
klass = mitogen.core.Message
1667

@@ -64,18 +115,9 @@ def test_data_hates_unicode(self):
64115
class PackTest(testlib.TestCase):
65116
klass = mitogen.core.Message
66117

67-
def test_header_format_sanity(self):
68-
self.assertEqual(self.klass.HEADER_LEN,
69-
struct.calcsize(self.klass.HEADER_FMT))
70-
71118
def test_header_length_correct(self):
72119
s = self.klass(dst_id=123, handle=123).pack()
73-
self.assertEqual(len(s), self.klass.HEADER_LEN)
74-
75-
def test_magic(self):
76-
s = self.klass(dst_id=123, handle=123).pack()
77-
magic, = struct.unpack('>h', s[:2])
78-
self.assertEqual(self.klass.HEADER_MAGIC, magic)
120+
self.assertEqual(len(s), mitogen.core.MessageHeader.SIZE)
79121

80122
def test_dst_id(self):
81123
s = self.klass(dst_id=123, handle=123).pack()

tests/mitogen_protocol_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_corruption(self):
1616
protocol = self.klass(router, 1)
1717
protocol.stream = stream
1818

19-
junk = mitogen.core.b('x') * mitogen.core.Message.HEADER_LEN
19+
junk = mitogen.core.b('x') * mitogen.core.MessageHeader.SIZE
2020

2121
capture = testlib.LogCapturer()
2222
capture.start()

0 commit comments

Comments
 (0)