ipython-kernel 0.6.1.0 → 0.6.1.1
raw patch · 5 files changed
+137/−20 lines, 5 files
Files
- examples/Calc.hs +1/−1
- ipython-kernel.cabal +1/−1
- src/IHaskell/IPython/Message/UUID.hs +3/−3
- src/IHaskell/IPython/Types.hs +2/−1
- src/IHaskell/IPython/ZeroMQ.hs +130/−14
examples/Calc.hs view
@@ -213,7 +213,7 @@ , displayResult = displayRes , displayOutput = displayOut , completion = langCompletion- , objectInfo = langInfo+ , inspectInfo = langInfo , run = parseAndRun , debug = False }
ipython-kernel.cabal view
@@ -1,5 +1,5 @@ name: ipython-kernel-version: 0.6.1.0+version: 0.6.1.1 synopsis: A library for creating kernels for IPython frontends description: ipython-kernel is a library for communicating with frontends for the interactive IPython framework. It is used extensively in IHaskell, the interactive Haskell environment.
src/IHaskell/IPython/Message/UUID.hs view
@@ -1,7 +1,7 @@ -- | Description : UUID generator and data structure -- -- Generate, parse, and pretty print UUIDs for use with IPython.-module IHaskell.IPython.Message.UUID (UUID, random, randoms) where+module IHaskell.IPython.Message.UUID (UUID, random, randoms, uuidToString) where import Control.Monad (mzero, replicateM) import Control.Applicative ((<$>))@@ -16,7 +16,7 @@ -- present in the correct locations. For the purposes of new UUIDs, it does not matter, -- but IPython expects UUIDs passed to kernels to be returned unchanged, so we cannot -- actually parse them.- UUID String+ UUID { uuidToString :: String } deriving (Show, Read, Eq, Ord) -- | Generate a list of random UUIDs.@@ -28,7 +28,7 @@ random :: IO UUID random = UUID <$> show <$> nextRandom --- Allows reading and writing UUIDs as Strings in JSON. +-- Allows reading and writing UUIDs as Strings in JSON. instance FromJSON UUID where parseJSON val@(String _) = UUID <$> parseJSON val
src/IHaskell/IPython/Types.hs view
@@ -25,6 +25,7 @@ HistoryAccessType(..), HistoryReplyElement(..), replyType,+ showMessageType, -- ** IPython display data message DisplayData(..),@@ -63,7 +64,7 @@ Profile { ip :: IP -- ^ The IP on which to listen. , transport :: Transport -- ^ The transport mechanism.- , stdinPort :: Port -- ^ The stdin channel port. + , stdinPort :: Port -- ^ The stdin channel port. , controlPort :: Port -- ^ The control channel port. , hbPort :: Port -- ^ The heartbeat channel port. , shellPort :: Port -- ^ The shell command port.
src/IHaskell/IPython/ZeroMQ.hs view
@@ -5,18 +5,28 @@ -- The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython, replacing it -- instead with a Haskell Channel based interface. The `serveProfile` function takes a IPython -- profile specification and returns the channel interface to use.-module IHaskell.IPython.ZeroMQ (ZeroMQInterface(..), ZeroMQStdin(..), serveProfile, serveStdin) where+module IHaskell.IPython.ZeroMQ (+ ZeroMQInterface(..),+ ZeroMQStdin(..),+ serveProfile,+ serveStdin,+ ZeroMQEphemeralPorts,+ withEphemeralPorts,+ ) where +import Control.Concurrent+import Control.Exception+import Control.Monad+import Data.Aeson import qualified Data.ByteString.Lazy as LBS import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as Char-import Control.Concurrent-import Control.Monad-import System.IO.Unsafe-import Data.Aeson (encode)-import System.ZMQ4 hiding (stdin)+import Data.Char import Data.Digest.Pure.SHA as SHA import Data.Monoid ((<>))+import qualified Data.Text.Encoding as Text+import System.ZMQ4 as ZMQ4 hiding (stdin)+import Text.Read (readMaybe) import IHaskell.IPython.Types import IHaskell.IPython.Message.Parser@@ -50,6 +60,23 @@ , stdinReplyChannel :: Chan Message } +-- | Create new channels for a ZeroMQInterface+newZeroMQInterface :: ByteString -> IO ZeroMQInterface+newZeroMQInterface key = do+ shellReqChan <- newChan+ shellRepChan <- newChan+ controlReqChan <- dupChan shellReqChan+ controlRepChan <- dupChan shellRepChan+ iopubChan <- newChan+ return $! Channels+ { shellRequestChannel = shellReqChan+ , shellReplyChannel = shellRepChan+ , controlRequestChannel = controlReqChan+ , controlReplyChannel = controlRepChan+ , iopubChannel = iopubChan+ , hmacKey = key+ }+ -- | Start responding on all ZeroMQ channels used to communicate with IPython | via the provided -- profile. Return a set of channels which can be used to | communicate with IPython in a more -- structured manner.@@ -57,14 +84,7 @@ -> Bool -- ^ Print debug output -> IO ZeroMQInterface -- ^ The Message-channel based interface to the sockets. serveProfile profile debug = do- -- Create all channels which will be used for higher level communication.- shellReqChan <- newChan- shellRepChan <- newChan- controlReqChan <- dupChan shellReqChan- controlRepChan <- dupChan shellRepChan- iopubChan <- newChan- let channels = Channels shellReqChan shellRepChan controlReqChan controlRepChan iopubChan- (signatureKey profile)+ channels <- newZeroMQInterface (signatureKey profile) -- Create the context in a separate thread that never finishes. If withContext or withSocket -- complete, the context or socket become invalid.@@ -81,6 +101,81 @@ return channels +-- | Describes ports used when creating an ephemeral ZeroMQ session. Used to generate the ipython+-- JSON config file.+data ZeroMQEphemeralPorts =+ ZeroMQEphemeralPorts+ { ephHbPort :: !Port+ , ephControlPort :: !Port+ , ephShellPort :: !Port+ , ephIOPubPort :: !Port+ , ephSignatureKey :: !ByteString+ }++instance ToJSON ZeroMQEphemeralPorts where+ toJSON ports =+ object+ [ "ip" .= ("127.0.0.1" :: String)+ , "transport" .= TCP+ , "control_port" .= ephControlPort ports+ , "hb_port" .= ephHbPort ports+ , "shell_port" .= ephShellPort ports+ , "iopub_port" .= ephIOPubPort ports+ , "key" .= Text.decodeUtf8 (ephSignatureKey ports)+ ]++parsePort :: String -> Maybe Int+parsePort s = readMaybe num+ where+ num = reverse (takeWhile isNumber (reverse s))++bindLocalEphemeralPort :: Socket a -> IO Int+bindLocalEphemeralPort socket = do+ bind socket $ "tcp://127.0.0.1:*"+ endpointString <- lastEndpoint socket+ case parsePort endpointString of+ Nothing ->+ fail $ "internalError: IHaskell.IPython.ZeroMQ.bindLocalEphemeralPort encountered a port index that could not be interpreted as an int."+ Just endpointIndex ->+ return endpointIndex++-- | Run session for communicating with an IPython instance on ephemerally allocated ZMQ4 sockets.+-- The sockets will be closed when the callback returns.+withEphemeralPorts :: ByteString+ -> + -- ^ HMAC encryption key+ Bool+ -> + -- ^ Print debug output+ (ZeroMQEphemeralPorts -> ZeroMQInterface -> IO a)+ -> + -- ^ Callback that takes the+ -- interface to the sockets.+ IO a+withEphemeralPorts key debug callback = do+ channels <- newZeroMQInterface key+ -- Create the ZMQ4 context+ withContext $ \context -> do+ -- Create the sockets to communicate with.+ withSocket context Rep $ \heartbeatSocket -> do+ withSocket context Router $ \controlportSocket -> do+ withSocket context Router $ \shellportSocket -> do+ withSocket context Pub $ \iopubSocket -> do+ -- Bind each socket to a local port, getting the port chosen.+ hbPort <- bindLocalEphemeralPort heartbeatSocket+ controlPort <- bindLocalEphemeralPort controlportSocket+ shellPort <- bindLocalEphemeralPort shellportSocket+ iopubPort <- bindLocalEphemeralPort iopubSocket+ -- Create object to store ephemeral ports+ let ports = ZeroMQEphemeralPorts { ephHbPort = hbPort, ephControlPort = controlPort, ephShellPort = shellPort, ephIOPubPort = iopubPort, ephSignatureKey = key }+ -- Launch actions to listen to communicate between channels and cockets.+ _ <- forkIO $ forever $ heartbeat channels heartbeatSocket+ _ <- forkIO $ forever $ control debug channels controlportSocket+ _ <- forkIO $ forever $ shell debug channels shellportSocket+ _ <- forkIO $ checkedIOpub debug channels iopubSocket+ -- Run callback function; provide it with both ports and channels.+ callback ports channels+ serveStdin :: Profile -> IO ZeroMQStdin serveStdin profile = do reqChannel <- newChan@@ -151,6 +246,27 @@ iopub :: Bool -> ZeroMQInterface -> Socket Pub -> IO () iopub debug channels socket = readChan (iopubChannel channels) >>= sendMessage debug (hmacKey channels) socket++-- | Attempt to send a message along the socket, returning true if successful.+trySendMessage :: Sender a => String -> Bool -> ByteString -> Socket a -> Message -> IO Bool+trySendMessage nm debug hmacKey socket message = do+ let zmqErrorHandler :: ZMQError -> IO Bool+ zmqErrorHandler e+ -- Ignore errors if we cannot send. We may want to forward this to the thread that tried put the+ -- message in the Chan initially.+ | errno e == 38 = return False+ | otherwise = throwIO e+ (sendMessage debug hmacKey socket message >> return True) `catch` zmqErrorHandler++-- | Send messages via the iopub channel. This reads messages from the ZeroMQ iopub interface+-- channel and then writes the messages to the socket. This is a checked implementation which will+-- stop if the socket is closed.+checkedIOpub :: Bool -> ZeroMQInterface -> Socket Pub -> IO ()+checkedIOpub debug channels socket = do+ msg <- readChan (iopubChannel channels)+ cont <- trySendMessage "io" debug (hmacKey channels) socket msg+ when cont $+ checkedIOpub debug channels socket -- | Receive and parse a message from a socket. receiveMessage :: Receiver a => Bool -> Socket a -> IO Message