diff --git a/pkg/hold/admin/handlers.go b/pkg/hold/admin/handlers.go index dc4eb42..2132fcd 100644 --- a/pkg/hold/admin/handlers.go +++ b/pkg/hold/admin/handlers.go @@ -6,11 +6,19 @@ import ( "net/http" "sort" "strconv" + "sync" + "time" "atcr.io/pkg/atproto" "github.com/go-chi/render" ) +// topUsersResolveTimeout bounds handle resolution on the dashboard's top-users +// panel. The shared identity directory allows each lookup 10s, which is the +// whole reverse proxy budget, so the panel caps its own resolution well short +// of that and renders bare DIDs for anything slower. +const topUsersResolveTimeout = 3 * time.Second + // DashboardStats contains dashboard statistics type DashboardStats struct { TotalCrewMembers int @@ -155,7 +163,6 @@ func (ui *AdminUI) handleTopUsersAPI(w http.ResponseWriter, r *http.Request) { for did, q := range allQuotas { users = append(users, UserUsage{ DID: did, - Handle: resolveHandle(ctx, did), Usage: q.TotalSize, UsageHuman: formatHumanBytes(q.TotalSize), BlobCount: q.UniqueBlobs, @@ -172,6 +179,25 @@ func (ui *AdminUI) handleTopUsersAPI(w http.ResponseWriter, r *http.Request) { users = users[:limit] } + // Resolve handles only for the rows that survived the limit. Doing this + // inside the loop above cost one network lookup per user on the hold to + // display ten of them, which blew past the identity directory's 10s HTTP + // timeout and left the client hanging up mid-render. Resolve concurrently + // under a deadline well inside the reverse proxy's budget so a stalled + // lookup degrades to a bare DID instead of a dead request. + resolveCtx, cancel := context.WithTimeout(ctx, topUsersResolveTimeout) + defer cancel() + + var wg sync.WaitGroup + for i := range users { + wg.Add(1) + go func(u *UserUsage) { + defer wg.Done() + u.Handle = resolveHandle(resolveCtx, u.DID) + }(&users[i]) + } + wg.Wait() + // If HTMX request, return HTML partial if r.Header.Get("HX-Request") == "true" { data := struct {