solara
solara copied to clipboard
Pin `ipykernel` to `<7.0.0` [breaking changes]
A major release of ipykernel has been released. We have tracked this as the cause of breaking changes to solara.
See: https://github.com/ipython/ipykernel/releases/tag/v7.0.0
Resulting error trace for context:
│ File "/usr/local/lib/python3.10/site-packages/comm/base_comm.py", line 130, in close │
│ self.publish_msg( │
│ File "/usr/local/lib/python3.10/site-packages/solara/server/kernel.py", line 102, in publish_msg │
│ parent=self.kernel.get_parent("shell"), │
│ File "/usr/local/lib/python3.10/site-packages/ipykernel/kernelbase.py", line 797, in get_parent │
│ return self._shell_parent.get() │
│ LookupError: <ContextVar name='shell_parent' at 0x7fec141ce1b0>
Hi! I spent some time preparing this but @chrisji beat me to it. In case it is helpful, i prepared some examples and such to illustrate the problem. I am not sure if it is a breaking change in solara, or we have been abusing it..
The ipykernel 7.0.0 breaking change seems to be related to improved async context isolation, which is generally a good thing, but it breaks this previously-working pattern in Solara.
Questions for Maintainers
- Is this pattern (calling a Solara task from within another async task after awaits) intended to be supported or was I abusing this and it got lucky because it worked before?
- Could Solara's task execution mechanism be updated to properly propagate or handle context variables in ipykernel 7.0.0?
- Are there any best practices or patterns you'd recommend for structuring async tasks that need to update state after async operations?
Summary
When calling a Solara task from within another async Solara task after await operations, the application crashes with a LookupError for the shell_parent context variable in ipykernel 7.0.0. This pattern works fine in ipykernel 6.x.
Environment
- Solara version: 1.51.1
- Python version: 3.12.9
- ipykernel 6.29: Works
- ipykernel 7.0.0+: Fails with
LookupError
Minimal Reproducible Example
import solara
import solara.tasks
import asyncio
# Simulated state
app_state_counter = solara.reactive(0)
@solara.tasks.task
def update_progress():
"""A simple sync task that updates state."""
app_state_counter.set(app_state_counter.value + 1)
@solara.tasks.task
async def async_operation():
"""An async task that calls another task after await operations."""
# Some async work
await asyncio.sleep(0.1)
# More async work
result = await some_async_api_call()
# Call another Solara task after await operations
update_progress() # ← This triggers the error in ipykernel 7.0.0
# Update state again
app_state_counter.set(app_state_counter.value + result)
@solara.component
def MyComponent():
solara.Button("Run", on_click=lambda: async_operation())
solara.Markdown(f"Counter: {app_state_counter.value}")
Error Traceback
LookupError: <ContextVar name='shell_parent' at 0x...>
File ".../solara/toestand.py", line 540, in set
self._storage.set(value)
File ".../solara/toestand.py", line 329, in set
self.fire(value, old)
File ".../solara/toestand.py", line 208, in fire
with context or nullcontext():
File ".../solara/toestand.py", line 944, in __exit__
res1 = self.render_context.__exit__(exc_type, exc_val, exc_tb)
File ".../reacton/core.py", line 1189, in __exit__
self._possible_rerender()
...
File ".../ipykernel/kernelbase.py", line 797, in get_parent
return self._shell_parent.get()
Expected Behavior
The nested task call should work as it does in ipykernel 6.x, allowing tasks to call other tasks regardless of async boundaries.
Actual Behavior
In ipykernel 7.0.0, the shell_parent context variable is not available when a Solara task is called from within another async task after await operations, causing a LookupError during widget cleanup/re-rendering.
Analysis
The issue appears to be related to ipykernel 7.0.0's stricter context variable isolation across async boundaries. When:
- An async Solara task performs
awaitoperations - Then calls another Solara task (which creates a new execution context)
- That nested task triggers state updates that cause re-renders
- The render process tries to access the kernel's
shell_parentContextVar - The ContextVar is no longer accessible in this nested, post-await context
Important note: Calling a Solara task from another task works fine if done before any await operations:
@solara.tasks.task
async def async_operation_that_works():
update_progress() # This works
await asyncio.sleep(0.1)
Current Workaround
Downgrade to ipykernel 6.x
@JovanVeljanoski - that's all the detail I could muster after a intense debugging session on a failing prod instance! 😅
Nice work on the more detailed follow-up 🙌
We're experiencing the same issue in the Mesa project. Our CI is failing with identical errors across all Solara-based visualization tests:
LookupError: <ContextVar name='shell_parent' at 0x...>
File ".../solara/server/kernel.py", line 102, in publish_msg
parent=self.kernel.get_parent("shell"),
File ".../ipykernel/kernelbase.py", line 797, in get_parent
return self._shell_parent.get()
Temporary workaround:
- https://github.com/widgetti/solara/pull/1112
Looking into this, leaving breadcrumbs here: https://github.com/ipython/ipykernel/pull/1186 (work was introduced here)
seems like it's a general breakage: https://github.com/ipython/ipykernel/issues/1450
This seems fixed here: https://github.com/ipython/ipykernel/pull/1451
- Is this pattern (calling a Solara task from within another async task after awaits) intended to be supported or was I abusing this and it got lucky because it worked before?
Yes, that should be possible
- Could Solara's task execution mechanism be updated to properly propagate or handle context variables in ipykernel 7.0.0?
lets wait to see if ipykernel 7.0.2 fixes this, but otherwise we'll have to fix it in solara.
3. Are there any best practices or patterns you'd recommend for structuring async tasks that need to update state after async operations?
No, things should just work like it did pre ipykernel 7.0.0
Thanks @maartenbreddels for the prompt response. Appreciate it