Skip to content

Commit 9934a41

Browse files
committed
refactor: provide AppState infrastructure to wait for schema cache load
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 d569467 commit 9934a41

1 file changed

Lines changed: 27 additions & 18 deletions

File tree

src/PostgREST/AppState.hs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ data AppState = AppState
8484
-- | Schema cache
8585
, stateSchemaCache :: IORef (Maybe SchemaCache)
8686
-- | The schema cache status
87-
, stateSCacheStatus :: IORef SchemaCacheStatus
87+
, stateSCacheStatus :: SchemaCacheStatus
8888
-- | State of the LISTEN channel
8989
, stateIsListenerOn :: IORef Bool
9090
-- | starts the connection worker with a debounce
@@ -111,11 +111,11 @@ data AppState = AppState
111111
, stateMetrics :: Metrics.MetricsState
112112
}
113113

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

120120
type AppSockets = (NS.Socket, Maybe NS.Socket)
121121

@@ -138,7 +138,7 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
138138
appState <- AppState pool
139139
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
140140
<*> newIORef Nothing
141-
<*> newIORef SCPending
141+
<*> newSchemaCacheStatus
142142
<*> newIORef False
143143
<*> pure (pure ())
144144
<*> newIORef conf
@@ -335,18 +335,15 @@ putIsListenerOn = atomicWriteIORef . stateIsListenerOn
335335

336336
isLoaded :: AppState -> IO Bool
337337
isLoaded x = do
338-
scacheStatus <- readIORef $ stateSCacheStatus x
338+
scacheLoaded <- isSchemaCacheLoaded x
339339
connEstablished <- isConnEstablished x
340-
return $ scacheStatus == SCLoaded && connEstablished
340+
return $ scacheLoaded && connEstablished
341341

342342
isPending :: AppState -> IO Bool
343343
isPending x = do
344-
scacheStatus <- readIORef $ stateSCacheStatus x
344+
scacheLoaded <- isSchemaCacheLoaded x
345345
connEstablished <- isConnEstablished x
346-
return $ scacheStatus == SCPending || not connEstablished
347-
348-
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
349-
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
346+
return $ not scacheLoaded || not connEstablished
350347

351348
getObserver :: AppState -> ObservationHandler
352349
getObserver = stateObserver
@@ -405,19 +402,19 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
405402
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
406403
case result of
407404
Left e -> do
408-
putSCacheStatus appState SCPending
405+
markSchemaCachePending appState
409406
putSchemaCache appState Nothing
410407
observer $ SchemaCacheErrorObs configDbSchemas configDbExtraSearchPath e
411408
return Nothing
412409

413410
Right sCache -> do
414411
-- 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
412+
-- 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.
413+
markSchemaCachePending appState
417414
putSchemaCache appState $ Just sCache
418415
observer $ SchemaCacheQueriedObs resultTime
419416
observer . uncurry SchemaCacheLoadedObs =<< timeItT (evaluate $ showSummary sCache)
420-
putSCacheStatus appState SCLoaded
417+
markSchemaCacheLoaded appState
421418
return $ Just sCache
422419

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

434431
oneSecondInUs = 1000000 -- one second in microseconds
435432

433+
newSchemaCacheStatus :: IO SchemaCacheStatus
434+
newSchemaCacheStatus = SchemaCacheStatus <$> newEmptyMVar
435+
436+
markSchemaCachePending :: AppState -> IO ()
437+
markSchemaCachePending = void . tryTakeMVar . getSCStatusMVar . stateSCacheStatus
438+
439+
markSchemaCacheLoaded :: AppState -> IO ()
440+
markSchemaCacheLoaded = void . (`tryPutMVar` ()) . getSCStatusMVar . stateSCacheStatus
441+
442+
isSchemaCacheLoaded :: AppState -> IO Bool
443+
isSchemaCacheLoaded = fmap not . isEmptyMVar . getSCStatusMVar . stateSCacheStatus
444+
436445
-- | Reads the in-db config and reads the config file again
437446
-- | 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.
438447
readInDbConfig :: Bool -> AppState -> IO ()

0 commit comments

Comments
 (0)