ephemeral-pg-0.1.0.0: src/EphemeralPg/Internal/Port.hs
-- | Port allocation for temporary PostgreSQL instances.
module EphemeralPg.Internal.Port
( -- * Port allocation
findFreePort,
isPortAvailable,
)
where
import Control.Exception (SomeException, bracket, try)
import Data.Text qualified as T
import Data.Word (Word16)
import EphemeralPg.Error (ResourceError (..), StartError (..))
import Network.Socket
( Family (AF_INET),
SockAddr (SockAddrInet),
Socket,
SocketType (Stream),
bind,
close,
socket,
socketPort,
tupleToHostAddress,
)
-- | Find a free port by binding to port 0 and checking what the OS assigns.
findFreePort :: IO (Either StartError Word16)
findFreePort = do
result <- try $ bracket openSocket close getAssignedPort
case result of
Left (e :: SomeException) ->
pure $ Left $ ResourceError $ PortAllocationFailed $ T.pack $ show e
Right port ->
pure $ Right port
where
openSocket :: IO Socket
openSocket = do
sock <- socket AF_INET Stream 0
-- Bind to localhost on port 0 (OS assigns a free port)
bind sock (SockAddrInet 0 (tupleToHostAddress (127, 0, 0, 1)))
pure sock
getAssignedPort :: Socket -> IO Word16
getAssignedPort sock = do
port <- socketPort sock
pure $ fromIntegral port
-- | Check if a specific port is available.
isPortAvailable :: Word16 -> IO Bool
isPortAvailable port = do
result <- try $ bracket openSocket close (const $ pure ())
case result of
Left (_ :: SomeException) -> pure False
Right () -> pure True
where
openSocket :: IO Socket
openSocket = do
sock <- socket AF_INET Stream 0
bind sock (SockAddrInet (fromIntegral port) (tupleToHostAddress (127, 0, 0, 1)))
pure sock