prevent GSSAPI error between Listener and pool
Brings back the the signaling/waiting between the connection pool and the Listener. Prevents the GSSAPI error shown on https://github.com/PostgREST/postgrest/issues/3569
This commit is contained in:
committed by
Steve Chavez
parent
70a8a80491
commit
4beac10d3d
@@ -29,6 +29,7 @@ module PostgREST.AppState
|
|||||||
, getObserver
|
, getObserver
|
||||||
, isLoaded
|
, isLoaded
|
||||||
, isPending
|
, isPending
|
||||||
|
, waitForListenerCanStart
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -98,6 +99,8 @@ data AppState = AppState
|
|||||||
, stateIsListenerOn :: IORef Bool
|
, stateIsListenerOn :: IORef Bool
|
||||||
-- | starts the connection worker with a debounce
|
-- | starts the connection worker with a debounce
|
||||||
, debouncedConnectionWorker :: IO ()
|
, debouncedConnectionWorker :: IO ()
|
||||||
|
-- | Binary semaphore used to sync the listener with the connectionWorker.
|
||||||
|
, stateListenerCanStart :: MVar ()
|
||||||
-- | Config that can change at runtime
|
-- | Config that can change at runtime
|
||||||
, stateConf :: IORef AppConfig
|
, stateConf :: IORef AppConfig
|
||||||
-- | Time used for verifying JWT expiration
|
-- | Time used for verifying JWT expiration
|
||||||
@@ -156,6 +159,7 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
|
|||||||
<*> newIORef ConnPending
|
<*> newIORef ConnPending
|
||||||
<*> newIORef False
|
<*> newIORef False
|
||||||
<*> pure (pure ())
|
<*> pure (pure ())
|
||||||
|
<*> newEmptyMVar
|
||||||
<*> newIORef conf
|
<*> newIORef conf
|
||||||
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
<*> myThreadId
|
<*> myThreadId
|
||||||
@@ -317,6 +321,26 @@ getNextListenerDelay = readIORef . stateNextListenerDelay
|
|||||||
putNextListenerDelay :: AppState -> Int -> IO ()
|
putNextListenerDelay :: AppState -> Int -> IO ()
|
||||||
putNextListenerDelay = atomicWriteIORef . stateNextListenerDelay
|
putNextListenerDelay = atomicWriteIORef . stateNextListenerDelay
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
-------------------------------------------IMPORTANT----------------------------------
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
-- Both of these function ensure there's no parallel connection attempts between the listener and the connection pool.
|
||||||
|
-- Doing that raised an error with GSSAPI as discussed on https://github.com/PostgREST/postgrest/issues/3569.
|
||||||
|
-- Until the root cause is found and solved, we need to prevent parallel connection attempts.
|
||||||
|
|
||||||
|
-- tryPutMVar doesn't lock the thread. It should always succeed since
|
||||||
|
-- the connectionWorker is the only mvar producer.
|
||||||
|
setListenerCanStart :: AppState -> IO ()
|
||||||
|
setListenerCanStart appState = void $ tryPutMVar (stateListenerCanStart appState) ()
|
||||||
|
|
||||||
|
-- | As this IO action uses `takeMVar` internally, it will only return once
|
||||||
|
-- `stateListenerCanStart` has been set using `setListenerCanStart`.
|
||||||
|
waitForListenerCanStart :: AppState -> IO ()
|
||||||
|
waitForListenerCanStart = takeMVar . stateListenerCanStart
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
getConfig :: AppState -> IO AppConfig
|
getConfig :: AppState -> IO AppConfig
|
||||||
getConfig = readIORef . stateConf
|
getConfig = readIORef . stateConf
|
||||||
|
|
||||||
@@ -436,6 +460,9 @@ internalConnectionWorker appState@AppState{stateObserver=observer, stateMainThre
|
|||||||
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
||||||
killThread mainThreadId
|
killThread mainThreadId
|
||||||
observer (DBConnectedObs $ pgvFullName actualPgVersion)
|
observer (DBConnectedObs $ pgvFullName actualPgVersion)
|
||||||
|
-- Wake up the Listener
|
||||||
|
when configDbChannelEnabled $
|
||||||
|
setListenerCanStart appState
|
||||||
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
||||||
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
||||||
when configDbConfig $ reReadConfig False appState
|
when configDbConfig $ reReadConfig False appState
|
||||||
|
|||||||
@@ -27,10 +27,16 @@ runListener appState = do
|
|||||||
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
||||||
retryingListen :: AppState -> IO ()
|
retryingListen :: AppState -> IO ()
|
||||||
retryingListen appState = do
|
retryingListen appState = do
|
||||||
|
AppState.waitForListenerCanStart appState
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
let
|
let
|
||||||
dbChannel = toS configDbChannel
|
dbChannel = toS configDbChannel
|
||||||
handleFinally err = do
|
handleFinally err = do
|
||||||
|
-- assume we lost notifications, call the connection worker which will also reload the schema cache
|
||||||
|
-- and will setListenerCanStart again
|
||||||
|
-- TODO: When the connection error is only on the Listener, it's wasteful to call the connectionWorker everytime.
|
||||||
|
AppState.connectionWorker appState
|
||||||
|
|
||||||
AppState.putIsListenerOn appState False
|
AppState.putIsListenerOn appState False
|
||||||
observer $ DBListenFail dbChannel (Right err)
|
observer $ DBListenFail dbChannel (Right err)
|
||||||
unless configDbPoolAutomaticRecovery $
|
unless configDbPoolAutomaticRecovery $
|
||||||
@@ -54,8 +60,6 @@ retryingListen appState = do
|
|||||||
|
|
||||||
delay <- AppState.getNextListenerDelay appState
|
delay <- AppState.getNextListenerDelay appState
|
||||||
when (delay > 1) $ do -- if we did a retry
|
when (delay > 1) $ do -- if we did a retry
|
||||||
-- assume we lost notifications, call the connection worker which will also reload the schema cache
|
|
||||||
AppState.connectionWorker appState
|
|
||||||
-- reset the delay
|
-- reset the delay
|
||||||
AppState.putNextListenerDelay appState 1
|
AppState.putNextListenerDelay appState 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user