Add resource limits, abuse protection and overload handling
Running games always continue; new work is refused when there is no
capacity; abuse is rejected immediately.
- Docker: CPU/memory/pids caps per container (overridable in .env);
uvicorn message size, queue and concurrency limits
- Server abuse limits: connections total/per IP (close 1013), message
rate per connection (token bucket, close 1008 on floods), lobby
creation rate per IP
- Load monitor: event loop lag, CPU of the own cgroup quota and
throttling, with hysteresis (normal/busy/overloaded, severe lag
overloads immediately)
- Admission control: create_lobby and start_game refused while
overloaded or MAX_ACTIVE_GAMES are running; server_status message
broadcast on changes; /health reports load but stays ok
- AI bridge: timeout sent with requests, in-flight cap, circuit breaker
- AI service: bounded workers and queue ("overloaded"), drops expired
requests, degrades to the heuristic agent when busy or too slow
- Website: busy notices, Create/Start disabled when not accepting
games, slow retry when the server is full
- Tests for limiters, load levels, admission control and AI service
limits; docs updated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+10
-1
@@ -53,7 +53,8 @@ The only stateful piece of the app.
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `status` | `connecting` \| `open` \| `closed` (will retry) \| `replaced` (opened in another tab, no retry) |
|
||||
| `status` | `connecting` \| `open` \| `closed` (will retry) \| `replaced` (opened in another tab, no retry) \| `busy` (server full, retries slowly) |
|
||||
| `serverStatus` | last `server_status` message: `{ load, accepting_games }` |
|
||||
| `playerId` | from `welcome` |
|
||||
| `lobby`, `game` | exactly what the last `state` message contained |
|
||||
| `completedTrick` | the last `trick_completed` event, kept for 2.2 s so the trick stays visible |
|
||||
@@ -73,6 +74,10 @@ The only stateful piece of the app.
|
||||
second tab is a different player.
|
||||
- On close it reconnects with exponential backoff (1, 2, 4, 8, 10, 10 … s),
|
||||
except after close code 4000.
|
||||
- If the server refuses the connection because it is full (an `error` with
|
||||
code `busy` before `welcome`, then close code 1013), the status becomes
|
||||
`busy` and it retries slowly (15 s, 30 s, 60 s, …) so waiting clients don't
|
||||
add load.
|
||||
- `send()` shows a toast if the socket is not open.
|
||||
|
||||
## Screen selection (`App.tsx`)
|
||||
@@ -96,6 +101,8 @@ shows the connection status when it is not `open`.
|
||||
- Code input + "Join" → `actions.joinLobby(code, name)`. The code is prefilled
|
||||
from `?lobby=` so invite links work.
|
||||
- Buttons are disabled while disconnected or without a name.
|
||||
- While `serverStatus.accepting_games` is false, "Create lobby" is disabled and
|
||||
a notice explains that the server is busy (joining still works).
|
||||
|
||||
### LobbyScreen
|
||||
|
||||
@@ -107,6 +114,8 @@ shows the connection status when it is not `open`.
|
||||
seats are filled). Others see the options read-only and "Waiting for the
|
||||
host…".
|
||||
- Shows a warning if AI players are present but the AI service is offline.
|
||||
- While `serverStatus.accepting_games` is false, "Start game" shows
|
||||
"Server busy" (disabled) with a notice; the lobby stays open.
|
||||
- Explains the fixed bidding rule (bids may not add up to the cards in hand).
|
||||
|
||||
### GameScreen (`screens/GameScreen/`)
|
||||
|
||||
Reference in New Issue
Block a user