on master help sign in
Fix memory leaks
commita4ff79c776d3031b0910a4ae72dd12ff11f9ac89 authorHamish Mackenzie <Hamish.K.Mackenzie@googlemail.com> dateWed 21 Jun 2017, 13:12 (9 years ago) parent8d61929
4 files changed+71−27
jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+3−3 jsaddle/src/Language/Javascript/JSaddle/Run.hs+35−12 jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+27−10 jsaddle/src/Language/Javascript/JSaddle/Types.hs+6−2
jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+3−3
@@ -62,9 +62,9 @@ wrapJSString :: MonadIO m => JSStringReceived -> m JSString
6262wrapJSString (JSStringReceived ref) = return $ JSString ref
6363
6464withJSVal :: MonadIO m => JSVal -> (JSValueForSend -> m a) -> m a
65−withJSVal v@(JSVal ref) f =
66− do result <- f (JSValueForSend ref)
67− liftIO $ touch v
65+withJSVal v@(JSVal ref) f = do
66+ result <- f (JSValueForSend ref)
67+ liftIO $ touch ref
6868 return result
6969
7070withJSVals :: MonadIO m => [JSVal] -> ([JSValueForSend] -> m a) -> m a
jsaddle/src/Language/Javascript/JSaddle/Run.hs+35−12
@@ -2,6 +2,8 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE LambdaCase #-}
44{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE MagicHash #-}
6+{-# LANGUAGE UnboxedTuples #-}
57-----------------------------------------------------------------------------
68--
79-- Module : Language.Javascript.JSaddle.Run
@@ -39,25 +41,30 @@ import Language.Javascript.JSaddle.Types (JSM)
3941import qualified JavaScript.Web.AnimationFrame as GHCJS
4042 (waitForAnimationFrame)
4143#else
42−import Control.Exception (throwIO)
44+import Control.Exception (throwIO, evaluate)
4345import Control.Monad (void, when, zipWithM_)
4446import Control.Monad.IO.Class (MonadIO(..))
4547import Control.Monad.Trans.Reader (ask, runReaderT)
4648import Control.Monad.STM (atomically)
47−import Control.Concurrent (forkIO)
49+import Control.Concurrent (forkIO, myThreadId)
4850import Control.Concurrent.STM.TChan
4951 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
5052import Control.Concurrent.STM.TVar
5153 (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
5254import Control.Concurrent.MVar
53− (tryTakeMVar, MVar, putMVar, takeMVar, newEmptyMVar, readMVar)
55+ (tryTakeMVar, MVar, putMVar, takeMVar, newMVar, newEmptyMVar, readMVar, modifyMVar)
5456
5557import System.IO.Unsafe (unsafeInterleaveIO)
56−import System.Mem.Weak (addFinalizer)
58+import System.Mem.Weak (addFinalizer, mkWeak)
5759
60+import GHC.Int (Int64(..))
61+import GHC.Base (IO(..), mkWeak#)
62+import GHC.Conc (ThreadId(..))
5863import Data.Monoid ((<>))
59−import qualified Data.Text as T (unpack)
64+import qualified Data.Text as T (unpack, pack)
6065import qualified Data.Map as M (lookup, delete, insert, empty, size)
66+import Data.Set (Set)
67+import qualified Data.Set as S (empty, member, insert, delete)
6168import Data.UUID.V4 (nextRandom)
6269import Data.Time.Clock (getCurrentTime,diffUTCTime)
6370import Data.IORef (newIORef, atomicWriteIORef, readIORef)
@@ -66,7 +73,6 @@ import Language.Javascript.JSaddle.Types
6673 (Command(..), AsyncCommand(..), Result(..), BatchResults(..), Results(..), JSContextRef(..), JSVal(..),
6774 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
6875import Language.Javascript.JSaddle.Exception (JSException(..))
69−-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
7076import Control.DeepSeq (deepseq)
7177import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
7278#endif
@@ -137,7 +143,7 @@ sendLazyCommand cmd = do
137143 nextRefTVar <- nextRef <$> JSM ask
138144 n <- liftIO . atomically $ do
139145 n <- subtract 1 <$> readTVar nextRefTVar
140− writeTVar nextRefTVar n
146+ writeTVar nextRefTVar $! n
141147 return n
142148 s <- doSendAsyncCommand <$> JSM ask
143149 liftIO $ s (cmd $ JSValueForSend n)
@@ -157,6 +163,7 @@ runJavaScript sendBatch entryPoint = do
157163 commandChan <- newTChanIO
158164 callbacks <- newTVarIO M.empty
159165 nextRef' <- newTVarIO 0
166+ finalizerThreads <- newMVar S.empty
160167 loggingEnabled <- newIORef False
161168 let ctx = JSContextRef {
162169 contextId = contextId'
@@ -173,13 +180,14 @@ runJavaScript sendBatch entryPoint = do
173180 , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar' callbacks (M.delete val)
174181 , nextRef = nextRef'
175182 , doEnableLogging = atomicWriteIORef loggingEnabled
183+ , finalizerThreads = finalizerThreads
176184 }
177185 let processResults :: Bool -> Results -> IO ()
178186 processResults syncCallbacks = \case
179187 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
180− (Callback n br f this a) -> do
188+ (Callback n br (JSValueReceived fNumber) f this a) -> do
181189 putMVar recvMVar (n, br)
182− f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
190+ f' <- runReaderT (unJSM $ wrapJSVal f) ctx
183191 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
184192 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
185193 logInfo (("Call " <> show fNumber <> " ") <>)
@@ -208,7 +216,7 @@ runJavaScript sendBatch entryPoint = do
208216 True -> show . currentBytesUsed <$> getGCStats
209217 False -> return "??"
210218 cbCount <- M.size <$> readTVarIO callbacks
211− putStrLn . s $ "M " <> currentBytesUsedStr <> "CB " <> show cbCount
219+ putStrLn . s $ "M " <> currentBytesUsedStr <> " CB " <> show cbCount <> " "
212220 False -> return ()
213221 _ <- forkIO . numberForeverFromM_ 1 $ \nBatch ->
214222 readBatch nBatch commandChan >>= \case
@@ -264,12 +272,27 @@ runJavaScript sendBatch entryPoint = do
264272 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
265273 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
266274
275+addThreadFinalizer :: ThreadId -> IO () -> IO ()
276+addThreadFinalizer t@(ThreadId t#) (IO finalizer) =
277+ IO $ \s -> case mkWeak# t# t finalizer s of { (# s1, _ #) -> (# s1, () #) }
278+
267279wrapJSVal :: JSValueReceived -> JSM JSVal
268280wrapJSVal (JSValueReceived ref) = do
269281 -- TODO make sure this ref has not already been wrapped (perhaps only in debug version)
270282 let result = JSVal ref
271− when (ref >= 5) $ do
283+ when (ref >= 5 || ref < 0) $ do
272284 ctx <- JSM ask
273− liftIO . addFinalizer result $ doSendAsyncCommand ctx $ FreeRef $ JSValueForSend ref
285+ liftIO . addFinalizer ref $ do
286+ ft <- takeMVar $ finalizerThreads ctx
287+ t@(ThreadId t#) <- myThreadId
288+ let tname = T.pack $ show t
289+ doSendAsyncCommand ctx $ FreeRef tname $ JSValueForSend ref
290+ if tname `S.member` ft
291+ then putMVar (finalizerThreads ctx) ft
292+ else do
293+ addThreadFinalizer t $ do
294+ modifyMVar (finalizerThreads ctx) $ \s -> return (S.delete tname s, ())
295+ doSendAsyncCommand ctx $ FreeRefs tname
296+ putMVar (finalizerThreads ctx) =<< evaluate (S.insert tname ft)
274297 return result
275298#endif
jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+27−10
@@ -37,6 +37,7 @@ indexHtml =
3737initState :: ByteString
3838initState = "\
3939 \ var jsaddle_values = new Map();\n\
40+ \ var jsaddle_free = new Map();\n\
4041 \ jsaddle_values.set(0, null);\n\
4142 \ jsaddle_values.set(1, undefined);\n\
4243 \ jsaddle_values.set(2, false);\n\
@@ -67,7 +68,15 @@ runBatch send sendSync = "\
6768 \ var d = cmd.Left;\n\
6869 \ switch (d.tag) {\n\
6970 \ case \"FreeRef\":\n\
70− \ jsaddle_values.delete(d.contents);\n\
71+ \ var refsToFree = jsaddle_free.get(d.contents[0]) || [];\n\
72+ \ refsToFree.push(d.contents[1]);\n\
73+ \ jsaddle_free.set(d.contents[0], refsToFree);\n\
74+ \ break;\n\
75+ \ case \"FreeRefs\":\n\
76+ \ var refsToFree = jsaddle_free.get(d.contents) || [];\n\
77+ \ for(var nRef = 0; nRef != refsToFree.length; nRef++)\n\
78+ \ jsaddle_values.delete(refsToFree[nRef]);\n\
79+ \ jsaddle_free.delete(d.contents);\n\
7180 \ break;\n\
7281 \ case \"SetPropertyByName\":\n\
7382 \ jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);\n\
@@ -102,7 +111,9 @@ runBatch send sendSync = "\
102111 \ case \"NewAsyncCallback\":\n\
103112 \ (function() {\n\
104113 \ var nFunction = d.contents;\n\
105− \ jsaddle_values.set(nFunction, function() {\n\
114+ \ var func = function() {\n\
115+ \ var nFunctionInFunc = ++jsaddle_index;\n\
116+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
106117 \ var nThis = ++jsaddle_index;\n\
107118 \ jsaddle_values.set(nThis, this);\n\
108119 \ var args = [];\n\
@@ -111,13 +122,17 @@ runBatch send sendSync = "\
111122 \ jsaddle_values.set(nArg, arguments[i]);\n\
112123 \ args[i] = nArg;\n\
113124 \ }\n\
114− \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n\
115− \ })})();\n\
125+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
126+ \ };\n\
127+ \ jsaddle_values.set(nFunction, func);\n\
128+ \ })();\n\
116129 \ break;\n\
117130 \ case \"NewSyncCallback\":\n\
118131 \ (function() {\n\
119132 \ var nFunction = d.contents;\n\
120− \ jsaddle_values.set(nFunction, function() {\n\
133+ \ var func = function() {\n\
134+ \ var nFunctionInFunc = ++jsaddle_index;\n\
135+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
121136 \ var nThis = ++jsaddle_index;\n\
122137 \ jsaddle_values.set(nThis, this);\n\
123138 \ var args = [];\n\
@@ -129,14 +144,16 @@ runBatch send sendSync = "\
129144 case sendSync of
130145 Just s ->
131146 " if(inCallback > 0) {\n\
132− \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n\
147+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
133148 \ } else {\n\
134− \ runBatch(" <> s "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> ", 1);\n\
149+ \ runBatch(" <> s "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> ", 1);\n\
135150 \ }\n"
136151 Nothing ->
137− " " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n"
152+ " " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n"
138153 ) <>
139− " })})();\n\
154+ " };\n\
155+ \ jsaddle_values.set(nFunction, func);\n\
156+ \ })();\n\
140157 \ break;\n\
141158 \ case \"CallAsFunction\":\n\
142159 \ var n = d.contents[3];\n\
@@ -179,7 +196,7 @@ runBatch send sendSync = "\
179196 \ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));\n\
180197 \ break;\n\
181198 \ case \"SyncWithAnimationFrame\":\n\
182− \ var n = d.contents[0];\n\
199+ \ var n = d.contents;\n\
183200 \ jsaddle_values.set(n, timestamp);\n\
184201 \ break;\n\
185202 \ case \"StartSyncBlock\":\n\
jsaddle/src/Language/Javascript/JSaddle/Types.hs+6−2
@@ -106,6 +106,8 @@ import Control.Monad.Trans.Class (MonadTrans(..))
106106import Control.Monad.Fix (MonadFix)
107107import Control.Monad.Ref (MonadAtomicRef(..), MonadRef(..))
108108import Control.Concurrent.STM.TVar (TVar)
109+import Control.Concurrent.MVar (MVar)
110+import Data.Set (Set)
109111import Data.Text (Text)
110112import Data.UUID (UUID)
111113import Data.Time.Clock (UTCTime(..))
@@ -138,6 +140,7 @@ data JSContextRef = JSContextRef {
138140 , freeCallback :: Object -> IO ()
139141 , nextRef :: TVar JSValueRef
140142 , doEnableLogging :: Bool -> IO ()
143+ , finalizerThreads :: MVar (Set Text)
141144}
142145#endif
143146
@@ -360,7 +363,8 @@ newtype JSStringForSend = JSStringForSend Text deriving(Show, ToJSON, FromJSON,
360363instance NFData JSStringForSend
361364
362365-- | Command sent to a JavaScript context for execution asynchronously
363−data AsyncCommand = FreeRef JSValueForSend
366+data AsyncCommand = FreeRef Text JSValueForSend
367+ | FreeRefs Text
364368 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
365369 | SetPropertyAtIndex JSObjectForSend Int JSValueForSend
366370 | StringToValue JSStringForSend JSValueForSend
@@ -452,7 +456,7 @@ instance FromJSON BatchResults
452456
453457data Results = BatchResults Int BatchResults
454458 | Duplicate Int Int
455− | Callback Int BatchResults JSValueReceived JSValueReceived [JSValueReceived]
459+ | Callback Int BatchResults JSValueReceived JSValueReceived JSValueReceived [JSValueReceived]
456460 | ProtocolError Text
457461 deriving (Show, Generic)
458462