fix(auth): add configurable lock acquisition timeout to prevent deadlocks - #1962
Conversation
…ocks Replace infinite lock timeout (-1) with configurable lockAcquireTimeout option that defaults to 60 seconds. This prevents production deadlocks caused by indefinite lock waiting when concurrent browser tabs or network issues interfere with lock acquisition. - Add lockAcquireTimeout option to GoTrueClientOptions (default: 60000ms) - Replace all infinite timeout (-1) lock acquisitions with configurable timeout - Users can customize the timeout or set to negative for infinite wait (not recommended) Fixes supabase#1594
mandarini
left a comment
There was a problem hiding this comment.
Thanks for this PR! This addresses a real pain point for users experiencing deadlocks (especially on Android Chrome). The implementation is clean and follows existing patterns.
Requested Changes
1. Enhanced JSDoc Documentation
The current JSDoc could be more explicit about error handling and edge cases:
/**
* The maximum time in milliseconds to wait for a lock to be acquired.
* If the lock cannot be acquired within this time, a `LockAcquireTimeoutError` is
thrown.
* You can catch this by checking `error.isAcquireTimeout === true`.
*
* - **Positive value**: Wait up to this many milliseconds before timing out
* - **Zero (0)**: Fail immediately if the lock is unavailable
* - **Negative value**: Wait indefinitely (not recommended - can cause deadlocks)
*
* Defaults to 60000 (1 minute).
*/
lockAcquireTimeout?: number2. Add Tests for Timeout Behavior
Please add tests to verify the timeout functionality works as expected. For example:
it('should throw LockAcquireTimeoutError when lock acquisition times out', async
() => {
// Test that a short timeout actually throws the expected error
})
it('should use custom lockAcquireTimeout when provided', async () => {
// Test that the option is respected
})3. Document User Recovery
When a timeout occurs, what should users do? Consider adding guidance. Something like: "If you encounter frequent timeouts, check for held locks via navigator.locks.query() and consider clearing browser data or restarting the browser."
Some questions to think about
-
Default timeout value: Is 60 seconds the right default? That's a long time for a user to wait on a white screen. Would 30s or even 10s be more appropriate? Curious about the reasoning.
-
Error handling guidance: When this timeout error is thrown, most apps probably won't handle it gracefully. Should we consider:
- Logging a helpful console message with recovery suggestions?
- Adding a retry mechanism with exponential backoff?
Overall this is a solid fix for a frustrating issue. Just needs a bit more documentation and test coverage. Thanks for tackling this and for contributing to Supabase! 💚
- Change default timeout from 60s to 10s for faster feedback - Add comprehensive JSDoc with error handling example - Add timeout behavior tests in locks.test.ts - Add configuration tests in GoTrueClient.test.ts - Add console.warn with context when timeout occurs
|
@mandarini 1. Enhanced JSDoc DocumentationAdded comprehensive JSDoc to /**
* The maximum time in milliseconds to wait for acquiring a cross-tab synchronization lock.
*
* When multiple browser tabs or windows use the auth client simultaneously, they coordinate
* via the Web Locks API to prevent race conditions during session refresh and other operations.
* This timeout controls how long to wait for the lock before failing.
*
* If the lock cannot be acquired within this time, a `LockAcquireTimeoutError` is thrown.
* You can catch this by checking `error.isAcquireTimeout === true`.
*
* - **Positive value**: Wait up to this many milliseconds before timing out
* - **Zero (0)**: Fail immediately if the lock is unavailable
* - **Negative value**: Wait indefinitely (not recommended - can cause deadlocks)
*
* @default 10000
*
* @example
* try {
* await client.auth.getSession()
* } catch (error) {
* if (error.isAcquireTimeout) {
* // Lock held by another tab/instance, or a previous operation is stuck.
* // Consider: closing other tabs, increasing timeout, or restarting the browser.
* console.error('Could not acquire lock within timeout period.')
* }
* }
*/2. Add Tests for Timeout BehaviorAdded tests in two locations: locks.test.ts (actual timeout behavior):
GoTrueClient.test.ts (configuration integration):
3. Document User RecoveryAdded recovery guidance in:
4. Answers to QuestionsQ: Is 60 seconds too long as default? I initially chose 60 seconds to account for edge cases like slow networks or heavily loaded devices where lock operations might take longer than usual. However, your point about user experience is valid — in actual deadlock situations, 60 seconds is far too long for users to wait on a white screen. Since lock operations typically complete in milliseconds, a shorter timeout still provides adequate buffer for edge cases while giving users faster feedback. Changed to 10 seconds (10000ms) as a balance between these considerations. Q: Should we add console logging? Added Q: Should we add a retry mechanism? For now, users can implement custom retry logic by catching I believe automatic retry should be considered in a follow-up PR because:
If there's interest, I'm happy to open a follow-up issue to discuss retry mechanism design. |
|
Thanks for this :) any idea what release this'll be in? Right now we're using the noop workaround |
|
My React Native app is exploding with timeout errors now. What is the best practice for RN? should i hunt all that is causing deadlocks or the previous behavior good enough for apps? |
|
@tadeumaia what version are you using? Do you think this PR caused your issue? |
|
@mandarini Yeah, when I updated to latest I started getting a bunch of warning for @supabase/gotrue-js: Lock "lock:sb-tvjkvpkckfxgxdhyggiy-auth-token" acquisition timed out after 10000ms. This may be caused by another operation holding the lock. Consider increasing lockAcquireTimeout or checking for stuck operations. I'm on "@supabase/supabase-js": "^2.90.1" was on "^2.87.1", before. Just no idea what the best practice and if the lock is necessary for RN or only for browser (multi tab stuff) so I should disable it or hunt for places where I might be calling locks too much. |
|
I've been trying out the new canary version in prod and it seems to have mainly fixed the main issue of deadlocks, but I get the feeling like theres something odd going on internally for it to be causing it. I still seem to be getting the occasional total lockout still, but its performing better than before (say 1 per day vs ~5). Could be a fault on my end, though it doesn't feel seemless. Total vibes tell me its multiple open sessions at the same time, but I'm assuming getSession is locally JWT checking so that shouldn't matter Though I'm just an end user and probs can't reproduce on a public repo 😔 |
Summary
lockAcquireTimeoutoption toGoTrueClientOptions(default: 60000ms)Fixes #1594
Test plan
npx nx build auth-js)