{"record":{"id":"7d198d583cb9f413","repo":"oauth2-proxy/oauth2-proxy","slug":"timeout-obtaining-session-lock","errorCode":null,"errorMessage":"timeout obtaining session lock","messagePattern":"timeout obtaining session lock","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/middleware/stored_session.go","lineNumber":168,"sourceCode":"}\n\n// refreshSessionIfNeeded will attempt to refresh a session if the session\n// is older than the refresh period.\n// Success or fail, we will then validate the session.\nfunc (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) error {\n\tif !needsRefresh(s.refreshPeriod, session) {\n\t\t// Refresh is disabled or the session is not old enough, do nothing\n\t\treturn nil\n\t}\n\n\tvar lockObtained bool\n\tctx, cancel := context.WithTimeout(context.Background(), sessionRefreshObtainTimeout)\n\tdefer cancel()\n\n\tfor !lockObtained {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn errors.New(\"timeout obtaining session lock\")\n\t\tdefault:\n\t\t\terr := session.ObtainLock(req.Context(), sessionRefreshLockDuration)\n\t\t\tif err != nil && !errors.Is(err, sessionsapi.ErrLockNotObtained) {\n\t\t\t\treturn fmt.Errorf(\"error occurred while trying to obtain lock: %v\", err)\n\t\t\t} else if errors.Is(err, sessionsapi.ErrLockNotObtained) {\n\t\t\t\ttime.Sleep(sessionRefreshRetryPeriod)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// No error means we obtained the lock\n\t\t\tlockObtained = true\n\t\t}\n\t}\n\n\t// The rest of this function is carried out under lock, but we must release it\n\t// wherever we exit from this function.\n\tdefer func() {\n\t\tif session == nil {\n\t\t\treturn","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/oauth2-proxy/oauth2-proxy/blob/33c2eb92dea78204f7a18bc2dfdbccc220f39257/pkg/middleware/stored_session.go#L150-L186","documentation":"refreshSessionIfNeeded in pkg/middleware/stored_session.go obtains a session lock (to safely refresh a session near expiry) with a bounded wait of sessionRefreshObtainTimeout. If the lock is not obtained before the context deadline, the loop's ctx.Done() branch returns this error. It prevents concurrent requests from deadlocking on the same session.","triggerScenarios":"Another request holds the session lock (session.ObtainLock keeps returning ErrLockNotObtained) for longer than sessionRefreshObtainTimeout while this request tries to refresh a session near its expiry.","commonSituations":"Long-running upstream request holding the lock while a parallel tab/polling request needs refresh; stuck lock in the session store after a crashed request; very short sessionRefreshObtainTimeout under load.","solutions":["Tune sessionRefreshObtainTimeout/sessionRefreshRetryPeriod to tolerate expected lock contention","Check the session store (e.g. Redis) for stale locks left by crashed requests and clear them","Retry the request after the lock holder completes; the error is transient by design","Investigate duplicate concurrent refreshes (polling clients) that create lock storms"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// before issuing a request that will trigger refresh, check remaining lock headroom\nif sessionRefreshObtainTimeout < sessionRefreshRetryPeriod {\n\treturn errors.New(\"obtain timeout shorter than retry period\")\n}","typeGuard":null,"tryCatchPattern":"session, err := getValidatedSession(req)\nif err != nil {\n\tif strings.Contains(err.Error(), \"timeout obtaining session lock\") {\n\t\t// transient: back off and retry once\n\t\ttime.Sleep(time.Second)\n\t\treturn getValidatedSession(req)\n\t}\n\treturn err\n}","preventionTips":["Size sessionRefreshObtainTimeout larger than the longest expected lock holder","Clean up stale locks in the session store (e.g. Redis TTL on lock keys)","Avoid request patterns that refresh the same session concurrently (long polling + refresh on expiry)","Monitor lock-not-obtained retries as a contention signal"],"tags":["go","sessions","locking","timeout"],"backgroundTag":"request-timeout","analyzedSha":"33c2eb92dea78204f7a18bc2dfdbccc220f39257","analyzedAt":"2026-09-06T08:51:53.077Z","contentChangedAt":"2026-09-06T08:51:53.077Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}