From 134f8ca552f7abce903c5180351805ddbca1f1cd Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:23:18 +0800 Subject: [PATCH] feat: refresh VFS --- .env.example | 7 +++++++ backend/src/backend/main.py | 8 ++++++++ backend/src/backend/scripts.py | 13 +++++++++++++ common/src/common/config.py | 4 ++++ 4 files changed, 32 insertions(+) diff --git a/.env.example b/.env.example index 2d6ab09..481b536 100644 --- a/.env.example +++ b/.env.example @@ -44,3 +44,10 @@ RUSTFS_VERSION_BUCKET=versions RUSTFS_RUN_LOG_BUCKET=run-logs RUSTFS_TRASH_BUCKET=trash RUSTFS_TRASH_RETENTION_DAYS=30 + +# rclone RC (HTTP control API). The runtime container starts rclone with +# `--rc --rc-addr 0.0.0.0:5572 --rc-no-auth` (see runtime/src/runtime/mount.py), +# so the backend can POST /vfs/refresh here to invalidate the FUSE dir-cache +# after writing new workspace files. Default points at the runtime service +# over the compose network. +RCLONE_RC_URL=http://runtime:5572 diff --git a/backend/src/backend/main.py b/backend/src/backend/main.py index c390cd4..6752caf 100644 --- a/backend/src/backend/main.py +++ b/backend/src/backend/main.py @@ -16,6 +16,7 @@ from backend.auth import router as auth_router from backend.jupyter import router as jupyter_router from backend.resources import router as resources_router from backend.runtime_client import RuntimeClient +from backend.rclone_rc_client import RcloneRCClient from backend.schedule_runs import router as schedule_runs_router from backend.schedules import router as schedules_router from backend.scripts import router as scripts_router @@ -59,9 +60,16 @@ async def lifespan(app: Any) -> AsyncIterator[None]: timeout=httpx.Timeout(30.0), ) app.state.runtime_client = RuntimeClient(runtime_http_client) + # Short timeout — refresh is best-effort and runs in a BackgroundTask. + rclone_http_client = httpx.AsyncClient( + base_url=settings.rclone_rc_url, + timeout=httpx.Timeout(30.0), + ) + app.state.rclone_rc_client = RcloneRCClient(rclone_http_client) try: yield finally: + await rclone_http_client.aclose() await runtime_http_client.aclose() await storage_http_client.aclose() await engine.dispose() diff --git a/backend/src/backend/scripts.py b/backend/src/backend/scripts.py index 5a1ace7..92e87f4 100644 --- a/backend/src/backend/scripts.py +++ b/backend/src/backend/scripts.py @@ -10,6 +10,7 @@ from typing import Any from fastapi import ( APIRouter, + BackgroundTasks, Depends, Header, HTTPException, @@ -344,6 +345,7 @@ async def create_script_record( async def create_script( payload: CreateScriptRequest, request: Request, + background_tasks: BackgroundTasks, context: RequestContext = Depends(request_context), session: AsyncSession = Depends(database_session), ) -> dict[str, Any]: @@ -359,6 +361,11 @@ async def create_script( context=context, session=session, ) + background_tasks.add_task( + request.app.state.rclone_rc_client.vfs_refresh, + dir_path=context.workspace.workspace_id, + recursive=True, + ) return { "request_id": context.request_id, "data": script_payload(script, storage_data), @@ -372,6 +379,7 @@ async def create_script( ) async def upload_script( request: Request, + background_tasks: BackgroundTasks, file_name: str = Query(min_length=1, max_length=255), parent_path: str = Query(default="", max_length=1024), visibility: str = Query( @@ -413,6 +421,11 @@ async def upload_script( context=context, session=session, ) + background_tasks.add_task( + request.app.state.rclone_rc_client.vfs_refresh, + dir_path=context.workspace.workspace_id, + recursive=True, + ) return { "request_id": context.request_id, "data": script_payload(script, storage_data), diff --git a/common/src/common/config.py b/common/src/common/config.py index 290c2e7..9a2eae3 100644 --- a/common/src/common/config.py +++ b/common/src/common/config.py @@ -54,6 +54,10 @@ class Settings(BaseSettings): default="http://runtime:8000", description="Backend → Runtime HTTP endpoint.", ) + rclone_rc_url: str = Field( + default="http://runtime:5572", + description="Backend → rclone RC HTTP endpoint (VFS cache invalidation).", + ) # ── RustFS object storage ──────────────────────────────────── rustfs_endpoint: str = Field(