Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: limit and configure number of virtual kernels to infinite (was 40) #546

Merged
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/server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class Config:

class Kernel(BaseSettings):
cull_timeout: str = "24h"
max_count: Optional[int] = None

class Config:
env_prefix = "solara_kernel_"
Expand Down
20 changes: 18 additions & 2 deletions solara/server/starlette.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import asyncio
import logging
import math
import os
import sys
import threading
import typing
from typing import Dict, List, Union, cast
from typing import Dict, List, Optional, Union, cast
from uuid import uuid4

import anyio
Expand Down Expand Up @@ -51,6 +53,19 @@
from .cdn_helper import cdn_url_path, get_path

os.environ["SERVER_SOFTWARE"] = "solara/" + str(solara.__version__)
limiter: Optional[anyio.CapacityLimiter] = None
lock = threading.Lock()


def _ensure_limiter():
# in older anyios (<4) the limiter can only be created in an async context
# so we call this in a starlette handler
global limiter
if limiter is None:
with lock:
if limiter is None:
limiter = anyio.CapacityLimiter(settings.kernel.max_count if settings.kernel.max_count is not None else math.inf)


logger = logging.getLogger("solara.server.fastapi")
# if we add these to the router, the server_test does not run (404's)
Expand Down Expand Up @@ -167,6 +182,7 @@ async def kernels(id):


async def kernel_connection(ws: starlette.websockets.WebSocket):
_ensure_limiter()
session_id = ws.cookies.get(server.COOKIE_KEY_SESSION_ID)

if settings.oauth.private and not has_auth_support:
Expand Down Expand Up @@ -224,7 +240,7 @@ async def run():
try:
async with anyio.from_thread.BlockingPortal() as portal:
ws_wrapper = WebsocketWrapper(ws, portal)
thread_return = anyio.to_thread.run_sync(websocket_thread_runner, ws, portal) # type: ignore
thread_return = anyio.to_thread.run_sync(websocket_thread_runner, ws, portal, limiter=limiter) # type: ignore
await thread_return
finally:
if settings.main.experimental_performance:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ However, when the websocket between the web page and the server disconnects, the

To optimize memory usage or address specific needs, one might opt for a shorter expiration duration. For instance, setting `SOLARA_KERNEL_CULL_TIMEOUT=1m` will cause sessions to expire after just 1 minute. Other possible options are `2d` (2 days), `3h` (3 hours), `30s` (30 seconds), etc. If no units are given, seconds are assumed.

### Maximum number of kernels connected

Each virtual kernel runs in its own thread, this ensures that one particular user (actually browser page) cannot block the execution of another virtual kernel. However, each thread consumes a bit of resources. If you want to limit the number of kernels, this can be done by setting the `SOLARA_KERNELS_MAX_COUNT` environment variable. The default is unlimited (empty string), but you can set it to any number you like. If the limit is reached, the server will refuse new connections until a kernel is closed.


## Handling Multiple Workers
In setups with multiple workers, it's possible for a page to reconnect to a different worker than its original. This would result in a loss of the virtual kernel (since it lives on a different worker), prompting the Solara app to initiate a fresh start. To prevent this scenario, a sticky session configuration is recommended, ensuring consistent client-worker connections. Utilizing a load balancer, such as [nginx](https://www.nginx.com/), can achieve this.
Expand Down
Loading