«Работает» — только после строки о готовности сервера
CI / test (push) Successful in 23s
Release / release (push) Successful in 30s

Регистрацию в Steam сервер печатает задолго до того, как мир загружен, и по
ней панель показывала «Работает» на сервере, который ещё никого не пустит.
Теперь готовность распознаётся только по «*** SERVER STARTED ****», а пока
идёт загрузка, состояние остаётся «Запускается» с пояснением под ним и
счётчиком времени загрузки.

Если строки о готовности так и не будет (своя версия, свои моды), через
пятнадцать минут менеджер сочтёт сервер запущенным и скажет об этом в
консоли: иначе панель навсегда осталась бы без опроса игроков.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 14:15:54 +03:00
co-authored by Claude Opus 5
parent 9a224bc070
commit 1e659678b9
8 changed files with 87 additions and 7 deletions
+1
View File
@@ -70,6 +70,7 @@ var messages = map[string]string{
"Останавливаю сервер командой quit...": "Stopping the server with the quit command...",
"Сервер не ответил на quit — отправляю SIGTERM": "The server did not answer quit — sending SIGTERM",
"Сервер завис — отправляю SIGKILL": "The server hung — sending SIGKILL",
"Сервер не сообщил о готовности — считаю его запущенным": "The server did not report that it is ready — treating it as running",
"Автоперезапуск отменён": "Automatic restart cancelled",
"Посмотрите ошибку выше и запустите сервер вручную.": "Check the error above and start the server manually.",
"Запускаю обновление — оно идёт отдельной службой и переживёт перезапуск панели": "Starting the update — it runs as a separate service and survives the panel restart",
+29 -3
View File
@@ -42,6 +42,11 @@ var (
// playersPollInterval — как часто менеджер спрашивает у сервера список игроков.
const playersPollInterval = 30 * time.Second
// startupGrace — сколько ждём строку о готовности. Загрузка мира с модами
// занимает минуты, поэтому запас большой: раньше времени объявлять сервер
// работающим хуже, чем подождать.
const startupGrace = 15 * time.Minute
// restartDelay — пауза перед автоподъёмом упавшего сервера.
const restartDelay = 10 * time.Second
@@ -355,10 +360,13 @@ func (s *Supervisor) handleLine(stream, text string) {
}
// isServerReady распознаёт строку, которой PZ сообщает о готовности принимать
// игроков.
// игроков: «LOG : Network f:0 st:81,093,763> *** SERVER STARTED ****».
//
// Других маркеров тут нет намеренно. Регистрацию в Steam сервер печатает
// задолго до того, как мир загружен, и по ней панель показывала «Работает» на
// сервере, который ещё никого не пустит.
func isServerReady(line string) bool {
return strings.Contains(line, "SERVER STARTED") ||
strings.Contains(line, "Server Steam ID")
return strings.Contains(strings.ToUpper(line), "SERVER STARTED")
}
// wait дожидается завершения процесса и решает, поднимать ли его заново.
@@ -542,6 +550,23 @@ func (s *Supervisor) WaitStopped(timeout time.Duration) bool {
return s.State() == StateStopped
}
// checkStartupGrace страхует от сервера, который так и не напечатал строку о
// готовности: со своими модами и своей версией он может назвать её иначе, а
// вечное «Запускается» лишило бы панель опроса игроков.
func (s *Supervisor) checkStartupGrace() {
s.mu.Lock()
overdue := s.state == StateStarting && !s.startedAt.IsZero() &&
time.Since(s.startedAt) > startupGrace
if overdue {
s.state = StateRunning
}
s.mu.Unlock()
if overdue {
s.AppendLog("manager", "Сервер не сообщил о готовности — считаю его запущенным")
}
}
// pollPlayers периодически спрашивает у работающего сервера, кто онлайн.
func (s *Supervisor) pollPlayers() {
ticker := time.NewTicker(playersPollInterval)
@@ -551,6 +576,7 @@ func (s *Supervisor) pollPlayers() {
case <-s.quit:
return
case <-ticker.C:
s.checkStartupGrace()
if s.State() != StateRunning {
continue
}
+33
View File
@@ -0,0 +1,33 @@
package pzserver
import "testing"
// Строка о готовности решает, когда панель перестанет писать «Запускается».
// Ошибиться в обе стороны одинаково неприятно: рано — покажем работающим
// сервер, который никого не пустит; поздно — сервер работает, а панель молчит.
func TestIsServerReady(t *testing.T) {
ready := []string{
"LOG : Network f:0 st:81,093,763> *** SERVER STARTED ****",
"*** SERVER STARTED ****",
"Server started",
}
for _, line := range ready {
if !isServerReady(line) {
t.Errorf("строка о готовности не распознана: %q", line)
}
}
// Регистрацию в Steam и загрузку мира сервер печатает задолго до того,
// как начнёт принимать подключения.
notReady := []string{
"Server Steam ID 90271993034366481",
"znet: Java_zombie_core_znet_SteamGameServer_Init",
"LOG : General f:0 st:1,234> loading world",
"Loading: SERVER settings",
}
for _, line := range notReady {
if isServerReady(line) {
t.Errorf("строка принята за готовность: %q", line)
}
}
}