v0.0.72: ретраи pull (3 попытки), корректная ошибка лимита сессии, счётчик дедупа
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
@@ -106,6 +106,7 @@
|
|||||||
- **2026-08-24-time-tickers-estimates.md** — v0.0.64–0.0.65: тикер текущего файла на всех этапах, мгновенные оценки времени по файлам и суммарно.
|
- **2026-08-24-time-tickers-estimates.md** — v0.0.64–0.0.65: тикер текущего файла на всех этапах, мгновенные оценки времени по файлам и суммарно.
|
||||||
- **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь).
|
- **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь).
|
||||||
- **2026-08-24-folder-zip-not-lost.md** — v0.0.69: архивы из папки без документов не теряются (добавляются как есть); раскрытие zip с документами; склонение счётчика.
|
- **2026-08-24-folder-zip-not-lost.md** — v0.0.69: архивы из папки без документов не теряются (добавляются как есть); раскрытие zip с документами; склонение счётчика.
|
||||||
|
- **2026-08-24-pull-retry-limit-counter.md** — v0.0.72: ретраи pull из ВМ, корректная ошибка лимита сессии, счётчик дедупа.
|
||||||
- **2026-08-24-upload-logic-schema.md** — подробная схема логики загрузки + найденные баги/несоответствия.
|
- **2026-08-24-upload-logic-schema.md** — подробная схема логики загрузки + найденные баги/несоответствия.
|
||||||
- **2026-08-24-help-button-limits-text.md** — v0.0.70: заметная кнопка HELP (вместо «?») + пояснение статуса «пропущен» в ограничениях.
|
- **2026-08-24-help-button-limits-text.md** — v0.0.70: заметная кнопка HELP (вместо «?») + пояснение статуса «пропущен» в ограничениях.
|
||||||
- **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия».
|
- **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия».
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# v0.0.72 — Ретраи pull, корректная ошибка лимита, счётчик дедупа (2026-08-24)
|
||||||
|
|
||||||
|
_code-fixes. По итогам код-ревью (см. `infra/2026-08-24-upload-logic-schema.md`)._
|
||||||
|
|
||||||
|
## 1. Ретраи pull (site/routes/api_bp.py, `upload_refs`)
|
||||||
|
- `PULL_RETRIES = 3`, `PULL_RETRY_DELAY = 2` (сек).
|
||||||
|
- Pull из ВМ-буфера теперь в цикле: при любой ошибке (DNS `gaierror -5`, сеть) — до 3 попыток
|
||||||
|
с паузой 2с. После исчерпания — проброс исходной ошибки (502 «Pull failed»).
|
||||||
|
- Закрывает инцидент 18:42 (разовый DNS-сбой ронял всю загрузку).
|
||||||
|
|
||||||
|
## 2. Корректная ошибка лимита сессии (`upload_refs`)
|
||||||
|
- `add_file` возвращает `False` и для «сессия исчезла», и для «превышен лимит 500МБ».
|
||||||
|
- Теперь: при `False` — если `get_files(sid) is None` → 404 «Session not found»;
|
||||||
|
иначе (лимит) → файл пропускается (`delete` с ВМ) и загрузка продолжается.
|
||||||
|
Раньше оба случая давали ложное «Session not found».
|
||||||
|
|
||||||
|
## 3. Счётчик «Добавлено из папки» (site/templates/index.html)
|
||||||
|
- `addFileWithDedup` теперь возвращает `true` (файл добавлен) / `false` (дедуп).
|
||||||
|
- `added++` только при `true` — дубли больше не завышают счётчик.
|
||||||
|
- Проверено: папка [Договор.txt, Договор.txt (дубль), Акт.txt] → «Добавлено из папки: 2 файла».
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
- `node --check` — OK, `py_compile` (app.py, api_bp.py) — OK.
|
||||||
|
- Локально: счётчик дедупа работает (2 файла при одном дубле).
|
||||||
|
- Ретраи/лимит — на проде после деплоя.
|
||||||
|
- Версия 0.0.71 → 0.0.72.
|
||||||
+1
-1
@@ -21,7 +21,7 @@ if _sys_path_root not in sys.path:
|
|||||||
sys.path.insert(0, _sys_path_root)
|
sys.path.insert(0, _sys_path_root)
|
||||||
|
|
||||||
# Версия приложения (меняется при изменениях)
|
# Версия приложения (меняется при изменениях)
|
||||||
VERSION = "0.0.71"
|
VERSION = "0.0.72"
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
|
|||||||
+31
-5
@@ -30,6 +30,10 @@ from session import (create_session, add_file, get_files, store_result,
|
|||||||
api_bp = Blueprint("api", __name__, url_prefix="/api")
|
api_bp = Blueprint("api", __name__, url_prefix="/api")
|
||||||
log = logging.getLogger("routes.api_bp")
|
log = logging.getLogger("routes.api_bp")
|
||||||
|
|
||||||
|
# Ретраи pull из ВМ-буфера: защита от разовых DNS/сетевых сбоев (gaierror -5 и т.п.)
|
||||||
|
PULL_RETRIES = 3
|
||||||
|
PULL_RETRY_DELAY = 2 # секунды между попытками
|
||||||
|
|
||||||
|
|
||||||
def _disconnect_exceptions():
|
def _disconnect_exceptions():
|
||||||
"""Исключения, означающие отключение клиента SSE."""
|
"""Исключения, означающие отключение клиента SSE."""
|
||||||
@@ -105,9 +109,23 @@ def upload_refs():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
continue
|
continue
|
||||||
with client.stream("GET", url) as resp:
|
# Pull с ретраями: разовые DNS/сетевые сбои не роняют всю загрузку
|
||||||
resp.raise_for_status()
|
content = None
|
||||||
content = b"".join(resp.iter_bytes())
|
last_err = None
|
||||||
|
for attempt in range(PULL_RETRIES):
|
||||||
|
try:
|
||||||
|
with client.stream("GET", url) as resp:
|
||||||
|
resp.raise_for_status()
|
||||||
|
content = b"".join(resp.iter_bytes())
|
||||||
|
last_err = None
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
log.warning("upload_refs: pull attempt %d/%d failed sid=%s file=%r: %r",
|
||||||
|
attempt + 1, PULL_RETRIES, sid, name, e)
|
||||||
|
time.sleep(PULL_RETRY_DELAY)
|
||||||
|
if content is None:
|
||||||
|
raise last_err if last_err else RuntimeError("pull failed")
|
||||||
log.info("upload_refs: pulled sid=%s file=%r size=%d", sid, name, len(content))
|
log.info("upload_refs: pulled sid=%s file=%r size=%d", sid, name, len(content))
|
||||||
if len(content) > MAX_FILE_BYTES:
|
if len(content) > MAX_FILE_BYTES:
|
||||||
log.warning("upload_refs: pulled file exceeds %dMB, skip sid=%s file=%r size=%d",
|
log.warning("upload_refs: pulled file exceeds %dMB, skip sid=%s file=%r size=%d",
|
||||||
@@ -118,8 +136,16 @@ def upload_refs():
|
|||||||
pass
|
pass
|
||||||
continue
|
continue
|
||||||
if not add_file(sid, name, content):
|
if not add_file(sid, name, content):
|
||||||
log.warning("upload_refs: session not found/limit, sid=%s file=%r", sid, name)
|
# Различить: сессия исчезла vs превышен суммарный лимит сессии
|
||||||
return jsonify({"ok": False, "error": "Session not found"}), 404
|
if get_files(sid) is None:
|
||||||
|
log.warning("upload_refs: session not found, sid=%s file=%r", sid, name)
|
||||||
|
return jsonify({"ok": False, "error": "Session not found"}), 404
|
||||||
|
log.warning("upload_refs: session limit exceeded, skip sid=%s file=%r", sid, name)
|
||||||
|
try:
|
||||||
|
client.delete(url)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
client.delete(url) # убрать файл с ВМ после загрузки
|
client.delete(url) # убрать файл с ВМ после загрузки
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ function addFileWithDedup(file) {
|
|||||||
const idx = sf.findIndex(f => f.name === name);
|
const idx = sf.findIndex(f => f.name === name);
|
||||||
if (idx >= 0) sf[idx] = new File([file], name, { lastModified: mtime });
|
if (idx >= 0) sf[idx] = new File([file], name, { lastModified: mtime });
|
||||||
}
|
}
|
||||||
return;
|
return false; // дедуп: файл не добавлен (обновлена только дата)
|
||||||
}
|
}
|
||||||
// Имя то же, размер другой — добавить с суффиксом _2, _3...
|
// Имя то же, размер другой — добавить с суффиксом _2, _3...
|
||||||
const dot = name.lastIndexOf('.');
|
const dot = name.lastIndexOf('.');
|
||||||
@@ -548,9 +548,10 @@ function addFileWithDedup(file) {
|
|||||||
if (over) {
|
if (over) {
|
||||||
overNames.add(name);
|
overNames.add(name);
|
||||||
sf.push(new File([file], name, { lastModified: mtime }));
|
sf.push(new File([file], name, { lastModified: mtime }));
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
sf.push(new File([file], name, { lastModified: mtime }));
|
sf.push(new File([file], name, { lastModified: mtime }));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.addEventListener('change', async () => {
|
fi.addEventListener('change', async () => {
|
||||||
@@ -613,21 +614,17 @@ folderInput.addEventListener('change', async () => {
|
|||||||
if (nested.length) {
|
if (nested.length) {
|
||||||
for (const nf of nested) {
|
for (const nf of nested) {
|
||||||
const nm = relDir ? relDir + '/' + nf.name : nf.name;
|
const nm = relDir ? relDir + '/' + nf.name : nf.name;
|
||||||
addFileWithDedup(new File([nf], nm, { lastModified: nf.lastModified }));
|
if (addFileWithDedup(new File([nf], nm, { lastModified: nf.lastModified }))) added++;
|
||||||
added++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// в архиве нет документов — добавить архив как есть, чтобы не терялся
|
// в архиве нет документов — добавить архив как есть, чтобы не терялся
|
||||||
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }));
|
if (addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }))) added++;
|
||||||
added++;
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified })); // zip как есть
|
if (addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }))) added++; // zip как есть
|
||||||
added++;
|
|
||||||
}
|
}
|
||||||
} else if (DOC_EXTS.some(e => low.endsWith(e))) {
|
} else if (DOC_EXTS.some(e => low.endsWith(e))) {
|
||||||
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }));
|
if (addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }))) added++;
|
||||||
added++;
|
|
||||||
}
|
}
|
||||||
// иначе — не документ, пропускаем
|
// иначе — не документ, пропускаем
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user