This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "snap-server".

The branch, master has been updated
       via  d49aa1bdffa623fec97b6a0a087908382fff027d (commit)
      from  1759bf85decea74b83a5d0035f7b05a5bae8e5cf (commit)


Summary of changes:
 src/Snap/Internal/Http/Server/LibevBackend.hs |   86 ++++++++++++++++---------
 test/common/Snap/Test/Common.hs               |   43 ++++++++++++-
 test/runTestsAndCoverage.sh                   |    5 +-
 test/suite/Snap/Internal/Http/Server/Tests.hs |   55 +++++++++++++++-
 test/suite/Test/Blackbox.hs                   |   36 ----------
 5 files changed, 154 insertions(+), 71 deletions(-)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit d49aa1bdffa623fec97b6a0a087908382fff027d
Author: Gregory Collins <[email protected]>
Date:   Tue Sep 21 14:58:30 2010 -0400

    Libev backend: kill active connections properly and add a test

diff --git a/src/Snap/Internal/Http/Server/LibevBackend.hs 
b/src/Snap/Internal/Http/Server/LibevBackend.hs
index 38a67ea..9e74833 100644
--- a/src/Snap/Internal/Http/Server/LibevBackend.hs
+++ b/src/Snap/Internal/Http/Server/LibevBackend.hs
@@ -79,7 +79,7 @@ data Backend = Backend
     , _asyncObj          :: !EvAsyncPtr
     , _killCb            :: !(FunPtr AsyncCallback)
     , _killObj           :: !EvAsyncPtr
-    , _connectionThreads :: !(HashMap ThreadId ())
+    , _connectionThreads :: !(HashMap ThreadId Connection)
     , _backendCPU        :: !Int
     , _backendFreed      :: !(MVar ())
     }
@@ -362,30 +362,47 @@ timerCallback loop tmr ioref tmv _ _ _ = do
           evTimerAgain loop tmr
 
 
-freeConnection :: Connection -> IO ()
-freeConnection conn = ignoreException $ do
-    withMVar loopLock $ \_ -> block $ do
-        debug $ "freeConnection (" ++ show fd ++ ")"
+-- if you already hold the loop lock, you are entitled to destroy a connection
+destroyConnection :: Connection -> IO ()
+destroyConnection conn = do
+    debug "Backend.destroyConnection: closing socket and killing connection"
+    c_close fd
 
-        c_close fd
+    -- stop and free timer object
+    evTimerStop loop timerObj
+    freeEvTimer timerObj
+    freeTimerCallback timerCb
 
-        -- stop and free timer object
-        evTimerStop loop timerObj
-        freeEvTimer timerObj
-        freeTimerCallback timerCb
+    -- stop and free i/o objects
+    evIoStop loop ioWrObj
+    freeEvIo ioWrObj
+    freeIoCallback ioWrCb
+
+    evIoStop loop ioRdObj
+    freeEvIo ioRdObj
+    freeIoCallback ioRdCb
+
+  where
+    backend    = _backend conn
+    loop       = _evLoop backend
 
-        -- stop and free i/o objects
-        evIoStop loop ioWrObj
-        freeEvIo ioWrObj
-        freeIoCallback ioWrCb
+    fd         = _socketFd conn
+    ioWrObj    = _connWriteIOObj conn
+    ioWrCb     = _connWriteIOCallback conn
+    ioRdObj    = _connReadIOObj conn
+    ioRdCb     = _connReadIOCallback conn
+    timerObj   = _timerObj conn
+    timerCb    = _timerCallback conn
 
-        evIoStop loop ioRdObj
-        freeEvIo ioRdObj
-        freeIoCallback ioRdCb
 
+freeConnection :: Connection -> IO ()
+freeConnection conn = ignoreException $ do
+    withMVar loopLock $ \_ -> block $ do
+        debug $ "freeConnection (" ++ show fd ++ ")"
+        destroyConnection conn
         tid <- readMVar $ _connThread conn
 
-        -- removal the thread id from the backend set
+        -- remove the thread id from the backend set
         H.delete tid $ _connectionThreads backend
 
         -- wake up the event loop so it can be apprised of the changes
@@ -396,14 +413,7 @@ freeConnection conn = ignoreException $ do
     loop       = _evLoop backend
     loopLock   = _loopLock backend
     asyncObj   = _asyncObj backend
-
     fd         = _socketFd conn
-    ioWrObj    = _connWriteIOObj conn
-    ioWrCb     = _connWriteIOCallback conn
-    ioRdObj    = _connReadIOObj conn
-    ioRdCb     = _connReadIOCallback conn
-    timerObj   = _timerObj conn
-    timerCb    = _timerCallback conn
 
 
 ignoreException :: IO () -> IO ()
@@ -412,13 +422,26 @@ ignoreException = handle (\(_::SomeException) -> return 
())
 
 freeBackend :: Backend -> IO ()
 freeBackend backend = ignoreException $ block $ do
-    -- note: we only get here after an unloop
+    -- note: we only get here after an unloop, so we have the loop lock
+    -- here. (?)
+
     -- kill everything in thread table
     tset <- H.toList $ _connectionThreads backend
+
+    let nthreads = Prelude.length tset
+
+    debug $ "Backend.freeBackend: killing active connection threads"
+
+    Prelude.mapM_ (destroyConnection . snd) tset
+
+    -- kill the threads twice, they're probably getting stuck in the
+    -- freeConnection 'finally' handler
     Prelude.mapM_ (killThread . fst) tset
+    Prelude.mapM_ (killThread . fst) tset
+
+    debug $ "Backend.freeBackend: " ++ show nthreads ++ " thread(s) killed"
+    debug $ "Backend.freeBackend: destroying libev resources"
 
-    debug $ "Backend.freeBackend: all threads killed"
-    debug $ "Backend.freeBackend: destroying resources"
     freeEvIo acceptObj
     freeIoCallback acceptCb
     c_close fd
@@ -453,7 +476,10 @@ freeBackend backend = ignoreException $ block $ do
 withConnection :: Backend -> Int -> (Connection -> IO ()) -> IO ()
 withConnection backend cpu proc = go
   where
-    threadProc conn = ignoreException (proc conn) `finally` freeConnection conn
+    threadProc conn = (do
+        x <- blocked
+        debug $ "withConnection/threadProc: we are blocked? " ++ show x
+        proc conn) `finally` freeConnection conn
 
     go = do
         debug $ "withConnection: reading from chan"
@@ -542,7 +568,7 @@ withConnection backend cpu proc = go
 
         tid <- forkOnIO cpu $ threadProc conn
 
-        H.update tid () (_connectionThreads backend)
+        H.update tid conn (_connectionThreads backend)
         putMVar thrmv tid
 
 
diff --git a/test/common/Snap/Test/Common.hs b/test/common/Snap/Test/Common.hs
index a8bbc0e..b4ddd9f 100644
--- a/test/common/Snap/Test/Common.hs
+++ b/test/common/Snap/Test/Common.hs
@@ -1,4 +1,6 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-# LANGUAGE OverloadedStrings    #-}
 {-# LANGUAGE ScopedTypeVariables  #-}
 
 
@@ -7,16 +9,19 @@ module Snap.Test.Common where
 import           Control.Exception (SomeException)
 import           Control.Monad
 import           Control.Monad.CatchIO
+import           Data.ByteString (ByteString)
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as L
 import           Data.ByteString.Internal (c2w)
+import qualified Data.DList as D
+import           Network.Socket
+import qualified Network.Socket.ByteString as N
 import           Prelude hiding (catch)
 import           Test.QuickCheck
 import           System.Timeout
 
 import           Snap.Internal.Iteratee.Debug ()
 
-import System.IO
 
 instance Arbitrary S.ByteString where
     arbitrary = liftM (S.pack . map c2w) arbitrary
@@ -45,3 +50,39 @@ expectExceptionBeforeTimeout act nsecs = do
                then return False
                else return True
                    
+
+withSock :: Int -> (Socket -> IO a) -> IO a
+withSock port go = do
+    addr <- liftM (addrAddress . Prelude.head) $
+            getAddrInfo (Just myHints)
+                        (Just "127.0.0.1")
+                        (Just $ show port)
+
+    sock <- socket AF_INET Stream defaultProtocol
+    connect sock addr
+
+    go sock `finally` sClose sock
+
+  where
+    myHints = defaultHints { addrFlags = [ AI_NUMERICHOST ] }
+
+
+recvAll :: Socket -> IO ByteString
+recvAll sock = do
+    d <- f D.empty sock
+    return $ S.concat $ D.toList d
+
+  where
+    f d sk = do
+        s <- N.recv sk 100000
+        if S.null s
+          then return d
+          else f (D.snoc d s) sk
+
+
+ditchHeaders :: [ByteString] -> [ByteString]
+ditchHeaders ("":xs)   = xs
+ditchHeaders ("\r":xs) = xs
+ditchHeaders (_:xs)    = ditchHeaders xs
+ditchHeaders []        = []
+
diff --git a/test/runTestsAndCoverage.sh b/test/runTestsAndCoverage.sh
index a1687c5..d615dec 100755
--- a/test/runTestsAndCoverage.sh
+++ b/test/runTestsAndCoverage.sh
@@ -2,7 +2,10 @@
 
 set -e
 
-export DEBUG=testsuite
+if [ "x$DEBUG" == "x" ]; then
+    export DEBUG=testsuite
+fi
+
 SUITE=./dist/build/testsuite/testsuite
 
 rm -f *.tix
diff --git a/test/suite/Snap/Internal/Http/Server/Tests.hs 
b/test/suite/Snap/Internal/Http/Server/Tests.hs
index 25fe6ea..e3f668f 100644
--- a/test/suite/Snap/Internal/Http/Server/Tests.hs
+++ b/test/suite/Snap/Internal/Http/Server/Tests.hs
@@ -7,10 +7,10 @@ module Snap.Internal.Http.Server.Tests
   ( tests ) where
 
 import             Control.Concurrent
-import             Control.Exception (try, SomeException)
+import             Control.Exception (try, throwIO, SomeException)
 import             Control.Monad
 import "monads-fd" Control.Monad.Trans
-import qualified   Data.ByteString as S
+import qualified   Data.ByteString.Char8 as S
 import qualified   Data.ByteString.Lazy as L
 import qualified   Data.ByteString.Lazy.Char8 as LC
 import             Data.ByteString (ByteString)
@@ -26,6 +26,7 @@ import             Data.Time.Calendar
 import             Data.Time.Clock
 import             Data.Word
 import qualified   Network.HTTP as HTTP
+import qualified   Network.Socket.ByteString as N
 import             Prelude hiding (take)
 import qualified   Prelude
 import             Test.Framework
@@ -34,9 +35,11 @@ import             Test.HUnit hiding (Test, path)
 
 import qualified   Snap.Http.Server as Svr
 
+import             Snap.Internal.Debug
 import             Snap.Internal.Http.Types
 import             Snap.Internal.Http.Server
 import             Snap.Iteratee
+import             Snap.Test.Common
 import             Snap.Types
 
 #ifdef LIBEV
@@ -63,6 +66,7 @@ tests = [ testHttpRequest1
         , testPartialParse
         , testMethodParsing
         , testServerStartupShutdown
+        , testServerShutdownWithOpenConns
         , testChunkOn1_0
         , testSendFile
         , testTrivials]
@@ -564,7 +568,7 @@ testChunkOn1_0 = testCase "server/transfer-encoding 
chunked" $ do
     assertBool "connection close" $ S.isInfixOf "connection: close" output
 
   where
-    lower = S.map (c2w . toLower . w2c) . S.concat . L.toChunks
+    lower = S.map toLower . S.concat . L.toChunks
 
     f :: ServerHandler
     f _ req = do
@@ -765,3 +769,48 @@ testServerStartupShutdown = testCase 
"server/startup/shutdown" $ do
     waitabit = threadDelay $ 2*((10::Int)^(6::Int))
     port = 8145
 
+
+testServerShutdownWithOpenConns :: Test
+testServerShutdownWithOpenConns = testCase "server/shutdown-open-conns" $ do
+    tid <- forkIO $
+           httpServe "*"
+                     port
+                     "localhost"
+                     Nothing
+                     Nothing
+                     (runSnap pongServer)
+
+    waitabit
+
+    result <- newEmptyMVar
+
+    forkIO $ do
+        e <- try $ withSock port $ \sock -> do
+                 N.sendAll sock "GET /"
+                 waitabit
+                 killThread tid
+                 waitabit
+                 N.sendAll sock "pong HTTP/1.1\r\n"
+                 N.sendAll sock "Host: 127.0.0.1\r\n"
+                 N.sendAll sock "Content-Length: 0\r\n"
+                 N.sendAll sock "Connection: close\r\n\r\n"
+
+                 resp <- recvAll sock
+                 when (S.null resp) $ throwIO 
Backend.BackendTerminatedException
+
+                 let s = S.unpack $ Prelude.head $ ditchHeaders $ S.lines resp
+                 debug $ "got HTTP response " ++ s ++ ", we shouldn't be 
here...."
+
+        putMVar result e
+
+    r <- takeMVar result
+
+    case r of
+      (Left (_::SomeException)) -> return ()
+      (Right _)                 -> assertFailure "socket didn't get killed"
+
+
+  where
+    waitabit = threadDelay $ 2*((10::Int)^(6::Int))
+    port = 8146
+
diff --git a/test/suite/Test/Blackbox.hs b/test/suite/Test/Blackbox.hs
index aa27836..ac827ca 100644
--- a/test/suite/Test/Blackbox.hs
+++ b/test/suite/Test/Blackbox.hs
@@ -117,22 +117,6 @@ testRot13 port = testProperty "blackbox/rot13" $
         QC.assert $ txt == rot13 doc
 
 
-withSock :: Int -> (Socket -> IO a) -> IO a
-withSock port go = do
-    addr <- liftM (addrAddress . Prelude.head) $
-            getAddrInfo (Just myHints)
-                        (Just "127.0.0.1")
-                        (Just $ show port)
-
-    sock <- socket AF_INET Stream defaultProtocol
-    connect sock addr
-
-    go sock `finally` sClose sock
-
-  where
-    myHints = defaultHints { addrFlags = [ AI_NUMERICHOST ] }
-
-
 testSlowLoris :: Int -> Test
 testSlowLoris port = testCase "blackbox/slowloris" $ withSock port go
 
@@ -153,26 +137,6 @@ testSlowLoris port = testCase "blackbox/slowloris" $ 
withSock port go
         loris sock
 
 
-ditchHeaders :: [ByteString] -> [ByteString]
-ditchHeaders ("":xs)   = xs
-ditchHeaders ("\r":xs) = xs
-ditchHeaders (_:xs)    = ditchHeaders xs
-ditchHeaders []        = []
-
-
-recvAll :: Socket -> IO ByteString
-recvAll sock = do
-    d <- f D.empty sock
-    return $ S.concat $ D.toList d
-
-  where
-    f d sk = do
-        s <- N.recv sk 100000
-        if S.null s
-          then return d
-          else f (D.snoc d s) sk
-
-
 testBlockingRead :: Int -> Test
 testBlockingRead port = testCase "blackbox/testBlockingRead" $
                         withSock port $ \sock -> do
-----------------------------------------------------------------------


hooks/post-receive
-- 
snap-server
_______________________________________________
Snap mailing list
[email protected]
http://mailman-mail5.webfaction.com/listinfo/snap

Reply via email to