4

我遇到了一个问题,我的 Scotty 应用程序似乎没有终止旧的 HTTP 请求线程。最终,在大量(10-20)个并发请求之后,我遇到了一个错误 with too many DB connections libpq: failed (FATAL: sorry, too many clients already)

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Database.PostgreSQL.Simple
import Control.Monad.IO.Class

connection :: IO Connection
connection = connect defaultConnectInfo
  { connectHost = "localhost", connectUser="postgres", connectPassword="mysecretpassword" }

main :: IO ()
main = scotty 8000 $ do
  get "/" $ do
    c <- liftIO $ connection
    text "test"

这也发生在 Warp 应用程序(Scotty)中:

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai 
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)
import Database.PostgreSQL.Simple
import Control.Monad.IO.Class

connection :: IO Connection
connection = connect defaultConnectInfo
  { connectHost = "localhost", connectUser="postgres", connectPassword="mysecretpassword" }

main = run 8000 app

app :: Application
app req respond = do
    respond $ responseStream status200 [] $ \write flush -> do
        print "test"
        con <- connection
        flush
        write $ "World\n"


为什么会这样?有没有一种简单的方法可以在最后“完成”请求?

我可以手动关闭连接,但理想情况下,我认为用任何其他相关资源终止线程是理想的。


我已经通过在 postgres 中运行以下命令来验证它是否保持连接打开:

SELECT sum(numbackends) FROM pg_stat_database;

Scotty 似乎需要几秒钟才能自动关闭它(在请求完成后)。

4

1 回答 1

0

postgresql-simple提供close :: Connection -> IO ()关闭连接和释放相关资源的功能。完成后您需要关闭连接。

但是一个常见的问题是:如果打开和关闭连接之间的代码抛出异常会发生什么?即使在这种情况下,如何确保连接关闭,这样我们就不会泄漏连接?

在 Haskell 中,这是使用bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c函数解决的。你向它传递一个IO分配一些资源的动作(在这种情况下,一个连接)一个函数,一旦我们完成它就会释放分配的资源(在我们的例子中,close函数)和一个说明我们真正想要的函数处理分配的资源(在这种情况下,执行查询)。

bracket有点类似于 Java 中的 try-with-resources 或 C# 中的“使用”语句。


与其在每个请求上打开和关闭连接,更好的方法是使用在请求线程之间共享的某种连接池。例如, persistent-postgresql使用资源池

于 2020-01-03T16:20:01.163 回答