Resolving Race Conditions in the JADA Booking Calendar and Maintenance Hub Deployment Pipeline
This post covers two critical infrastructure fixes we've completed and deployed across our event management and maintenance systems: eliminating a race condition in the JADA booking calendar availability fetch, and preparing the Maintenance Hub for production promotion from staging.
The JADA Booking Calendar Race Condition
We discovered a subtle but consistently reproducible race condition in jadaOpenBook() where the booking calendar was returning availability data before the underlying Google Calendar fetch had completed. This manifested as stale or missing availability slots when users queried the booking interface, particularly under load or on slower connections.
Root Cause Analysis
The original implementation in tools/jada_booking.gs was structured as follows:
function jadaOpenBook() {
const availability = fetchAvailability();
return buildCalendarResponse(availability);
}
function fetchAvailability() {
// Non-blocking async call to Google Calendar API
return getCalendarEvents(JADA_CALENDAR_ID);
}
The issue: getCalendarEvents() was being invoked but not awaited. In Google Apps Script's execution model, even though we're not using native Promises, the Calendar API call was queued but the function returned before the data arrived, causing the response builder to work with undefined or partial data.
The Fix: Synchronous Blocking Pattern
We refactored jadaOpenBook() to explicitly block on the availability fetch:
function jadaOpenBook() {
// Block until calendar fetch completes
const calendarData = CalendarApp.getCalendarById(JADA_CALENDAR_ID);
const now = new Date();
const endDate = new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000); // 90 days ahead
const events = calendarData.getEvents(now, endDate);
const availability = parseAvailabilityFromEvents(events);
// Only return after data is populated
return {
status: 'ready',
slots: availability,
lastRefresh: new Date().toISOString()
};
}
Why this approach: Google Apps Script's CalendarApp.getEvents() is synchronous and blocks execution until the Calendar API returns data. By using the native Apps Script Calendar service directly instead of wrapping it in an async pattern we weren't properly handling, we guarantee that availability is fully populated before the response is constructed.
Verification and Testing
We added a smoke test in tests/jada_booking_test.gs that verifies the blocking behavior:
function testJadaOpenBookReturnsValidSlots() {
const response = jadaOpenBook();
assertEquals(response.status, 'ready', 'Status must be ready before returning');
assertTrue(Array.isArray(response.slots), 'Slots must be array');
assertTrue(response.slots.length > 0, 'Should return at least one availability slot');
}
This test runs as part of our pre-deployment validation pipeline and ensures the blocking semantics are maintained.
Maintenance Hub Production Deployment
The Maintenance Hub—a comprehensive checklist and operations management dashboard—has been staged at queenofsandiego.com/_staging/maintenance/ and is ready for promotion to production at queenofsandiego.com/maintenance/.
Current Staging Setup
- Document root:
s3://qdn-web-content/staging/maintenance/ - CloudFront distribution:
dapj8vk4n2.cloudfront.net(staging alias) - Route53 CNAME:
_staging.maintenance→ CloudFront staging distribution - Core files:
index.html— main dashboardcss/maintenance.css— styling (includes print-optimized checklist layouts)js/maintenance-ui.js— state management and form handlingdata/maintenance-schedule.json— wake/sleep procedures, daily/weekly/2-day checklists
Production Promotion Plan
To promote Maintenance Hub to production, we need to:
- Copy S3 objects: Clone the staging bucket contents to
s3://qdn-web-content/production/maintenance/aws s3 sync s3://qdn-web-content/staging/maintenance/ \ s3://qdn-web-content/production/maintenance/ \ --delete \ --cache-control "public, max-age=3600" - Update CloudFront: Add origin
qdn-web-content/production/maintenance/to the production CloudFront distribution (d2x4nk7p9q8r.cloudfront.net) - Update Route53: Create CNAME record
maintenance.queenofsandiego.com→ production CloudFront distribution - Invalidate cache: Purge CloudFront paths
/maintenance/*on production distribution to ensure fresh contentaws cloudfront create-invalidation \ --distribution-id d2x4nk7p9q8r \ --paths "/maintenance/*" - Verify: Test both
queenofsandiego.com/maintenance/andqueenofsandiego.com/_staging/maintenance/to confirm routing
Events.html Concurrent Promotion
We're also promoting events.html from staging to production simultaneously. This file is staged at queenofsandiego.com/_staging/events.html and pulls event data from Google Calendar via the published iCal feed.
Key point: Both promotions depend on the same CloudFront invalidation cycle. We'll batch them in a single invalidation request to minimize cache miss latency:
aws cloudfront create-invalidation \
--distribution-id d2x4nk7p9q8r \
--paths "/maintenance/*" "/events.html"
Google Apps Script One-Time Setups
Two critical GAS configurations need to be run once on the server:
maintenancePersistenceSetup()— Initializes Google Sheets backing store for maintenance checklists. Creates a new Sheet named "MaintenanceLog" with columns for timestamp, task, status, and notes. This enables state persistence across sessions.jadaCalendarScanSetup()— Scans the JADA event calendar and creates index entries in a helper Sheet for rapid availability queries. Reduces per-request latency by pre-computing availability windows.
We're explicitly skipping warmLe