1
+ {-# LANGUAGE BangPatterns #-}
2
+ {-# LANGUAGE ExistentialQuantification #-}
3
+ {-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
1
4
-- | This module defines the 'WeakBag' type, which represents a mutable
2
5
-- collection of items that does not cause the items to be retained in memory.
3
6
-- This is useful for situations where a value needs to be inspected or modified
@@ -12,7 +15,6 @@ module Data.WeakBag
12
15
, remove
13
16
) where
14
17
15
- import Control.Concurrent.STM
16
18
import Control.Exception
17
19
import Control.Monad hiding (forM_ , mapM_ )
18
20
import Control.Monad.IO.Class
@@ -28,15 +30,15 @@ import Prelude hiding (mapM_, traverse)
28
30
-- that is, they can still be garbage-collected. As long as the @a@s remain
29
31
-- alive, the 'WeakBag' will continue to refer to them.
30
32
data WeakBag a = WeakBag
31
- { _weakBag_nextId :: {-# UNPACK #-} ! (TVar Int ) -- TODO: what if this wraps around?
32
- , _weakBag_children :: {-# UNPACK #-} ! (TVar (IntMap (Weak a )))
33
+ { _weakBag_nextId :: {-# UNPACK #-} ! (IORef Int ) -- TODO: what if this wraps around?
34
+ , _weakBag_children :: {-# UNPACK #-} ! (IORef (IntMap (Weak a )))
33
35
}
34
36
35
37
-- | When inserting an item into a 'WeakBag', a 'WeakBagTicket' is returned. If
36
38
-- the caller retains the ticket, the item is guranteed to stay in memory (and
37
39
-- thus in the 'WeakBag'). The ticket can also be used to remove the item from
38
40
-- the 'WeakBag' prematurely (i.e. while it is still alive), using 'remove'.
39
- data WeakBagTicket a = WeakBagTicket
41
+ data WeakBagTicket = forall a . WeakBagTicket
40
42
{ _weakBagTicket_weakItem :: {-# UNPACK #-} ! (Weak a )
41
43
, _weakBagTicket_item :: {-# NOUNPACK #-} ! a
42
44
}
@@ -50,30 +52,24 @@ insert :: a -- ^ The item
50
52
-> (b -> IO () ) -- ^ A callback to be invoked when the item is removed
51
53
-- (whether automatically by the item being garbage
52
54
-- collected or manually via 'remove')
53
- -> IO (WeakBagTicket a ) -- ^ Returns a 'WeakBagTicket' that ensures the
54
- -- item is retained and allows the item to be
55
- -- removed.
55
+ -> IO WeakBagTicket -- ^ Returns a 'WeakBagTicket' that ensures the item
56
+ -- is retained and allows the item to be removed.
56
57
insert a (WeakBag nextId children) wbRef finalizer = {-# SCC "insert" #-} do
57
58
a' <- evaluate a
58
59
wbRef' <- evaluate wbRef
59
- myId <- atomically $ do
60
- myId <- readTVar nextId
61
- writeTVar nextId $! succ myId
62
- return myId
60
+ myId <- atomicModifyIORef' nextId $ \ n -> (succ n, n)
63
61
let cleanup = do
64
62
wb <- readIORef wbRef'
65
63
mb <- deRefWeak wb
66
64
forM_ mb $ \ b -> do
67
- isLastNode <- atomically $ do -- TODO: Should this run even when mb is Nothing?
68
- cs <- readTVar children
69
- let csWithoutMe = IntMap. delete myId cs
70
- writeTVar children $! csWithoutMe
71
- return $ IntMap. size csWithoutMe == 0
72
- when isLastNode $ finalizer b
65
+ csWithoutMe <- atomicModifyIORef children $ \ cs ->
66
+ let ! csWithoutMe = IntMap. delete myId cs
67
+ in (csWithoutMe, csWithoutMe)
68
+ when (IntMap. null csWithoutMe) $ finalizer b
73
69
return ()
74
70
return ()
75
71
wa <- mkWeakPtr a' $ Just cleanup
76
- atomically $ modifyTVar ' children $ IntMap. insert myId wa
72
+ atomicModifyIORef ' children $ \ cs -> ( IntMap. insert myId wa cs, () )
77
73
return $ WeakBagTicket
78
74
{ _weakBagTicket_weakItem = wa
79
75
, _weakBagTicket_item = a'
@@ -83,8 +79,8 @@ insert a (WeakBag nextId children) wbRef finalizer = {-# SCC "insert" #-} do
83
79
{-# INLINE empty #-}
84
80
empty :: IO (WeakBag a )
85
81
empty = {-# SCC "empty" #-} do
86
- nextId <- newTVarIO 1
87
- children <- newTVarIO IntMap. empty
82
+ nextId <- newIORef 1
83
+ children <- newIORef IntMap. empty
88
84
let bag = WeakBag
89
85
{ _weakBag_nextId = nextId
90
86
, _weakBag_children = children
@@ -94,7 +90,7 @@ empty = {-# SCC "empty" #-} do
94
90
-- | Create a 'WeakBag' with one item; equivalent to creating the 'WeakBag' with
95
91
-- 'empty', then using 'insert'.
96
92
{-# INLINE singleton #-}
97
- singleton :: a -> IORef (Weak b ) -> (b -> IO () ) -> IO (WeakBag a , WeakBagTicket a )
93
+ singleton :: a -> IORef (Weak b ) -> (b -> IO () ) -> IO (WeakBag a , WeakBagTicket )
98
94
singleton a wbRef finalizer = {-# SCC "singleton" #-} do
99
95
bag <- empty
100
96
ticket <- insert a bag wbRef finalizer
@@ -107,14 +103,16 @@ singleton a wbRef finalizer = {-# SCC "singleton" #-} do
107
103
-- is made about the order of the traversal.
108
104
traverse :: MonadIO m => WeakBag a -> (a -> m () ) -> m ()
109
105
traverse (WeakBag _ children) f = {-# SCC "traverse" #-} do
110
- cs <- liftIO $ readTVarIO children
106
+ cs <- liftIO $ readIORef children
111
107
forM_ cs $ \ c -> do
112
108
ma <- liftIO $ deRefWeak c
113
109
mapM_ f ma
114
110
115
111
-- | Remove an item from the 'WeakBag'; does nothing if invoked multiple times
116
112
-- on the same 'WeakBagTicket'.
117
113
{-# INLINE remove #-}
118
- remove :: WeakBagTicket a -> IO ()
119
- remove = {-# SCC "remove" #-} finalize . _weakBagTicket_weakItem
114
+ remove :: WeakBagTicket -> IO ()
115
+ remove ( WeakBagTicket w _) = {-# SCC "remove" #-} finalize w
120
116
-- TODO: Should 'remove' also drop the reference to the item?
117
+
118
+ -- TODO: can/should we provide a null WeakBagTicket?
0 commit comments