From a4959f2ec2462aa9c85a6a5f3b585fe8db357534 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 10:29:55 -1000 Subject: [PATCH 01/19] Always specify the id as a keyword argument. --- src/mktl/protocol/request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 3979feb2..4a08da68 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -127,7 +127,7 @@ def _rep_incoming(self, parts): # allow it to pass, assuming the users know what they're doing. pass - response = message.Message('REP', target, payload, response_id) + response = message.Message('REP', target, payload, id=response_id) pending._complete(response) del self.pending[response_id] @@ -415,7 +415,7 @@ def req_incoming(self, parts): # allow it to pass, assuming the users know what they're doing. pass - request = message.Request(req_type, target, payload, req_id) + request = message.Request(req_type, target, payload, id=req_id) request.prefix = (ident,) payload = None error = None @@ -442,7 +442,7 @@ def req_incoming(self, parts): elif payload.error is None: payload.error = error - response = message.Message('REP', target, payload, req_id) + response = message.Message('REP', target, payload, id=req_id) response.prefix = request.prefix self.send(response) From 257484f9b8de6f51e876bcd4edff272f97f58f05 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 13:08:59 -1000 Subject: [PATCH 02/19] Honor a client request to omit the ACK response. --- src/mktl/daemon.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mktl/daemon.py b/src/mktl/daemon.py index e50a5f4b..f0577118 100644 --- a/src/mktl/daemon.py +++ b/src/mktl/daemon.py @@ -480,7 +480,13 @@ def req_handler(self, request): will be generated. """ - self.req_ack(request) + try: + ack_requested = request.payload.ack + except: + ack_requested = True + + if ack_requested: + self.req_ack(request) type = request.type target = request.target From c7ad4fd8bf2b04afd216665faa7824d3c097085a Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 13:09:32 -1000 Subject: [PATCH 03/19] Honor a client request to omit the ACK response. --- sbin/mkbrokerd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sbin/mkbrokerd b/sbin/mkbrokerd index ea28d3cf..f43bd5ac 100755 --- a/sbin/mkbrokerd +++ b/sbin/mkbrokerd @@ -414,7 +414,13 @@ class RequestServer(mktl.protocol.request.Server): will be generated. """ - self.req_ack(request) + try: + ack_requested = request.payload.ack + except: + ack_requested = True + + if ack_requested: + self.req_ack(request) type = request.type target = request.target From f3a58114a680ac33c158fbf2c15796248008123b Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 13:10:01 -1000 Subject: [PATCH 04/19] Allow arbitrary additional arguments in a payload. Use that capability to signal a lack of interest in an ACK response. --- src/mktl/item.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mktl/item.py b/src/mktl/item.py index 6cf8119c..a802e64c 100644 --- a/src/mktl/item.py +++ b/src/mktl/item.py @@ -684,7 +684,11 @@ def set(self, new_value, wait=True, formatted=False, quantity=False): else: raise ValueError('formatted+quantity arguments must be boolean') - payload = self.to_payload(new_value) + if wait == False: + payload = self.to_payload(new_value, ack=False) + else: + payload = self.to_payload(new_value) + message = protocol.message.Request('SET', self.full_key, payload) self.req.send(message) @@ -821,7 +825,7 @@ def to_format(self, value): return formatted - def to_payload(self, value=None, timestamp=None): + def to_payload(self, value=None, timestamp=None, **kwargs): """ Interpret the provided arguments into a :class:`mktl.protocol.message.Payload` instance; if the *value* is not specified the current value of this :class:`Item` will be @@ -857,11 +861,11 @@ def to_payload(self, value=None, timestamp=None): bulk = value.tobytes() except AttributeError: bulk = None - payload = protocol.message.Payload(value, timestamp) + payload = protocol.message.Payload(value, timestamp, **kwargs) else: shape = value.shape dtype = str(value.dtype) - payload = protocol.message.Payload(None, timestamp, bulk=bulk, shape=shape, dtype=dtype) + payload = protocol.message.Payload(None, timestamp, bulk=bulk, shape=shape, dtype=dtype, **kwargs) return payload From dec3a212befb5c211fde7e8b1ee2177f062c040c Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 13:11:03 -1000 Subject: [PATCH 05/19] Disable the high water mark for request/response; it was coming up with a default of 1000, which was good for about 4000 fire-and-forget requests before the buffer ran out. --- src/mktl/protocol/request.py | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 4a08da68..78d96e73 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -40,6 +40,7 @@ def __init__(self, address, port): self.socket = zmq_context.socket(zmq.DEALER) self.socket.setsockopt(zmq.LINGER, 0) + self.socket.set_hwm(0) self.socket.identity = identity.encode() self.socket.connect(server) @@ -168,18 +169,28 @@ def run(self): https://github.com/zeromq/libzmq/issues/1108 """ - poller = zmq.Poller() - poller.register(self.socket, zmq.POLLIN) - poller.register(self.request_receive, zmq.POLLIN) + incoming = zmq.Poller() + incoming.register(self.socket, zmq.POLLIN) + incoming.register(self.request_receive, zmq.POLLIN) + + outgoing = zmq.Poller() + outgoing.register(self.socket, zmq.POLLOUT) while True: - sockets = poller.poll(10000) # milliseconds - for active, flag in sockets: + inbound_sockets = incoming.poll(10000) # milliseconds + for inbound, flag in inbound_sockets: + + if self.request_receive == inbound: + # Success is assumed on this next polling request. + # A failure will result in lost data on the outbound + # socket; any polling delays here should only occur + # in super high throughput cases, presumably because + # a transmission buffer is full. - if self.request_receive == active: + outbound_sockets = outgoing.poll(1000) self._req_outgoing() - elif self.socket == active: + elif self.socket == inbound: parts = self.socket.recv_multipart() self._rep_incoming(parts) @@ -199,7 +210,15 @@ def send(self, message): self.requests.put(message) self.request_signal.send(b'') - ack = message.wait_ack(self.timeout) + try: + ack_requested = message.payload.ack + except: + ack_requested = True + + if ack_requested: + ack = message.wait_ack(self.timeout) + else: + return if ack == False: error = '%s @ %s:%d: no response received in %.2f sec' @@ -240,6 +259,7 @@ def __init__(self, hostname=None, port=None, avoid=set()): self.hostname = hostname self.socket = zmq_context.socket(zmq.ROUTER) self.socket.setsockopt(zmq.LINGER, 0) + self.socket.set_hwm(0) # If the port is set, use it; otherwise, look for the first available # port within the default range. @@ -358,7 +378,13 @@ def req_handler(self, request): structure of what's happening in the daemon code. """ - self.req_ack(request) + try: + ack_requested = request.payload.ack + except: + ack_requested = True + + if ack_requested: + self.req_ack(request) response = message.Message('REP', target, id=request.id) response.prefix = request.prefix From 9039a28f94ee18f0ceaf39a40718f56b83f8707f Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 13:15:51 -1000 Subject: [PATCH 06/19] Put the client background thread back the way it was, the changes there weren't necessary. --- src/mktl/protocol/request.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 78d96e73..74f3624e 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -169,28 +169,18 @@ def run(self): https://github.com/zeromq/libzmq/issues/1108 """ - incoming = zmq.Poller() - incoming.register(self.socket, zmq.POLLIN) - incoming.register(self.request_receive, zmq.POLLIN) - - outgoing = zmq.Poller() - outgoing.register(self.socket, zmq.POLLOUT) + poller = zmq.Poller() + poller.register(self.socket, zmq.POLLIN) + poller.register(self.request_receive, zmq.POLLIN) while True: - inbound_sockets = incoming.poll(10000) # milliseconds - for inbound, flag in inbound_sockets: - - if self.request_receive == inbound: - # Success is assumed on this next polling request. - # A failure will result in lost data on the outbound - # socket; any polling delays here should only occur - # in super high throughput cases, presumably because - # a transmission buffer is full. + sockets = poller.poll(10000) # milliseconds + for socket, flag in sockets: - outbound_sockets = outgoing.poll(1000) + if self.request_receive == socket: self._req_outgoing() - elif self.socket == inbound: + elif self.socket == socket: parts = self.socket.recv_multipart() self._rep_incoming(parts) From 99ca2a09795d85cde063ea5fc1b3da79948df696 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:01:37 -1000 Subject: [PATCH 07/19] Change the new 'ack' argument to set() to be 'response' instead. --- src/mktl/item.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mktl/item.py b/src/mktl/item.py index a802e64c..90bd3b28 100644 --- a/src/mktl/item.py +++ b/src/mktl/item.py @@ -647,15 +647,18 @@ def req_set(self, request): return payload - def set(self, new_value, wait=True, formatted=False, quantity=False): + def set(self, new_value, wait=True, response=True, formatted=False, quantity=False): """ Set a new value. Set *wait* to True to block until the request completes; this is the default behavior. If *wait* is set to False, the caller will be returned a :class:`mktl.protocol.message.Request` instance, which has a :func:`mktl.protocol.message.Request.wait` method that can optionally be invoked to block until completion of the request; the wait will return immediately once the request is - satisfied. There is no return value for a blocking request; failed - requests will raise exceptions. + satisfied. Set *response* to False to disable all error handling and + acknowledgements for the request (fire and forget); setting + *response* to False implies *wait* is also False. + There is no return value for a blocking request; failed requests + will raise exceptions. The optional *formatted* and *quantity* options enable calling :func:`set` with either the string-formatted representation or @@ -684,10 +687,11 @@ def set(self, new_value, wait=True, formatted=False, quantity=False): else: raise ValueError('formatted+quantity arguments must be boolean') - if wait == False: - payload = self.to_payload(new_value, ack=False) - else: + if response: payload = self.to_payload(new_value) + else: + payload = self.to_payload(new_value, ack=False) + wait = False message = protocol.message.Request('SET', self.full_key, payload) self.req.send(message) From deb8d753b490fb00f206a19b2bbce642a8bde1fc Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:23:56 -1000 Subject: [PATCH 08/19] Trade out the 'ack' field of the payload for 'silent'. --- sbin/mkbrokerd | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sbin/mkbrokerd b/sbin/mkbrokerd index f43bd5ac..86bf0043 100755 --- a/sbin/mkbrokerd +++ b/sbin/mkbrokerd @@ -415,12 +415,14 @@ class RequestServer(mktl.protocol.request.Server): """ try: - ack_requested = request.payload.ack + silent = request.payload.silent except: - ack_requested = True + silent = False - if ack_requested: - self.req_ack(request) + if silent: + return + + self.req_ack(request) type = request.type target = request.target From 1b887423950eacaaf94e7fb9dc426e6d75438c6f Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:24:37 -1000 Subject: [PATCH 09/19] Trade out the 'ack' field of the payload for 'silent'. --- src/mktl/daemon.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mktl/daemon.py b/src/mktl/daemon.py index f0577118..7874c1ca 100644 --- a/src/mktl/daemon.py +++ b/src/mktl/daemon.py @@ -481,11 +481,11 @@ def req_handler(self, request): """ try: - ack_requested = request.payload.ack + silent = request.payload.silent except: - ack_requested = True + silent = False - if ack_requested: + if silent == False: self.req_ack(request) type = request.type @@ -505,7 +505,10 @@ def req_handler(self, request): else: raise ValueError('unhandled request type: ' + type) - return response + if silent: + return None + else: + return response def req_get(self, request): From dcdec27b88ecb3cfea5d7f1c6519f9187d46365f Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:24:52 -1000 Subject: [PATCH 10/19] Trade out the response argument in set() for silent. --- src/mktl/item.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mktl/item.py b/src/mktl/item.py index 90bd3b28..ccdf6180 100644 --- a/src/mktl/item.py +++ b/src/mktl/item.py @@ -647,16 +647,16 @@ def req_set(self, request): return payload - def set(self, new_value, wait=True, response=True, formatted=False, quantity=False): + def set(self, new_value, wait=True, silent=False, formatted=False, quantity=False): """ Set a new value. Set *wait* to True to block until the request completes; this is the default behavior. If *wait* is set to False, the caller will be returned a :class:`mktl.protocol.message.Request` instance, which has a :func:`mktl.protocol.message.Request.wait` method that can optionally be invoked to block until completion of the request; the wait will return immediately once the request is - satisfied. Set *response* to False to disable all error handling and + satisfied. Set *silent* to False to disable all error handling and acknowledgements for the request (fire and forget); setting - *response* to False implies *wait* is also False. + *silent* to True implies *wait* is also False. There is no return value for a blocking request; failed requests will raise exceptions. @@ -687,11 +687,11 @@ def set(self, new_value, wait=True, response=True, formatted=False, quantity=Fal else: raise ValueError('formatted+quantity arguments must be boolean') - if response: - payload = self.to_payload(new_value) - else: - payload = self.to_payload(new_value, ack=False) + if silent: + payload = self.to_payload(new_value, silent=True) wait = False + else: + payload = self.to_payload(new_value) message = protocol.message.Request('SET', self.full_key, payload) self.req.send(message) From 8d1a21dda4b1d7fa9ed57f2cd845b8cdd8b7ce3e Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:25:32 -1000 Subject: [PATCH 11/19] Trade out the 'ack' field of the payload for 'silent'. --- src/mktl/protocol/request.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 74f3624e..eceb743f 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -201,15 +201,15 @@ def send(self, message): self.request_signal.send(b'') try: - ack_requested = message.payload.ack + silent = message.payload.silent except: - ack_requested = True + silent = False - if ack_requested: - ack = message.wait_ack(self.timeout) - else: + if silent: return + ack = message.wait_ack(self.timeout) + if ack == False: error = '%s @ %s:%d: no response received in %.2f sec' args = (message.type, self.address, self.port, self.timeout) @@ -369,12 +369,14 @@ def req_handler(self, request): """ try: - ack_requested = request.payload.ack + silent = request.payload.silent except: - ack_requested = True + silent = False + + if silent: + return - if ack_requested: - self.req_ack(request) + self.req_ack(request) response = message.Message('REP', target, id=request.id) response.prefix = request.prefix From ca3b193c9e9970dc20eeaeeeaa51a7fb895bde3c Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 14:31:18 -1000 Subject: [PATCH 12/19] Added a comment adjacent to the call to req_handler() indicating that the absence of a response could also be requested by the client. --- src/mktl/protocol/request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index eceb743f..bdbb69c4 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -450,7 +450,8 @@ def req_incoming(self, parts): if payload is None and error is None: # The handler should only return None when no response is # immediately forthcoming-- the handler has invoked some - # other processing chain that will issue a proper response. + # other processing chain that will issue a proper response, + # or the client explicitly requested no response. return if error is not None: From eecd838a260594df940aadc004c42deea9b4058f Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 15:17:39 -1000 Subject: [PATCH 13/19] Docstring tweak. --- src/mktl/daemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mktl/daemon.py b/src/mktl/daemon.py index 7874c1ca..22c7ea29 100644 --- a/src/mktl/daemon.py +++ b/src/mktl/daemon.py @@ -476,8 +476,8 @@ def req_config(self, request): def req_handler(self, request): - """ Inspect the incoming request type and decide how a response - will be generated. + """ Inspect the incoming request type and call an appropriate + method to handle that specific request. """ try: From 4bc95ffb82dc58b1621a4f53bddcb277672f99c7 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Tue, 3 Feb 2026 15:21:08 -1000 Subject: [PATCH 14/19] Added a comment about potential out-of-order processing for high frequency requests. --- src/mktl/protocol/request.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index bdbb69c4..64441e25 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -483,6 +483,9 @@ def run(self): elif self.socket == active: parts = self.socket.recv_multipart() # Calling submit() will block if a worker is not available. + # Note that for high frequency operations this can result + # in out-of-order handling of requests, for example, if a + # stream of SET requests are inbound for a single item. self.workers.submit(self.req_incoming, parts) From 65b990b033f99005f5b50fcf3102528d7dee8cc9 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Wed, 4 Feb 2026 09:41:44 -1000 Subject: [PATCH 15/19] Trade out the 'silent' argument for 'reply' instead. --- sbin/mkbrokerd | 8 +++++--- src/mktl/daemon.py | 12 ++++++------ src/mktl/item.py | 14 +++++++------- src/mktl/protocol/request.py | 16 ++++++++++------ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/sbin/mkbrokerd b/sbin/mkbrokerd index 86bf0043..01aef9a5 100755 --- a/sbin/mkbrokerd +++ b/sbin/mkbrokerd @@ -415,11 +415,13 @@ class RequestServer(mktl.protocol.request.Server): """ try: - silent = request.payload.silent + reply = request.payload.reply except: - silent = False + reply = True - if silent: + if reply: + pass + else: return self.req_ack(request) diff --git a/src/mktl/daemon.py b/src/mktl/daemon.py index 22c7ea29..a6ee7b29 100644 --- a/src/mktl/daemon.py +++ b/src/mktl/daemon.py @@ -481,11 +481,11 @@ def req_handler(self, request): """ try: - silent = request.payload.silent + reply = request.payload.reply except: - silent = False + reply = True - if silent == False: + if reply: self.req_ack(request) type = request.type @@ -505,10 +505,10 @@ def req_handler(self, request): else: raise ValueError('unhandled request type: ' + type) - if silent: - return None - else: + if reply: return response + else: + return None def req_get(self, request): diff --git a/src/mktl/item.py b/src/mktl/item.py index ccdf6180..f7e26d5b 100644 --- a/src/mktl/item.py +++ b/src/mktl/item.py @@ -647,16 +647,16 @@ def req_set(self, request): return payload - def set(self, new_value, wait=True, silent=False, formatted=False, quantity=False): + def set(self, new_value, wait=True, reply=True, formatted=False, quantity=False): """ Set a new value. Set *wait* to True to block until the request completes; this is the default behavior. If *wait* is set to False, the caller will be returned a :class:`mktl.protocol.message.Request` instance, which has a :func:`mktl.protocol.message.Request.wait` method that can optionally be invoked to block until completion of the request; the wait will return immediately once the request is - satisfied. Set *silent* to False to disable all error handling and + satisfied. Set *reply* to False to disable all error handling and acknowledgements for the request (fire and forget); setting - *silent* to True implies *wait* is also False. + *reply to False implies *wait* is also False. There is no return value for a blocking request; failed requests will raise exceptions. @@ -687,11 +687,11 @@ def set(self, new_value, wait=True, silent=False, formatted=False, quantity=Fals else: raise ValueError('formatted+quantity arguments must be boolean') - if silent: - payload = self.to_payload(new_value, silent=True) - wait = False - else: + if reply: payload = self.to_payload(new_value) + else: + payload = self.to_payload(new_value, reply=False) + wait = False message = protocol.message.Request('SET', self.full_key, payload) self.req.send(message) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 64441e25..46ae625f 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -201,11 +201,13 @@ def send(self, message): self.request_signal.send(b'') try: - silent = message.payload.silent + reply = message.payload.reply except: - silent = False + reply = True - if silent: + if reply: + pass + else: return ack = message.wait_ack(self.timeout) @@ -369,11 +371,13 @@ def req_handler(self, request): """ try: - silent = request.payload.silent + reply = request.payload.reply except: - silent = False + reply = True - if silent: + if reply: + pass + else: return self.req_ack(request) From 22bd3a3ab634af43867a61f4c83df7e96eb76094 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Fri, 6 Feb 2026 11:44:11 -1000 Subject: [PATCH 16/19] I was trying to realign that background thread with the original contents but I missed a few of the names. --- src/mktl/protocol/request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 46ae625f..6a3e06ca 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -175,12 +175,12 @@ def run(self): while True: sockets = poller.poll(10000) # milliseconds - for socket, flag in sockets: + for active, flag in sockets: - if self.request_receive == socket: + if self.request_receive == active: self._req_outgoing() - elif self.socket == socket: + elif self.socket == active: parts = self.socket.recv_multipart() self._rep_incoming(parts) From 3426b8ef88aa26ab233fe57f4e9768a4bddd062e Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Mon, 9 Feb 2026 17:25:54 -1000 Subject: [PATCH 17/19] Use a property for the 'reply' attribute on a Payload. --- src/mktl/protocol/message.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/mktl/protocol/message.py b/src/mktl/protocol/message.py index 6c9bad8c..a2c8ac22 100644 --- a/src/mktl/protocol/message.py +++ b/src/mktl/protocol/message.py @@ -375,6 +375,30 @@ def encapsulate(self): return payload + @property + def reply(self): + """ The reply attribute is generally only set to indicate that a + reply is not necessary. Establishing a property to return the + current value allows the exception handling to be done once, + here, and not everywhere the reply attribute might be inspected. + By a happy coincidence, the existence of this property does not + trigger the inclusion of 'reply' in the output of vars(), which + is how the :func:`encapsulate` method determines which local + attributes to include in the final output. + """ + + try: + return self.__reply + except AttributeError: + # Message replies are enabled by default. + return True + + + @reply.setter + def reply(self, new_value): + self.__reply = new_value + + # end of class Payload From b278fa6038bf3de14f44831ba819b0705f6a1545 Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Mon, 9 Feb 2026 17:33:26 -1000 Subject: [PATCH 18/19] Mirror the Payload.reply attribute as Message.reply to simplify exception handling. --- src/mktl/protocol/message.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/mktl/protocol/message.py b/src/mktl/protocol/message.py index a2c8ac22..d8f63cd2 100644 --- a/src/mktl/protocol/message.py +++ b/src/mktl/protocol/message.py @@ -128,6 +128,21 @@ def _finalize(self): self._parts = parts + @property + def reply(self): + """ The payload reply attribute is mirrored here for the sake of + simplifying exception handling elsewhere in the mKTL code base. + Otherwise, the other code would need to catch the AttributeError + thrown when the local payload is None. + """ + + try: + return self.payload.reply + except AttributeError: + # There is no payload, and message replies are enabled by default. + return True + + # end of class Message From 374da6c8f9c7116062ce5e453679f518a0a2ff7e Mon Sep 17 00:00:00 2001 From: Kyle Lanclos Date: Mon, 9 Feb 2026 17:34:31 -1000 Subject: [PATCH 19/19] Payload.reply is now a property (mirrored to Message.reply) to simplify exception handling. --- sbin/mkbrokerd | 7 +------ src/mktl/daemon.py | 5 +---- src/mktl/protocol/request.py | 14 ++------------ 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/sbin/mkbrokerd b/sbin/mkbrokerd index 01aef9a5..61833354 100755 --- a/sbin/mkbrokerd +++ b/sbin/mkbrokerd @@ -414,12 +414,7 @@ class RequestServer(mktl.protocol.request.Server): will be generated. """ - try: - reply = request.payload.reply - except: - reply = True - - if reply: + if request.reply: pass else: return diff --git a/src/mktl/daemon.py b/src/mktl/daemon.py index a6ee7b29..1c72abe8 100644 --- a/src/mktl/daemon.py +++ b/src/mktl/daemon.py @@ -480,10 +480,7 @@ def req_handler(self, request): method to handle that specific request. """ - try: - reply = request.payload.reply - except: - reply = True + reply = request.reply if reply: self.req_ack(request) diff --git a/src/mktl/protocol/request.py b/src/mktl/protocol/request.py index 6a3e06ca..4f0bf286 100644 --- a/src/mktl/protocol/request.py +++ b/src/mktl/protocol/request.py @@ -200,12 +200,7 @@ def send(self, message): self.requests.put(message) self.request_signal.send(b'') - try: - reply = message.payload.reply - except: - reply = True - - if reply: + if message.reply: pass else: return @@ -370,12 +365,7 @@ def req_handler(self, request): structure of what's happening in the daemon code. """ - try: - reply = request.payload.reply - except: - reply = True - - if reply: + if request.reply: pass else: return