Skip to content

Commit 0c944cd

Browse files
committed
prevent deadlock when set_state is called during render
1 parent 801a140 commit 0c944cd

1 file changed

Lines changed: 8 additions & 19 deletions

File tree

src/reactpy/core/layout.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -210,32 +210,17 @@ async def _serial_render(self) -> LayoutUpdateMessage: # nocov
210210
return await self._create_layout_update(model_state)
211211

212212
async def _parallel_render(self) -> LayoutUpdateMessage:
213-
"""Await to fetch the first completed render within our asyncio task group.
214-
We use the `asyncio.tasks.wait` API in order to return the first completed task.
215-
"""
213+
"""Await to fetch the first completed render within our asyncio task group."""
216214
while True:
217215
await self._render_tasks_ready.acquire()
216+
218217
if not self._render_tasks: # nocov
219218
continue
219+
220220
done, _ = await wait(self._render_tasks, return_when=FIRST_COMPLETED)
221221
update_task: Task[LayoutUpdateMessage] = done.pop()
222222
self._render_tasks.discard(update_task)
223223

224-
lcs_id = self._render_task_to_lcs_id.pop(update_task, None)
225-
if (
226-
lcs_id is not None
227-
and self._render_tasks_by_id.get(lcs_id) is not update_task
228-
):
229-
# A newer render has been scheduled for this component
230-
# while we were in-flight. Drop this stale result so the
231-
# client only receives the latest render per component.
232-
continue
233-
if (
234-
lcs_id is not None
235-
and self._render_tasks_by_id.get(lcs_id) is update_task
236-
):
237-
del self._render_tasks_by_id[lcs_id]
238-
239224
try:
240225
return update_task.result()
241226
except CancelledError: # nocov
@@ -330,13 +315,17 @@ async def _render_component(
330315
self._model_states_by_life_cycle_state_id[life_cycle_state.id] = new_state
331316

332317
# If this component is scheduled to render, we can cancel that task since we are
333-
# rendering it now.
318+
# rendering it now. Drain the orphaned ``_render_tasks_ready`` semaphore token
319+
# (which was already released by ``_schedule_render_task`` for this task) via a
320+
# fire-and-forget acquire so ``_parallel_render`` does not deadlock.
334321
if life_cycle_state.id in self._render_tasks_by_id:
335322
task = self._render_tasks_by_id[life_cycle_state.id]
336323
if task is not current_task():
337324
del self._render_tasks_by_id[life_cycle_state.id]
325+
self._render_task_to_lcs_id.pop(task, None)
338326
task.cancel()
339327
self._render_tasks.discard(task)
328+
create_task(self._render_tasks_ready.acquire())
340329

341330
await life_cycle_hook.affect_component_will_render(component)
342331
exit_stack.push_async_callback(life_cycle_hook.affect_layout_did_render)

0 commit comments

Comments
 (0)