module Simulation.Aivika.Resource.Preemption.Base
(
Resource,
newResource,
newResourceWithMaxCount,
resourceMaxCount,
resourceCount,
requestResourceWithPriority,
releaseResource,
usingResourceWithPriority,
incResourceCount,
decResourceCount,
alterResourceCount) where
import Data.IORef
import Data.Maybe
import Control.Monad
import Control.Monad.Trans
import Control.Exception
import Simulation.Aivika.Internal.Specs
import Simulation.Aivika.Internal.Simulation
import Simulation.Aivika.Internal.Event
import Simulation.Aivika.Internal.Cont
import Simulation.Aivika.Internal.Process
import Simulation.Aivika.QueueStrategy
import qualified Simulation.Aivika.PriorityQueue as PQ
data Resource =
Resource { resourceMaxCount :: Maybe Int,
resourceCountRef :: IORef Int,
resourceActingQueue :: PQ.PriorityQueue ResourceActingItem,
resourceWaitQueue :: PQ.PriorityQueue ResourceAwaitingItem }
data ResourceActingItem =
ResourceActingItem { actingItemPriority :: Double,
actingItemId :: ProcessId }
type ResourceAwaitingItem = Either ResourceRequestingItem ResourcePreemptedItem
data ResourceRequestingItem =
ResourceRequestingItem { requestingItemPriority :: Double,
requestingItemId :: ProcessId,
requestingItemCont :: FrozenCont () }
data ResourcePreemptedItem =
ResourcePreemptedItem { preemptedItemPriority :: Double,
preemptedItemId :: ProcessId }
instance Eq Resource where
x == y = resourceCountRef x == resourceCountRef y
instance Eq ResourceActingItem where
x == y = actingItemId x == actingItemId y
newResource :: Int
-> Simulation Resource
newResource count =
Simulation $ \r ->
do when (count < 0) $
error $
"The resource count cannot be negative: " ++
"newResource."
countRef <- newIORef count
actingQueue <- PQ.newQueue
waitQueue <- PQ.newQueue
return Resource { resourceMaxCount = Just count,
resourceCountRef = countRef,
resourceActingQueue = actingQueue,
resourceWaitQueue = waitQueue }
newResourceWithMaxCount :: Int
-> Maybe Int
-> Simulation Resource
newResourceWithMaxCount count maxCount =
Simulation $ \r ->
do when (count < 0) $
error $
"The resource count cannot be negative: " ++
"newResourceWithMaxCount."
case maxCount of
Just maxCount | count > maxCount ->
error $
"The resource count cannot be greater than " ++
"its maximum value: newResourceWithMaxCount."
_ ->
return ()
countRef <- newIORef count
actingQueue <- PQ.newQueue
waitQueue <- PQ.newQueue
return Resource { resourceMaxCount = maxCount,
resourceCountRef = countRef,
resourceActingQueue = actingQueue,
resourceWaitQueue = waitQueue }
resourceCount :: Resource -> Event Int
resourceCount r =
Event $ \p -> readIORef (resourceCountRef r)
requestResourceWithPriority :: Resource
-> Double
-> Process ()
requestResourceWithPriority r priority =
Process $ \pid ->
Cont $ \c ->
Event $ \p ->
do a <- readIORef (resourceCountRef r)
if a == 0
then do f <- PQ.queueNull (resourceActingQueue r)
if f
then do c <- invokeEvent p $
freezeContReentering c () $
invokeCont c $
invokeProcess pid $
requestResourceWithPriority r priority
PQ.enqueue (resourceWaitQueue r) priority (Left $ ResourceRequestingItem priority pid c)
else do (p0', item0) <- PQ.queueFront (resourceActingQueue r)
let p0 = p0'
pid0 = actingItemId item0
if priority < p0
then do PQ.dequeue (resourceActingQueue r)
PQ.enqueue (resourceActingQueue r) ( priority) $ ResourceActingItem priority pid
PQ.enqueue (resourceWaitQueue r) p0 (Right $ ResourcePreemptedItem p0 pid0)
invokeEvent p $ processPreemptionBegin pid0
invokeEvent p $ resumeCont c ()
else do c <- invokeEvent p $
freezeContReentering c () $
invokeCont c $
invokeProcess pid $
requestResourceWithPriority r priority
PQ.enqueue (resourceWaitQueue r) priority (Left $ ResourceRequestingItem priority pid c)
else do let a' = a 1
a' `seq` writeIORef (resourceCountRef r) a'
PQ.enqueue (resourceActingQueue r) ( priority) $ ResourceActingItem priority pid
invokeEvent p $ resumeCont c ()
releaseResource :: Resource
-> Process ()
releaseResource r =
Process $ \pid ->
Cont $ \c ->
Event $ \p ->
do f <- fmap isJust $ PQ.queueDeleteBy (resourceActingQueue r) (\item -> actingItemId item == pid)
if f
then do invokeEvent p $ releaseResource' r
invokeEvent p $ resumeCont c ()
else error $
"The resource was not acquired by this process: releaseResource"
releaseResource' :: Resource
-> Event ()
releaseResource' r =
Event $ \p ->
do a <- readIORef (resourceCountRef r)
let a' = a + 1
case resourceMaxCount r of
Just maxCount | a' > maxCount ->
error $
"The resource count cannot be greater than " ++
"its maximum value: releaseResource'."
_ ->
return ()
f <- PQ.queueNull (resourceWaitQueue r)
if f
then a' `seq` writeIORef (resourceCountRef r) a'
else do (priority', item) <- PQ.queueFront (resourceWaitQueue r)
PQ.dequeue (resourceWaitQueue r)
case item of
Left (ResourceRequestingItem priority pid c) ->
do c <- invokeEvent p $ unfreezeCont c
case c of
Nothing ->
invokeEvent p $ releaseResource' r
Just c ->
do PQ.enqueue (resourceActingQueue r) ( priority) $ ResourceActingItem priority pid
invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()
Right (ResourcePreemptedItem priority pid) ->
do f <- invokeEvent p $ processCancelled pid
case f of
True ->
invokeEvent p $ releaseResource' r
False ->
do PQ.enqueue (resourceActingQueue r) ( priority) $ ResourceActingItem priority pid
invokeEvent p $ processPreemptionEnd pid
usingResourceWithPriority :: Resource
-> Double
-> Process a
-> Process a
usingResourceWithPriority r priority m =
do requestResourceWithPriority r priority
finallyProcess m $ releaseResource r
decResourceCount' :: Resource -> Event ()
decResourceCount' r =
Event $ \p ->
do a <- readIORef (resourceCountRef r)
when (a == 0) $
error $
"The resource exceeded and its count is zero: decResourceCount'"
f <- PQ.queueNull (resourceActingQueue r)
unless f $
do (p0', item0) <- PQ.queueFront (resourceActingQueue r)
let p0 = p0'
pid0 = actingItemId item0
PQ.dequeue (resourceActingQueue r)
PQ.enqueue (resourceWaitQueue r) p0 (Right $ ResourcePreemptedItem p0 pid0)
invokeEvent p $ processPreemptionBegin pid0
let a' = a 1
a' `seq` writeIORef (resourceCountRef r) a'
incResourceCount :: Resource
-> Int
-> Event ()
incResourceCount r n
| n < 0 = error "The increment cannot be negative: incResourceCount"
| n == 0 = return ()
| otherwise =
do releaseResource' r
incResourceCount r (n 1)
decResourceCount :: Resource
-> Int
-> Event ()
decResourceCount r n
| n < 0 = error "The decrement cannot be negative: decResourceCount"
| n == 0 = return ()
| otherwise =
do decResourceCount' r
decResourceCount r (n 1)
alterResourceCount :: Resource
-> Int
-> Event ()
alterResourceCount r n
| n < 0 = decResourceCount r ( n)
| n > 0 = incResourceCount r n
| n == 0 = return ()