snap-server-0.2.4: src/Snap/Internal/Http/Server/Date.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
module Snap.Internal.Http.Server.Date
( getDateString
, getLogDateString
, getCurrentDateTime) where
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.ByteString (ByteString)
import Data.IORef
import Foreign.C.Types
import System.IO.Unsafe
#ifndef PORTABLE
import System.Posix.Time
#else
import Data.Time.Clock.POSIX
#endif
import Snap.Internal.Http.Types (formatHttpTime, formatLogTime)
-- Here comes a dirty hack. We don't want to be wasting context switches
-- building date strings, so we're only going to compute one every two
-- seconds. (Approximate timestamps to within a couple of seconds are OK here,
-- and we'll reduce overhead.)
--
-- Note that we also don't want to wake up a potentially sleeping CPU by just
-- running the computation on a timer. We'll allow client traffic to trigger
-- the process.
data DateState = DateState {
_cachedDateString :: !(IORef ByteString)
, _cachedLogString :: !(IORef ByteString)
, _cachedDate :: !(IORef CTime)
, _valueIsOld :: !(IORef Bool)
, _morePlease :: !(MVar ())
, _dataAvailable :: !(MVar ())
, _dateThread :: !(MVar ThreadId)
}
dateState :: DateState
dateState = unsafePerformIO $ do
(s1,s2,date) <- fetchTime
bs1 <- newIORef s1
bs2 <- newIORef s2
dt <- newIORef date
ov <- newIORef False
th <- newEmptyMVar
mp <- newMVar ()
da <- newMVar ()
let d = DateState bs1 bs2 dt ov mp da th
t <- forkIO $ dateThread d
putMVar th t
return d
#ifdef PORTABLE
epochTime :: IO CTime
epochTime = do
t <- getPOSIXTime
return $ fromInteger $ truncate t
#endif
fetchTime :: IO (ByteString,ByteString,CTime)
fetchTime = do
now <- epochTime
t1 <- formatHttpTime now
t2 <- formatLogTime now
return (t1, t2, now)
dateThread :: DateState -> IO ()
dateThread ds@(DateState dateString logString time valueIsOld morePlease
dataAvailable _) = do
-- a lot of effort to make sure we don't deadlock
takeMVar morePlease
(s1,s2,now) <- fetchTime
atomicModifyIORef dateString $ const (s1,())
atomicModifyIORef logString $ const (s2,())
atomicModifyIORef time $ const (now,())
writeIORef valueIsOld False
tryPutMVar dataAvailable ()
threadDelay 2000000
takeMVar dataAvailable
writeIORef valueIsOld True
dateThread ds
ensureFreshDate :: IO ()
ensureFreshDate = block $ do
old <- readIORef $ _valueIsOld dateState
when old $ do
tryPutMVar (_morePlease dateState) ()
readMVar $ _dataAvailable dateState
getDateString :: IO ByteString
getDateString = block $ do
ensureFreshDate
readIORef $ _cachedDateString dateState
getLogDateString :: IO ByteString
getLogDateString = block $ do
ensureFreshDate
readIORef $ _cachedLogString dateState
getCurrentDateTime :: IO CTime
getCurrentDateTime = block $ do
ensureFreshDate
readIORef $ _cachedDate dateState