Skip to content

Spaghetti-OJ/Sandbox

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

170 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📦 Sandbox Online Judge System

這是一個專為 Online Judge (OJ) 設計的高效能、非同步程式碼評測沙盒服務。核心基於 isolate 技術,提供安全、資源受限的程式碼執行環境。

本專案採用 Producer-Consumer 架構,利用 Python 的 asyncio 實現高並發處理,確保在高負載下 API 依然能快速回應。


🗺️ 系統架構與互動流程

為了讓您快速理解系統運作,以下使用圖形化方式呈現各元件的互動關係。

0. 服務端口總覽 (Service Ports Overview)

服務名稱 容器內埠 對外埠 說明
sandbox-oj 8000 8000 主評測 API 服務
static-analysis 8000 8001 靜態分析 API 服務
llm-test-data 8787 8787 LLM 測資生成服務
portal 9999 9999 監控入口頁面

1. 核心架構圖

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                              🌐 外部系統 (External Systems)                                  │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                             │
│   ┌──────────────────┐        HTTP Requests        ┌──────────────────┐                    │
│   │  Backend Service │  ─────────────────────────▶ │  Sandbox API     │                    │
│   │  (後端主服務)     │                             │  Port: 8000      │                    │
│   │                  │  ◀───── Webhook Callback ─── │                  │                    │
│   └──────────────────┘                             └──────────────────┘                    │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                              📦 Sandbox 服務內部架構                                          │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                             │
│  ┌───────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                           api_server.py (FastAPI :8000)                               │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │  │
│  │  │ /submissions│  │ /checkers   │  │ /queue/*    │  │ /health     │  │ /dashboard  │  │  │
│  │  │ 提交評測     │  │ 上傳Checker │  │ 佇列控制    │  │ 健康檢查    │  │ 監控頁面    │  │  │
│  │  └──────┬──────┘  └──────┬──────┘  └─────────────┘  └─────────────┘  └─────────────┘  │  │
│  │         │                │                                                            │  │
│  │         │ ① 驗證 API Key │ ② 儲存 Checker                                             │  │
│  │         │ ② 驗證參數     │    到 /checkers                                            │  │
│  │         │ ③ 確認題包     │                                                            │  │
│  │         ▼                ▼                                                            │  │
│  └───────────────────────────────────────────────────────────────────────────────────────┘  │
│                         │                                                                   │
│                         │ Push Job                                                          │
│                         ▼                                                                   │
│  ┌───────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                        queue_manager.py (Priority Queue)                              │  │
│  │  ┌───────────────────────────────────────────────────────────────────────────────┐   │  │
│  │  │  [Priority 1] ─▶ [Priority 5] ─▶ [Priority 10] ─▶ [Priority 10] ─▶ ...      │   │  │
│  │  │     比賽提交       高優先級         一般提交          一般提交                    │   │  │
│  │  └───────────────────────────────────────────────────────────────────────────────┘   │  │
│  │     ⬆ pause() / resume() / clear()  (佇列控制 API)                                   │  │
│  └───────────────────────────────────────────────────────────────────────────────────────┘  │
│                         │                                                                   │
│                         │ Pop Job (依優先級)                                                 │
│                         ▼                                                                   │
│  ┌───────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                           worker.py (Worker Pool)                                     │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                  │  │
│  │  │  Worker 0   │  │  Worker 1   │  │  Worker 2   │  │  Worker 3   │  ...             │  │
│  │  │  Box ID: 0  │  │  Box ID: 1  │  │  Box ID: 2  │  │  Box ID: 3  │                  │  │
│  │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘                  │  │
│  │         │                │                │                │                          │  │
│  │         └────────────────┴────────────────┴────────────────┘                          │  │
│  │                                    │                                                  │  │
│  └────────────────────────────────────┼──────────────────────────────────────────────────┘  │
│                                       │                                                     │
│              ┌────────────────────────┼────────────────────────┐                           │
│              │                        │                        │                           │
│              ▼                        ▼                        ▼                           │
│  ┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐                     │
│  │  ProxyManager    │    │  SidecarManager  │    │  IsolateRunner   │                     │
│  │  (TinyProxy)     │    │  (Docker)        │    │  (Isolate CLI)   │                     │
│  │  網路白名單過濾   │    │  Redis/MySQL等   │    │  安全沙盒執行     │                     │
│  └──────────────────┘    └──────────────────┘    └────────┬─────────┘                     │
│                                                           │                               │
│                                                           ▼                               │
│                                                  ┌──────────────────┐                     │
│                                                  │ /usr/bin/isolate │                     │
│                                                  │  Linux Kernel    │                     │
│                                                  │  沙盒隔離執行     │                     │
│                                                  └──────────────────┘                     │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                              🔬 靜態分析服務 (Port: 8001)                                     │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                        static-analysis-service/api/app.py                             │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                  │  │
│  │  │ POST /scan  │  │ GET /health │  │ GET /stats  │  │ /dashboard  │                  │  │
│  │  │ 執行分析    │  │ 健康檢查    │  │ 統計資訊    │  │ 監控頁面    │                  │  │
│  │  └──────┬──────┘  └─────────────┘  └─────────────┘  └─────────────┘                  │  │
│  │         │                                                                             │  │
│  │         ▼                                                                             │  │
│  │  ┌───────────────────────────────────────────────────────────────────────────────┐   │  │
│  │  │  Clang-Tidy Engine (Custom Checks)                                            │   │  │
│  │  │  • misc-forbid-loops     禁止迴圈                                             │   │  │
│  │  │  • misc-forbid-arrays    禁止陣列                                             │   │  │
│  │  │  • misc-forbid-stl       禁止 STL                                             │   │  │
│  │  │  • misc-forbid-functions 禁止特定函式                                         │   │  │
│  │  └───────────────────────────────────────────────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. 檔案職責說明

檔案名稱 角色 功能描述
api_server.py 櫃台接待 (Receptionist) 系統入口。負責接收 HTTP 請求、驗證參數、將任務推入隊列,並提供 /health/logs 查詢介面。
queue_manager.py 點餐單 (Order Rail) 管理一個優先級隊列 (Priority Queue)。確保高優先級的任務 (如比賽) 能被優先處理。
worker.py 廚師 (Chef) 背景工作者。不斷從隊列取出任務,準備環境,並指揮 Runner 執行程式。每個 Worker 綁定一個獨立的 Box ID。負責管理 Proxy 與 Sidecar。
isolate_runner.py 廚具 (Tools) 底層驅動。負責與 isolate 二進位檔溝通,處理檔案 I/O、解壓縮、執行指令並解析執行結果 (Meta file)。
models.py 菜單規格 (Schema) 定義資料結構 (Pydantic Models),如 SubmissionRequest, Job,確保系統內部資料傳遞的一致性。
docker-compose.yml 餐廳藍圖 (Blueprint) 定義系統如何部署。設定 Port 映射、Volume 掛載 (Log 保存) 與容器權限。

🔄 完整評測流程圖 (Complete Evaluation Flow)

📋 總覽流程圖 (Master Flow Diagram)

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                     📤 後端提交評測請求                                                       │
│                                  POST /api/v1/submissions                                                   │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                               Phase 1: 請求驗證 (Request Validation)                                 │  │
│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐            │  │
│  │  │  ① API Key 驗證  │───▶│  ② 參數格式驗證  │───▶│ ③ 檔案Hash驗證  │───▶│  ④ 儲存上傳檔案 │            │  │
│  │  │  X-API-KEY      │    │  Form Data      │    │  SHA256 校驗    │    │  /tmp/sandbox/  │            │  │
│  │  │                 │    │  Pydantic Model │    │                 │    │  submissions/   │            │  │
│  │  └─────────────────┘    └─────────────────┘    └─────────────────┘    └─────────────────┘            │  │
│  │         ❌ 403                  ❌ 422                  ❌ 400                                        │  │
│  │        驗證失敗                格式錯誤                Hash不符                                       │  │
│  │        (Log記錄)               (回傳錯誤)              (回傳錯誤)                                       │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │ ✅ 驗證通過
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 2: 題包管理 (Problem Package Management)                           │  │
│  │                                                                                                       │  │
│  │    ┌─────────────────────────────────────────────────────────────────────────────────────────────┐   │  │
│  │    │                          ensure_problem_package(problem_id, problem_hash)                   │   │  │
│  │    └─────────────────────────────────────────────────────────────────────────────────────────────┘   │  │
│  │                                                │                                                     │  │
│  │                       ┌────────────────────────┴────────────────────────┐                           │  │
│  │                       ▼                                                 ▼                           │  │
│  │          ┌─────────────────────────┐                      ┌─────────────────────────┐               │  │
│  │          │  檢查本地 .hash 檔案     │                      │  Hash 不符 / 不存在     │               │  │
│  │          │  test_data/problems/    │                      │                         │               │  │
│  │          │  {problem_id}/.hash     │                      │  ⬇ 向後端索取題包        │               │  │
│  │          └───────────┬─────────────┘                      │  GET /api/v1/problems/  │               │  │
│  │                      │                                    │      {id}/package       │               │  │
│  │          ┌───────────┴───────────┐                        └───────────┬─────────────┘               │  │
│  │          ▼                       ▼                                    │                             │  │
│  │   ┌─────────────┐         ┌─────────────┐                            ▼                             │  │
│  │   │ Hash 符合   │         │ Hash 不符   │               ┌─────────────────────────┐                │  │
│  │   │ ✅ 使用快取  │         │ ➡ 重新下載  │               │  ⑤ 解壓縮題包 (ZIP)      │                │  │
│  │   └─────────────┘         └─────────────┘               │  ⑥ 寫入 .hash 檔案      │                │  │
│  │                                                         │  ⑦ 題包前處理完成       │                │  │
│  │                                                         │     (Log 記錄更新)      │                │  │
│  │                                                         └─────────────────────────┘                │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 3: 建立 Job 並推入佇列                                              │  │
│  │                                                                                                       │  │
│  │    ┌──────────────────────────────────────────────────────────────────────────────────────────────┐  │  │
│  │    │  Job 物件包含:                                                                                │  │  │
│  │    │  • submission_id, problem_id, language, mode                                                 │  │  │
│  │    │  • file_path (上傳的檔案), file_hash, problem_hash                                           │  │  │
│  │    │  • use_checker, checker_file_path                   ◀── Checker 設定                        │  │  │
│  │    │  • use_static_analysis, static_analysis_config      ◀── 靜態分析設定                        │  │  │
│  │    │  • allow_network, network_whitelist                 ◀── 網路設定                            │  │  │
│  │    │  • sidecar_image                                    ◀── Sidecar 設定                        │  │  │
│  │    │  • callback_url, callback_token                     ◀── Webhook 回調設定                    │  │  │
│  │    └──────────────────────────────────────────────────────────────────────────────────────────────┘  │  │
│  │                                                │                                                     │  │
│  │                                                ▼                                                     │  │
│  │                              ┌──────────────────────────────────┐                                    │  │
│  │                              │  job_queue.push(job)             │                                    │  │
│  │                              │  Priority Queue (Max: 20000)     │                                    │  │
│  │                              │  依 priority + timestamp 排序    │                                    │  │
│  │                              └──────────────────────────────────┘                                    │  │
│  │                                                │                                                     │  │
│  │                                                ▼                                                     │  │
│  │                              ┌──────────────────────────────────┐                                    │  │
│  │                              │  回傳 HTTP 202 Accepted          │                                    │  │
│  │                              │  { submission_id, queue_position }│                                    │  │
│  │                              └──────────────────────────────────┘                                    │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                   ═══════════════════════════
                                   ║   非同步處理 (Async)    ║
                                   ═══════════════════════════
                                                │
                                                ▼

📋 Worker 評測流程圖 (Worker Evaluation Flow)

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                     Worker 從佇列取得 Job                                                    │
│                                     job = await job_queue.pop()                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 4: 環境準備 (Environment Setup)                                    │  │
│  │                                                                                                       │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑧ Sidecar 啟動 (可選)                                                                       │    │  │
│  │   │  ┌─────────────────────┐                                                                    │    │  │
│  │   │  │ job.sidecar_image?  │──Yes──▶ SidecarManager.start(image)                               │    │  │
│  │   │  │ redis:alpine        │        • Docker 拉取映像                                           │    │  │
│  │   │  │ mysql:8.0           │        • 啟動容器 (Network: container:sandbox-oj)                  │    │  │
│  │   │  │ postgres:14-alpine  │        • 等待服務就緒 (5s)                                         │    │  │
│  │   │  │ mongo:6.0           │        • Log: "Sidecar started"                                   │    │  │
│  │   │  └─────────────────────┘                                                                    │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  │                                                │                                                     │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑨ 網路代理啟動 (可選)                                                                       │    │  │
│  │   │  ┌─────────────────────────────┐                                                            │    │  │
│  │   │  │ job.allow_network = true?   │                                                            │    │  │
│  │   │  └─────────────┬───────────────┘                                                            │    │  │
│  │   │                │                                                                            │    │  │
│  │   │     ┌──────────┴──────────┐                                                                 │    │  │
│  │   │     ▼                     ▼                                                                 │    │  │
│  │   │  有白名單              無白名單                                                              │    │  │
│  │   │  ┌─────────────┐      ┌─────────────┐                                                       │    │  │
│  │   │  │ ProxyManager│      │ 直接開放網路 │                                                       │    │  │
│  │   │  │ .start()    │      │ (警告 Log)  │                                                       │    │  │
│  │   │  │ TinyProxy   │      └─────────────┘                                                       │    │  │
│  │   │  │ ACL 過濾    │                                                                            │    │  │
│  │   │  └─────────────┘                                                                            │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 5: 編譯階段 (Compilation Phase)                                    │  │
│  │                                                                                                       │  │
│  │                           ┌─────────────────────────────────────────┐                                │  │
│  │                           │       判斷提交模式 (job.mode)            │                                │  │
│  │                           └──────────────────┬──────────────────────┘                                │  │
│  │                                              │                                                       │  │
│  │          ┌───────────────────────────────────┼───────────────────────────────────┐                  │  │
│  │          ▼                                   ▼                                   ▼                  │  │
│  │  ┌───────────────────┐           ┌───────────────────┐           ┌───────────────────┐              │  │
│  │  │  Mode: "normal"   │           │  Mode: "zip"      │           │  Mode: "normal"   │              │  │
│  │  │  單一檔案模式     │           │  ZIP 專案模式     │           │  函式實作模式     │              │  │
│  │  │  (Single File)    │           │  (ZIP Project)    │           │  (Implementation) │              │  │
│  │  └─────────┬─────────┘           └─────────┬─────────┘           └─────────┬─────────┘              │  │
│  │            │                               │                               │                        │  │
│  │            ▼                               ▼                               ▼                        │  │
│  │  ┌───────────────────┐           ┌───────────────────┐           ┌───────────────────┐              │  │
│  │  │ ⑩ 讀取程式碼      │           │ ⑩ 解壓縮 ZIP      │           │ ⑩ 載入題目提供    │              │  │
│  │  │    main.c/cpp/py  │           │    檢查內容物:    │           │    的檔案:        │              │  │
│  │  │                   │           │    • Makefile?    │           │    • main.c/cpp   │              │  │
│  │  │                   │           │    • main.cpp?    │           │    • Makefile     │              │  │
│  │  │                   │           │    • main.c?      │           │    • header.h     │              │  │
│  │  └─────────┬─────────┘           └─────────┬─────────┘           └─────────┬─────────┘              │  │
│  │            │                               │                               │                        │  │
│  │            │                               │                               │                        │  │
│  │            ▼                               ▼                               ▼                        │  │
│  │  ┌─────────────────────────────────────────────────────────────────────────────────────────────┐   │  │
│  │  │                              編譯策略決定 (Compile Strategy)                                │   │  │
│  │  │                                                                                             │   │  │
│  │  │   Python:                                                                                   │   │  │
│  │  │   ┌─────────────────────────────────────────────────────────────────────────────────────┐  │   │  │
│  │  │   │ ✓ 無需編譯,直接設定執行命令: ["/usr/bin/python3", "main.py"]                       │  │   │  │
│  │  │   └─────────────────────────────────────────────────────────────────────────────────────┘  │   │  │
│  │  │                                                                                             │   │  │
│  │  │   C/C++ (有 Makefile):                                                                      │   │  │
│  │  │   ┌─────────────────────────────────────────────────────────────────────────────────────┐  │   │  │
│  │  │   │ ⑪ 執行 make 編譯                                                                    │  │   │  │
│  │  │   │    command: ["/usr/bin/make"]                                                       │  │   │  │
│  │  │   │    limits: time=30s, wall=60s, processes=50                                         │  │   │  │
│  │  │   │    輸出執行檔: ./main                                                               │  │   │  │
│  │  │   └─────────────────────────────────────────────────────────────────────────────────────┘  │   │  │
│  │  │                                                                                             │   │  │
│  │  │   C/C++ (無 Makefile):                                                                      │   │  │
│  │  │   ┌─────────────────────────────────────────────────────────────────────────────────────┐  │   │  │
│  │  │   │ ⑪ 自動編譯所有原始碼                                                               │  │   │  │
│  │  │   │    C:   gcc *.c -o main                                                             │  │   │  │
│  │  │   │    C++: g++ *.cpp -o main                                                           │  │   │  │
│  │  │   │    limits: time=10s, wall=20s, processes=20                                         │  │   │  │
│  │  │   └─────────────────────────────────────────────────────────────────────────────────────┘  │   │  │
│  │  │                                                                                             │   │  │
│  │  │   編譯結果:                                                                                 │   │  │
│  │  │   ┌──────────────────────────────┐    ┌──────────────────────────────┐                     │   │  │
│  │  │   │ ✅ 編譯成功                   │    │ ❌ 編譯失敗 (CE)              │                     │   │  │
│  │  │   │ compile_info.status = "OK"   │    │ compile_info.status = "CE"   │                     │   │  │
│  │  │   │ 繼續執行測試                 │    │ 記錄 stdout/stderr           │                     │   │  │
│  │  │   │                              │    │ 跳過執行階段                 │                     │   │  │
│  │  │   └──────────────────────────────┘    └──────────────────────────────┘                     │   │  │
│  │  └─────────────────────────────────────────────────────────────────────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 6: 測資執行 (Test Case Execution)                                  │  │
│  │                                                                                                       │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑫ 讀取 meta.json 決定測試模式                                                              │    │  │
│  │   │                                                                                             │    │  │
│  │   │  meta.json 範例:                                                                            │    │  │
│  │   │  {                                                                                          │    │  │
│  │   │      "tasks": [                                                                             │    │  │
│  │   │          { "taskScore": 50, "caseCount": 3, "timeLimit": 1000, "memoryLimit": 256 },        │    │  │
│  │   │          { "taskScore": 50, "caseCount": 2, "timeLimit": 2000, "memoryLimit": 512 }         │    │  │
│  │   │      ]                                                                                      │    │  │
│  │   │  }                                                                                          │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  │                                                │                                                     │  │
│  │                                                ▼                                                     │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑬ 遍歷所有 Task & Case                                                                    │    │  │
│  │   │                                                                                             │    │  │
│  │   │  for task in tasks:                                                                         │    │  │
│  │   │      for case in range(caseCount):                                                          │    │  │
│  │   │                                                                                             │    │  │
│  │   │          ┌────────────────────────────────────────────────────────────────────────────┐    │    │  │
│  │   │          │  讀取測資檔案                                                              │    │    │  │
│  │   │          │  testcase/{taskIdx:02d}{caseIdx+1:02d}.in   (輸入)                        │    │    │  │
│  │   │          │  testcase/{taskIdx:02d}{caseIdx+1:02d}.out  (預期輸出)                    │    │    │  │
│  │   │          └────────────────────────────────────────────────────────────────────────────┘    │    │  │
│  │   │                                              │                                              │    │  │
│  │   │                                              ▼                                              │    │  │
│  │   │          ┌────────────────────────────────────────────────────────────────────────────┐    │    │  │
│  │   │          │  ⑭ IsolateRunner.execute()                                                │    │    │  │
│  │   │          │                                                                            │    │    │  │
│  │   │          │  • command: ["./main"] 或 ["/usr/bin/python3", "main.py"]                 │    │    │  │
│  │   │          │  • stdin: 測資輸入內容                                                    │    │    │  │
│  │   │          │  • limits: {time, wall_time, memory, processes}                           │    │    │  │
│  │   │          │  • share_net: 是否共享網路                                                │    │    │  │
│  │   │          │  • env: 環境變數 (Proxy 設定)                                             │    │    │  │
│  │   │          └────────────────────────────────────────────────────────────────────────────┘    │    │  │
│  │   │                                              │                                              │    │  │
│  │   │                                              ▼                                              │    │  │
│  │   │          ┌────────────────────────────────────────────────────────────────────────────┐    │    │  │
│  │   │          │  執行結果狀態判定                                                          │    │    │  │
│  │   │          │                                                                            │    │    │  │
│  │   │          │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐         │    │    │  │
│  │   │          │  │  OK      │ │  TO      │ │  RE      │ │  SG      │ │  XX      │         │    │    │  │
│  │   │          │  │ 正常結束 │ │ 超時     │ │ 運行錯誤 │ │ 信號終止 │ │ 內部錯誤 │         │    │    │  │
│  │   │          │  └────┬─────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘         │    │    │  │
│  │   │          │       │                                                                    │    │    │  │
│  │   │          │       ▼                                                                    │    │    │  │
│  │   │          │  ┌──────────────────────────────────────────────────────────────────┐     │    │    │  │
│  │   │          │  │  ⑮ 輸出比對 (Output Verification)                               │     │    │    │  │
│  │   │          │  └──────────────────────────────────────────────────────────────────┘     │    │    │  │
│  │   │          └────────────────────────────────────────────────────────────────────────────┘    │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 7: 輸出驗證 (Output Verification)                                  │  │
│  │                                                                                                       │  │
│  │                      ┌──────────────────────────────────────────────────────┐                        │  │
│  │                      │            job.use_checker == true ?                 │                        │  │
│  │                      └────────────────────────┬─────────────────────────────┘                        │  │
│  │                                               │                                                      │  │
│  │             ┌─────────────────────────────────┴─────────────────────────────────┐                   │  │
│  │             ▼                                                                   ▼                   │  │
│  │  ┌─────────────────────────────────┐                      ┌─────────────────────────────────┐       │  │
│  │  │     使用預設 diff 比對           │                      │     使用自訂 Checker            │       │  │
│  │  │                                 │                      │                                 │       │  │
│  │  │  actual_output.strip()          │                      │  checker_cmd = [                │       │  │
│  │  │     ==                          │                      │      checker_path,              │       │  │
│  │  │  expected_output.strip()        │                      │      input_file,                │       │  │
│  │  │                                 │                      │      output_file,               │       │  │
│  │  │  ┌───────────┐ ┌───────────┐    │                      │      answer_file                │       │  │
│  │  │  │ 相同: AC  │ │ 不同: WA  │    │                      │  ]                              │       │  │
│  │  │  └───────────┘ └───────────┘    │                      │                                 │       │  │
│  │  └─────────────────────────────────┘                      │  ⑯ subprocess.run(checker_cmd) │       │  │
│  │                                                           │                                 │       │  │
│  │                                                           │  return_code == 0 ? AC : WA    │       │  │
│  │                                                           │  stderr -> checker message     │       │  │
│  │                                                           └─────────────────────────────────┘       │  │
│  │                                                                                                      │  │
│  │  ⬆ Checker 來源:                                                                                    │  │
│  │  • POST /api/v1/checkers 上傳自訂 Checker                                                           │  │
│  │  • 儲存於 /sandbox/checkers/{name}                                                                  │  │
│  │  • 支援 Binary 或 Python Script                                                                     │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 8: 靜態分析 (Static Analysis) [可選]                               │  │
│  │                                                                                                       │  │
│  │                      ┌──────────────────────────────────────────────────────┐                        │  │
│  │                      │     job.use_static_analysis == true ?                │                        │  │
│  │                      └────────────────────────┬─────────────────────────────┘                        │  │
│  │                                               │ Yes                                                  │  │
│  │                                               ▼                                                      │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐   │  │
│  │   │  ⑰ 呼叫靜態分析服務                                                                        │   │  │
│  │   │                                                                                             │   │  │
│  │   │  POST http://static-analysis:8000/scan                                                     │   │  │
│  │   │                                                                                             │   │  │
│  │   │  Request Body:                                                                              │   │  │
│  │   │  {                                                                                          │   │  │
│  │   │      "code": "<source_code>",                                                               │   │  │
│  │   │      "language": "cpp",                                                                     │   │  │
│  │   │      "rules": ["--forbid-loops", "--forbid-arrays", "--forbid-stl"]                        │   │  │
│  │   │  }                                                                                          │   │  │
│  │   │                                                                                             │   │  │
│  │   │  Response:                                                                                  │   │  │
│  │   │  {                                                                                          │   │  │
│  │   │      "success": true,                                                                       │   │  │
│  │   │      "violations": [                                                                        │   │  │
│  │   │          { "rule": "forbid-loops", "line": 10, "message": "for loop detected" }            │   │  │
│  │   │      ]                                                                                      │   │  │
│  │   │  }                                                                                          │   │  │
│  │   │                                                                                             │   │  │
│  │   │  結果附加到 result_data["static_analysis"]                                                  │   │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘   │  │
│  │                                                                                                      │  │
│  │  ⬆ 靜態分析設定來源:                                                                                │  │
│  │  • 提交時透過 static_analysis_config 參數傳入 (字串格式)                                            │  │
│  │  • 格式: "forbid_loops,forbid_arrays" 或 "--forbid-loops --forbid-arrays"                          │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
                                                │
                                                ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                              Phase 9: 結果回報與清理 (Result & Cleanup)                               │  │
│  │                                                                                                       │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑱ 清理資源                                                                                │    │  │
│  │   │                                                                                             │    │  │
│  │   │  • 停止 TinyProxy (若有啟動),收集 Proxy Log                                                │    │  │
│  │   │  • 停止 Sidecar 容器 (若有啟動),收集 Sidecar Log (錯誤時)                                  │    │  │
│  │   │  • 清理暫存檔案 (/tmp/sandbox/submissions/{submission_id})                                  │    │  │
│  │   │  • Isolate Box 在 Worker 生命週期內保持,僅清理內容                                         │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  │                                                │                                                     │  │
│  │                                                ▼                                                     │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑲ 發送 Webhook 回調 (若有設定)                                                             │    │  │
│  │   │                                                                                             │    │  │
│  │   │  POST {callback_url}                                                                        │    │  │
│  │   │  Headers: Authorization: Bearer {callback_token}                                            │    │  │
│  │   │                                                                                             │    │  │
│  │   │  Body:                                                                                      │    │  │
│  │   │  {                                                                                          │    │  │
│  │   │      "submission_id": "xxx",                                                                │    │  │
│  │   │      "problem_id": "yyy",                                                                   │    │  │
│  │   │      "result": {                                                                            │    │  │
│  │   │          "status": "AC",                                                                    │    │  │
│  │   │          "score": 100,                                                                      │    │  │
│  │   │          "tasks": [...],                                                                    │    │  │
│  │   │          "compile_info": {...},                                                             │    │  │
│  │   │          "static_analysis": {...}                                                           │    │  │
│  │   │      }                                                                                      │    │  │
│  │   │  }                                                                                          │    │  │
│  │   │                                                                                             │    │  │
│  │   │  重試機制: 3 次, Backoff: 1s, 2s, 3s                                                        │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  │                                                │                                                     │  │
│  │                                                ▼                                                     │  │
│  │   ┌─────────────────────────────────────────────────────────────────────────────────────────────┐    │  │
│  │   │  ⑳ 記錄歷史                                                                                │    │  │
│  │   │                                                                                             │    │  │
│  │   │  • WorkerManager.add_history() - 記憶體歷史 (最近 50 筆)                                    │    │  │
│  │   │  • logs/submission_history.jsonl - 持久化紀錄 (JSONL 格式)                                  │    │  │
│  │   │  • logs/sandbox.log - 詳細評測 Log                                                          │    │  │
│  │   └─────────────────────────────────────────────────────────────────────────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

當一個評測請求進來時,系統內部發生了什麼事?

sequenceDiagram
    participant User as User / Frontend
    participant Backend as Backend Service
    participant Sandbox as Sandbox API
    participant Worker as Sandbox Worker

    User->>Backend: 提交程式碼
    Backend->>Sandbox: POST /api/v1/submissions
    activate Sandbox
    
    opt 題目包不存在 (Cache Miss)
        Sandbox->>Backend: GET /api/v1/problems/{id}/package
        Backend-->>Sandbox: 回傳題目 ZIP
    end

    Sandbox-->>Backend: 202 Accepted (已排程)
    deactivate Sandbox
    
    Note over Sandbox,Worker: 任務進入記憶體佇列 (Priority Queue)

    loop 非同步評測 (Async Worker)
        Worker->>Worker: 從佇列取出任務
        activate Worker
        Worker->>Worker: 建立沙盒 & 執行評測
        
        opt 靜態分析 (Static Analysis)
            Worker->>Worker: 呼叫 Clang-Tidy Service
        end

        Worker->>Backend: POST {callback_url} (回傳結果)
        deactivate Worker
    end
    
    Backend-->>User: 通知評測結果
Loading
1. 接收 (Receive)
   Client -> POST /api/v1/submissions
      |
      +-> api_server.py: 驗證 API Key 與參數
      +-> api_server.py: 產生 Submission ID
      +-> api_server.py: 將任務包裝成 Job 物件 -> Push to Queue
      +-> Client: 收到 HTTP 202 Accepted (立即回應,不阻塞)

2. 排程 (Queueing)
   Job 停留在 queue_manager.py 中等待空閒的 Worker。
   (依照 Priority 排序,數字越小越優先)

3. 執行 (Execution)
   Worker (空閒中) -> Pop Job from Queue
      |
      +-> worker.py: 建立暫存目錄,寫入程式碼檔案 (支援 ZIP 解壓)
      +-> worker.py: (Optional) 啟動 Sidecar 容器 (如 Redis/MySQL)
      +-> worker.py: (Optional) 啟動 TinyProxy 進行網路白名單過濾
      +-> worker.py: 呼叫 isolate_runner.py 初始化沙盒 (Init Box)
      +-> isolate_runner.py: 將檔案複製進沙盒內部
      +-> isolate_runner.py: 執行 isolate 指令 (Run)
             |
             +-> [ISOLATE SANDBOX]: 執行程式 (限制 CPU/Mem/Network)
             +-> [ISOLATE SANDBOX]: 產出 stdout, stderr, meta file
             |
      +-> isolate_runner.py: 讀取執行結果與資源使用量
      +-> worker.py: (Optional) 呼叫 Static Analysis Service
      +-> worker.py: 清理沙盒、暫存檔、Sidecar 與 Proxy

4. 結算 (Result)
   worker.py 取得結果 (AC/WA/TLE...)
      |
      +-> Log: 寫入 logs/sandbox.log (包含時間、記憶體)
      +-> History: 更新 WorkerManager 的記憶體歷史紀錄
      +-> Webhook: (若有設定) POST 結果到 callback_url

5. 查詢 (Query)
   Client -> GET /logs/view 或 /api/v1/submissions/{id}
      |
      +-> api_server.py: 從 Log 檔或記憶體中讀取資料回傳

� API 端點總覽 (API Endpoints Summary)

主服務 API (sandbox-oj:8000)

端點 方法 認證 說明 狀態
/api/v1/submissions POST 提交評測任務 (正式) ✅ 完成
/api/v1/selftest-submissions POST 學生自測提交 ✅ 完成
/api/v1/checkers POST 上傳自訂 Checker ✅ 完成
/api/v1/test_local/{problem_id} POST 測試本地題目 (Dev) ✅ 完成
/api/v1/submissions/{submission_id} GET 查詢提交結果 ✅ 完成
/api/v1/history GET 獲取評測歷史 ✅ 完成
/api/v1/auth-history GET 獲取認證失敗紀錄 ✅ 完成
/api/v1/system_stats GET Docker 容器資源統計 ✅ 完成
/api/v1/queue/pause POST 暫停佇列處理 ✅ 完成
/api/v1/queue/resume POST 恢復佇列處理 ✅ 完成
/api/v1/queue/clear POST 清空佇列 ✅ 完成
/health GET 健康檢查 ✅ 完成
/dashboard GET 監控儀表板 ✅ 完成
/logs/view GET Log 檢視頁面 ✅ 完成
/logs/raw GET 原始 Log ✅ 完成

靜態分析服務 API (static-analysis:8001)

端點 方法 認證 說明 狀態
/scan POST 執行程式碼靜態分析 ✅ 完成
/health GET 健康檢查 ✅ 完成
/stats GET 分析統計資訊 ✅ 完成
/dashboard GET 分析儀表板 ✅ 完成

後端需實作的 API (供 Sandbox 呼叫)

端點 方法 說明 備註
/api/v1/problems/{id}/package GET 取得題目包 (ZIP) Sandbox 會帶入 problem_hash 驗證
{callback_url} POST 接收評測結果 由提交時指定

�🚀 如何開始 (Getting Started)

1. 環境需求

  • Docker & Docker Compose (強烈建議,因為 isolate 依賴 Linux Kernel 特性)
  • 若在 Windows/Mac 開發,必須使用 Docker。

2. 啟動服務

我們已經配置好了 docker-compose.yml,一鍵啟動:

# 建置並啟動 (背景執行)
docker-compose up -d --build

3. 驗證運作

啟動後,您可以透過以下方式確認系統是否活著:

  • 健康檢查 API: http://localhost:8000/health
  • 即時 Log 儀表板: http://localhost:8000/logs/view
  • 系統狀態儀表板: http://localhost:8000/dashboard

🔌 API 詳細指南 (API Reference)

1. 提交評測 (Submit Job)

  • Endpoint: POST /api/v1/submissions
  • Content-Type: multipart/form-data
  • Header: X-API-KEY: <your-api-key>

請求參數 (Form Data)

參數名稱 類型 必填 預設值 說明
submission_id String Yes - 唯一的提交 ID (通常由後端生成 UUID)。
problem_id String Yes - 題目 ID (對應 test_data/problems/{id})。
language String Yes - 程式語言 (c, cpp, python, java, go)。
file File Yes - 程式碼檔案 (單檔或 ZIP)。
file_hash String Yes - 檔案的 SHA256 Hash (用於驗證完整性)。
mode String Yes normal normal (單檔) 或 zip (多檔專案,支援 Makefile)。
time_limit Float No 2.0 CPU 時間限制 (秒)。
memory_limit Int No 256000 記憶體限制 (KB)。
use_checker Bool No false 是否使用 Checker。
checker_name String No diff Checker 名稱 (diff 或自定義 Checker 檔名)。
use_static_analysis Bool No false 是否啟用靜態分析。
static_analysis_config String No - 分析規則 (如 --forbid-loops)。
callback_url String No - 評測完成後通知的 Webhook URL。
allow_network Bool No false 是否允許沙盒對外連線。
network_whitelist String No - 網路白名單 (逗號分隔 Domain),需搭配 allow_network=true
sidecar_image String No - 啟動 Sidecar 容器 (如 redis:alpine, mysql:8.0)。

提交模式詳解 (Submission Modes)

系統支援以下三種程式碼提交與編譯策略:

  1. 單一檔案 (Single File)

    • 參數: mode="normal"
    • 說明: 學生上傳單一 .c, .cpp.py 檔。系統自動編譯 (C/C++) 或直譯 (Python)。
  2. ZIP 專案包 (ZIP Project)

    • 參數: mode="zip"
    • 說明: 學生上傳包含完整專案的 .zip 檔。
    • 編譯策略:
      • 若 ZIP 內含 Makefile:執行 make (產出執行檔需命名為 main)。
      • 若無 Makefile:自動搜尋 main.cmain.cpp 進行編譯。
  3. 函式實作 / 填空題 (Function Implementation)

    • 參數: mode="normal" (通常)
    • 說明: 題目提供框架程式碼 (如 main.c, header.h),學生僅上傳實作檔 (如 solution.c)。
    • 設定: 需在題目 meta.json 中設定 "provided_files": ["main.c", "Makefile", ...]
    • 編譯策略: 系統會自動將學生檔案與題目提供的檔案合併編譯。若題目提供了 Makefile,將優先使用該 Makefile。

1.1 學生自測提交 (Student Self-Test)

  • Endpoint: POST /api/v1/selftest-submissions
  • 說明: 功能與參數完全同上,專供學生在練習區進行自我測試使用 (區隔正式提交)。

2. 上傳 Checker (Upload Checker)

  • Endpoint: POST /api/v1/checkers
  • 用途: 上傳自定義的 Special Judge 程式 (Python 或 Binary)。

3. 系統管理 API

Endpoint Method 說明
/api/v1/queue/pause POST 暫停佇列處理 (維護用)。
/api/v1/queue/resume POST 恢復佇列處理。
/api/v1/queue/clear POST 清空佇列中所有等待的任務。
/api/v1/system_stats GET 獲取 Docker 容器即時資源使用量 (CPU/Mem)。
/api/v1/history GET 獲取最近的評測歷史紀錄。

🛡️ 網路與安全機制 (Network & Security)

本系統提供多層次的網路控制,以滿足不同題目的需求。

1. 預設模式 (Default)

  • 網路狀態: Blocked
  • 說明: 沙盒內完全無法存取外部網路,最安全的模式。

2. 白名單模式 (Whitelist Proxy)

  • 啟用方式: allow_network=true 且提供 network_whitelist (e.g., google.com,github.com)。
  • 機制: 系統會為該次評測啟動一個專屬的 TinyProxy,並配置 ACL 只允許特定 Domain。
  • 用途: 爬蟲練習題、API 串接題。

3. Sidecar 服務 (Sidecar Service)

  • 啟用方式: 設定 sidecar_image (e.g., redis:alpine)。
  • 機制: 系統會啟動一個 Docker 容器,並與沙盒共享 Network Namespace (localhost 互通)。
  • 支援清單: redis:alpine, mysql:8.0, postgres:14-alpine, mongo:6.0
  • 用途: 資料庫練習題、快取機制實作題。

📂 題目結構與設定 (Problem Structure)

系統依賴 test_data/problems/{problem_id} 目錄下的檔案進行評測。

目錄結構範例

test_data/problems/sum_n_integers/
├── meta.json          # 題目設定檔 (重要)
├── solution.py        # (選用) 參考解答
└── testcase/          # 測資目錄
    ├── 0001.in        # Task 0, Case 1 輸入
    ├── 0001.out       # Task 0, Case 1 輸出
    ├── 0002.in
    └── 0002.out

meta.json 設定

meta.json 定義了評測的具體規則,優先級高於 API 傳入的參數

{
    "tasks": [
        {
            "taskScore": 100,      // 該 Task 的總分
            "caseCount": 2,        // 該 Task 有幾個測資點
            "timeLimit": 1000,     // 時間限制 (ms)
            "memoryLimit": 256     // 記憶體限制 (MB)
        }
    ],
    "allow_network": false,        // (選用) 是否允許網路
    "network_whitelist": [],       // (選用) 白名單
    "sidecar_image": "redis:alpine" // (選用) Sidecar 映像檔
}

🔍 靜態分析服務 (Static Analysis Service)

本系統整合了基於 Clang-Tidy 的靜態分析微服務,允許出題者對提交的程式碼進行語法結構檢查。

1. 服務架構

+-----------+           HTTP POST /scan           +-------------------------+
|           | ----------------------------------> |                         |
| worker.py |        {code, rules, lang}          | static-analysis-service |
|           |                                     |      (Port 8001)        |
|           | <---------------------------------- |                         |
+-----------+        {success, violations}        +-------------------------+

2. 支援規則

規則參數 說明 範例
--forbid-loops 禁止使用 for, while, do-while 迴圈 遞迴練習題
--forbid-arrays 禁止宣告 C/C++ 陣列 (int a[10]) 指標練習題
--forbid-stl 禁止使用 C++ STL (std::vector, std::sort...) 手刻資料結構題
--forbid-functions=... 禁止使用特定函式 (逗號分隔) --forbid-functions=printf,scanf

🧪 Custom Checker (Special Judge)

當題目的答案不唯一(例如:輸出任意一組解),標準的 diff 比對無法使用,此時需要 Custom Checker。

Checker 上傳流程

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                               Custom Checker 管理流程                                         │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                             │
│   1. 上傳 Checker                                                                           │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  POST /api/v1/checkers                                                            │    │
│   │                                                                                   │    │
│   │  Headers: X-API-KEY: <your-api-key>                                               │    │
│   │  Content-Type: multipart/form-data                                                │    │
│   │                                                                                   │    │
│   │  Form Data:                                                                       │    │
│   │  • name: "my_checker"                   (Checker 名稱)                            │    │
│   │  • file: <binary or python script>     (Checker 執行檔)                           │    │
│   │                                                                                   │    │
│   │  ▼                                                                                │    │
│   │  儲存至: /sandbox/checkers/my_checker                                             │    │
│   │  設定權限: chmod 755                                                              │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
│   2. 使用 Checker (提交評測時)                                                              │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  POST /api/v1/submissions                                                         │    │
│   │                                                                                   │    │
│   │  Form Data:                                                                       │    │
│   │  • use_checker: true                                                              │    │
│   │  • checker_name: "my_checker"                                                     │    │
│   │  • ... (其他參數)                                                                  │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
│   3. Checker 執行方式                                                                       │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  # Python Checker                                                                 │    │
│   │  python3 /sandbox/checkers/my_checker.py <input> <output> <answer>               │    │
│   │                                                                                   │    │
│   │  # Binary Checker                                                                 │    │
│   │  /sandbox/checkers/my_checker <input> <output> <answer>                          │    │
│   │                                                                                   │    │
│   │  Return Code:                                                                     │    │
│   │  • 0 = AC (Accepted)                                                              │    │
│   │  • Non-zero = WA (Wrong Answer)                                                   │    │
│   │  • stderr = Checker 訊息 (會回傳給使用者)                                          │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
  1. 撰寫 Checker: 接收三個參數 input, user_output, answer
  2. 上傳 Checker: 透過 /api/v1/checkers 上傳。
  3. 使用 Checker: 提交評測時設定 use_checker=truechecker_name=你的checker檔名

📂 Log 系統與錯誤處理

Log 系統說明

系統運作的紀錄對於除錯至關重要。

  • 儲存位置: 專案根目錄下的 logs/ 資料夾。
  • 持久化: 透過 Docker Volume 掛載 (./logs:/sandbox/logs),即使容器刪除,Log 檔案也會保留在您的電腦上。
  • 檔案格式:
    • sandbox.log: 當天的最新 Log。
    • sandbox.log.YYYY-MM-DD: 歷史 Log (系統每天午夜自動切割並封存)。
    • submission_history.jsonl: 詳細的評測歷史紀錄 (JSON Lines 格式)。
    • auth_failures.jsonl: API Key 驗證失敗紀錄。

Log 記錄點 (Logging Points)

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                               系統 Log 記錄點總覽                                             │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                             │
│   api_server.py:                                                                            │
│   ├── INFO:  收到提交請求 (submission_id, problem_id, language, mode)                       │
│   ├── INFO:  題包更新完成 / 使用快取                                                        │
│   ├── WARN:  API Key 驗證失敗 (記錄 IP, UA, timestamp)                                      │
│   ├── ERROR: 檔案儲存失敗 / Hash 驗證失敗                                                   │
│   └── ERROR: 題包下載失敗                                                                   │
│                                                                                             │
│   queue_manager.py:                                                                         │
│   ├── INFO:  Job 加入佇列 (submission_id, priority, queue_size)                            │
│   ├── INFO:  Job 取出佇列                                                                   │
│   ├── WARN:  佇列已滿 (拒絕提交)                                                            │
│   └── INFO:  佇列暫停 / 恢復 / 清空                                                         │
│                                                                                             │
│   worker.py:                                                                                │
│   ├── INFO:  Worker 啟動 / 停止 (worker_id, box_id)                                        │
│   ├── INFO:  開始處理 Job (submission_id)                                                   │
│   ├── INFO:  Sidecar 啟動 / 停止 (image)                                                   │
│   ├── INFO:  Proxy 啟動 / 停止 (port, whitelist)                                           │
│   ├── INFO:  編譯結果 (CE/OK)                                                               │
│   ├── INFO:  Job 詳細結果 (JSON: status, time, memory, tasks)                              │
│   ├── INFO:  Final Verdict (status, time, memory)                                          │
│   ├── INFO:  Callback 發送成功 / 失敗                                                       │
│   ├── ERROR: Worker 執行錯誤 (exc_info=True)                                               │
│   └── ERROR: Cleanup 錯誤                                                                   │
│                                                                                             │
│   isolate_runner.py:                                                                        │
│   ├── INFO:  Box 初始化 / 清理                                                              │
│   ├── INFO:  檔案寫入沙盒 (file_count)                                                      │
│   ├── INFO:  ZIP 解壓縮完成                                                                 │
│   ├── DEBUG: Isolate 執行命令 (完整命令列)                                                  │
│   ├── ERROR: Init 失敗 / 執行錯誤 (含 traceback)                                           │
│   └── WARN:  Security 警告 (允許不安全 syscall 時)                                          │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

錯誤處理與容錯機制

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                               錯誤處理機制總覽                                                │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                             │
│   1. API 層錯誤處理 (api_server.py)                                                         │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  ┌─────────────────┐                                                              │    │
│   │  │ API Key 錯誤     │ ─▶ 403 Forbidden + Log 記錄 (auth_failures.jsonl)           │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 參數驗證失敗     │ ─▶ 422 Validation Error (Pydantic 自動處理)                 │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ Hash 不符        │ ─▶ 400 Bad Request (檔案完整性錯誤)                         │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 佇列已滿         │ ─▶ 503 Service Unavailable (可重試)                         │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 題包下載失敗     │ ─▶ 使用本地快取 (若有) 或 503 錯誤                          │    │
│   │  └─────────────────┘                                                              │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
│   2. Worker 層容錯 (worker.py)                                                              │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  ┌─────────────────┐                                                              │    │
│   │  │ Sidecar 啟動失敗│ ─▶ 記錄錯誤, 回傳 status="XX", 跳過執行                      │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 編譯失敗         │ ─▶ 記錄 stderr, 回傳 status="CE", 跳過測資執行              │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 執行超時         │ ─▶ Isolate 內建處理, 回傳 status="TO"                       │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 執行時錯誤       │ ─▶ 回傳 status="RE", 附帶 stderr 訊息                       │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ Callback 失敗    │ ─▶ 重試 3 次 (Backoff: 1s, 2s, 3s), 記錄失敗                │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 靜態分析失敗     │ ─▶ 不影響評測結果, 附加 error 訊息到 static_analysis       │    │
│   │  ├─────────────────┤                                                              │    │
│   │  │ 任意異常         │ ─▶ try-except 包覆, 記錄 exc_info, Worker 繼續運行          │    │
│   │  └─────────────────┘                                                              │    │
│   │                                                                                   │    │
│   │  ⚠️ 重要: Worker 不會因為單一 Job 錯誤而崩潰                                      │    │
│   │     所有異常都被捕捉並記錄, Worker 會繼續處理下一個 Job                           │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
│   3. 資源清理保證 (Finally Block)                                                           │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  無論執行成功或失敗, 都會執行:                                                     │    │
│   │  • Proxy 停止 & Log 收集                                                          │    │
│   │  • Sidecar 容器停止 & Log 收集 (錯誤時)                                           │    │
│   │  • 暫存檔案清理 (/tmp/sandbox/submissions/{id})                                   │    │
│   │  • Worker 狀態重設 (is_busy=False, current_job=None)                              │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
│   4. 系統級容錯                                                                             │
│   ┌───────────────────────────────────────────────────────────────────────────────────┐    │
│   │  • Docker restart: unless-stopped (容器異常退出自動重啟)                          │    │
│   │  • Worker Pool: 多個 Worker 並行, 單一 Worker 故障不影響整體                      │    │
│   │  • Priority Queue: 持久化 (記憶體內, 但有 History 備份)                           │    │
│   │  • Log 輪替: TimedRotatingFileHandler (每日切割, 保留 30 天)                      │    │
│   └───────────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

評測結果狀態碼

狀態碼 全稱 說明
AC Accepted 答案正確
WA Wrong Answer 答案錯誤
CE Compilation Error 編譯錯誤
RE Runtime Error 執行時錯誤 (Segfault, Exception 等)
TO Time Out 超過時間限制
SG Signal 被信號終止 (如 SIGKILL)
XX Internal Error 系統內部錯誤
OK OK 執行成功 (未驗證輸出時的狀態)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors