packages feed

hexpat 0.19 → 0.19.1

raw patch · 16 files changed

+187/−25 lines, 16 filesdep ~textPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: text

API changes (from Hackage documentation)

Files

Text/XML/Expat/Internal/IO.hs view
@@ -56,6 +56,7 @@   encodingToString   ) where +import Control.Concurrent import Control.Exception (bracket) import Control.DeepSeq import Control.Monad@@ -200,6 +201,7 @@     return $ XMLParseError err loc  data ExpatHandlers = ExpatHandlers+    (FunPtr CXMLDeclarationHandler)     (FunPtr CStartElementHandler)     (FunPtr CEndElementHandler)     (FunPtr CCharacterDataHandler)@@ -258,12 +260,13 @@               (xmlSetSkippedEntityHandler pp)               mSkipH     -        return $ ExpatHandlers cStartH cEndH cCharH mExtH mSkipH +        return $ ExpatHandlers cXMLDeclH cStartH cEndH cCharH mExtH mSkipH              cStartCDataH cEndCDataH cProcessingInstructionH cCommentH          unsafeReleaseHandlers :: ExpatHandlers -> IO ()-    unsafeReleaseHandlers (ExpatHandlers cStartH cEndH cCharH mcExtH mcSkipH +    unsafeReleaseHandlers (ExpatHandlers cXMLDeclH cStartH cEndH cCharH mcExtH mcSkipH              cStartCDataH cEndCDataH cProcessingInstructionH cCommentH) = do+        freeHaskellFunPtr cXMLDeclH         freeHaskellFunPtr cStartH         freeHaskellFunPtr cEndH         freeHaskellFunPtr cCharH@@ -280,13 +283,64 @@ cFromBool  = fromBool  doParseChunk :: ParserPtr -> BS.ByteString -> Bool -> IO (Bool)-doParseChunk a1 a2 a3 =+doParseChunk = ensureBoundThread $ \a1 a2 a3 ->   withBStringLen a2 $ \(a2'1, a2'2) ->   let {a3' = cFromBool a3} in   doParseChunk'_ a1 a2'1  a2'2 a3' >>= \res ->   let {res' = unStatus res} in   return (res') +data WorkerIface = WorkerIface (MVar (ParserPtr, BS.ByteString, Bool)) (MVar Bool)++workerIfaceRef :: IORef (Maybe WorkerIface)+{-# NOINLINE workerIfaceRef #-}+workerIfaceRef = unsafePerformIO $ newIORef Nothing++-- If the calling thread is not bound, we delegate to a bound thread, because+-- otherwise we get a thread explosion (this is true in ghc-6.12.X).+-- See test/thread-leak/ directory for a test case.+ensureBoundThread :: (ParserPtr -> BS.ByteString -> Bool -> IO Bool)+                  -> ParserPtr+                  -> BS.ByteString+                  -> Bool+                  -> IO Bool+ensureBoundThread doit p bs last = do+    bound <- isCurrentThreadBound+    if rtsSupportsBoundThreads && not bound+        then delegate+        else doit p bs last+  where+    delegate = do+        mIface <- readIORef workerIfaceRef+        case mIface of+            Just iface -> pipeTo iface+            Nothing -> do+                inV <- newEmptyMVar+                outV <- newEmptyMVar+                let iface = WorkerIface inV outV+                justSetItGlobally <- atomicModifyIORef workerIfaceRef $ \mIface ->+                    case mIface of+                        Just _  -> (mIface, False)+                        Nothing -> (Just iface, True) +                if justSetItGlobally+                    then do+                        _ <- forkOS $ worker iface+                        pipeTo iface+                    else+                        -- If it wasn't changed, then this is because we got a race+                        -- condition with another thread.  We resolve this by trying+                        -- again.  We'll succeed on the second attempt.  The mvars+                        -- we allocated here will be GC'd.+                        delegate++    pipeTo (WorkerIface inV outV) =+        putMVar inV (p, bs, last) >> takeMVar outV++    worker (WorkerIface inV outV) = forever $+        putMVar outV =<< uncurry3 doit =<< takeMVar inV+      where+        uncurry3 f (a, b, c) = f a b c + -- | Parse error, consisting of message text and error location data XMLParseError = XMLParseError String XMLParseLocation deriving (Eq, Show) @@ -622,7 +676,7 @@                               -> Ptr CChar   -- publicID                               -> IO () -foreign import ccall safe "wrapper"+foreign import ccall unsafe "wrapper"   mkCExternalEntityRefHandler :: CExternalEntityRefHandler                               -> IO (FunPtr CExternalEntityRefHandler) 
hexpat.cabal view
@@ -1,6 +1,6 @@ Cabal-Version: >= 1.6 Name: hexpat-Version: 0.19+Version: 0.19.1 Synopsis: XML parser/formatter based on expat Description:   This package provides a general purpose Haskell XML library using Expat to@@ -42,11 +42,21 @@   .   Ensure @libexpat.dll@ can be found in your system PATH (or copy it into your executable's directory).   .+  BOUND VS. UNBOUND THREADS: GHC (at least versions 6.12.X) will spawn threads+  if you call a safe FFI callback from an unbound thread.  This can get out of+  control in a busy application.  To avoid this, from version 0.19.1 we now delegate+  processing to a single worker thread if the calling thread is not bound.+  This essentially means that hexpat currently won't exploit multicores very well.+  It also means that hexpat may be more efficient on threads spawned with forkOS+  (to give you a bound thread) rather than forkIO.+  .   ChangeLog: 0.15 changes intended to fix a (rare) \"error: a C finalizer called back into Haskell.\"     that seemed only to happen only on ghc6.12.X; 0.15.1 Fix broken Annotated parse;     0.16 switch from mtl to transformers; 0.17 fix mapNodeContainer & rename some things.;     0.18 rename defaultEncoding to overrideEncoding. 0.18.3 formatG and indent were demanding list-    items more than once (inefficient in chunked processing); 0.19 add Extended.hs.+    items more than once (inefficient in chunked processing); 0.19 add Extended.hs;+    0.19.1 fix a memory leak introduced in 0.19, delegate parsing to bound thread+    if unbound (see note above) Category: XML License: BSD3 License-File: LICENSE@@ -75,7 +85,16 @@   test/suite/Text/XML/Expat/UnitTests.hs,   test/suite/Text/XML/Expat/Tests.hs,   test/suite/Text/XML/Expat/Cursor/Tests.hs,-  test/suite/Text/XML/Expat/ParseFormat.hs+  test/suite/Text/XML/Expat/ParseFormat.hs,+  test/thread-leak/build.sh,+  test/thread-leak/callme.c,+  test/thread-leak/cleak.c,+  test/thread-leak/clean.sh,+  test/thread-leak/thread-leak.hs,+  test/hexpat-leak/instant-message.llsd,+  test/hexpat-leak/Parse.hs,+  test/hexpat-leak/run.sh,+  test/hexpat-leak/build.sh Build-Type: Simple Stability: beta source-repository head@@ -87,7 +106,7 @@     base >= 3 && < 5,     bytestring,     transformers,-    text >= 0.5 && < 0.8,+    text >= 0.5 && < 0.9,     utf8-string == 0.3.*,     deepseq == 1.1.*,     containers,
+ test/hexpat-leak/Parse.hs view
@@ -0,0 +1,24 @@+-- Thanks to Bryan O'Sullivan for this test case.+-- hexpat will spawn zillions of threads (which is seen as huge virtual memory+-- usage in top).  This is now fixed in 0.19.1.++import Control.Concurrent+import Control.Monad+import qualified Data.ByteString as B+import Text.XML.Expat.Tree+import System.Environment++main = do+  [path, threads, reads] <- getArgs+  let nthreads = read threads+  qs <- newQSem 0+  replicateM_ nthreads $ do+    forkIO $ do+      replicateM_ (read reads) $ do+        bs <- B.readFile path+        case parse' defaultParseOptions bs of+          Left err -> print err+          Right p -> print (p :: UNode B.ByteString)+      signalQSem qs+  replicateM_ nthreads $ waitQSem qs+  putStrLn "done"
+ test/hexpat-leak/build.sh view
@@ -0,0 +1,2 @@+rm -f Parse.o+ghc -O Parse.hs --make -threaded
+ test/hexpat-leak/instant-message.llsd view
@@ -0,0 +1,3 @@+<llsd><map><key>action</key><string>instant_message_session</string><key>params</key><map><key>agent_params</key><map><key>agent_id</key><uuid>b6cc3bbb-fd33-4776-9326-3e9c8667b0f8</uuid><key>check_estate</key><integer>1</integer><key>god_level</key><integer>0</integer><key>limited_to_estate</key><integer>1</integer></map><key>message_params</key><map><key>data</key><map><key>binary_bucket</key><binary encoding="base64">AA==</binary></map><key>from_group</key><integer>0</integer><key>from_id</key><uuid>b6cc3bbb-fd33-4776-9326-3e9c8667b0f8</uuid><key>from_name</key><string>Lolita Cheri</string><key>id</key><uuid>61e7b1ae-36c2-bb2c-feca-12a96c88ff22</uuid><key>message</key><string>/*/*/*/*JEUX*/*/*/*/*GAMES*/*/*/*/+*/*NANCY*/*ARTISY*/*DIEGO*/*WACKY*/*ALICE*/*NOUVEAU! Le multi NANCY .. jouer entre amis!*/*ALLEZ! VENEZ VOUS AMUSER ET GAGNER DES L$!+http://slurl.com/secondlife/Alia/170/161/25</string><key>offline</key><integer>0</integer><key>parent_estate_id</key><integer>1</integer><key>position</key><array><real>41.1074981689453125</real><real>224.397003173828125</real><real>35.55573272705078125</real></array><key>region_id</key><uuid>c54746d1-99bd-4715-a4ca-5a1d1064b8b1</uuid><key>source</key><integer>0</integer><key>timestamp</key><integer>0</integer><key>to_id</key><uuid>61e7b1ae-36c2-bb2c-feca-12a96c88ff22</uuid><key>ttl</key><integer>1</integer><key>type</key><integer>17</integer></map></map></map></llsd>
+ test/hexpat-leak/run.sh view
@@ -0,0 +1,1 @@+./Parse instant-message.llsd 10 10000 >/dev/null
test/hexpat-tests.cabal view
@@ -11,7 +11,7 @@    build-depends:     HUnit < 1.3,-    QuickCheck == 1.2.0.0,+    QuickCheck >= 2.1.0.3 && < 2.2,     base >= 3 && < 5,     bytestring,     containers,@@ -19,15 +19,16 @@     haskell98,     transformers,     deepseq >= 1.1.0.0,-    parallel < 2.2.0.0,-    QuickCheck == 1.2.0.0,-    test-framework < 0.3,-    test-framework-hunit < 0.3,-    test-framework-quickcheck < 0.3,+    parallel == 3.1.*,+    test-framework == 0.3.*,+    test-framework-hunit == 0.2.*,+    test-framework-quickcheck2 == 0.2.*,     text >= 0.5,     utf8-string >= 0.3.3,-    List >= 0.4+    List >= 0.4,+    monads-fd,+    random -  ghc-options: -Wall -fhpc+  ghc-options: -Wall -fhpc -threaded   if impl(ghc >= 6.8)     ghc-options: -fwarn-tabs
test/suite/TestSuite.hs view
@@ -4,6 +4,7 @@ import qualified Text.XML.Expat.Cursor.Tests import qualified Text.XML.Expat.Proc.Tests import qualified Text.XML.Expat.ParseFormat+import qualified Text.XML.Expat.ParallelTest  import Test.Framework (defaultMain, testGroup) @@ -17,5 +18,7 @@                             Text.XML.Expat.Cursor.Tests.tests                 , testGroup "Text.XML.Expat.ParseFormat"                             Text.XML.Expat.ParseFormat.tests+                , testGroup "Text.XML.Expat.ParallelTest"+                            Text.XML.Expat.ParallelTest.tests                 ] 
test/suite/Text/XML/Expat/Cursor/Tests.hs view
@@ -6,7 +6,7 @@ import           Control.Monad (replicateM) import           Data.Maybe import           Test.Framework (Test)-import           Test.Framework.Providers.QuickCheck+import           Test.Framework.Providers.QuickCheck2 import           Test.QuickCheck import           Text.XML.Expat.Tests import           Text.XML.Expat.Cursor
test/suite/Text/XML/Expat/Proc/Tests.hs view
@@ -6,7 +6,7 @@  import           Data.Maybe import           Test.Framework (Test)-import           Test.Framework.Providers.QuickCheck+import           Test.Framework.Providers.QuickCheck2 import           Test.QuickCheck import           Text.XML.Expat.Tests import           Text.XML.Expat.Proc
test/suite/Text/XML/Expat/Tests.hs view
@@ -9,8 +9,10 @@   , testAttrSet ) where +import           Control.Applicative import           Control.Monad (liftM) import           Data.ByteString.Char8 (ByteString)+import qualified Data.Map as M import           Test.QuickCheck import           Text.XML.Expat.Cursor (Cursor) import           Text.XML.Expat.Tree@@ -49,14 +51,12 @@   instance Arbitrary TNode where-    coarbitrary = undefined--    arbitrary = depth 0+    arbitrary = mkElem 0       where         depth :: Int -> Gen TNode         depth n = do-            which <- (arbitrary :: Gen Bool)-            if which then mkElem n else mkText+            prob <- (choose (0, 1) :: Gen Float)+            if prob < 0.75 then mkElem n else mkText           mkAttr = do@@ -70,8 +70,9 @@             nchildren <- if n > 3                            then return 0                            else choose ((0,6) :: (Int,Int))-            nattrs    <- choose ((0,3) :: (Int,Int))-            attrs     <- sequence $ replicate nattrs mkAttr+            nattrs    <- choose ((0,4) :: (Int,Int))+            attrs     <- M.toList . M.fromList    -- remove duplicate attributes+                      <$> sequence (replicate nattrs mkAttr)             children  <- sequence $ replicate nchildren (depth (n+1))             tagname   <- elements testTagSet 
+ test/thread-leak/build.sh view
@@ -0,0 +1,1 @@+ghc thread-leak.hs callme.c --make -threaded
+ test/thread-leak/callme.c view
@@ -0,0 +1,9 @@++void callme(void (*cb)())+{+    int i;++    for (i = 0; i < 10; i++)+        cb();+}+
+ test/thread-leak/cleak.c view
@@ -0,0 +1,1 @@+
+ test/thread-leak/clean.sh view
@@ -0,0 +1,1 @@+rm -f *.hi *.o thread-leak thread-leak_stub.*
+ test/thread-leak/thread-leak.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE ForeignFunctionInterface, CPP #-}++-- | In ghc 6.12.3, this program spawns lots of thread when os = False.+-- If you set os = True, then it doesn't.+--+-- You can observe this either by seeing the virtual memory go crazy in top,+-- or by running in gdb and pressing ctrl-C.+import Control.Concurrent+import Control.Exception+import Control.Monad+import qualified Data.ByteString as B+import Text.XML.Expat.Tree+import System.Environment+import Data.IORef+import Foreign++os = False++foreign import ccall safe "callme" callme :: FunPtr (IO ()) -> IO ()+foreign import ccall safe "wrapper" mkPlain :: IO () -> IO (FunPtr (IO ()))++main = do+  args <- getArgs+  let (nthreads, nloops) = case args of+          threads : loops : _ -> (read threads, read loops)+          _                   -> (10, 10000)+  putStrLn $ show nthreads++" threads with "++show nloops++" loops each"+++             ", using '"++(if os then "forkOS" else "forkIO")++"'"+  qs <- newQSem 0+  replicateM_ nthreads $ do+    (if os then forkOS else forkIO) $ do+      cRef <- newIORef 0+      cb <- mkPlain $ modifyIORef cRef $ \x -> x `seq` (x+1)  +      replicateM_ nloops $ callme cb+      freeHaskellFunPtr cb+      c <- readIORef cRef+      -- 'callme' calls us back 10 times+      when (c /= nloops*10) $ fail $ "went really wrong: "++show (c, nloops*10)+      signalQSem qs+  replicateM_ nthreads $ waitQSem qs+  putStrLn "done"+