on develop help sign in
GC test fully working

Verified that it is correctly broken before the strictness fix in ImmediateDomBuilderT

commit17baefe8d1612ef3f0ac07ddeb8f6848d66f7ce9 authorRyan Trinkle <ryan.trinkle@gmail.com> dateFri 3 Mar 2017, 13:06 (10 years ago) parentc3b24ca
11 files changed+165−120
default.nix+4−2 reflex-dom-core/default.nix+24−44 reflex-dom-core/reflex-dom-core.cabal+17−5 reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+54−45 reflex-dom-core/src/Reflex/Dom/Main.hs+1−1 reflex-dom-core/src/Reflex/Dom/Old.hs+4−4 reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+3−3 reflex-dom-core/src/Reflex/Dom/Xhr.hs−1 reflex-dom-core/src/Reflex/Dom/Xhr/Foreign.hs+1−3 reflex-dom-core/test/gc.hs+50−12 reflex-dom-core/test/hlint.hs+7
default.nix+4−2
@@ -1,4 +1,6 @@
1−haskellPackages: {
1+haskellPackages: nixpkgs: {
22 reflex-dom = haskellPackages.callPackage ./reflex-dom {};
3− reflex-dom-core = haskellPackages.callPackage ./reflex-dom-core {};
3+ reflex-dom-core = haskellPackages.callPackage ./reflex-dom-core {
4+ inherit (nixpkgs) xvfb_run chromium;
5+ };
46}
reflex-dom-core/default.nix+24−44
@@ -1,48 +1,28 @@
1−{ mkDerivation, pkgs, dependent-map, ghcjs-dom, lens
2−, mtl, ref-tf, reflex, text, these
3−, transformers, data-default, semigroups, blaze-builder, aeson
4−, ghc, exception-transformers
5−, dependent-sum-template, bifunctors, bimap
6−, raw-strings-qq, zenc, random, monad-control, keycode, hlint
7−, unbounded-delays, jsaddle
1+{ mkDerivation, ghc, aeson, base, bifunctors, bimap, blaze-builder
2+, bytestring, containers, contravariant, data-default
3+, dependent-map, dependent-sum, dependent-sum-template, directory
4+, exception-transformers, ghcjs-dom, hlint, jsaddle, keycode, lens
5+, monad-control, mtl, primitive, random, ref-tf, reflex, semigroups
6+, stdenv, stm, template-haskell, temporary, text, these, time
7+, transformers, unbounded-delays, unix, zenc, hashable, xvfb_run
8+, chromium, process, jsaddle-warp
89}:
9−
1010mkDerivation {
1111 pname = "reflex-dom-core";
12− version = "0.3";
13− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
14− buildDepends = [
15− aeson
16− bifunctors
17− bimap
18− blaze-builder
19− data-default
20− dependent-map
21− dependent-sum-template
22− exception-transformers
23− ghcjs-dom
24− jsaddle
25− keycode
26− lens
27− monad-control
28− mtl
29− random
30− ref-tf
31− reflex
32− semigroups
33− text
34− these
35− transformers
36− unbounded-delays
37− zenc
38− ] ++ (if ghc.isGhcjs or false then [] else [
39− raw-strings-qq
40− ]);
41− testDepends = if ghc.isGhcjs or false then [] else [
42− hlint
43− ];
44− pkgconfigDepends = if ghc.isGhcjs or false then [] else [
45− raw-strings-qq
46− ];
47− license = null;
12+ version = "0.4";
13+ src = ./.;
14+ libraryHaskellDepends = [
15+ aeson base bifunctors bimap blaze-builder bytestring containers
16+ contravariant data-default dependent-map dependent-sum
17+ dependent-sum-template directory exception-transformers ghcjs-dom
18+ jsaddle keycode lens monad-control mtl primitive random ref-tf
19+ reflex semigroups stm template-haskell text these time transformers
20+ unbounded-delays unix zenc
21+ ] ++ (if ghc.isGhcjs or false then [
22+ hashable
23+ ] else []);
24+ testHaskellDepends = [ base hlint temporary jsaddle-warp process ];
25+ testSystemDepends = [ xvfb_run chromium ];
26+ description = "Functional Reactive Web Apps with Reflex";
27+ license = stdenv.lib.licenses.bsd3;
4828}
reflex-dom-core/reflex-dom-core.cabal+17−5
@@ -123,11 +123,23 @@ library
123123 build-depends:
124124 dependent-sum == 0.4.*
125125
126−--test-suite hlint
127−-- build-depends: base, hlint == 1.9.*
128−-- hs-source-dirs: test
129−-- main-is: hlint.hs
130−-- type: exitcode-stdio-1.0
126+test-suite hlint
127+ build-depends: base, hlint == 1.9.*
128+ hs-source-dirs: test
129+ main-is: hlint.hs
130+ type: exitcode-stdio-1.0
131+
132+test-suite gc
133+ build-depends: base
134+ , reflex-dom-core
135+ , jsaddle-warp
136+ , process
137+ , temporary
138+ hs-source-dirs: test
139+ ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
140+ ghc-prof-options: -fprof-auto
141+ main-is: gc.hs
142+ type: exitcode-stdio-1.0
131143
132144source-repository head
133145 type: git
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+54−45
@@ -1,3 +1,4 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE CPP #-}
23{-# LANGUAGE ConstraintKinds #-}
34{-# LANGUAGE DataKinds #-}
@@ -31,7 +32,6 @@ import Reflex.TriggerEvent.Base hiding (askEvents)
3132import Reflex.TriggerEvent.Class
3233
3334import Control.Concurrent.Chan
34−import Control.Exception (evaluate)
3535import Control.Lens hiding (element, ix)
3636import Control.Monad.Exception
3737import Control.Monad.Primitive
@@ -55,6 +55,7 @@ import Data.Monoid
5555import Data.Some (Some)
5656import qualified Data.Some as Some
5757import Data.Text (Text)
58+import qualified Data.Text as T
5859import GHCJS.DOM.Document (Document, createDocumentFragmentUnchecked, createElementUnchecked, createElementNSUnchecked, createTextNodeUnchecked)
5960import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
6061import qualified GHCJS.DOM.Element as Element
@@ -81,8 +82,6 @@ import GHCJS.DOM.UIEvent
8182import qualified GHCJS.DOM.Window as Window
8283import Language.Javascript.JSaddle (call, eval)
8384
84−import Debug.Trace hiding (traceEvent)
85−
8685data TriggerRef t a = TriggerRef { unTriggerRef :: IORef (Maybe (EventTrigger t a)) }
8786
8887data ImmediateDomBuilderEnv t
@@ -146,31 +145,36 @@ textNodeInternal t = do
146145{-# INLINABLE deleteBetweenInclusive #-}
147146deleteBetweenInclusive :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m ()
148147deleteBetweenInclusive s e = liftJSM $ do
149− f <- eval ("(function(s,e){ \
150− p = e.parentNode; \
151− if(p !== null) { \
152− for(;;){ \
153− x = e.previousSibling; \
154− p.removeChild(x); \
155− if(s===x) break; \
156− } \
157− p.removeChild(e); \
158− }})" :: Text)
148+ f <- eval $ T.unlines
149+ [ "(function(s,e){"
150+ , " p = e.parentNode;"
151+ , " if(p !== null) {"
152+ , " for(;;){"
153+ , " x = e.previousSibling;"
154+ , " p.removeChild(x);"
155+ , " if(s===x) break;"
156+ , " }"
157+ , " p.removeChild(e);"
158+ , " }"
159+ , "})"
160+ ]
159161 void $ call f f (s, e)
160162
161163-- | s and e must both be children of the same node and s must precede e;
162164-- all nodes between s and e will be removed, but s and e will not be removed
163165deleteBetweenExclusive :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m ()
164166deleteBetweenExclusive s e = liftJSM $ do
165− f <- eval ("(function(s,e){ \
166− p = e.parentNode; \
167− if(p !== null) { \
168− for(;;){ \
169− x = e.previousSibling; \
170− if(s===x) break; \
171− p.removeChild(x); \
172− } \
173− }})" :: Text)
167+ f <- eval $ T.unlines
168+ [ "(function(s,e){"
169+ , " p = e.parentNode;"
170+ , " if(p !== null) {"
171+ , " for(;;){"
172+ , " x = e.previousSibling;"
173+ , " if(s===x) break;"
174+ , " p.removeChild(x);"
175+ , " }"
176+ , " }})"
177+ ]
174178 void $ call f f (s, e)
175179
176180-- | s and e must both be children of the same node and s must precede e;
@@ -178,27 +182,31 @@ deleteBetweenExclusive s e = liftJSM $ do
178182{-# INLINABLE deleteUpTo #-}
179183deleteUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m ()
180184deleteUpTo s e = liftJSM $ do
181− f <- eval ("(function(s,e){ \
182− p = e.parentNode; \
183− if(p !== null) { \
184− for(;;) { \
185− x = e.previousSibling; \
186− p.removeChild(x); \
187− if(s===x) break; \
188− } \
189− }})" :: Text)
185+ f <- eval $ T.unlines
186+ [ "(function(s,e){"
187+ , " p = e.parentNode;"
188+ , " if(p !== null) {"
189+ , " for(;;) {"
190+ , " x = e.previousSibling;"
191+ , " p.removeChild(x);"
192+ , " if(s===x) break;"
193+ , " }"
194+ , " }})"
195+ ]
190196 void $ call f f (s, e)
191197
192198-- | s and all nodes between s and e will be removed, but e will not be removed
193199{-# INLINABLE deleteUpToGivenParent #-}
194200deleteUpToGivenParent :: (MonadJSM m, IsNode parent, IsNode start, IsNode end) => parent -> start -> end -> m ()
195201deleteUpToGivenParent currentParent s e = liftJSM $ do
196− f <- eval ("(function(p,s,e){ \
197− for(;;){ \
198− x = e.previousSibling; \
199− p.removeChild(x); \
200− if(s===x) break; \
201− }})" :: Text)
202+ f <- eval $ T.unlines
203+ [ "(function(p,s,e){"
204+ , " for(;;){"
205+ , " x = e.previousSibling;"
206+ , " p.removeChild(x);"
207+ , " if(s===x) break;"
208+ , " }})"
209+ ]
202210 void $ call f f (currentParent, s, e)
203211
204212type SupportsImmediateDomBuilder t m = (Reflex t, MonadJSM m, MonadJSM (Performable m), MonadHold t m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, MonadAdjust t m)
@@ -225,9 +233,8 @@ wrap e cfg = do
225233 liftIO $ forM_ mv $ \v -> writeChan events [TriggerRef triggerRef :=> TriggerInvocation v (return ())]
226234 return $ en :=> EventFilterTriggerRef triggerRef
227235 es <- do
228− let h :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en))
229− h = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
230− _ <- liftIO $ evaluate h
236+ let h :: GhcjsEventHandler er
237+ !h = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
231238 ctx <- askJSM
232239 newFanEventWithTrigger $ \(WrapArg en) t ->
233240 case DMap.lookup en eventTriggerRefs of
@@ -237,7 +244,7 @@ wrap e cfg = do
237244 writeIORef r Nothing
238245 Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
239246 evt <- DOM.event
240− mv <- lift $ h (en, GhcjsDomEvent evt)
247+ mv <- lift $ unGhcjsEventHandler h (en, GhcjsDomEvent evt)
241248 case mv of
242249 Nothing -> return ()
243250 Just v -> liftIO $ do
@@ -286,7 +293,7 @@ instance DomSpace GhcjsDomSpace where
286293 { _ghcjsEventSpec_filters =
287294 let f' = Just . GhcjsEventFilter . \case
288295 Nothing -> \evt -> do
289− mEventResult <- _ghcjsEventSpec_handler es (en, evt)
296+ mEventResult <- unGhcjsEventHandler (_ghcjsEventSpec_handler es) (en, evt)
290297 return (f mEventResult, return mEventResult)
291298 Just (GhcjsEventFilter oldFilter) -> \evt -> do
292299 (oldFlags, oldContinuation) <- oldFilter evt
@@ -304,9 +311,11 @@ data Maybe1 f a = Nothing1 | Just1 (f a)
304311
305312data GhcjsEventSpec er = GhcjsEventSpec
306313 { _ghcjsEventSpec_filters :: DMap EventName (GhcjsEventFilter er)
307− , _ghcjsEventSpec_handler :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en))
314+ , _ghcjsEventSpec_handler :: GhcjsEventHandler er
308315 }
309316
317+newtype GhcjsEventHandler er = GhcjsEventHandler { unGhcjsEventHandler :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)) }
318+
310319#ifndef USE_TEMPLATE_HASKELL
311320phantom2 :: (Functor f, Contravariant f) => f a -> f b
312321phantom2 = phantom
@@ -316,14 +325,14 @@ ghcjsEventSpec_filters :: forall er . Lens' (GhcjsEventSpec er) (DMap EventName
316325ghcjsEventSpec_filters f (GhcjsEventSpec a b) = (\a' -> GhcjsEventSpec a' b) <$> f a
317326{-# INLINE ghcjsEventSpec_filters #-}
318327ghcjsEventSpec_handler :: forall er en . Getter (GhcjsEventSpec er) ((EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)))
319−ghcjsEventSpec_handler f (GhcjsEventSpec _ b) = phantom2 (f b)
328+ghcjsEventSpec_handler f (GhcjsEventSpec _ (GhcjsEventHandler b)) = phantom2 (f b)
320329{-# INLINE ghcjsEventSpec_handler #-}
321330#endif
322331
323332instance er ~ EventResult => Default (GhcjsEventSpec er) where
324333 def = GhcjsEventSpec
325334 { _ghcjsEventSpec_filters = mempty
326− , _ghcjsEventSpec_handler = \(en, GhcjsDomEvent evt) -> do
335+ , _ghcjsEventSpec_handler = GhcjsEventHandler $ \(en, GhcjsDomEvent evt) -> do
327336 t :: DOM.EventTarget <- withIsEvent en $ Event.getTargetUnchecked evt --TODO: Rework this; defaultDomEventHandler shouldn't need to take this as an argument
328337 let e = uncheckedCastTo DOM.Element t
329338 runReaderT (defaultDomEventHandler e en) evt
reflex-dom-core/src/Reflex/Dom/Main.hs+1−1
@@ -43,7 +43,7 @@ import GHCJS.DOM.Document
4343import GHCJS.DOM.Element
4444import GHCJS.DOM.Node
4545import qualified GHCJS.DOM.Types as DOM
46−import GHCJS.DOM.Types (MonadJSM(..), JSM)
46+import GHCJS.DOM.Types (JSM)
4747
4848{-# INLINABLE mainWidget #-}
4949mainWidget :: (forall x. Widget x ()) -> JSM ()
reflex-dom-core/src/Reflex/Dom/Old.hs+4−4
@@ -69,9 +69,9 @@ import Foreign.JavaScript.TH
6969import qualified GHCJS.DOM.Element as Element
7070import GHCJS.DOM.EventM (EventM)
7171import GHCJS.DOM.NamedNodeMap as NNM
72−import GHCJS.DOM.Node (getFirstChild, getNodeName, getParentNode, getPreviousSibling, removeChildUnchecked)
72+import GHCJS.DOM.Node (getFirstChild, getNodeName, removeChildUnchecked)
7373import GHCJS.DOM.Types
74− (MonadJSM, strictEqual, liftJSM, JSM, IsElement, IsNode)
74+ (MonadJSM, liftJSM, JSM, IsElement, IsNode)
7575import qualified GHCJS.DOM.Types as DOM
7676import Reflex.Class
7777import Reflex.Dom.Builder.Class
@@ -236,7 +236,7 @@ wrapElement eh e = do
236236 h (en, GhcjsDomEvent evt) = runReaderT (eh e en) evt
237237 wrapRawElement e $ (def :: RawElementConfig EventResult t m)
238238 { _rawElementConfig_eventSpec = def
239− { _ghcjsEventSpec_handler = h
239+ { _ghcjsEventSpec_handler = GhcjsEventHandler h
240240 }
241241 }
242242
@@ -259,7 +259,7 @@ nodeClear n = do
259259 case mfc of
260260 Nothing -> return ()
261261 Just fc -> do
262− removeChildUnchecked n $ Just fc
262+ _ <- removeChildUnchecked n $ Just fc
263263 nodeClear n
264264
265265elStopPropagationNS :: forall t m en a. (MonadWidget t m) => Maybe Text -> Text -> EventName en -> m a -> m a
reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+3−3
@@ -296,12 +296,12 @@ checkboxView dAttrs dValue = do
296296 , _elementConfig_initialAttributes = Map.mapKeys (AttributeName Nothing) permanentAttrs
297297 , _elementConfig_eventSpec = GhcjsEventSpec
298298 { _ghcjsEventSpec_filters = filters
299− , _ghcjsEventSpec_handler = \(en, GhcjsDomEvent evt) -> case en of
299+ , _ghcjsEventSpec_handler = GhcjsEventHandler $ \(en, GhcjsDomEvent evt) -> case en of
300300 Click -> error "impossible"
301301 _ -> do
302302 e :: DOM.EventTarget <- withIsEvent en $ Event.getTargetUnchecked evt
303− let element = uncheckedCastTo DOM.Element e
304− mr <- runReaderT (defaultDomEventHandler element en) evt
303+ let myElement = uncheckedCastTo DOM.Element e
304+ mr <- runReaderT (defaultDomEventHandler myElement en) evt
305305 return $ ffor mr $ \(EventResult r) -> CheckboxViewEventResult $ regularToCheckboxViewEventType en r
306306 }
307307 }
reflex-dom-core/src/Reflex/Dom/Xhr.hs−1
@@ -246,7 +246,6 @@ newXMLHttpRequestWithError
246246 -> m XMLHttpRequest
247247 -- ^ The XHR request, which could for example be aborted.
248248newXMLHttpRequestWithError req cb = do
249− wv <- askJSContext
250249 xhr <- xmlHttpRequestNew
251250 ctx <- askJSM
252251 void $ liftIO $ forkIO $ handle ((`runJSM` ctx) . cb . Left) $ void . (`runJSM` ctx) $ do
reflex-dom-core/src/Reflex/Dom/Xhr/Foreign.hs+1−3
@@ -9,14 +9,12 @@ module Reflex.Dom.Xhr.Foreign (
99 , module Reflex.Dom.Xhr.Foreign
1010) where
1111
12−import Control.Exception (catch, throwIO)
12+import Control.Exception (throwIO)
1313import Control.Monad (join)
1414import Control.Monad.IO.Class (MonadIO(..))
1515import Data.Maybe (fromMaybe)
1616import Data.Text (Text)
1717import Data.ByteString (ByteString)
18−import GHCJS.Marshal (fromJSValUnchecked)
19−import GHCJS.DOM
2018import GHCJS.DOM.Enums
2119import GHCJS.DOM.EventM (EventM, on)
2220import GHCJS.DOM.EventTarget (dispatchEvent)
reflex-dom-core/test/gc.hs+50−12
@@ -3,19 +3,57 @@
33import Control.Concurrent
44import Control.Monad
55import Control.Monad.IO.Class
6+import Data.Int
7+import GHC.Stats
68import Language.Javascript.JSaddle.Warp
79import Reflex.Dom.Core
8−import Reflex.Dom.Main
10+import System.Exit
11+import System.IO.Temp
12+import System.Mem
13+import System.Process
14+
15+-- In initial testing, the minimum live bytes count was 233128 and maximum was
16+-- 363712. Going over the maximum means the test has actually failed - we
17+-- probably have a memory leak; going under the minimum doesn't indicate a
18+-- memory leak, but may mean the test needs to be updated.
19+minBytesAllowed, maxBytesAllowed :: Int64
20+(minBytesAllowed, maxBytesAllowed) = (200000, 400000)
921
1022main :: IO ()
11−main = run 3911 $ do
12− mainWidget $ do
13− let w = do
14− rec let modifyAttrs = flip pushAlways (updated d) $ \_ -> sample $ current d
15− (e, _) <- element "button" (def & modifyAttributes .~ modifyAttrs) blank
16− d <- holdDyn mempty $ mempty <$ domEvent Click e
17− return ()
18− redraw <- button "Redraw"
19− _ <- widgetHold w $ w <$ redraw
20− return ()
21− liftIO $ forever $ threadDelay 1000000000
23+main = do
24+ mainThread <- myThreadId
25+ withSystemTempDirectory "reflex-dom-core_test_gc" $ \tmp -> do
26+ browserProcess <- spawnCommand $ "xvfb-run -a chromium --user-data-dir=" ++ tmp ++ " http://localhost:3911"
27+ let finishTest result = do
28+ interruptProcessGroupOf browserProcess
29+ throwTo mainThread result
30+ run 3911 $ do
31+ liftIO $ putStrLn "Running..."
32+ mainWidget $ do
33+ let w = do
34+ rec let modifyAttrs = flip pushAlways (updated d) $ \_ -> sample $ current d
35+ (e, _) <- element "button" (def & modifyAttributes .~ modifyAttrs) blank
36+ d <- holdDyn mempty $ mempty <$ domEvent Click e
37+ return ()
38+ postBuild <- getPostBuild
39+ let f n = liftIO $ if n < 3000
40+ then do performMajorGC
41+ gcStats <- getGCStats
42+ print $ currentBytesUsed gcStats
43+ when (currentBytesUsed gcStats < minBytesAllowed) $ do
44+ putStrLn "FAILED: currentBytesUsed < minBytesAllowed"
45+ finishTest $ ExitFailure 2
46+ when (currentBytesUsed gcStats > maxBytesAllowed) $ do
47+ putStrLn "FAILED: currentBytesUsed > maxBytesAllowed"
48+ finishTest $ ExitFailure 1
49+ return $ Just $ succ n
50+ else do putStrLn "SUCCEEDED"
51+ finishTest ExitSuccess
52+ return Nothing
53+ rec redraw <- performEvent <=< delay 0 $ f <$> leftmost
54+ [ (0 :: Int) <$ postBuild
55+ , fmapMaybe id redraw
56+ ]
57+ _ <- widgetHold w $ w <$ redraw
58+ return ()
59+ liftIO $ forever $ threadDelay 1000000000
reflex-dom-core/test/hlint.hs+7
@@ -8,5 +8,12 @@ main = do
88 , "--ignore=Redundant do"
99 , "--ignore=Use camelCase"
1010 , "--ignore=Redundant $"
11+ , "--ignore=Use &&"
12+ , "--ignore=Use &&&"
13+ , "--ignore=Use const"
14+ , "--ignore=Use >=>"
15+ , "--ignore=Use ."
16+ , "--ignore=Use unless"
17+ , "--cpp-define=USE_TEMPLATE_HASKELL"
1118 ]
1219 if null ideas then exitSuccess else exitFailure