{"record":{"id":"b85a94b3726c4718","repo":"Tencent/WeKnora","slug":"mark-session-s-stale-w","errorCode":null,"errorMessage":"mark session %s stale: %w","messagePattern":"mark session (.+?) stale: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/sandbox/session_binding.go","lineNumber":232,"sourceCode":"\t\t\t}\n\t\t\tif binding.ConfigID != wanted || binding.StaleAt != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\twrote, err := store.markBindingStale(\n\t\t\t\tmarkCtx, key, *binding, time.Now().UTC(),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif wrote {\n\t\t\t\tmarked++\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tacquireTimer.Stop()\n\t\tcancel()\n\t\tif err != nil {\n\t\t\tfailures = append(failures, fmt.Errorf(\"mark session %s stale: %w\", key.SessionID, err))\n\t\t}\n\t}\n\treturn marked, errors.Join(failures...)\n}\n\ntype lifecycleOwnershipContextKey struct{}\n\nfunc withLifecycleOwnershipContext(\n\tctx context.Context,\n\townershipCtx context.Context,\n) context.Context {\n\treturn context.WithValue(ctx, lifecycleOwnershipContextKey{}, ownershipCtx)\n}\n\nfunc lifecycleOwnershipContext(ctx context.Context) context.Context {\n\tif ownershipCtx, ok := ctx.Value(lifecycleOwnershipContextKey{}).(context.Context); ok {\n\t\treturn ownershipCtx\n\t}","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/session_binding.go#L214-L250","documentation":"InvalidateByConfig marks every binding for a config stale in parallel; the per-session markBindingStale work runs under an acquire timer. If marking one session fails, the error is collected (not returned immediately) as \"mark session <id> stale: %w\" and joined with other failures via errors.Join. InvalidateByConfig ultimately surfaces the joined error to its caller.","triggerScenarios":"Invalidating bindings by config while the store is degraded: Redis timeouts exceeding the acquire deadline, connection pool exhaustion, or a single key being locked by another worker's lifecycle lock.","commonSituations":"Redis under load during incident-driven mass invalidation; lock contention between concurrent reapers and invalidators; partial network failures hitting only some sessions.","solutions":["Inspect the joined error to find which session IDs failed and retry InvalidateByConfig or targeted marks for those","Increase the acquire/lifecycle-lock timeout or reduce concurrency of invalidation sweeps","Check Redis health/latency; the failures are usually store-side contention or timeouts","Verify no other process holds the lifecycle lock on the affected sessions"],"exampleFix":"// before\nif err := store.InvalidateByConfig(ctx, tenant, cfg); err != nil {\n    log.Fatal(err) // opaque joined error\n}\n// after\nif err := store.InvalidateByConfig(ctx, tenant, cfg); err != nil {\n    for _, part := range strings.Split(err.Error(), \"\\n\") {\n        log.Warnf(\"partial invalidate: %s\", part) // retry per failed session\n    }\n}","handlingStrategy":"try-catch","validationCode":"if err := store.Ping(ctx); err != nil {\n    return fmt.Errorf(\"postpone invalidation: store degraded: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := store.InvalidateByConfig(ctx, tenant, cfg); err != nil {\n    for _, failure := range strings.Split(err.Error(), \"\\n\") {\n        if id, ok := parseFailedSession(failure); ok {\n            retryMarkStale(ctx, tenant, cfg, id)\n        }\n    }\n}","preventionTips":["Retry failed per-session marks individually after a sweep","Spread large invalidations to reduce lock contention","Monitor Redis latency; raise acquire budgets under load"],"tags":["go","sandbox","locking","partial-failure"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}