feat(chat): асинхронная обработка входящих — отзывчивый интерфейс
Some checks failed
CI / test (push) Failing after 31s
CI / build-and-package (amd64, linux) (push) Successful in 43s
CI / build-and-package (amd64, windows) (push) Successful in 43s

Router теперь обрабатывает входящие в воркер-горутине (FIFO-очередь с
буфером 256) вместо синхронного вызова onUserMsg из long-poll цикла
канала. Долгий вызов аналитика (Decide) больше не блокирует приём
новых сообщений от Telegram: цикл getUpdates продолжает работать.

- router.go: NewRouter запускает processLoop; handleIncoming кладёт
  событие в канал и возвращается; маршрутизация + pending по-прежнему
  обновляются синхронно под мьютексом.
- router_test.go: fakeOnMsg стал потокобезопасным с ожиданием числа
  входящих (wait), т.к. обработка теперь асинхронная.

Преимущества: интерфейс не замирает на время анализа; порядок входящих
сохраняется (FIFO). Ограничение: воркер один — при очень долгом аналитике
следующие сообщения ждут в очереди, но канал их продолжает принимать.
This commit is contained in:
Hermes
2026-08-18 19:55:46 +05:00
parent 774ebf135a
commit a64e3d6cc3
2 changed files with 97 additions and 16 deletions

View File

@@ -18,6 +18,13 @@ type Router struct {
// Hook, вызываемый на каждое входящее событие (обычно → process_turn).
onUserMsg func(Incoming)
// Асинхронная обработка входящих: handleIncoming кладёт событие в канал,
// воркер-горутина последовательно вызывает onUserMsg. Благодаря этому
// long-poll цикл канала (Telegram) не блокируется на время долгого
// вызова аналитика и продолжает принимать новые сообщения.
incoming chan Incoming
wg sync.WaitGroup
}
// NewRouter создаёт роутер. onUserMsg — колбэк обработки входящего.
@@ -25,11 +32,22 @@ func NewRouter(onUserMsg func(Incoming)) *Router {
if onUserMsg == nil {
onUserMsg = func(Incoming) {}
}
return &Router{
r := &Router{
sessions: map[UserID]any{},
routes: map[UserID]Route{},
pending: map[UserID]PendingQ{},
onUserMsg: onUserMsg,
incoming: make(chan Incoming, 256),
}
go r.processLoop()
return r
}
// processLoop — воркер асинхронной обработки входящих (FIFO).
func (r *Router) processLoop() {
for inc := range r.incoming {
r.onUserMsg(inc)
r.wg.Done()
}
}
@@ -59,7 +77,11 @@ func (r *Router) handleIncoming(inc Incoming) {
delete(r.pending, inc.UserID)
}
r.mu.Unlock()
r.onUserMsg(inc)
// Асинхронная обработка: кладём событие в очередь воркера и сразу
// возвращаемся, не блокируя вызывающий long-poll цикл канала.
r.wg.Add(1)
r.incoming <- inc
}
// Send уведомляет пользователя через текущий маршрут. M1 (нет маршрута) — no-op,