packages feed

amqp 0.8.2 → 0.8.3

raw patch · 4 files changed

+81/−22 lines, 4 filesdep +vector

Dependencies added: vector

Files

Network/AMQP.hs view
@@ -54,6 +54,7 @@     openConnection,
     openConnection',
     openConnection'',
+    closeChannel,
     closeConnection,
     addConnectionClosedHandler,
 
+ Network/AMQP/ChannelAllocator.hs view
@@ -0,0 +1,50 @@+module Network.AMQP.ChannelAllocator where
+
+import qualified Data.Vector.Mutable as V
+import Data.Word
+import Data.Bits
+
+newtype ChannelAllocator = ChannelAllocator (V.IOVector Word64)
+
+newChannelAllocator :: IO ChannelAllocator
+newChannelAllocator =
+    fmap ChannelAllocator $ V.replicate 1024 0
+
+allocateChannel :: ChannelAllocator -> IO Int
+allocateChannel (ChannelAllocator c) = do
+    maybeIx <- findFreeIndex c
+    case maybeIx of
+        Just chunk -> do
+            word <- V.read c chunk
+            let offset = findUnsetBit word
+            V.write c chunk (setBit word offset)
+            return (chunk*64 + offset)
+        Nothing -> error "all channels are already allocated"
+
+freeChannel :: ChannelAllocator -> Int -> IO Bool
+freeChannel (ChannelAllocator c) ix = do
+    let (chunk, offset) = divMod ix 64
+    word <- V.read c chunk
+    if testBit word offset
+        then do
+            V.write c chunk $ clearBit word offset
+            return True
+        else return False
+
+findUnsetBit :: Word64 -> Int
+findUnsetBit w = go 0
+  where
+    go 65 = error "findUnsetBit"
+    go ix | not (testBit w ix) = ix
+    go ix = go (ix+1)
+
+findFreeIndex :: V.IOVector Word64 -> IO (Maybe Int)
+findFreeIndex vec = go 0
+  where
+    -- TODO: make this faster
+    go 1024 = return Nothing
+    go ix = do
+        v <- V.read vec ix
+        if v /= 0xffffffffffffffff
+            then return $ Just ix
+            else go $! ix+1
Network/AMQP/Internal.hs view
@@ -28,6 +28,7 @@ import Network.AMQP.Types
 import Network.AMQP.Helpers
 import Network.AMQP.Generated
+import Network.AMQP.ChannelAllocator
 
 
 data DeliveryMode = Persistent -- ^ the message will survive server restarts (if the queue is durable)
@@ -127,13 +128,13 @@ 
 data Connection = Connection {
                     connHandle :: Conn.Connection,
-                    connChannels :: (MVar (IM.IntMap (Channel, ThreadId))), --open channels (channelID => (Channel, ChannelThread))
+                    connChanAllocator :: ChannelAllocator,
+                    connChannels :: MVar (IM.IntMap (Channel, ThreadId)), -- open channels (channelID => (Channel, ChannelThread))
                     connMaxFrameSize :: Int, --negotiated maximum frame size
                     connClosed :: MVar (Maybe String),
                     connClosedLock :: MVar (), -- used by closeConnection to block until connection-close handshake is complete
                     connWriteLock :: MVar (), -- to ensure atomic writes to the socket
                     connClosedHandlers :: MVar [IO ()],
-                    lastChannelID :: MVar Int, -- for auto-incrementing the channelIDs
                     connLastReceived :: MVar Int64, -- the timestamp from a monotonic clock when the last frame was received
                     connLastSent :: MVar Int64 -- the timestamp from a monotonic clock when the last frame was written
                 }
@@ -235,14 +236,15 @@ 
     --build Connection object
     cChannels <- newMVar IM.empty
-    lastChanID <- newMVar 0
     cClosed <- newMVar Nothing
+    cChanAllocator <- newChannelAllocator
+    _ <- allocateChannel cChanAllocator -- allocate channel 0
     writeLock <- newMVar ()
     ccl <- newEmptyMVar
     cClosedHandlers <- newMVar []
     cLastReceived <- getTimestamp >>= newMVar
     cLastSent <- getTimestamp >>= newMVar
-    let conn = Connection handle cChannels (fromIntegral maxFrameSize) cClosed ccl writeLock cClosedHandlers lastChanID cLastReceived cLastSent
+    let conn = Connection handle cChanAllocator cChannels (fromIntegral maxFrameSize) cClosed ccl writeLock cClosedHandlers cLastReceived cLastSent
 
     --spawn the connectionReceiver
     connThread <- forkIO $ CE.finally (connectionReceiver conn) $ do
@@ -539,11 +541,14 @@ -- closes the channel internally; but doesn't tell the server
 closeChannel' :: Channel -> Text -> IO ()
 closeChannel' c reason = do
-    modifyMVar_ (connChannels $ connection c) $ \old -> return $ IM.delete (fromIntegral $ channelID c) old
-    -- mark channel as closed
     modifyMVar_ (chanClosed c) $ \x -> do
         if isNothing x
             then do
+                modifyMVar_ (connChannels $ connection c) $ \old -> do
+                    ret <- freeChannel (connChanAllocator $ connection c) $ fromIntegral $ channelID c
+                    when (not ret) $ putStrLn "closeChannel error: channel already freed"
+                    return $ IM.delete (fromIntegral $ channelID c) old
+
                 void $ killLock $ chanActive c
                 killOutstandingResponses $ outstandingResponses c
                 return $ Just $ maybe (T.unpack reason) id x
@@ -556,32 +561,33 @@             return undefined
 
 -- | opens a new channel on the connection
---
--- There's currently no closeChannel method, but you can always just close the connection (the maximum number of channels is 65535).
 openChannel :: Connection -> IO Channel
 openChannel c = do
     newInQueue <- newChan
     outRes <- newMVar Seq.empty
     lastConsTag <- newMVar 0
     ca <- newLock
-
     closed <- newMVar Nothing
     conss <- newMVar M.empty
     listeners <- newMVar []
 
-    --get a new unused channelID
-    newChannelID <- modifyMVar (lastChannelID c) $ \x -> return (x+1, x+1)
-
-    let newChannel = Channel c newInQueue outRes (fromIntegral newChannelID) lastConsTag ca closed conss listeners
-
-    thrID <- forkIO $ CE.finally (channelReceiver newChannel)
-        (closeChannel' newChannel "closed")
-
     --add new channel to connection's channel map
-    modifyMVar_ (connChannels c) (return . IM.insert newChannelID (newChannel, thrID))
+    newChannel <- modifyMVar (connChannels c) $ \mp -> do
+        newChannelID <- allocateChannel (connChanAllocator c)
+        let newChannel = Channel c newInQueue outRes (fromIntegral newChannelID) lastConsTag ca closed conss listeners
+        thrID <- forkIO $ CE.finally (channelReceiver newChannel)
+            (closeChannel' newChannel "closed")
+        when (IM.member newChannelID mp) $ CE.throwIO $ userError "openChannel fail: channel already open"
+        return (IM.insert newChannelID (newChannel, thrID) mp, newChannel)
 
-    (SimpleMethod (Channel_open_ok _)) <- request newChannel (SimpleMethod (Channel_open (ShortString "")))
+    SimpleMethod (Channel_open_ok _) <- request newChannel (SimpleMethod (Channel_open (ShortString "")))
     return newChannel
+
+-- | closes a channel. It is typically not necessary to manually call this as closing a connection will implicitly close all channels.
+closeChannel :: Channel -> IO ()
+closeChannel c = do
+    SimpleMethod Channel_close_ok <- request c $ SimpleMethod $ Channel_close 0 (ShortString "") 0 0
+    closeChannel' c "user called closeChannel"
 
 -- | writes multiple frames to the channel atomically
 writeFrames :: Channel -> [FramePayload] -> IO ()
amqp.cabal view
@@ -1,5 +1,5 @@ Name:                amqp
-Version:             0.8.2
+Version:             0.8.3
 Synopsis:            Client library for AMQP servers (currently only RabbitMQ)
 Description:         Client library for AMQP servers (currently only RabbitMQ)
                      .
@@ -29,9 +29,10 @@     split>=0.2,
     clock >= 0.4.0.1,
     monad-control >= 0.3,
-    connection == 0.2.*
+    connection == 0.2.*,
+    vector
   Exposed-modules:    Network.AMQP, Network.AMQP.Types, Network.AMQP.Lifted
-  Other-modules:      Network.AMQP.Generated, Network.AMQP.Helpers, Network.AMQP.Protocol, Network.AMQP.Internal
+  Other-modules:      Network.AMQP.ChannelAllocator, Network.AMQP.Generated, Network.AMQP.Helpers, Network.AMQP.Protocol, Network.AMQP.Internal
   GHC-Options:        -Wall
 
 Executable amqp-builder
@@ -76,3 +77,4 @@     , hspec              >= 1.3
     , hspec-expectations >= 0.3.3
     , connection == 0.2.*
+    , vector