Skip to content
Open
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
1 change: 1 addition & 0 deletions solara/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def _using_solara_server():
from .autorouting import generate_routes, generate_routes_directory, RenderPage, RoutingProvider, DefaultLayout
from .checks import check_jupyter
from .scope import get_kernel_id, get_session_id
from .server.reload import create_reload_checker


def display(*objs, **kwargs):
Expand Down
24 changes: 24 additions & 0 deletions solara/server/reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def __init__(self, on_change: Optional[Callable[[str], None]] = None) -> None:
# * https://github.com/widgetti/solara/issues/177
# * https://github.com/widgetti/solara/issues/148
self.aggresive_reload = False
self._reload_count = 0

def start(self):
if self._first:
Expand Down Expand Up @@ -192,6 +193,7 @@ def get_reload_module_names(self):
return reload

def reload(self):
self._reload_count += 1
# before we did this:
# # don't reload modules like solara.server and react
# # that may cause issues (like 2 Element classes existing)
Expand Down Expand Up @@ -249,3 +251,25 @@ def watch(self):
# there is only a reloader, and there should be only 1 app
# that connect to the on_change
reloader = Reloader()


def create_reload_checker() -> Callable[[], bool]:
"""Returns a function that checks if a hot reload occurred since creation.

This is useful for long-running tasks that need to detect when the app
has been reloaded and should stop running to avoid issues with stale
references to old module state.

Example:
did_reload = solara.create_reload_checker()

while not did_reload():
# do work...
await asyncio.sleep(1)
"""
snapshot = reloader._reload_count

def did_reload() -> bool:
return reloader._reload_count != snapshot

return did_reload
22 changes: 22 additions & 0 deletions solara/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,28 @@ def Page():
solara.Text("Click the button to fetch data")
```

## Hot reload

During development, Solara's hot reload feature may reload your app when you save changes.
Long-running tasks started before the reload will continue running with references to old module state,
which can cause issues.

Use [`solara.create_reload_checker()`](/documentation/advanced/reference/reloading#long-running-tasks-and-hot-reload)
to detect when a reload has occurred and exit your task gracefully:

```python
did_reload = solara.create_reload_checker()

@task
async def my_long_running_task():
while not did_reload():
# do work...
await asyncio.sleep(1)
```

Note: Hot reload only occurs in development mode. In production mode (`solara run --production`),
file watching is disabled and this is not a concern.

## Arguments

- `f`: Function to turn into task or None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@ You don't need to care about this feature if you only use solara, this is only r
If the `--auto-restart/-a` flag is passed to solara-server and any changes occur in the `solara` package (excluding `solara.webpage`), solara-server will restart. This speeds up development on `solara-server` for developers since you do not
need to manually restart the server in the terminal.

## Long-running tasks and hot reload

When a hot reload occurs, long-running tasks (threads, async tasks, etc.) that were started before the reload may continue running with references to old module state. This can cause issues like class mismatches or stale data.

Use `solara.create_reload_checker()` to detect when a reload has occurred and gracefully stop your task:

```python
import solara
import asyncio

did_reload = solara.create_reload_checker()

async def my_long_running_task():
while not did_reload():
# do work...
await asyncio.sleep(1)
# Task exits gracefully after hot reload
```

The function returns a checker that captures the current reload state. Call the checker periodically in your task loop - it returns `True` once a reload has occurred since the checker was created.

This is particularly useful for:

- Background polling tasks
- WebSocket connections
- Database connections with ORM models
- Any task that holds references to module-level objects

## Disabling reloading

In production mode (pass the `--production` argument to `solara run`) watching of files is disabled, and no reloading of files or vue templates will occur. If you run solara integrated in flask or uvicorn as laid out in [deployment documentation](https://solara.dev/documentation/getting_started/deploying/self-hosted)
24 changes: 24 additions & 0 deletions tests/unit/reload_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,27 @@ def test_callback_cleanup():
assert test_callback_cleanup in [k.callback for k in solara.lifecycle._on_kernel_start_callbacks]
cleanup()
assert test_callback_cleanup not in [k.callback for k in solara.lifecycle._on_kernel_start_callbacks]


def test_create_reload_checker():
from solara.server.reload import reloader

# Create a checker before any reload
did_reload = solara.create_reload_checker()
assert did_reload() is False

# Simulate a reload by incrementing the counter
reloader._reload_count += 1

# The checker should now detect a reload
assert did_reload() is True

# A new checker created after the reload should not detect a reload
did_reload_new = solara.create_reload_checker()
assert did_reload_new() is False

# But the old checker still detects it
assert did_reload() is True

# Clean up: reset the counter for other tests
reloader._reload_count -= 1
Loading