Skip to content

fix(goals): prevent race condition on period reset and fix UTC boundary#345

Merged
Priyanshu-byte-coder merged 1 commit into
Priyanshu-byte-coder:mainfrom
anshul23102:fix/goals-race-condition-and-utc-period-reset
May 19, 2026
Merged

fix(goals): prevent race condition on period reset and fix UTC boundary#345
Priyanshu-byte-coder merged 1 commit into
Priyanshu-byte-coder:mainfrom
anshul23102:fix/goals-race-condition-and-utc-period-reset

Conversation

@anshul23102
Copy link
Copy Markdown
Contributor

Closes #344

What was wrong

Two bugs in `src/app/api/goals/route.ts` caused goal progress to be lost at period boundaries.

Bug 1 - Race condition:
`Promise.all` fires all goal-reset writes concurrently. Two simultaneous GET requests both read the same stale `period_start`, both issue `UPDATE current=0`, and the second write zeros out any progress recorded between the two reads.

Bug 2 - Local timezone boundary:
`getPeriodStart` used local-timezone methods (`getDay`, `setDate`, `setHours`, `Date(year, month, ...)`). On servers not running in UTC the weekly and monthly reset boundaries shift by the UTC offset, triggering resets a day early or late.

What changed

  • Added `.lt("period_start", periodStart)` to the reset update so only one concurrent write wins. The losing write re-fetches the current DB row instead of returning stale data.
  • Switched `getPeriodStart` to UTC methods (`getUTCDay`, `setUTCDate`, `setUTCHours`, `Date.UTC`) so boundaries are timezone-independent.

Files changed

  • `src/app/api/goals/route.ts`

Type of change

  • Bug fix
  • Data integrity improvement

Two bugs in the GET /api/goals handler caused goal progress to be lost
incorrectly at period boundaries.

Bug 1 - Race condition on period reset:
Promise.all fires all goal-reset DB writes concurrently. When two requests
arrive simultaneously (two browser tabs, mobile + desktop), both read the
same stale period_start, both decide a reset is needed, and both issue
UPDATE current=0. Any progress written between the two reads is silently
zeroed by the second update.

Fixed by adding .lt("period_start", periodStart) to the update so only
one concurrent write wins. The losing request re-fetches the current row
instead of returning a stale or double-zeroed value.

Bug 2 - Local timezone used for period boundary calculation:
getPeriodStart used getDay()/setDate()/setHours() which all operate in
the server's local timezone. On servers not running in UTC the weekly
Monday boundary and monthly first-of-month boundary shift by the UTC
offset, resetting goals a day early or late for affected users.

Fixed by switching to getUTCDay()/setUTCDate()/setUTCHours() and
Date.UTC() so the boundary is always calculated in UTC regardless of
server timezone.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

@anshul23102 is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Priyanshu-byte-coder Priyanshu-byte-coder marked this pull request as ready for review May 19, 2026 05:14
@Priyanshu-byte-coder Priyanshu-byte-coder self-requested a review as a code owner May 19, 2026 05:14
Copy link
Copy Markdown
Owner

@Priyanshu-byte-coder Priyanshu-byte-coder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixes are correct. The optimistic lock (.lt('period_start', ...)) ensures only one concurrent reset wins — the losing request re-fetches the already-reset row instead of silently zeroing progress. The UTC boundary fix (getUTCDay/setUTCHours) prevents server-timezone drift from shifting Monday to Sunday or Tuesday.

@Priyanshu-byte-coder Priyanshu-byte-coder merged commit 9975624 into Priyanshu-byte-coder:main May 19, 2026
7 checks passed
@Priyanshu-byte-coder Priyanshu-byte-coder added gssoc:approved GSSoC: PR approved for scoring level:advanced GSSoC: Advanced difficulty (55 pts) gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix quality:exceptional GSSoC: Exceptional quality multiplier (×1.5) labels May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC: PR approved for scoring gssoc26 GSSoC 2026 contribution level:advanced GSSoC: Advanced difficulty (55 pts) quality:exceptional GSSoC: Exceptional quality multiplier (×1.5) type:bug GSSoC type bonus: bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Goal period reset has race condition and uses local timezone instead of UTC

2 participants