Skip to content

Commit 0259ffb

Browse files
committed
refactor: provide AppState.waitForSchemaCacheLoaded function
This commit replaces ioRef based implementation of schema cache status tracking to MVar based, so that it is possible to wait for schema cache loading. Waiting for schema cache loading is necessary to implement zero-downtime upgrades with SO_REUSEPORT, where listening on a socket must wait for schema cache loading.
1 parent f657ac4 commit 0259ffb

1 file changed

Lines changed: 31 additions & 18 deletions

File tree

src/PostgREST/AppState.hs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module PostgREST.AppState
2525
, usePool
2626
, readInDbConfig
2727
, schemaCacheLoader
28+
, waitForSchemaCacheLoaded
2829
, getObserver
2930
, isLoaded
3031
, isPending
@@ -84,7 +85,7 @@ data AppState = AppState
8485
-- | Schema cache
8586
, stateSchemaCache :: IORef (Maybe SchemaCache)
8687
-- | The schema cache status
87-
, stateSCacheStatus :: IORef SchemaCacheStatus
88+
, stateSCacheStatus :: SchemaCacheStatus
8889
-- | State of the LISTEN channel
8990
, stateIsListenerOn :: IORef Bool
9091
-- | starts the connection worker with a debounce
@@ -111,11 +112,11 @@ data AppState = AppState
111112
, stateMetrics :: Metrics.MetricsState
112113
}
113114

114-
-- | Schema cache status
115-
data SchemaCacheStatus
116-
= SCLoaded
117-
| SCPending
118-
deriving Eq
115+
-- | Schema cache status.
116+
-- Empty means pending and full means loaded.
117+
newtype SchemaCacheStatus = SchemaCacheStatus
118+
{ getSCStatusMVar :: MVar ()
119+
}
119120

120121
type AppSockets = (NS.Socket, Maybe NS.Socket)
121122

@@ -138,7 +139,7 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
138139
appState <- AppState pool
139140
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
140141
<*> newIORef Nothing
141-
<*> newIORef SCPending
142+
<*> newSchemaCacheStatus
142143
<*> newIORef False
143144
<*> pure (pure ())
144145
<*> newIORef conf
@@ -292,6 +293,9 @@ putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
292293
schemaCacheLoader :: AppState -> IO ()
293294
schemaCacheLoader = debouncedSCacheLoader
294295

296+
waitForSchemaCacheLoaded :: AppState -> IO ()
297+
waitForSchemaCacheLoaded = void . readMVar . getSCStatusMVar . stateSCacheStatus
298+
295299
getNextDelay :: AppState -> IO Int
296300
getNextDelay = readIORef . stateNextDelay
297301

@@ -335,18 +339,15 @@ putIsListenerOn = atomicWriteIORef . stateIsListenerOn
335339

336340
isLoaded :: AppState -> IO Bool
337341
isLoaded x = do
338-
scacheStatus <- readIORef $ stateSCacheStatus x
342+
scacheLoaded <- isSchemaCacheLoaded x
339343
connEstablished <- isConnEstablished x
340-
return $ scacheStatus == SCLoaded && connEstablished
344+
return $ scacheLoaded && connEstablished
341345

342346
isPending :: AppState -> IO Bool
343347
isPending x = do
344-
scacheStatus <- readIORef $ stateSCacheStatus x
348+
scacheLoaded <- isSchemaCacheLoaded x
345349
connEstablished <- isConnEstablished x
346-
return $ scacheStatus == SCPending || not connEstablished
347-
348-
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
349-
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
350+
return $ not scacheLoaded || not connEstablished
350351

351352
getObserver :: AppState -> ObservationHandler
352353
getObserver = stateObserver
@@ -405,19 +406,19 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
405406
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
406407
case result of
407408
Left e -> do
408-
putSCacheStatus appState SCPending
409+
markSchemaCachePending appState
409410
putSchemaCache appState Nothing
410411
observer $ SchemaCacheErrorObs configDbSchemas configDbExtraSearchPath e
411412
return Nothing
412413

413414
Right sCache -> do
414415
-- IMPORTANT: While the pending schema cache state starts from running the above querySchemaCache, only at this stage we block API requests due to the usage of an
415-
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
416-
putSCacheStatus appState SCPending
416+
-- IORef on putSchemaCache. This is why schema cache status is marked as pending here to signal the Admin server (using isPending) that we're on a recovery state.
417+
markSchemaCachePending appState
417418
putSchemaCache appState $ Just sCache
418419
observer $ SchemaCacheQueriedObs resultTime
419420
observer . uncurry SchemaCacheLoadedObs =<< timeItT (evaluate $ showSummary sCache)
420-
putSCacheStatus appState SCLoaded
421+
markSchemaCacheLoaded appState
421422
return $ Just sCache
422423

423424
shouldRetry :: RetryStatus -> (Maybe PgVersion, Maybe SchemaCache) -> IO Bool
@@ -433,6 +434,18 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
433434

434435
oneSecondInUs = 1000000 -- one second in microseconds
435436

437+
newSchemaCacheStatus :: IO SchemaCacheStatus
438+
newSchemaCacheStatus = SchemaCacheStatus <$> newEmptyMVar
439+
440+
markSchemaCachePending :: AppState -> IO ()
441+
markSchemaCachePending = void . tryTakeMVar . getSCStatusMVar . stateSCacheStatus
442+
443+
markSchemaCacheLoaded :: AppState -> IO ()
444+
markSchemaCacheLoaded = void . (`tryPutMVar` ()) . getSCStatusMVar . stateSCacheStatus
445+
446+
isSchemaCacheLoaded :: AppState -> IO Bool
447+
isSchemaCacheLoaded = fmap not . isEmptyMVar . getSCStatusMVar . stateSCacheStatus
448+
436449
-- | Reads the in-db config and reads the config file again
437450
-- | We don't retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue.
438451
readInDbConfig :: Bool -> AppState -> IO ()

0 commit comments

Comments
 (0)