-
Notifications
You must be signed in to change notification settings - Fork 0
temp fix to race condition #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,14 +39,14 @@ export async function readUserById(id: string): Promise<User | undefined> { | |
| export async function createUser(userInsert: UserInsert): Promise<User> { | ||
| // this is done in transaction to avoid race condition when creating user, for conflicts on authId | ||
| return await db.transaction(async (trx) => { | ||
| // First try to find the user by authId | ||
| // Also check by email to handle edge cases | ||
| const [existingUser] = await trx | ||
| .select() | ||
| .from(user) | ||
| .where( | ||
| and( | ||
| eq(user.authId, userInsert.authId), | ||
| eq(user.email, userInsert.email), | ||
| eq(user.authId, userInsert.authId), | ||
| ), | ||
| ) | ||
| .execute(); | ||
|
|
@@ -59,13 +59,13 @@ export async function createUser(userInsert: UserInsert): Promise<User> { | |
| const [insertedUser] = await trx | ||
| .insert(user) | ||
| .values(userInsert) | ||
| .returning() | ||
| .onConflictDoUpdate({ | ||
| target: [user.email], | ||
| target: [user.authId], | ||
| set: { | ||
| authId: userInsert.authId, | ||
| email: userInsert.email, | ||
|
||
| }, | ||
| }) | ||
| .returning() | ||
| .execute(); | ||
|
|
||
| return insertedUser; | ||
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -77,15 +77,50 @@ export const getServerUser = async (): Promise<User> => { | |||||||||||||||||||||||||||||||||||
| throw new Error(" New user has no email address"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const createdUser = await createUser({ | ||||||||||||||||||||||||||||||||||||
| email, | ||||||||||||||||||||||||||||||||||||
| authId, | ||||||||||||||||||||||||||||||||||||
| firstName: clerkUser.firstName || "", | ||||||||||||||||||||||||||||||||||||
| lastName: clerkUser.lastName || "", | ||||||||||||||||||||||||||||||||||||
| picture: clerkUser.imageUrl, | ||||||||||||||||||||||||||||||||||||
| privacyPolicyAcceptedAt: new Date(), | ||||||||||||||||||||||||||||||||||||
| termsOfUseAcceptedAt: new Date(), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| // Retry mechanism for race conditions when creating new users | ||||||||||||||||||||||||||||||||||||
| let createdUser: User | undefined; | ||||||||||||||||||||||||||||||||||||
| let retryCount = 0; | ||||||||||||||||||||||||||||||||||||
| const maxRetries = 3; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| while (retryCount < maxRetries) { | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| createdUser = await createUser({ | ||||||||||||||||||||||||||||||||||||
| email, | ||||||||||||||||||||||||||||||||||||
| authId, | ||||||||||||||||||||||||||||||||||||
| firstName: clerkUser.firstName || "", | ||||||||||||||||||||||||||||||||||||
| lastName: clerkUser.lastName || "", | ||||||||||||||||||||||||||||||||||||
| picture: clerkUser.imageUrl, | ||||||||||||||||||||||||||||||||||||
| privacyPolicyAcceptedAt: new Date(), | ||||||||||||||||||||||||||||||||||||
| termsOfUseAcceptedAt: new Date(), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| break; // Success, exit the retry loop | ||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | |
| } catch (error: unknown) { |
Copilot
AI
Aug 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using 'any' type for error handling defeats TypeScript's type safety. Consider using 'unknown' or a more specific error type to maintain type safety.
| } catch (error: any) { | |
| } catch (error: unknown) { |
Copilot
AI
Aug 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry logic doesn't differentiate between error types. The comment mentions 'duplicate key error' but the code retries for any error. This could lead to unnecessary retries for non-recoverable errors like network issues or permission errors.
| if (retryCount < maxRetries) { | |
| // Only retry on duplicate key error | |
| const isDuplicateKeyError = | |
| // PostgreSQL unique violation | |
| (error && error.code === '23505') || | |
| // MongoDB duplicate key | |
| (error && error.code === 11000) || | |
| // MySQL duplicate entry | |
| (error && error.errno === 1062) || | |
| // Fallback: error message contains 'duplicate' or 'unique' | |
| (typeof error?.message === 'string' && | |
| /duplicate|unique/i.test(error.message)); | |
| retryCount++; | |
| // If it's a duplicate key error and we haven't exceeded max retries, try again | |
| if (isDuplicateKeyError && retryCount < maxRetries) { |
Copilot
AI
Aug 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry logic doesn't check for specific error types. This could cause unnecessary retries for errors that aren't related to race conditions (e.g., network errors, validation errors). Consider checking for specific database constraint violation errors before retrying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment 'Also check by email to handle edge cases' is vague. It should explain what specific edge cases this addresses and why checking both email and authId is necessary.