Stability hardening (webui/app): - readFileLimited(): защита от OOM при чтении audit JSON (100 MB), component-status DB (10 MB) и лога задачи (50 MB) - jobs.go: буферизованный лог задачи — один открытый fd на задачу вместо open/write/close на каждую строку (устраняет тысячи syscall/сек при GPU стресс-тестах) - stability.go: экспоненциальный backoff в goRecoverLoop (2s→4s→…→60s), сброс при успешном прогоне >30s, счётчик перезапусков в slog - kill_workers.go: таймаут 5s на скан /proc, warn при срабатывании - bee-web.service: MemoryMax=3G — OOM killer защищён Build script: - build.sh: удалён блок генерации grub-pc/grub.cfg + live.cfg.in — мёртвый код с v8.25; grub-pc игнорируется live-build, а генерируемый live.cfg.in перезаписывал правильный статический файл устаревшей версией без tuning-параметров ядра и пунктов gsp-off/kms+gsp-off - build.sh: dump_memtest_debug теперь логирует grub-efi/grub.cfg вместо grub-pc/grub.cfg (было всегда "missing") GRUB: - live-theme/bee-logo.png: логотип пчелы 400×400px на чёрном фоне - live-theme/theme.txt: + image компонент по центру в верхней трети экрана; меню сдвинуто с 62% до 65% Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
72 lines
1.5 KiB
Go
72 lines
1.5 KiB
Go
package webui
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"runtime/debug"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
recoverLoopMaxDelay = 60 * time.Second
|
|
recoverLoopResetAfter = 30 * time.Second
|
|
)
|
|
|
|
// goRecoverLoop starts fn in a goroutine, restarting after panics.
|
|
// restartDelay is the initial delay; successive panics double it up to
|
|
// recoverLoopMaxDelay. The delay resets to restartDelay once fn runs
|
|
// successfully for recoverLoopResetAfter without panicking.
|
|
func goRecoverLoop(name string, restartDelay time.Duration, fn func()) {
|
|
go func() {
|
|
delay := restartDelay
|
|
consecutive := 0
|
|
for {
|
|
start := time.Now()
|
|
panicked := runRecoverable(name, fn)
|
|
if !panicked {
|
|
return
|
|
}
|
|
consecutive++
|
|
if time.Since(start) >= recoverLoopResetAfter {
|
|
delay = restartDelay
|
|
consecutive = 1
|
|
}
|
|
slog.Warn("goroutine restarting after panic",
|
|
"component", name,
|
|
"consecutive_panics", consecutive,
|
|
"next_delay", delay,
|
|
)
|
|
if delay > 0 {
|
|
time.Sleep(delay)
|
|
}
|
|
if delay < recoverLoopMaxDelay {
|
|
delay *= 2
|
|
if delay > recoverLoopMaxDelay {
|
|
delay = recoverLoopMaxDelay
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func goRecoverOnce(name string, fn func()) {
|
|
go func() {
|
|
_ = runRecoverable(name, fn)
|
|
}()
|
|
}
|
|
|
|
func runRecoverable(name string, fn func()) (panicked bool) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
panicked = true
|
|
slog.Error("recovered panic",
|
|
"component", name,
|
|
"panic", fmt.Sprint(rec),
|
|
"stack", string(debug.Stack()),
|
|
)
|
|
}
|
|
}()
|
|
fn()
|
|
return false
|
|
}
|