From 04624154915f7dea4a2c75626fd750fb54ca0bbd Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 29 Sep 2025 13:21:11 -0400 Subject: [PATCH] `_ems`: tolerate and warn on already popped execs In the `translate_and_relay_brokerd_events()` loop task that is, such that we never crash on a `status_msg = book._active.pop(oid)` in the 'closed' status handler whenever a double removal happens. Turns out there were unforeseen races here when a benign backend error would cause an order-mode dialog to be cancelled (incorrectly) and then a UI side `.on_cancel()` would trigger too-early removal from the `book._active` table despite the backend sending an actual 'closed' event (much) later, this would crash on the now missing entry.. So instead we now, - obviously use `book._active.pop(oid, None)` - emit a `log.warning()` (not info lol) on a null-read and with a less "one-line-y" message explaining the double removal and maybe *why*. --- piker/clearing/_ems.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/piker/clearing/_ems.py b/piker/clearing/_ems.py index 6c49b8e9..d452daac 100644 --- a/piker/clearing/_ems.py +++ b/piker/clearing/_ems.py @@ -388,6 +388,7 @@ async def open_brokerd_dialog( for ep_name in [ 'open_trade_dialog', # probably final name? 'trades_dialogue', # legacy + # ^!TODO, rm this since all backends ported no ?!? ]: trades_endpoint = getattr( brokermod, @@ -1019,8 +1020,18 @@ async def translate_and_relay_brokerd_events( ) if status == 'closed': - log.info(f'Execution for {oid} is complete!') - status_msg = book._active.pop(oid) + log.info( + f'Execution is complete!\n' + f'oid: {oid!r}\n' + ) + status_msg = book._active.pop(oid, None) + if status_msg is None: + log.warning( + f'Order was already cleared from book ??\n' + f'oid: {oid!r}\n' + f'\n' + f'Maybe the order cancelled before submitted ??\n' + ) elif status == 'canceled': log.cancel(f'Cancellation for {oid} is complete!') @@ -1544,19 +1555,18 @@ async def maybe_open_trade_relays( @tractor.context async def _emsd_main( - ctx: tractor.Context, + ctx: tractor.Context, # becomes `ems_ctx` below fqme: str, exec_mode: str, # ('paper', 'live') loglevel: str|None = None, -) -> tuple[ - dict[ - # brokername, acctid - tuple[str, str], +) -> tuple[ # `ctx.started()` value! + dict[ # positions + tuple[str, str], # brokername, acctid list[BrokerdPosition], ], - list[str], - dict[str, Status], + list[str], # accounts + dict[str, Status], # dialogs ]: ''' EMS (sub)actor entrypoint providing the execution management