Skip to content

Commit

Permalink
feat: Add Retry-After hint (#1916)
Browse files Browse the repository at this point in the history
Add Retry-After header when response status is 503.
Its value is the connection worker delay(seconds) when
it's recovering.

This closes issue #1817.
  • Loading branch information
gautam1168 authored Aug 18, 2021
1 parent 5874482 commit d9f1ae6
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added

- #1783, Include partitioned tables into the schema cache. Allows embedding, UPSERT, INSERT with Location response, OPTIONS request and OpenAPI support for partitioned tables - @laurenceisla

- #1878, Add Retry-After hint header when in recovery mode - @gautam1168

### Fixed

## [8.0.0] - 2021-07-25
Expand Down
14 changes: 10 additions & 4 deletions src/PostgREST/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,19 @@ postgrest logLev appState connWorker =
runExceptT $ postgrestResponse conf maybeDbStructure jsonDbS pgVer (AppState.getPool appState) time req

response <- either Error.errorResponseFor identity <$> eitherResponse

-- Launch the connWorker when the connection is down. The postgrest
-- function can respond successfully (with a stale schema cache) before
-- the connWorker is done.
when (Wai.responseStatus response == HTTP.status503) connWorker

respond response
let isPGAway = Wai.responseStatus response == HTTP.status503
when isPGAway connWorker
resp <- addRetryHint isPGAway appState response
respond resp

addRetryHint :: Bool -> AppState -> Wai.Response -> IO Wai.Response
addRetryHint shouldAdd appState response = do
delay <- AppState.getRetryNextIn appState
let h = ("Retry-After", BS8.pack $ show delay)
return $ Wai.mapResponseHeaders (\hs -> if shouldAdd then h:hs else hs) response

postgrestResponse
:: AppConfig
Expand Down
11 changes: 11 additions & 0 deletions src/PostgREST/AppState.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module PostgREST.AppState
, getPgVersion
, getPool
, getTime
, getRetryNextIn
, init
, initWithPool
, logWithZTime
Expand All @@ -18,6 +19,7 @@ module PostgREST.AppState
, putIsWorkerOn
, putJsonDbS
, putPgVersion
, putRetryNextIn
, releasePool
, signalListener
, waitListener
Expand Down Expand Up @@ -60,6 +62,8 @@ data AppState = AppState
, stateGetZTime :: IO ZonedTime
-- | Used for killing the main thread in case a subthread fails
, stateMainThreadId :: ThreadId
-- | Keeps track of when the next retry for connecting to database is scheduled
, stateRetryNextIn :: IORef Int
}

init :: AppConfig -> IO AppState
Expand All @@ -79,6 +83,7 @@ initWithPool newPool conf =
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getZonedTime }
<*> myThreadId
<*> newIORef 0

initPool :: AppConfig -> IO P.Pool
initPool AppConfig{..} =
Expand Down Expand Up @@ -115,6 +120,12 @@ getIsWorkerOn = readIORef . stateIsWorkerOn
putIsWorkerOn :: AppState -> Bool -> IO ()
putIsWorkerOn = atomicWriteIORef . stateIsWorkerOn

getRetryNextIn :: AppState -> IO Int
getRetryNextIn = readIORef . stateRetryNextIn

putRetryNextIn :: AppState -> Int -> IO ()
putRetryNextIn = atomicWriteIORef . stateRetryNextIn

getConfig :: AppState -> IO AppConfig
getConfig = readIORef . stateConf

Expand Down
1 change: 1 addition & 0 deletions src/PostgREST/Workers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ connectionStatus appState =
"Attempting to reconnect to the database in "
<> (show delay::Text)
<> " seconds..."
when itShould $ AppState.putRetryNextIn appState delay
return itShould

-- | Load the DbStructure by using a connection from the pool.
Expand Down

0 comments on commit d9f1ae6

Please sign in to comment.