solara icon indicating copy to clipboard operation
solara copied to clipboard

Pin `ipykernel` to `<7.0.0` [breaking changes]

Open chrisji opened this issue 1 month ago • 7 comments

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>

chrisji avatar Oct 14 '25 16:10 chrisji

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

  1. 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?
  2. Could Solara's task execution mechanism be updated to properly propagate or handle context variables in ipykernel 7.0.0?
  3. 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:

  1. An async Solara task performs await operations
  2. Then calls another Solara task (which creates a new execution context)
  3. That nested task triggers state updates that cause re-renders
  4. The render process tries to access the kernel's shell_parent ContextVar
  5. 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 avatar Oct 15 '25 10:10 JovanVeljanoski

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

chrisji avatar Oct 16 '25 10:10 chrisji

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()

EwoutH avatar Oct 16 '25 12:10 EwoutH

Temporary workaround:

  • https://github.com/widgetti/solara/pull/1112

EwoutH avatar Oct 17 '25 07:10 EwoutH

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

maartenbreddels avatar Oct 17 '25 08:10 maartenbreddels

  1. 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

maartenbreddels avatar Oct 17 '25 10:10 maartenbreddels

Thanks @maartenbreddels for the prompt response. Appreciate it

JovanVeljanoski avatar Oct 17 '25 17:10 JovanVeljanoski