fix: semaphore+singleflight for ensureUserNS, fix 504 on 10 parallel new users (v0.8.12)

This commit is contained in:
Naeel
2026-04-25 14:41:35 +03:00
parent ca43d0a7d7
commit 3b9c6e2c5e
15 changed files with 3414 additions and 67 deletions
+110
View File
@@ -0,0 +1,110 @@
# Задачи после code review (2026-04-19)
Приоритет: **критично** → сделать до merge в master.
---
## КРИТИЧНО
### 1. k8s ошибки протекают как 502 — нужны правильные HTTP коды
**Файл:** `console/main.go`
**Проблема:** `apierrors.IsAlreadyExists` и `apierrors.IsInvalid` не перехватываются → клиент получает 502 вместо 409/400.
**Что сделать:**
- В `handleCreateFunction`: перехватить `apierrors.IsAlreadyExists` → HTTP 409
- В `handleCreateFunction`: перехватить `apierrors.IsInvalid` → HTTP 400
- Аналогично проверить `handleUpdateFunction`
**Пример:**
```go
if apierrors.IsAlreadyExists(err) {
writeJSONError(w, http.StatusConflict, fmt.Sprintf("function %q already exists", req.Name))
return
}
if apierrors.IsInvalid(err) {
writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("invalid function spec: %v", err))
return
}
```
---
### 2. Валидация имени функции на уровне API
**Файл:** `console/main.go`
**Проблема:** имя с пробелами/спецсимволами уходит в k8s и возвращается 502.
**Что сделать:** добавить regex-валидацию сразу после парсинга запроса в `handleCreateFunction` и `handleUpdateFunction`.
**Пример:**
```go
var validName = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`)
if !validName.MatchString(req.Name) || len(req.Name) > 63 {
writeJSONError(w, http.StatusBadRequest, "invalid function name: must match ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ and be <= 63 chars")
return
}
```
---
## ВАЖНО (не блокирует merge)
### 3. Reaper: сканирование orphan packages
**Файл:** `console/main.go`
**Проблема:** если под упал в момент удаления функции, Package может остаться без matching Function.
**Что сделать:** в цикле reaper дополнительно итерироваться по packages и удалять те, у которых нет соответствующей function с тем же именем (по конвенции `{fn-name}-pkg`).
---
### 4. Лимит размера кода
**Файл:** `console/main.go`
**Проблема:** нет ограничения на размер `req.Code` — можно залить мегабайты.
**Что сделать:** после парсинга тела запроса добавить:
```go
const maxCodeSize = 1 << 20 // 1 MB
if len(req.Code) > maxCodeSize {
writeJSONError(w, http.StatusBadRequest, "code exceeds 1MB limit")
return
}
```
---
### 5. Namespace race condition
**Файл:** `console/main.go`
**Проблема:** при одновременных первых запросах одного пользователя `Create(namespace)` может вернуть `AlreadyExists`.
**Что сделать:** убедиться что в `ensureNamespace` (или аналогичной функции) ошибка `AlreadyExists` при создании namespace игнорируется:
```go
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
```
---
## НЕ СРОЧНО
### 6. Тест-скрипт: RUN_ID уникальность
**Файл:** `tests_v2.sh`
**Проблема:** два параллельных запуска с одинаковым timestamp дают одинаковый RUN_ID → Block D FAIL.
**Что сделать:** добавить случайный суффикс:
```bash
RUN_ID=$(date +%s%N | sha256sum | head -c 8)
```
---
## Документировать (без кода)
- Python env: `def main():` без аргументов — задокументировать в README/examples
- Версионирование функций — не делать сейчас, отложить