Skip to content

feat(core): wait for debuglink task before restarting Codec v1 event loop #5577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions core/src/apps/debug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ async def return_layout_change(

# wait for layout change
while True:
if _EXIT_FLAG:
# a response will be sent after restarting the event loop
# (since `storage.layout_watcher` is set).
return

if not detect_deadlock or not layout_change_box.is_empty():
# short-circuit if there is a result already waiting
next_layout = await layout_change_box
Expand Down Expand Up @@ -428,6 +433,28 @@ async def dispatch_WipeDevice(msg: WipeDevice) -> None:
async def _no_op(_msg: Any) -> Success:
return Success()

_EXIT_FLAG = False
_EXIT_BOX = loop.mailbox()
_SESSION_TASK: loop.spawn | None = None

_CLOSE_TIMEOUT_MS = const(5000)

async def close_session() -> None:
if _SESSION_TASK is None:
return

global _EXIT_FLAG

_EXIT_FLAG = True
_EXIT_BOX.put(None)
if layout_change_box.is_empty():
# make sure `DebugLinkGetState` won't get stuck
layout_change_box.put(None)

res = await loop.race(_SESSION_TASK, loop.sleep(_CLOSE_TIMEOUT_MS))
if res is not None:
log.error(__name__, "debuglink session is stuck")

async def handle_session(iface: WireInterface) -> None:
from trezor import protobuf, wire
from trezor.wire.codec import codec_v1
Expand All @@ -443,7 +470,15 @@ async def handle_session(iface: WireInterface) -> None:
except Exception as e:
log.exception(__name__, e)

while True:
read_wait = loop.wait(iface.iface_num() | io.POLL_READ)

while not _EXIT_FLAG:
await loop.race(read_wait, _EXIT_BOX)
if _EXIT_FLAG:
# in case both `read_wait` and `_EXIT_BOX` are ready,
# don't handle the message and exit the loop.
break

try:
try:
msg = await ctx.read_from_wire()
Expand Down Expand Up @@ -515,4 +550,6 @@ async def handle_session(iface: WireInterface) -> None:
def boot() -> None:
import usb

loop.schedule(handle_session(usb.iface_debug))
global _SESSION_TASK

_SESSION_TASK = loop.spawn(handle_session(usb.iface_debug))
2 changes: 1 addition & 1 deletion core/src/trezor/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ def _finish(self, task: Task, result: Any) -> None:
if not self.finished:
self.finished = True
self.exit(task)
schedule(self.callback, result)
_step(self.callback, result)

def __iter__(self) -> Task:
try:
Expand Down
6 changes: 4 additions & 2 deletions core/src/trezor/wire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,11 @@ async def handle_session(iface: WireInterface) -> None:
if not do_not_restart:
# Wait for all active workflows to finish.
await workflow.join_all()
# Let the session be restarted from `main`.
if __debug__:
log.debug(__name__, "loop.clear()", iface=iface)
import apps.debug

await apps.debug.close_session()
# Let the session be restarted from `main`.
loop.clear()
return # pylint: disable=lost-exception

Expand Down
12 changes: 6 additions & 6 deletions tests/ui_tests/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@
"T2T1_cs_test_passphrase_bde.py::test_passphrase_prompt_disappears": "f8ff949ad62592c2e8e7034c538ec071444d850f51a0644477a0c892a5448818",
"T2T1_cs_test_pin.py::test_last_digit_timeout": "e08c1377c568f3bae148014a2c445d5342947e2b204409126df9b13f42fe0036",
"T2T1_cs_test_pin.py::test_long_press_digit": "69759f059e1a4b38ab908e9292620face39fee527fc4123e07f293c2d12c88d7",
"T2T1_cs_test_pin.py::test_pin_cancel": "00cd88148f3633cff32d1cf4b45515d8f5815b38839ec386334fd090d40d6b7e",
"T2T1_cs_test_pin.py::test_pin_cancel": "c5f1339f60ad984d0686eb05b2f18e758d4792e546fd86cf2523e450e3ee0c42",
"T2T1_cs_test_pin.py::test_pin_change": "e8c993f44502edd539370c7e3273bdadabb456b10a04aa08bb85631aa4c5bffd",
"T2T1_cs_test_pin.py::test_pin_delete_hold": "e0677ccaa31dd5798c7bca4ad495f57668064914bbe9ab8af4c1e1f8abf3aff3",
"T2T1_cs_test_pin.py::test_pin_empty_cannot_send": "c686613163a308630a30e8086bd3c4aca47ea708d066c9aae48994371c30e907",
Expand Down Expand Up @@ -855,7 +855,7 @@
"T2T1_de_test_passphrase_bde.py::test_passphrase_prompt_disappears": "5a9f803b8252df5ca01557ef1c9b0162775708dc340214e0f0964396af057fd1",
"T2T1_de_test_pin.py::test_last_digit_timeout": "892b5959c689361e58c9d996567ddebcca24390f70da2f87623851c7b5628d24",
"T2T1_de_test_pin.py::test_long_press_digit": "09e454075c7a6552564962c53ade8daa72da821ed00d0e1d454e06114e31abe1",
"T2T1_de_test_pin.py::test_pin_cancel": "6b4c548f7854f248ad2797f76afbc6d0bc35677184c50896c396c5b0b7796a7c",
"T2T1_de_test_pin.py::test_pin_cancel": "703652c41a354dc68d3a8b9dfea343395bd31355c07f96b9e24551fdfdf67a34",
"T2T1_de_test_pin.py::test_pin_change": "1eccd53cf1bc9871e891effaa08fb6114273e6f147f2134b992a5db41a758e77",
"T2T1_de_test_pin.py::test_pin_delete_hold": "3a92537f7c1c10edd01eb74b4ac12d1bb7fa62fd44bd03896b5be670e200f851",
"T2T1_de_test_pin.py::test_pin_empty_cannot_send": "e84725a26b14d022a3593e4cecb73f6473180f4cdf9d5a785660ba9a6690cb70",
Expand Down Expand Up @@ -910,7 +910,7 @@
"T2T1_en_test_passphrase_bde.py::test_passphrase_prompt_disappears": "888767326ba692427077a717c0169a903d1eae77150a0052897c2296ebc9860f",
"T2T1_en_test_pin.py::test_last_digit_timeout": "6ca215c2f62c4d97c923f68c4a7293e55eaadc03df1ff089d6972d301ecfde56",
"T2T1_en_test_pin.py::test_long_press_digit": "2acea84d185050fd27052c74cb5ebe901263e782167b1cf29f582b654634334e",
"T2T1_en_test_pin.py::test_pin_cancel": "6925b022e7d245154b0f2d6aa08e1e2194827e6a3b0964daa3e786c496e987b3",
"T2T1_en_test_pin.py::test_pin_cancel": "42670d998eb490ba59f126a14a2c1fc5e7ea42089242f46bee04644ffce5fa97",
"T2T1_en_test_pin.py::test_pin_change": "615d2c9619afbfc6f7bd9107237f0896871f5dc12d4f7fafb517b0fef30908f7",
"T2T1_en_test_pin.py::test_pin_delete_hold": "0f84c4bf4f24552bfd826db46699b053db814c9c7077fd65c63acdb004d406ef",
"T2T1_en_test_pin.py::test_pin_empty_cannot_send": "126eb473b472bbd24377d68f165260427535c0ddbc20c3f8a85390f5d95b720b",
Expand Down Expand Up @@ -965,7 +965,7 @@
"T2T1_es_test_passphrase_bde.py::test_passphrase_prompt_disappears": "0cb973a9787b3ca94f0768c03e06437d4668f9edc4909dceaa96fbbe53b4de2d",
"T2T1_es_test_pin.py::test_last_digit_timeout": "24355add7b41290c9eedfe15c9b6f264636223d6d1bb2f32dc5ff3273e4c0480",
"T2T1_es_test_pin.py::test_long_press_digit": "56ca6d0a6737b3a34c44db222e33c04166419a9b54a88172cfa52b5e7056ea79",
"T2T1_es_test_pin.py::test_pin_cancel": "212aae36f4359d4333db3691e8b484d4172ab124a8fe7919e5108d2ed1aacab8",
"T2T1_es_test_pin.py::test_pin_cancel": "ad1f2c76b292a5f6cdfea96ca50ef03706bf678adb6a6bb0529f3921e132447d",
"T2T1_es_test_pin.py::test_pin_change": "0476cf9650fa9da961508662c64b6efa630bfd11a0748fdb4f2f655b3cc4b6f9",
"T2T1_es_test_pin.py::test_pin_delete_hold": "78168fb67e05d2b365d372cbf8c36452f1bce8774133f00c84945296bcf32c4b",
"T2T1_es_test_pin.py::test_pin_empty_cannot_send": "673fcf6ded02fbb7d654cd1388a5fa72741cf6a5b19578645ce3d7898fe1f6a5",
Expand Down Expand Up @@ -1020,7 +1020,7 @@
"T2T1_fr_test_passphrase_bde.py::test_passphrase_prompt_disappears": "0e6b209df1421cb051d97c220c568405bb60bc815fe24d198f07b0a5ed52c4f7",
"T2T1_fr_test_pin.py::test_last_digit_timeout": "781433513b748daf0e285e1fb1e906bfab97cddee3a94aaef0a0d09d1010599a",
"T2T1_fr_test_pin.py::test_long_press_digit": "edc2379a7c23439715c780a2b51098dbbb6282b1b13ff307558a74a58a421d48",
"T2T1_fr_test_pin.py::test_pin_cancel": "591201786ba7a008a6593019b61616ce0d34f34d833d9a906ebb7a01005e8ee8",
"T2T1_fr_test_pin.py::test_pin_cancel": "69cbc1787d0aa1733f8c643dbbd93c39c00291b80660f37ef84f90a9543f2ad0",
"T2T1_fr_test_pin.py::test_pin_change": "35be4e7ed394f809531f62cc01e30a9c73bb1a3462abf7586b5feea4043a613f",
"T2T1_fr_test_pin.py::test_pin_delete_hold": "d10a429dc769f8e67e62a06798c50d332a9fcc76ae97acdc8e7e23554ad840e0",
"T2T1_fr_test_pin.py::test_pin_empty_cannot_send": "6bc3d997f24267efe06c9ac4d37d6fadd8d18b5c9fb896095b8f2a6dfcf59265",
Expand Down Expand Up @@ -1075,7 +1075,7 @@
"T2T1_pt_test_passphrase_bde.py::test_passphrase_prompt_disappears": "a9f60f9d7f3cd8a1daded5709366211a5a12d172057d00a1202779f21b115e3f",
"T2T1_pt_test_pin.py::test_last_digit_timeout": "b08d854fdccdf4b14a1b8c6901786e798ea8078ac9c1704edb1ea7b54bf2d634",
"T2T1_pt_test_pin.py::test_long_press_digit": "2f38e18ba70f161f96e6a947415a937d1792ffd7dfc6824eb1c72fbc128ff630",
"T2T1_pt_test_pin.py::test_pin_cancel": "fac3d06f69db9a73ec1162fd260406b968d71734a9a508c01ebcf9ed195cc0bc",
"T2T1_pt_test_pin.py::test_pin_cancel": "fe7bfb5174f22f11d64db6db0c3a40f7c869caa74009033185201b3ae1f4d2fa",
"T2T1_pt_test_pin.py::test_pin_change": "9915bd713bd0ce7a7118123c9fbcf9c02f8c2d4b456a05551f275723f7f758ed",
"T2T1_pt_test_pin.py::test_pin_delete_hold": "cf7dc3062be3a17d38e74c78de441259bd58b3ae066b0b1e42046edbee277cc3",
"T2T1_pt_test_pin.py::test_pin_empty_cannot_send": "bf1b509e362ff71cbb982b320c6375173cccfcddf259f1ad48a53a5ad9f29009",
Expand Down