From 265ff1dd5dd10f093eae5f7c140dd4d29a24118a Mon Sep 17 00:00:00 2001 From: chiyi Date: Mon, 10 Aug 2026 09:15:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(leonardo):=20session=20keepalive=20?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=88=90=201=20=E5=88=86=E9=92=9F=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=EF=BC=8C=E5=B9=B6=E8=AE=B0=E5=BD=95=E7=BB=AD=E6=9C=9F?= =?UTF-8?q?/=E5=A4=B1=E8=B4=A5=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maintenance 的一次 tick 可能跑几十分钟(光 adobe cookie profile 就 219 个),挂在里面会把 5 分钟的保活拉长成 tick 的实际耗时。 --- backend/internal/service/maintenance.go | 29 ++++++++++++++++++++----- backend/internal/service/tokens.go | 6 +++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/backend/internal/service/maintenance.go b/backend/internal/service/maintenance.go index 1f4ff41..dd831dd 100644 --- a/backend/internal/service/maintenance.go +++ b/backend/internal/service/maintenance.go @@ -63,6 +63,10 @@ func NewMaintenanceService(tokens *repo.TokenRepository, tokenSvc *TokenService, func (m *MaintenanceService) Run(ctx context.Context) { ticker := time.NewTicker(m.interval) defer ticker.Stop() + // The leonardo session keep-alive gets its own loop: one tick() can take tens + // of minutes (219 adobe cookie profiles alone), which would stretch a 5-minute + // keep-alive to the tick's real duration. + go m.runLeonardoKeepalive(ctx) m.tick(ctx) for { select { @@ -74,6 +78,25 @@ func (m *MaintenanceService) Run(ctx context.Context) { } } +// runLeonardoKeepalive re-checks every minute which leonardo accounts are due for +// a session renewal (the 5-minute due-ness itself is read per account from the DB, +// so a restart can't skip one). +func (m *MaintenanceService) runLeonardoKeepalive(ctx context.Context) { + if m.tokenSvc == nil { + return + } + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + for { + m.tokenSvc.RefreshLeonardoSessions(ctx) + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + // syncRecoveredQuota re-probes each just-recovered account so its displayed // balance reflects the post-reset value (these providers only sync quota when // accessed). krea additionally needs /app (Activate) to actually grant the daily @@ -165,12 +188,6 @@ func (m *MaintenanceService) tick(ctx context.Context) { // means the membership lapsed → disable+dead; otherwise re-sync the // credits balance and 恢复时间 (from the credits' weekly reset). m.tokenSvc.RefreshGrokLiveness(ctx) - // 1f. Keep leonardo's better-auth session rolling: get-session extends the - // session and rotates the cookie, so re-minting stops a dormant - // account's cookie from lapsing (a lapsed one can only be fixed by - // re-importing a browser cookie). Due-ness is read per account from - // meta["session_kept_at"], so a restart can't skip a renewal. - m.tokenSvc.RefreshLeonardoSessions(ctx) } // 2. Auto-renew Adobe cookies whose refresh interval has elapsed. diff --git a/backend/internal/service/tokens.go b/backend/internal/service/tokens.go index 9cfe6a8..cd9b42e 100644 --- a/backend/internal/service/tokens.go +++ b/backend/internal/service/tokens.go @@ -172,6 +172,7 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { } due := time.Now().Add(-leonardoKeepaliveEvery).Unix() proxied := false + renewed, failed := 0, 0 for i := range items { a := items[i] if a.Dead || a.Status == "disabled" || strings.TrimSpace(a.Value) == "" { @@ -188,6 +189,7 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { _, err := s.leonardo.ProbeSession(callCtx, a.Value) if err != nil { cancel() + failed++ if errors.Is(err, leonardo.ErrAuth) { log.Printf("leonardo %s: session keepalive auth failure (%v)", a.ID, err) } @@ -202,6 +204,10 @@ func (s *TokenService) RefreshLeonardoSessions(ctx context.Context) { fields["meta"] = meta _, _ = s.tokens.Update(callCtx, "leonardo", a.ID, fields) cancel() + renewed++ + } + if renewed > 0 || failed > 0 { + log.Printf("leonardo: session keepalive renewed %d, failed %d", renewed, failed) } }() }