@@ -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