{-# LANGUAGE CPP #-}

-- This module selects the internal implementation (POSIX, Windows, wasm/js)
-- with CPP and wraps it with user-facing types and documentation.
--
-- Benefits:
--
--  - All the documentation lives in a single place, instead of being duplicated
--    across every implementation.
--  - We can document platform-dependent behaviour, as opposed to e.g. the
--    Hackage page only showing POSIX-specific documentation.

-- | Platform-independent internal API.
module System.Semaphore.Internal
  ( ClientSemaphore(..), ServerSemaphore(..), SemaphoreToken(..)
  , clientSemaphoreName, serverClientSemaphore

  , waitOnSemaphore, tryWaitOnSemaphore
  , releaseSemaphoreToken

  , destroyClientSemaphore, destroyServerSemaphore

  , getSemaphoreValue

    -- * Internals
  , create_sem, open_sem_raw
  , Impl.get_time_seed
  ) where

-- base
import Data.Coerce ( coerce )
import GHC.Stack ( HasCallStack )

-- semaphore-compat
import System.Semaphore.Internal.Version

#if defined(wasm32_HOST_ARCH) || defined(javascript_HOST_ARCH)
import qualified System.Semaphore.Internal.Unsupported as Impl
#elif defined(mingw32_HOST_OS)
import qualified System.Semaphore.Internal.Win32 as Impl
#else
import qualified System.Semaphore.Internal.Posix as Impl
#endif

---------------------------------------
-- User-facing API

-- | A client-side semaphore: the handle a jobclient uses to acquire and release
-- tokens from a semaphore created by a jobserver.
--
-- Retrieve the underlying name with 'clientSemaphoreName'.
newtype ClientSemaphore = ClientSemaphore Impl.ClientSemaphore

-- | A server-side semaphore: a 'ClientSemaphore' together with the book-keeping
-- needed to track resource usage by all clients.
--
-- Retrieve the corresponding client semaphore with 'serverClientSemaphore'.
newtype ServerSemaphore = ServerSemaphore Impl.ServerSemaphore

-- | A held semaphore token: evidence of one unit of resource acquired from a
-- semaphore.
--
-- Use 'releaseSemaphoreToken' or 'withSemaphoreToken' to ensure prompt release
-- of semaphore tokens.
--
-- Platform-dependent behaviour:
--
--  - On POSIX, if all references to a 'SemaphoreToken' are dropped without
--    being released, a finalizer returns the token to the semaphore.
--  - On Windows, tokens held by a crashed client are permanently lost.
newtype SemaphoreToken = SemaphoreToken Impl.SemaphoreToken

-- | Retrieve the underlying name of a client-side semaphore.
clientSemaphoreName :: ClientSemaphore -> SemaphoreName
clientSemaphoreName :: ClientSemaphore -> SemaphoreName
clientSemaphoreName = (ClientSemaphore -> SemaphoreName)
-> ClientSemaphore -> SemaphoreName
forall a b. Coercible a b => a -> b
coerce ClientSemaphore -> SemaphoreName
Impl.clientSemaphoreName

-- | Retrieve the client-side semaphore corresponding to a server-side semaphore.
serverClientSemaphore :: ServerSemaphore -> ClientSemaphore
serverClientSemaphore :: ServerSemaphore -> ClientSemaphore
serverClientSemaphore = (ServerSemaphore -> ClientSemaphore)
-> ServerSemaphore -> ClientSemaphore
forall a b. Coercible a b => a -> b
coerce ServerSemaphore -> ClientSemaphore
Impl.serverClientSemaphore

-- | Acquire a token from the semaphore, blocking until one is available.
--
-- The returned 'SemaphoreToken' must be released with 'releaseSemaphoreToken'.
-- For prompt and predictable release of resources, callers should use
-- 'System.Semaphore.withSemaphoreToken' or 'releaseSemaphoreToken'.
--
-- This operation is interruptible: it can be cancelled by
-- 'Control.Concurrent.throwTo', 'Control.Concurrent.killThread', etc.
-- If interrupted, any transiently acquired token is automatically returned.
waitOnSemaphore :: HasCallStack => ClientSemaphore -> IO SemaphoreToken
waitOnSemaphore :: HasCallStack => ClientSemaphore -> IO SemaphoreToken
waitOnSemaphore = (ClientSemaphore -> IO SemaphoreToken)
-> ClientSemaphore -> IO SemaphoreToken
forall a b. Coercible a b => a -> b
coerce HasCallStack => ClientSemaphore -> IO SemaphoreToken
ClientSemaphore -> IO SemaphoreToken
Impl.waitOnSemaphore

-- | Try to acquire a token from the semaphore without blocking.
--
-- Returns @Just token@ if a token was available, @Nothing@ otherwise.
--
-- This is __not__ an interruptible operation, but that should not be a problem:
-- this function is not expected to block for long, as the server is supposed
-- to respond immediately.
tryWaitOnSemaphore :: HasCallStack => ClientSemaphore -> IO (Maybe SemaphoreToken)
tryWaitOnSemaphore :: HasCallStack => ClientSemaphore -> IO (Maybe SemaphoreToken)
tryWaitOnSemaphore = (ClientSemaphore -> IO (Maybe SemaphoreToken))
-> ClientSemaphore -> IO (Maybe SemaphoreToken)
forall a b. Coercible a b => a -> b
coerce HasCallStack => ClientSemaphore -> IO (Maybe SemaphoreToken)
ClientSemaphore -> IO (Maybe SemaphoreToken)
Impl.tryWaitOnSemaphore

-- | Release a semaphore token, returning it to the pool.
--
-- Idempotent: a second call on the same token is a safe no-op.
releaseSemaphoreToken :: HasCallStack => SemaphoreToken -> IO ()
releaseSemaphoreToken :: HasCallStack => SemaphoreToken -> IO ()
releaseSemaphoreToken = (SemaphoreToken -> IO ()) -> SemaphoreToken -> IO ()
forall a b. Coercible a b => a -> b
coerce HasCallStack => SemaphoreToken -> IO ()
SemaphoreToken -> IO ()
Impl.releaseSemaphoreToken

-- | Destroy a client-side semaphore.
--
-- This is an idempotent operation: double calls to 'destroyClientSemaphore'
-- do not cause any problems.
destroyClientSemaphore :: ClientSemaphore -> IO ()
destroyClientSemaphore :: ClientSemaphore -> IO ()
destroyClientSemaphore = (ClientSemaphore -> IO ()) -> ClientSemaphore -> IO ()
forall a b. Coercible a b => a -> b
coerce ClientSemaphore -> IO ()
Impl.destroyClientSemaphore

-- | Destroy a server-side semaphore.
--
-- Idempotent. Not interruptible.
destroyServerSemaphore :: ServerSemaphore -> IO ()
destroyServerSemaphore :: ServerSemaphore -> IO ()
destroyServerSemaphore = (ServerSemaphore -> IO ()) -> ServerSemaphore -> IO ()
forall a b. Coercible a b => a -> b
coerce ServerSemaphore -> IO ()
Impl.destroyServerSemaphore

-- | Query the current semaphore value (how many tokens it has available).
--
-- This is mainly for debugging use, as it is easy to introduce race conditions
-- when nontrivial program logic depends on the value returned by this function.
getSemaphoreValue :: ServerSemaphore -> IO Int
getSemaphoreValue :: ServerSemaphore -> IO Int
getSemaphoreValue = (ServerSemaphore -> IO Int) -> ServerSemaphore -> IO Int
forall a b. Coercible a b => a -> b
coerce ServerSemaphore -> IO Int
Impl.getSemaphoreValue

--------------------------------------------------------------------------------
-- Internals

create_sem :: SemaphoreName -> Int -> IO (Either SemaphoreError ServerSemaphore)
create_sem :: SemaphoreName -> Int -> IO (Either SemaphoreError ServerSemaphore)
create_sem = (SemaphoreName
 -> Int -> IO (Either SemaphoreError ServerSemaphore))
-> SemaphoreName
-> Int
-> IO (Either SemaphoreError ServerSemaphore)
forall a b. Coercible a b => a -> b
coerce SemaphoreName -> Int -> IO (Either SemaphoreError ServerSemaphore)
Impl.create_sem

open_sem_raw :: SemaphoreName -> IO (Either SemaphoreError ClientSemaphore)
open_sem_raw :: SemaphoreName -> IO (Either SemaphoreError ClientSemaphore)
open_sem_raw = (SemaphoreName -> IO (Either SemaphoreError ClientSemaphore))
-> SemaphoreName -> IO (Either SemaphoreError ClientSemaphore)
forall a b. Coercible a b => a -> b
coerce SemaphoreName -> IO (Either SemaphoreError ClientSemaphore)
Impl.open_sem_raw