Skip to content

Commit

Permalink
feat: speed up unmarshalling headers (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jan 7, 2025
1 parent 50b377e commit 5825758
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 12 deletions.
19 changes: 17 additions & 2 deletions src/dbus_fast/_private/unmarshaller.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,18 @@ cdef unsigned int HEADER_SIGNATURE_SIZE
cdef unsigned int LITTLE_ENDIAN
cdef unsigned int BIG_ENDIAN
cdef unsigned int PROTOCOL_VERSION


cdef unsigned int HEADER_PATH_IDX
cdef unsigned int HEADER_INTERFACE_IDX
cdef unsigned int HEADER_MEMBER_IDX
cdef unsigned int HEADER_ERROR_NAME_IDX
cdef unsigned int HEADER_REPLY_SERIAL_IDX
cdef unsigned int HEADER_DESTINATION_IDX
cdef unsigned int HEADER_SENDER_IDX
cdef unsigned int HEADER_SIGNATURE_IDX
cdef unsigned int HEADER_UNIX_FDS_IDX

cdef cython.list HEADER_IDX_TO_ARG_NAME

cdef str UINT32_CAST
Expand Down Expand Up @@ -95,6 +106,8 @@ cdef unsigned int TOKEN_LEFT_PAREN_AS_INT
cdef object MARSHALL_STREAM_END_ERROR
cdef object DEFAULT_BUFFER_SIZE

cdef list _EMPTY_HEADERS

cdef cython.uint EAGAIN
cdef cython.uint EWOULDBLOCK

Expand Down Expand Up @@ -222,9 +235,10 @@ cdef class Unmarshaller:

@cython.locals(
body=cython.list,
header_fields=cython.dict,
header_fields=cython.list,
token_as_int=cython.uint,
signature=cython.str,
message=Message
)
cdef _read_body(self)

Expand All @@ -237,5 +251,6 @@ cdef class Unmarshaller:
o=cython.ulong,
token_as_int=cython.uint,
signature_len=cython.uint,
headers=cython.list
)
cdef cython.dict _header_fields(self, unsigned int header_length)
cdef cython.list _header_fields(self, unsigned int header_length)
33 changes: 25 additions & 8 deletions src/dbus_fast/_private/unmarshaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,18 @@
"signature",
"unix_fds",
]
HEADER_PATH_IDX = HEADER_IDX_TO_ARG_NAME.index("path")
HEADER_INTERFACE_IDX = HEADER_IDX_TO_ARG_NAME.index("interface")
HEADER_MEMBER_IDX = HEADER_IDX_TO_ARG_NAME.index("member")
HEADER_ERROR_NAME_IDX = HEADER_IDX_TO_ARG_NAME.index("error_name")
HEADER_REPLY_SERIAL_IDX = HEADER_IDX_TO_ARG_NAME.index("reply_serial")
HEADER_DESTINATION_IDX = HEADER_IDX_TO_ARG_NAME.index("destination")
HEADER_SENDER_IDX = HEADER_IDX_TO_ARG_NAME.index("sender")
HEADER_SIGNATURE_IDX = HEADER_IDX_TO_ARG_NAME.index("signature")
HEADER_UNIX_FDS_IDX = HEADER_IDX_TO_ARG_NAME.index("unix_fds")

_EMPTY_HEADERS = [None] * len(HEADER_IDX_TO_ARG_NAME)

_SignatureType = SignatureType
_int = int

Expand Down Expand Up @@ -596,12 +606,12 @@ def read_array(self, type_: _SignatureType) -> Iterable[Any]:
result_list.append(reader(self, child_type))
return result_list

def _header_fields(self, header_length: _int) -> dict[str, Any]:
def _header_fields(self, header_length: _int) -> list[Any]:
"""Header fields are always a(yv)."""
beginning_pos = self._pos
headers = {}
buf = self._buf
readers = self._readers
headers = _EMPTY_HEADERS.copy()
while self._pos - beginning_pos < header_length:
# Now read the y (byte) of struct (yv)
self._pos += (-self._pos & 7) + 1 # align 8 + 1 for 'y' byte
Expand All @@ -616,18 +626,19 @@ def _header_fields(self, header_length: _int) -> dict[str, Any]:
continue
token_as_int = buf[o]
# Now that we have the token we can read the variant value
key = HEADER_IDX_TO_ARG_NAME[field_0]
# Strings and signatures are the most common types
# so we inline them for performance
if token_as_int == TOKEN_O_AS_INT or token_as_int == TOKEN_S_AS_INT:
headers[key] = self._read_string_unpack()
headers[field_0] = self._read_string_unpack()
elif token_as_int == TOKEN_G_AS_INT:
headers[key] = self._read_signature()
headers[field_0] = self._read_signature()
else:
token = buf[o : o + signature_len].decode()
# There shouldn't be any other types in the header
# but just in case, we'll read it using the slow path
headers[key] = readers[token](self, get_signature_tree(token).types[0])
headers[field_0] = readers[token](
self, get_signature_tree(token).types[0]
)
return headers

def _read_header(self) -> None:
Expand Down Expand Up @@ -694,7 +705,7 @@ def _read_body(self) -> None:
self._pos = HEADER_ARRAY_OF_STRUCT_SIGNATURE_POSITION
header_fields = self._header_fields(self._header_len)
self._pos += -self._pos & 7 # align 8
signature = header_fields.pop("signature", "")
signature = header_fields[HEADER_SIGNATURE_IDX]
if not self._body_len:
tree = SIGNATURE_TREE_EMPTY
body: list[Any] = []
Expand Down Expand Up @@ -749,7 +760,13 @@ def _read_body(self) -> None:
# The D-Bus implementation already validates the message,
# so we don't need to do it again.
validate=False,
**header_fields,
destination=header_fields[HEADER_DESTINATION_IDX],
path=header_fields[HEADER_PATH_IDX],
interface=header_fields[HEADER_INTERFACE_IDX],
member=header_fields[HEADER_MEMBER_IDX],
reply_serial=header_fields[HEADER_REPLY_SERIAL_IDX],
error_name=header_fields[HEADER_ERROR_NAME_IDX],
sender=header_fields[HEADER_SENDER_IDX],
)
self._read_complete = True

Expand Down
4 changes: 2 additions & 2 deletions src/dbus_fast/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ def __init__(
message_type: MessageType = MESSAGE_TYPE_METHOD_CALL,
flags: Union[MessageFlag, int] = MESSAGE_FLAG_NONE,
error_name: Optional[Union[str, ErrorType]] = None,
reply_serial: int = 0,
reply_serial: Optional[int] = None,
sender: Optional[str] = None,
unix_fds: list[int] = [],
signature: Optional[Union[SignatureTree, str]] = None,
body: list[Any] = [],
serial: int = 0,
serial: Optional[int] = None,
validate: bool = True,
) -> None:
self.destination = destination
Expand Down

0 comments on commit 5825758

Please sign in to comment.