fix: don't hide async exceptions in logs
Fixes #4646. Using the repro on #4646, this now produces the log: ``` 11/Feb/2026:09:40:08 -0500: Warp server error: stack overflow ``` When: ``` $ curl localhost:3000/ curl: (52) Empty reply from server ```
This commit is contained in:
committed by
Steve Chavez
parent
c3500bd3af
commit
e95e815483
@@ -15,6 +15,8 @@ All notable changes to this project will be documented in this file. From versio
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't hide async exceptions in logs by @stevechavez in #4646
|
||||
|
||||
### Changed
|
||||
|
||||
- Log error when `db-schemas` config contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
|
||||
|
||||
+26
-4
@@ -9,18 +9,24 @@ Some of its functionality includes:
|
||||
- Producing HTTP Headers according to RFCs.
|
||||
- Content Negotiation
|
||||
-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
module PostgREST.App
|
||||
( postgrest
|
||||
, run
|
||||
) where
|
||||
|
||||
|
||||
import GHC.IO.Exception (IOErrorType (..))
|
||||
import System.IO.Error (ioeGetErrorType)
|
||||
|
||||
import Control.Monad.Except (liftEither)
|
||||
import Data.Either.Combinators (mapLeft, whenLeft)
|
||||
import Data.Maybe (fromJust)
|
||||
import Data.String (IsString (..))
|
||||
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
||||
import Network.Wai.Handler.Warp (defaultSettings, setHost,
|
||||
setOnException, setPort,
|
||||
setServerName)
|
||||
|
||||
import qualified Data.Text.Encoding as T
|
||||
@@ -63,7 +69,6 @@ type Handler = ExceptT Error
|
||||
|
||||
run :: AppState -> IO ()
|
||||
run appState = do
|
||||
let observer = AppState.getObserver appState
|
||||
conf@AppConfig{..} <- AppState.getConfig appState
|
||||
|
||||
AppState.schemaCacheLoader appState -- Loads the initial SchemaCache
|
||||
@@ -79,7 +84,24 @@ run appState = do
|
||||
address <- resolveSocketToAddress (AppState.getSocketREST appState)
|
||||
observer $ AppServerAddressObs address
|
||||
|
||||
Warp.runSettingsSocket (serverSettings conf) (AppState.getSocketREST appState) app
|
||||
Warp.runSettingsSocket (serverSettings conf & setOnException onWarpException) (AppState.getSocketREST appState) app
|
||||
where
|
||||
observer = AppState.getObserver appState
|
||||
|
||||
onWarpException :: Maybe Wai.Request -> SomeException -> IO ()
|
||||
onWarpException _ ex =
|
||||
when (shouldDisplayException ex) $
|
||||
observer $ WarpErrorObs $ show ex
|
||||
|
||||
-- Similar to wai defaultShouldDisplayException in
|
||||
-- https://github.com/yesodweb/wai//blob/8c3882c60f6abe043889fc20c7efd3fa9747fa4a/warp/Network/Wai/Handler/Warp/Settings.hs#L251-L258
|
||||
-- but without omitting AsyncException since it's important to log for ThreadKilled, StackOverflow and other cases.
|
||||
-- We want to reuse this to avoid flooding the logs for some transient failure cases.
|
||||
shouldDisplayException :: SomeException -> Bool
|
||||
shouldDisplayException se
|
||||
| Just (_ :: Warp.InvalidRequest) <- fromException se = False
|
||||
| Just (ioeGetErrorType -> et) <- fromException se, et == ResourceVanished || et == InvalidArgument = False
|
||||
| otherwise = True
|
||||
|
||||
serverSettings :: AppConfig -> Warp.Settings
|
||||
serverSettings AppConfig{..} =
|
||||
|
||||
@@ -65,6 +65,7 @@ data Observation
|
||||
| PoolRequestFullfilled
|
||||
| JwtCacheLookup Bool
|
||||
| JwtCacheEviction
|
||||
| WarpErrorObs Text
|
||||
|
||||
data ObsFatalError = ServerAuthError | ServerPgrstBug | ServerError42P05 | ServerError08P01
|
||||
|
||||
@@ -161,6 +162,8 @@ observationMessage = \case
|
||||
"Looked up a JWT in JWT cache"
|
||||
JwtCacheEviction ->
|
||||
"Evicted entry from JWT cache"
|
||||
WarpErrorObs txt ->
|
||||
"Warp server error: " <> txt
|
||||
where
|
||||
showMillis :: Double -> Text
|
||||
showMillis x = toS $ showFFloat (Just 1) x ""
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from postgrest import run
|
||||
|
||||
@@ -54,6 +55,25 @@ def test_openapi_in_big_schema(defaultenv):
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_stackoverflow_is_logged(defaultenv):
|
||||
"Stack overflow errors should be logged with the Warp error message"
|
||||
|
||||
env = {
|
||||
**defaultenv,
|
||||
"PGRST_DB_SCHEMAS": "apflora",
|
||||
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous",
|
||||
}
|
||||
|
||||
with run(env=env, wait_max_seconds=30, no_startup_stdout=False) as postgrest:
|
||||
with pytest.raises(requests.exceptions.ConnectionError):
|
||||
postgrest.session.get("/")
|
||||
|
||||
output = postgrest.read_stdout(nlines=10)
|
||||
output.extend(postgrest.read_stdout(nlines=10))
|
||||
|
||||
assert any("Warp server error: stack overflow" in line for line in output)
|
||||
|
||||
|
||||
# See: https://github.com/PostgREST/postgrest/issues/3329
|
||||
def test_should_not_fail_with_stack_overflow(defaultenv):
|
||||
"requesting a non-existent relationship should not fail with stack overflow due to fuzzy search of candidates"
|
||||
|
||||
Reference in New Issue
Block a user