packages feed

persistent-postgresql-2.14.3.0: conn-killed/Main.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}

-- | This executable is a test of the issue raised in #1199.
module Main where

import Prelude hiding (show)
import qualified Prelude

import qualified Control.Monad as Monad
import Control.Monad.IO.Class
import Control.Monad.Logger
import qualified Control.Monad.Logger as Logger
import Control.Monad.Trans
import Control.Monad.Trans.Reader
import qualified Data.ByteString as BS
import Data.Coerce
import qualified Data.Pool as Pool
import qualified Data.Text as Text
import Data.Time
import qualified Database.Persist as Persist
import qualified Database.Persist.Postgresql as Persist
import qualified Database.Persist.Sql as Persist
import UnliftIO
import qualified UnliftIO.Concurrent as Concurrent
import qualified UnliftIO.Exception as Exception

newtype LogPrefixT m a = LogPrefixT {runLogPrefixT :: ReaderT LogStr m a}
    deriving newtype
        (Functor, Applicative, Monad, MonadIO, MonadTrans)

instance (MonadLogger m) => MonadLogger (LogPrefixT m) where
    monadLoggerLog loc src lvl msg = LogPrefixT $ ReaderT $ \prefix ->
        monadLoggerLog loc src lvl (toLogStr prefix <> toLogStr msg)

deriving newtype instance
    (forall a b. (Coercible a b) => Coercible (m a) (m b), MonadUnliftIO m)
    => MonadUnliftIO (LogPrefixT m)

prefixLogs :: Text.Text -> LogPrefixT m a -> m a
prefixLogs prefix =
    flip runReaderT (toLogStr $! mconcat ["[", prefix, "] "]) . runLogPrefixT

infixr 5 `prefixLogs`
show :: (Show a) => a -> Text.Text
show = Text.pack . Prelude.show

main :: IO ()
main =
    runStdoutLoggingT $
        Concurrent.myThreadId >>= \tid -> prefixLogs (show tid) $ do
            -- I started a postgres server with:
            -- docker run --rm --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres
            pool <-
                Logger.runNoLoggingT $
                    Persist.createPostgresqlPool
                        "postgresql://postgres:secret@localhost:5433/postgres"
                        1

            logInfoN "creating table..."
            Monad.void $ liftIO $ createTableFoo pool

            liftIO getCurrentTime >>= \now ->
                simulateFailedLongRunningPostgresCall pool

            -- logInfoN "destroying resources"
            -- liftIO $ Pool.destroyAllResources pool

            logInfoN "pg_sleep"
            result :: Either Exception.SomeException [Persist.Single (Maybe String)] <-
                Exception.try . (liftIO . (flip Persist.runSqlPersistMPool) pool) $ do
                    Persist.rawSql @(Persist.Single (Maybe String)) "select pg_sleep(2)" []

            -- when we try the above we get back:
            -- 'result: Left libpq: failed (another command is already in progress'
            -- this is because the connection went back into the pool before it was ready
            -- or perhaps it should have been destroyed and a new connection created and put into the pool?
            logInfoN $ "result: " <> show result

createTableFoo :: Pool.Pool Persist.SqlBackend -> IO ()
createTableFoo pool = (flip Persist.runSqlPersistMPool) pool $ do
    Persist.rawExecute "CREATE table if not exists foo(id int);" []

simulateFailedLongRunningPostgresCall
    :: ( MonadLogger m
       , MonadUnliftIO m
       , forall a b. (Coercible a b) => Coercible (m a) (m b)
       )
    => Pool.Pool Persist.SqlBackend -> m ()
simulateFailedLongRunningPostgresCall pool = do
    threadId <-
        Concurrent.forkIO $
            ( do
                me <- Concurrent.myThreadId
                prefixLogs (show me) $ do
                    let
                        numThings :: Int = 100000000
                    logInfoN $ "start inserting " <> show numThings <> " things"

                    (`Persist.runSqlPool` pool) $ do
                        logInfoN "inside of thing"
                        Monad.forM_ [1 .. numThings] $ \i -> do
                            Monad.when (i `mod` 1000 == 0) $
                                logInfoN $
                                    "Thing #: " <> show i
                            Persist.rawExecute "insert into foo values(1);" []
            )
    Concurrent.threadDelay 1000000
    Monad.void $ Concurrent.killThread threadId
    logInfoN "killed thread"