Holumbus-Distribution (empty) → 0.0.1
raw patch · 34 files changed
+5485/−0 lines, 34 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, containers, editline, haskell98, hslogger, hxt, network, time, unix
Files
- Examples/ClientServer/Client.hs +61/−0
- Examples/ClientServer/Makefile +31/−0
- Examples/ClientServer/Server.hs +75/−0
- Examples/Makefile +13/−0
- Examples/Ports/Makefile +31/−0
- Examples/Ports/Receiver.hs +98/−0
- Examples/Ports/Sender.hs +105/−0
- Examples/Ports/Single.hs +108/−0
- Holumbus-Distribution.cabal +74/−0
- LICENSE +21/−0
- Makefile +56/−0
- Programs/Makefile +18/−0
- Programs/PortRegistry/Makefile +30/−0
- Programs/PortRegistry/PortRegistry.hs +95/−0
- README +156/−0
- Setup.hs +2/−0
- source/Holumbus/Common/Debug.hs +24/−0
- source/Holumbus/Common/FileHandling.hs +213/−0
- source/Holumbus/Common/Logging.hs +66/−0
- source/Holumbus/Common/Threading.hs +206/−0
- source/Holumbus/Common/Utils.hs +134/−0
- source/Holumbus/Console/Console.hs +242/−0
- source/Holumbus/Data/AccuMap.hs +122/−0
- source/Holumbus/Data/KeyMap.hs +127/−0
- source/Holumbus/Data/MultiMap.hs +178/−0
- source/Holumbus/Network/Communication.hs +959/−0
- source/Holumbus/Network/Core.hs +266/−0
- source/Holumbus/Network/Messages.hs +264/−0
- source/Holumbus/Network/Port.hs +982/−0
- source/Holumbus/Network/PortRegistry.hs +81/−0
- source/Holumbus/Network/PortRegistry/Messages.hs +121/−0
- source/Holumbus/Network/PortRegistry/PortRegistryData.hs +163/−0
- source/Holumbus/Network/PortRegistry/PortRegistryPort.hs +144/−0
- source/Holumbus/Network/Site.hs +219/−0
+ Examples/ClientServer/Client.hs view
@@ -0,0 +1,61 @@++module Main(main) where++import Holumbus.Common.Debug+import Holumbus.Common.Logging+import Holumbus.Common.Utils+import Holumbus.Network.PortRegistry.PortRegistryPort+import Holumbus.Network.Communication++version :: String+version = "Holumbus-Communication Client 0.1"+++main :: IO ()+main+ = do+ putStrLn version+ handleAll (\e -> putStrLn $ "EXCEPTION: " ++ show e) $+ do+ initializeLogging []+ p <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry p+ client <- initializeData+ waitForInput client+ deinitializeData client++waitForInput :: Client -> IO ()+waitForInput client+ = do+ line <- getLine+ case line of+ "exit" -> + return ()+ "debug" -> + do+ printDebug client+ waitForInput client+ _ -> + waitForInput client+++dispatch + :: String + -> IO (Maybe String)+dispatch msg+ = do+ putStrLn $ "message arrived: " ++ msg+ return Nothing+++initializeData :: IO (Client)+initializeData + = do+ client <- newClient "server" Nothing dispatch+ return client+++deinitializeData :: Client -> IO ()+deinitializeData client+ = do+ closeClient client
+ Examples/ClientServer/Makefile view
@@ -0,0 +1,31 @@+# Making FileSystem-Standalone Programs++HOME = ../..++SOURCE = .++GHC_FLAGS = -Wall -threaded -O2 -hidir $(OUTPUT) -odir $(OUTPUT)+GHC = ghc $(GHC_FLAGS)++RM_FLAGS = -rf+RM = rm $(RM_FLAGS)++PROG = Client Server++OUTPUT = output++all : $(PROG)++#prof : $(PROG).hs+# [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+# $(RM) $(OUTPUT)/*+# $(GHC) -prof -auto-all -ignore-package Holumbus -i../../source/ --make -o $(PROG)_p $(PROG).hs++% : %.hs+ [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+ $(RM) $(OUTPUT)/*+ $(GHC) --make -o $@ $<++clean :+ $(RM) $(PROG) $(OUTPUT) *.port+
+ Examples/ClientServer/Server.hs view
@@ -0,0 +1,75 @@++module Main(main) where++import Holumbus.Common.Debug+import Holumbus.Common.Logging+import Holumbus.Common.Utils+import Holumbus.Network.PortRegistry.PortRegistryPort+import Holumbus.Network.Communication+++version :: String+version = "Holumbus-Communication Server 0.1"+++main :: IO ()+main+ = do+ putStrLn version+ handleAll (\e -> putStrLn $ "EXCEPTION: " ++ show e) $+ do+ initializeLogging []+ p <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry p+ server <- initializeData+ waitForInput server+ deinitializeData server+++waitForInput :: Server -> IO ()+waitForInput server+ = do+ line <- getLine+ case line of+ "exit" -> + return ()+ "debug" -> + do+ printDebug server+ waitForInput server+ _ -> + waitForInput server+++dispatch + :: String + -> IO (Maybe String)+dispatch msg+ = do+ putStrLn $ "message arrived: " ++ msg+ return Nothing+++register :: IdType -> ClientPort -> IO ()+register i cp+ = do+ putStrLn $ "register request: Id: " ++ show i ++ " -- Port: " ++ show cp+++unregister :: IdType -> ClientPort -> IO ()+unregister i cp+ = do+ putStrLn $ "unregister request: Id: " ++ show i ++ " -- Port: " ++ show cp+++initializeData :: IO (Server)+initializeData + = do+ server <- newServer "server" Nothing dispatch (Just register) (Just unregister)+ return server+++deinitializeData :: Server -> IO ()+deinitializeData server+ = do+ closeServer server
+ Examples/Makefile view
@@ -0,0 +1,13 @@+# Making all examples++ALLEXAMPLES = ClientServer Ports+++all :+ $(foreach i,$(ALLEXAMPLES),$(MAKE) -C $i all ;)++wc :+ @wc -l `find . -wholename './_darcs/*' -prune -o -name "*.hs" -print`++clean :+ $(foreach i,$(ALLEXAMPLES),$(MAKE) -C $i $@ ;)
+ Examples/Ports/Makefile view
@@ -0,0 +1,31 @@+# Making FileSystem-Standalone Programs++HOME = ../..++SOURCE = .++GHC_FLAGS = -Wall -threaded -O2 -hidir $(OUTPUT) -odir $(OUTPUT)+GHC = ghc $(GHC_FLAGS)++RM_FLAGS = -rf+RM = rm $(RM_FLAGS)++PROG = Single Sender Receiver++OUTPUT = output++all : $(PROG)++#prof : $(PROG).hs+# [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+# $(RM) $(OUTPUT)/*+# $(GHC) -prof -auto-all -ignore-package Holumbus -i../../source/ --make -o $(PROG)_p $(PROG).hs++% : %.hs+ [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+ $(RM) $(OUTPUT)/*+ $(GHC) --make -o $@ $<++clean :+ $(RM) $(PROG) $(OUTPUT) p.port port.xml+
+ Examples/Ports/Receiver.hs view
@@ -0,0 +1,98 @@+-- ----------------------------------------------------------------------------+{- |+ Module : + Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++-}+-- ----------------------------------------------------------------------------++module Main(main) where++import Control.Concurrent+import qualified Data.ByteString.Lazy as B++import Holumbus.Common.Logging+import Holumbus.Common.Utils+import Holumbus.Network.Port+import Holumbus.Network.PortRegistry.PortRegistryPort+import qualified Holumbus.Console.Console as Console+++type StringStream = Stream String+type StringPort = Port String+++newStringStream :: IO StringStream+newStringStream = newLocalStream Nothing+++decodeStringPort :: Maybe B.ByteString -> Maybe StringPort+decodeStringPort = decodeMaybe+++version :: String+version = "Ports-Demo Receiver 0.1"+++main :: IO ()+main+ = do+ initializeLogging []+ putStrLn version + putStrLn "Begin"+ reg <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry reg+ printStreamController+ putStrLn "initialising streams..."+ s <- newStringStream+ p <- newPortFromStream s+ globalS <- newStream STGlobal (Just "global") (Just 10002)+ putStrLn "streams initialised"+ writePortToFile p "p.port"+ forkIO $ printMessages "global" globalS+ Console.handleUserInput createConsole ()+++printMessages :: String -> StringStream -> IO ()+printMessages info s+ = do+ msg <- readStreamMsg s+ putStrLn $ "port: " ++ info+ putStrLn "Incoming Message:"+ putStrLn $ show msg+ let replyPort = decodeStringPort $ getGenericData msg+ let text = getMessageData msg+ case replyPort of+ (Nothing) -> + do+ putStrLn "no reply port in message"+ (Just p) ->+ do+ send p ("ECHO:" ++ text)+ printMessages info s+ + +createConsole :: Console.ConsoleData ()+createConsole =+ Console.addConsoleCommand "debug" printDebug "prints internal state of the filesystem (DEBUG)" $ + Console.addConsoleCommand "version" printVersion "prints the version" $ + Console.initializeConsole++ +printDebug :: () -> [String] -> IO ()+printDebug _ _+ = do+ printStreamController+ +printVersion :: () -> [String] -> IO ()+printVersion _ _+ = do+ putStrLn version+
+ Examples/Ports/Sender.hs view
@@ -0,0 +1,105 @@+-- ----------------------------------------------------------------------------+{- |+ Module : + Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++-}+-- ----------------------------------------------------------------------------++module Main(main) where+ +import Data.List+-- import Data.Binary++import Holumbus.Common.Logging+import Holumbus.Network.Port+import Holumbus.Network.PortRegistry.PortRegistryPort+import qualified Holumbus.Console.Console as Console+++version :: String+version = "Ports-Demo Sender 0.1"+++main :: IO ()+main+ = do+ initializeLogging []+ putStrLn version+ putStrLn "Begin"+ printStreamController + reg <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry reg+ p <- readPortFromFile "p.port"+ Console.handleUserInput createConsole p++ +createConsole :: Console.ConsoleData (Port String)+createConsole =+ -- Console.addConsoleCommand "send" sendMsg "sends a message" $+ -- Console.addConsoleCommand "sendP" sendPMsg "sends a message to the private port of the receiver" $+ -- Console.addConsoleCommand "sendN" sendNMsg "sends a message to the named port of the receiver" $+ Console.addConsoleCommand "sendG" sendGMsg "sends a message to the global port of the receiver" $+ Console.addConsoleCommand "debug" printDebug "prints internal state of the filesystem (DEBUG)" $ + Console.addConsoleCommand "version" printVersion "prints the version" $ + Console.initializeConsole++{-+sendMsg :: StringPort -> [String] -> IO ()+sendMsg p line+ = do+ respStream <- newStringStream+ respPort <- newPortFromStream respStream+ let msg = intercalate " " line+ sendWithGeneric p msg (encode respPort)+ response <- tryWaitReadStreamMsg respStream time10+ case response of+ -- if no response+ Nothing -> putStrLn "no response in 10 seconds" + -- handle the response+ (Just r) -> putStrLn $ "ResponseMessage:\n" ++ show r+ closeStream respStream+ +sendPMsg :: StringPort -> [String] -> IO ()+sendPMsg _ line+ = do+ privPort <- (newPort "private" "localhost" 10000)::IO StringPort+ let msg = intercalate " " line+ send privPort msg+ + +sendNMsg :: StringPort -> [String] -> IO ()+sendNMsg _ line+ = do+ namedPort <- (newPort "named" "localhost" 10001)::IO StringPort+ let msg = intercalate " " line+ send namedPort msg+-}++sendGMsg :: (Port String) -> [String] -> IO ()+sendGMsg _ line+ = do+ globalPort <- (newGlobalPort "global")::IO (Port String)+ let msg = intercalate " " line+ send globalPort msg+++printDebug :: (Port String) -> [String] -> IO ()+printDebug _ _+ = do+ printStreamController+++++printVersion :: (Port String) -> [String] -> IO ()+printVersion _ _+ = do+ putStrLn version
+ Examples/Ports/Single.hs view
@@ -0,0 +1,108 @@+-- ----------------------------------------------------------------------------+{- |+ Module : + Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++-}+-- ----------------------------------------------------------------------------++module Main(main) where++import Data.Binary+import qualified Data.ByteString.Lazy as B++import Holumbus.Common.Logging+import Holumbus.Network.Port+++version :: String+version = "Holumbus-Ports Single-Demo 0.1"+++type StringStream = Stream String+type StringPort = Port String+++decodeMaybe :: (Binary a) => Maybe B.ByteString -> Maybe a+decodeMaybe Nothing = Nothing+decodeMaybe (Just b) = (Just $ decode b)+++decodeStringPort :: Maybe B.ByteString -> Maybe StringPort+decodeStringPort = decodeMaybe++++main :: IO ()+main+ = do+ initializeLogging []+ putStrLn version+ + putStrLn ""+ putStrLn "opening private Streams and Ports"+ + printStreamController+ stream1 <- (newLocalStream (Just "stream1")):: IO StringStream+ stream2 <- (newLocalStream (Just "stream2")):: IO StringStream+ printStreamController+ + testCase stream1 stream2 "Hello World"+ + putStrLn ""+ putStrLn "closing streams..."+ closeStream stream1+ closeStream stream2+ printStreamController+ + + +testCase :: StringStream -> StringStream -> String -> IO ()+testCase stream1 stream2 t+ = do+ putStrLn ""+ putStrLn "Begin"++ port1 <- newPortFromStream stream1+ port2 <- newPortFromStream stream2+ + putStrLn ""+ putStrLn $ "Sending: " ++ t+ sendWithGeneric port1 t (encode port2)+ printStreamController++ putStrLn ""+ putStrLn "Reading result..."+ msg1 <- readStreamMsg stream1+ putStrLn "Result:"+ putStrLn $ "Text: " ++ getMessageData msg1+ putStrLn $ "Msg: " ++ show msg1+ + putStrLn ""+ putStrLn "echoing result..."+ let replyPort = decodeStringPort $ getGenericData msg1+ let text = getMessageData msg1+ case replyPort of + (Nothing) ->+ do+ putStrLn "no reply port in message"+ (Just p) ->+ do+ send p ("ECHO:" ++ text)+ ++ putStrLn "Reading echo..."+ msg2 <- readStreamMsg stream2+ putStrLn "Result:"+ putStrLn $ "Text: " ++ getMessageData msg2+ putStrLn $ "Msg: " ++ show msg2+ + putStrLn ""+ putStrLn "End"
+ Holumbus-Distribution.cabal view
@@ -0,0 +1,74 @@+name: Holumbus-Distribution+version: 0.0.1+license: OtherLicense+license-file: LICENSE+author: Stefan Schmidt, Uwe Schmidt+copyright: Copyright (c) 2008 Stefan Schmidt, Uwe Schmidt+maintainer: Stefan Schmidt <sts@holumbus.org>+stability: experimental+category: Distributed Computing, Network+synopsis: intra- and inter-program communication+homepage: http://holumbus.fh-wedel.de+description: Holumbus-Distribution offers Erlang-Style mailboxes for an easy implementation+ of distributed systems in Haskell. The mailboxes can be used to exchange messages+ between threads inside a single program or between programs over the network.+build-type: Simple+cabal-version: >=1.6++extra-source-files:+ README+ Makefile+ Programs/Makefile+ Programs/PortRegistry/Makefile+ Programs/PortRegistry/PortRegistry.hs+ Examples/Makefile+ Examples/ClientServer/Client.hs+ Examples/ClientServer/Makefile+ Examples/ClientServer/Server.hs+ Examples/Ports/Makefile+ Examples/Ports/Receiver.hs+ Examples/Ports/Sender.hs+ Examples/Ports/Single.hs++library+ build-depends: base >= 4+ , binary >= 0.4+ , bytestring >= 0.9+ , containers >= 0.1+ , editline >= 0.2+ , haskell98 >= 1+ , hslogger >= 1.0+ , hxt >= 8.2+ , network >= 2.1+ , time >= 1.1+ , unix >= 2.3++ hs-source-dirs: + source+ exposed-modules: Holumbus.Data.AccuMap+ , Holumbus.Data.KeyMap+ , Holumbus.Data.MultiMap+ , Holumbus.Network.Communication+ , Holumbus.Network.Core+ , Holumbus.Network.Messages+ , Holumbus.Network.Port+ , Holumbus.Network.Site+ , Holumbus.Network.PortRegistry+ , Holumbus.Network.PortRegistry.Messages+ , Holumbus.Network.PortRegistry.PortRegistryData+ , Holumbus.Network.PortRegistry.PortRegistryPort+ , Holumbus.Console.Console+ , Holumbus.Common.Debug+ , Holumbus.Common.FileHandling+ , Holumbus.Common.Logging+ , Holumbus.Common.Threading+ , Holumbus.Common.Utils++ ghc-options: -Wall -threaded+ extensions: Arrows DeriveDataTypeable ExistentialQuantification++executable PortRegistry+ main-is: PortRegistry.hs+ hs-source-dirs: Programs/PortRegistry source+ ghc-options: -Wall -threaded+ extensions: Arrows DeriveDataTypeable ExistentialQuantification
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License++Copyright (c) 2008 Stefan Schmidt++Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"),+to deal in the Software without restriction, including without limitation the+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
+ Makefile view
@@ -0,0 +1,56 @@+# Making all+#+# Building the library and generating documentation is done by cabal,+# only the tests and the examples will be built using make.++# TEST_BASE = test+EXAMPLES_BASE = Examples+PROGRAMS_BASE = Programs++all : configure build doc dist++configure :+ @runhaskell Setup.hs configure++doc : configure+ @runhaskell Setup.hs haddock --hyperlink-source --hscolour-css=hscolour.css++build : configure+ @runhaskell Setup.hs build++install :+ @runhaskell Setup.hs install --global++dist :+ @runhaskell Setup.hs sdist++prof :+ @runhaskell Setup.hs configure -p+ @runhaskell Setup.hs build++# alltests :+# $(MAKE) -C $(TEST_BASE) all++examples :+ $(MAKE) -C $(EXAMPLES_BASE) all++programs : + $(MAKE) -C $(PROGRAMS_BASE) all++wc :+ @wc -l `find ./source/Holumbus -wholename './_darcs/*' -prune -o -name "*.hs" -print` + # $(MAKE) -C $(TEST_BASE) wc+ $(MAKE) -C $(EXAMPLES_BASE) wc+ $(MAKE) -C $(PROGRAMS_BASE) wc++zip: clean+ @zip -r holumbus_mapreduce$$(date +%Y%m%d_%H%M).zip *++clean :+ @runhaskell Setup.hs clean+ @rm -rf holumbus_mapreduce*.zip+ # $(MAKE) -C $(TEST_BASE) clean+ $(MAKE) -C $(EXAMPLES_BASE) clean+ $(MAKE) -C $(PROGRAMS_BASE) clean++.PHONY : configure build doc dist clean zip wc examples programs
+ Programs/Makefile view
@@ -0,0 +1,18 @@+# Making all examples++# COMMUNICATION = Communication/ClientServer Communication/Ports+PORTREGISTRY = PortRegistry+ALLPROGRAMS = $(PORTREGISTRY)+++all :+ $(foreach i,$(ALLPROGRAMS),$(MAKE) -C $i all ;)++registry :+ $(foreach i,$(PORTREGISTRY),$(MAKE) -C $i all ;)++wc :+ @wc -l `find . -wholename './_darcs/*' -prune -o -name "*.hs" -print`++clean :+ $(foreach i,$(ALLPROGRAMS),$(MAKE) -C $i $@ ;)
+ Programs/PortRegistry/Makefile view
@@ -0,0 +1,30 @@++HOME = ../..++SOURCE = .++GHC_FLAGS = -Wall -threaded -O2 -hidir $(OUTPUT) -odir $(OUTPUT)+GHC = ghc $(GHC_FLAGS)++RM_FLAGS = -rf+RM = rm $(RM_FLAGS)++PROG = PortRegistry++OUTPUT = output++all : $(PROG)++#prof : $(PROG).hs+# [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+# $(RM) $(OUTPUT)/*+# $(GHC) -prof -auto-all -ignore-package Holumbus -i../../source/ --make -o $(PROG)_p $(PROG).hs++% : %.hs+ [ -d $(OUTPUT) ] || mkdir -p $(OUTPUT)+ $(RM) $(OUTPUT)/*+ $(GHC) --make -o $@ $<++clean :+ $(RM) $(PROG) $(OUTPUT) registry.xml registry.port+
+ Programs/PortRegistry/PortRegistry.hs view
@@ -0,0 +1,95 @@+-- ----------------------------------------------------------------------------+{- |+ Module : + Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++-}+-- ----------------------------------------------------------------------------++module Main(main) where++import Holumbus.Common.Logging+import Holumbus.Common.FileHandling+import Holumbus.Common.Utils+import Holumbus.Network.Port+import Holumbus.Network.PortRegistry+import Holumbus.Network.PortRegistry.PortRegistryData+import qualified Holumbus.Console.Console as Console+++version :: String+version = "Holumbus-PortRegistry 0.1"+++main :: IO ()+main+ = do+ initializeLogging []+ let sn = "portregistry"+ let po = (Just 9000)+ r <- newPortRegistryData sn po+ p <- getPortRegistryRequestPort r+ writePortToFile p "/tmp/registry.port"+ saveToXmlFile "/tmp/registry.xml" p+ Console.handleUserInput createConsole r + putStrLn version++ +createConsole :: Console.ConsoleData PortRegistryData+createConsole =+ Console.addConsoleCommand "register" hdlRegister "registers a port manually" $+ Console.addConsoleCommand "unregister" hdlUnregister "unregisters a port manually" $+ Console.addConsoleCommand "lookup" hdlLookup "gets the socket id for a port" $+ Console.addConsoleCommand "ports" hdlGetPorts "lists all ports" $ + Console.addConsoleCommand "version" hdlVersion "prints the version" $+ Console.initializeConsole+++hdlRegister :: PortRegistryData -> [String] -> IO ()+hdlRegister prd opts+ = do+ handleAll (\_ -> putStrLn "usage: register <streamname> <hostname> <socketId>") $+ do+ let sn = head opts+ let hn = head $ tail opts+ let po = fromInteger $ read $ head $ tail $ tail opts+ registerPort sn (SocketId hn po) prd+++hdlUnregister :: PortRegistryData -> [String] -> IO ()+hdlUnregister prd opts+ = handleAll (\_ -> putStrLn "usage: unregister <streamname>") $+ do+ let sn = head opts+ unregisterPort sn prd+++hdlLookup :: PortRegistryData -> [String] -> IO ()+hdlLookup prd opts+ = handleAll (\_ -> putStrLn "usage: lookup <streamname>") $+ do+ let sn = head opts+ soid <- lookupPort sn prd+ putStrLn $ show soid+++hdlGetPorts :: PortRegistryData -> [String] -> IO ()+hdlGetPorts prd _+ = do+ ls <- getPorts prd+ putStrLn $ show ls+++hdlVersion :: PortRegistryData -> [String] -> IO ()+hdlVersion _ _+ = do+ putStrLn version++-- ------------------------------------------------------------
+ README view
@@ -0,0 +1,156 @@+This is the Holumbus-Distribution Library++Version 0.0.1++Stefan Schmidt sts@holumbus.org++http://holumbus.fh-wedel.de+++About+-----++Holumbus is a set of Haskell libraries. This package contains the +Holumbus-Distribution library for building and running a distributed systems.+One of the core elements of this library is an Erlang-like mailbox +system for interchanging messages between different threads or applications. ++This library itself is independent from other Holumbus libraries.+++Documentation:+------------------------------------------++The "Chan" data type form Control.Concurrent.Chan is pretty useful.+You can easily use it as a queue for the consumer-producer-problem. One+thread writes into the channel, the other reads the data from it. But this+only works, if the two threads are running in the same address space.+That means the Chan data type cannot be used for the communication of+distinct applications, but this scenario is a normal use case.++Therefore it would be very useful to have a single framework for inter- +and intra-process communication. One thread can create a mailbox and other +threads can send messages to it, though it should make no difference, +if the threads are in the same address space or not.++This libary offers a "Stream-Port" implementation, borrowed from the+multi-paradigm programming language Mozart/Oz.++A stream in this context is like a mailbox or a receiver. Other threads can send+messages to it via a port. A port in this context has nothing to do with the+port number of a unix-socket. Think of it as a sender.++To address a stream over the network easily, you can give him a unique name.+Then you can create a port, give him that stream name and the messages will+be send directly to the stream. This requires you to start the PortRegistry.++The PortRegistry keeps a log for all global communication streams in the+network. It MUST be started BEFORE all other programs. In future versions this+explicit execution sequence might not be necessary any more, but for now the+FIRST thing you have to start is the PortRegistry. There is ONLY ONE one+instance allowed in the whole communication network (this might also change).++You can find the main parts for the communication in the module+"Holumbus.Network.Port"++This library also contains some parts which might be useful, e.g. some+specialized maps (Holumbus.Data) and a commandline user interface+(Holumbus.Console). They are used in other Holumbus libraries.+++Contents+--------++Examples Some example applications+Programs The applications you need to run a distributed system.+source Source code of the Holumbus-Distribution library.+++Requirements+------------++So far, this library is only tested under Linux, please tell me, if you have +problems under Windows or other OS.+The Holumbus-Distribution library requires at least GHC 6.10 and the +following packages (available via Hackage).++ containers+ hslogger+ network + unix+ time+ bytestring+ binary+ hxt+++Installation+------------++A Cabal file is provided, therefore Holumbus-Distribution can be installed using+the standard Cabal way:++$ runhaskell Setup.hs configure+$ runhaskell Setup.hs build+$ runhaskell Setup.hs install --global # with root privileges++This will generate the library and the PortRegestry program.++For those who prefer to build it the old way with make:++$ make build+$ make install # with root privileges++If you want to run your own distributed system, you'll need to compile the+PortRegistry, too. This can be done with++$ make programs+++Steps to make a distributed system running+--------------------------------++Before you can use the mailboxes in your own programs, you need to start the+PortRegistry BEFORE running any of you programs. If you have compiled the+PortRegistry, you can start it with: ++$ cd Programs/PortRegistry+$ ./PortRegistry++This will create a file "registry.xml" in your "/tmp" directory. This file+contains all information for your programs to access the PortRegistry.+It is wise to copy this file to every computer, on which you want to run a +program of your system.++Before you can send/receive messages in your programs, you'll need to+load the "registry.xml" file. This can be done by two simple lines in the+IO-monad:++p <- newPortRegistryFromXmlFile "/tmp/registry.xml"+setPortRegistry p++To create a simple Mailbox for receiving messages, you can look a the following+demo-program:+ +main :: IO ()+main+ = do+ reg <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry reg+ gS <- (newGlobalStream "global"):: IO (Stream String)+ msg <- readStream gS+ putStrLn msg++This program will set up a mailbox and wait till the first message is received+and print it out. Then is will terminate.+And the following program will send a message to your mailbox: + +main :: IO ()+main+ = do+ reg <- newPortRegistryFromXmlFile "/tmp/registry.xml"+ setPortRegistry reg+ gP <- (newGlobalPort "global")::IO (Port String)+ send gP "Hello World"+ +This program just sends one message to your mailbox.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ source/Holumbus/Common/Debug.hs view
@@ -0,0 +1,24 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Common.Debug+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ A typeclass for printing debug output.++-}+-- ----------------------------------------------------------------------------++module Holumbus.Common.Debug where+++class Debug m where++ -- | Just print out some debug output. + printDebug :: m -> IO ()
+ source/Holumbus/Common/FileHandling.hs view
@@ -0,0 +1,213 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Common.FileHandling+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ Some nice helper functions for strict file writing, appending and+ reading. All read-functions are strict, in the sense that+ the whole file is read at once and the content is stored into memory.+ So you won't have any semi-closed handles which might bring you in trouble+ from time to time. The files are immedialty closed after the reading.+ This Module can handle four filetypes: XML-Files, List-Files,+ Binary-Files and Text-Files.+ + XML-Files:+ Normal textfiles, but the content is stored as a xml-representation. For+ the pickling, we use the HXT-Library + (see http:\/\/www.fh-wedel.de\/~si\/HXmlToolbox\/)+ + List-Files:+ Binary-Files, which store a list of data-objects. The main difference+ between Binary-Files and List-Files is, that you can append data to a+ List-File which will be automatically concatenated to the existing list.+ If you try this with a normal Binary-File, you'll get only the original+ list and the appended data will be lost.+ + Binary-Files:+ Normal binary files, for the encoding and decoding, we use the Haskell+ binary-package.+ + Text-Files:+ Normal text files.++-}+-- ----------------------------------------------------------------------------++module Holumbus.Common.FileHandling+ (+ -- * xml files+ loadFromXmlFile+ , saveToXmlFile++ -- * lists in binary files+ , writeToListFile+ , appendToListFile+ , readFromListFile+ , parseByteStringToList++ -- * bytestring file handling+ , writeToBinFile+ , appendToBinFile+ , readFromBinFile++ -- * string file handling+ , writeToTextFile+ , appendToTextFile+ , readFromTextFile+ )+where+++import Control.Exception+import Data.Binary+import qualified Data.ByteString.Lazy as B+import Data.Char+import Foreign+import System.IO+import System.IO.Unsafe++import Text.XML.HXT.Arrow++-- ----------------------------------------------------------------------------+-- xml files+-- ----------------------------------------------------------------------------+++-- | Loads an XML-File and parses the content to a specified datatype.+loadFromXmlFile :: (XmlPickler a) => FilePath -> IO a+loadFromXmlFile f+ = do+ r <- runX (xunpickleDocument xpickle options f)+ return $! head r+ where+ options = [ (a_validate,v_0), (a_remove_whitespace, v_1), (a_encoding, utf8), (a_validate, v_0) ]+++-- | Converts a dataset to XML and saves the XML-string into a file.+saveToXmlFile :: (XmlPickler a) => FilePath -> a -> IO ()+saveToXmlFile f i + = do+ runX (constA i >>> xpickleDocument xpickle options f)+ return ()+ where+ options = [ (a_indent, v_1), (a_output_encoding, utf8), (a_validate, v_0) ]++-- ----------------------------------------------------------------------------+-- generic lists+-- ----------------------------------------------------------------------------+++-- | Writes data to a list file.+writeToListFile :: (Binary a) => FilePath -> [a] -> IO ()+writeToListFile fp bs = writeToBinFile fp $ B.concat $ map encode bs+++-- | Appends data to a list file.+appendToListFile :: (Binary a) => FilePath -> [a] -> IO ()+appendToListFile fp bs = appendToBinFile fp $ B.concat $ map encode bs++-- | reads from a list file.+readFromListFile :: (Binary a) => FilePath -> IO [a]+readFromListFile f+ = do+ b <- readFromBinFile f + return $ parseByteStringToList b+++-- | You'll need this function, if you read the files a a normal binary file,+-- but the content itself is a list. This function encodes the bytestring+-- into a list of the specified datatype.+parseByteStringToList :: (Binary a) => B.ByteString -> [a]+parseByteStringToList b = reverse $ parse b []+ where+ parse :: (Binary a) => B.ByteString -> [a] -> [a]+ parse bs accu+ | (B.null bs) = accu+ | otherwise = parse (B.drop count bs) ([nextElem] ++ accu) + where+ nextElem = decode bs+ count = B.length (encode nextElem)+ +++-- ---------------------------------------------------------------------------- +-- strict functions, bytestrings only +-- ----------------------------------------------------------------------------++-- | Write data to a binary file.+writeToBinFile :: FilePath -> B.ByteString -> IO ()+writeToBinFile = B.writeFile+++-- | Append data to a binary file, this doesn't mean, that the contents+-- are really concatenated when you read the file. +appendToBinFile :: FilePath -> B.ByteString -> IO ()+appendToBinFile = B.appendFile+++-- | Reads the data from a binary file as a bytestring.+readFromBinFile :: FilePath -> IO B.ByteString+readFromBinFile f + = bracket (openBinaryFile f ReadMode) hClose $ + \h -> do+ s <- hFileSize h+ c <- B.hGetNonBlocking h (fromInteger s)+ return $! c +++-- ---------------------------------------------------------------------------- +-- strict functions, strings only +-- ----------------------------------------------------------------------------++ +-- | Writes a sting to a text file.+writeToTextFile :: FilePath -> String -> IO ()+writeToTextFile = writeFile++-- | Appends a string to a text file.+appendToTextFile :: FilePath -> String -> IO ()+appendToTextFile = appendFile+++-- | Strict file reading by Simon Marlow.+-- found on+-- http:\/\/users.aber.ac.uk\/afc\/stricthaskell.html+readFromTextFile :: FilePath -> IO String+readFromTextFile f+ = do+ bracket (openFile f ReadMode) hClose $ + \h -> do+ s <- hFileSize h+ fp <- mallocForeignPtrBytes (fromIntegral s)+ len <- withForeignPtr fp $ \buf -> hGetBuf h buf (fromIntegral s)+ lazySlurp fp 0 len+++buf_size :: Int+buf_size = 4096+++lazySlurp :: ForeignPtr Word8 -> Int -> Int -> IO String+lazySlurp fp ix len+ | fp `seq` False = undefined+ | ix >= len = return []+ | otherwise = do+ cs <- unsafeInterleaveIO (lazySlurp fp (ix + buf_size) len)+ ws <- withForeignPtr fp $ \p -> loop' (min (len-ix) buf_size - 1) + ((p :: Ptr Word8) `plusPtr` ix) cs+ return ws+ where+ loop' :: Int -> Ptr Word8 -> String -> IO String+ loop' len' p acc+ | len' `seq` p `seq` False = undefined+ | len' < 0 = return acc+ | otherwise = do+ w <- peekElemOff p len'+ loop' (len'-1) p (chr (fromIntegral w):acc)
+ source/Holumbus/Common/Logging.hs view
@@ -0,0 +1,66 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Common.Logging+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ In this part of the Holumbus-Framework, we use hslogging. With this, we are+ able to activate and deactivate some nice debugging-Output.+ This is the global configuration for the logging-output. Of cource, if you+ want to use this output, you have to invoke initializeLogging.++-}+-- ----------------------------------------------------------------------------++module Holumbus.Common.Logging+(+-- * Configuration+initializeLogging+)+where++import System.IO++import System.Log.Logger+import System.Log.Handler.Simple+-- import System.Log.Handler.Syslog+++++-- ----------------------------------------------------------------------------+-- Configuration+-- ----------------------------------------------------------------------------+++-- | configures the logging-parameters for the Holumbus-Framework+initializeLogging :: [(String, Priority)] -> IO ()+initializeLogging ls = do++ -- log all verbose + v <- verboseStreamHandler stderr DEBUG+ updateGlobalLogger rootLoggerName (setLevel DEBUG . setHandlers [v])++ -- set all logLevels for all loggers+ mapM_ (\(s,p) -> updateGlobalLogger s (setLevel p)) ls++ -- log all to syslog+ -- s <- openlog "SyslogStuff" [PID] USER DEBUG+ -- updateGlobalLogger rootLoggerName (addHandler s)+ + -- this will be removed in next versions+ -- updateGlobalLogger "Holumbus" (setLevel DEBUG)+ -- updateGlobalLogger "Holumbus.Network" (setLevel WARNING)+ -- updateGlobalLogger "Holumbus.MapReduce.TaskProcessor.task" (setLevel WARNING)+ -- updateGlobalLogger "Holumbus.FileSystem.Storage.FileStorage" (setLevel WARNING)+ -- updateGlobalLogger "Holumbus.FileSystem.Node.NodeData" (setLevel INFO)+ -- updateGlobalLogger "Holumbus.MapReduce.Types" (setLevel INFO)+ -- updateGlobalLogger "Holumbus.Network.Communication" (setLevel DEBUG)+ -- updateGlobalLogger "Holumbus.MapReduce.JobController.cycle" (setLevel WARNING)+
+ source/Holumbus/Common/Threading.hs view
@@ -0,0 +1,206 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Common.Threading+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ Operations to start and stop threads which will not be killed when a regular+ exception occurs. In this case, the thread will continue working. Such a + thread can only be killed by the stop-method. This whole thing is a wrapper+ around the normal lightweight thread functions.+ + The created threads execute a function in an infinite loop. This is the+ normal usecase for message dispatcher threads.+ +-}++-- ----------------------------------------------------------------------------++module Holumbus.Common.Threading+(+ Thread+, newThread+, setThreadDelay+, setThreadAction+, setThreadErrorHandler++, startThread+, stopThread+)+where++{- 6.8+import qualified Control.Exception as E+import Data.Typeable+-}+import Control.Exception ( AsyncException(..)+ , catchJust+ )+import Control.Concurrent++import Data.Maybe++import System.Log.Logger++import Holumbus.Common.Utils ( handleAll )++localLogger :: String+localLogger = "Holumbus.Common.Threading"+++-- ----------------------------------------------------------------------------+-- Thread-Control+-- ----------------------------------------------------------------------------++-- | Exception to stop a thread, so we can distinguish this request from+-- "real" exceptions.++{- 6.8+data KillThreadException = KillThreadException ThreadId+ deriving (Typeable, Show)+-}++-- | The data needed to access and control the thread. +data ThreadData = ThreadData {+ thd_Id :: Maybe ThreadId+ , thd_Running :: Bool+ , thd_Delay :: Maybe Int+ , thd_Action :: (IO ())+ , thd_Error :: (IO ())+ }+ + +-- | The thread datatype+type Thread = MVar ThreadData +++-- | Creates a new thread object. The thread will not be running.+newThread :: IO Thread+newThread+ = do + newMVar $ ThreadData Nothing False defaultDelay noAction noAction+ where+ noAction = return ()+ defaultDelay = Nothing+++-- | Sets the delay between two loop cycles. Default value: no delay.+setThreadDelay :: Int -> Thread -> IO ()+setThreadDelay d thread+ = do+ modifyMVar thread $ \thd -> return (thd {thd_Delay = Just d},())+ + +-- | Sets the action function, which will be executed in each cycle+setThreadAction :: (IO ()) -> Thread -> IO ()+setThreadAction f thread+ = do+ modifyMVar thread $ \thd -> return (thd {thd_Action = f},())+++-- | Sets the error handler. It is activated, when the action function+-- will raise an exception.+setThreadErrorHandler :: (IO ()) -> Thread -> IO ()+setThreadErrorHandler e thread+ = do+ modifyMVar thread $ \thd -> return (thd {thd_Error = e},())+++-- | Starts the thread.+startThread :: Thread -> IO ()+startThread th+ = modifyMVar th $+ \thd ->+ do+ -- check, if thread is running (it has a threadId)+ servId' <- case (thd_Id thd) of+ i@(Just _) -> return i+ (Nothing) ->+ do+ -- if not running, start the loop+ i <- forkIO $ doAction th+ return (Just i)+ return (thd {thd_Id = servId', thd_Running = True},())+ where + -- the action loop+ doAction thread+ = do+ thd <- readMVar thread+ if (thd_Running thd)+ -- if thread is still running + then do+ {- 6.8 E.handle -}+ handleAll+ ( \ e -> do+ -- if a normal exception occurs, the error handler should be excuted+ warningM localLogger $ show e+ thd_Error thd+ doAction thread+ ) $+ do+ -- catch the exception which tells us to kill the dispatcher and kill it+ {- 6.8 E.catchDyn -}+ catchJust isThreadKilledException+ ( do+ -- wait for next watch-cycle+ if (isJust (thd_Delay thd)) + then do threadDelay $ fromJust (thd_Delay thd)+ else do return ()+ -- do the action+ thd_Action thd+ -- and again+ doAction thread+ )+ killHandler+ else do+ debugM localLogger $ "thread normally closed by himself"+ deleteThreadId thread+ where+ isThreadKilledException :: AsyncException -> Maybe ()+ isThreadKilledException ThreadKilled = Just ()+ isThreadKilledException _ = Nothing++ killHandler :: () -> IO ()+ killHandler _+ = do+ debugM localLogger $ "thread normally closed by other thread "+ deleteThreadId thread+{- 6.8+ killHandler :: KillThreadException -> IO ()+ killHandler (KillThreadException i)+ = do+ debugM localLogger $ "thread normally closed by other thread " ++ show i+ deleteThreadId thread+-}+ deleteThreadId :: Thread -> IO ()+ deleteThreadId t+ = modifyMVar t $ \thd -> return (thd {thd_Id = Nothing, thd_Running = False},())+++-- | Stops the thread. If the thread itself wants to stop from within the action+-- function, the current cycle will be executed till the end. So statements+-- after this function will still be executed.+stopThread :: Thread -> IO ()+stopThread thread+ = do+ me <- myThreadId+ him <- withMVar thread $ \thd -> return $ thd_Id thd+ if (isJust him) + then do+ let he = fromJust him+ if (me == he) + -- if stop is called from the thread, we want to finish our work+ then do + modifyMVar thread $ \thd -> return (thd {thd_Running = False},())+ -- else we kill it the unfriendly way...+ else do+ {- 6.8: E.throwDynTo he (KillThreadException me) -}+ killThread he+ else do+ return ()
+ source/Holumbus/Common/Utils.hs view
@@ -0,0 +1,134 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Common.Utils+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ Some nice functions, needed everywhere.++-}++-- ----------------------------------------------------------------------------++module Holumbus.Common.Utils+ ( + decodeMaybe++ -- , lookupList+ -- , lookupMaybe++ -- , cutMaybePair+ , filterEmptyList+ , setEmptyList+ -- , filterBlanks++ , prettyRecordLine+ -- , prettyResultLine+ -- , handleExitError++ , handleAll -- exception handling for all exceptions+ )+where++import Control.Exception ( SomeException+ , handle+ )+import Data.Binary+import qualified Data.ByteString.Lazy as B++-- import qualified Data.Map as Map++import Data.Maybe+import Data.Char++-- import System.Exit++-- ------------------------------------------------------------+--+-- handle with type sinature++{- 6.8+handleAll :: (Exception -> IO a) -> IO a -> IO a+-}++-- ghc 6.10 requires a type signature for handle++handleAll :: (SomeException -> IO a) -> IO a -> IO a+handleAll = handle++-- ------------------------------------------------------------++-- | parses something from a maybe bytestring, if Nothing, then Nothing+decodeMaybe :: (Binary a) => Maybe B.ByteString -> Maybe a+decodeMaybe Nothing = Nothing+decodeMaybe (Just b) = (Just $ decode b)++{-+lookupMaybe :: (Ord k) => Map.Map k v -> Maybe k -> Maybe v+lookupMaybe _ Nothing = Nothing+lookupMaybe m (Just k) = Map.lookup k m+-}+{-+lookupList :: (Ord k) => Map.Map k v -> [k] -> [v]+lookupList m ks = mapMaybe (\k' -> lookupMaybe m $ Just k') ks+-} +{-+cutMaybePair :: Maybe (Maybe a, Maybe b) -> (Maybe a, Maybe b)+cutMaybePair (Nothing) = (Nothing, Nothing)+cutMaybePair (Just p) = p+-}++filterEmptyList :: Maybe [k] -> [k]+filterEmptyList (Nothing) = []+filterEmptyList (Just ls) = ls+++setEmptyList :: [k] -> Maybe [k]+setEmptyList [] = Nothing+setEmptyList ls = Just ls ++{-+filterBlanks :: Maybe String -> Maybe String+filterBlanks (Nothing) = Nothing+filterBlanks (Just "") = Nothing+filterBlanks (Just s) = if s' == "" then Nothing else (Just s)+ where+ s' = filter isPrint s +-}+++-- | For the nice output of key-value-pairs +prettyRecordLine :: (Show b, Show a) => Int -> a -> b -> String+prettyRecordLine n a b = stra ++ (replicate diff ' ') ++ show strb+ where+ stra = show a+ strb = show b+ diff = n - length stra+ + +{-+prettyResultLine :: (Show e, Show a, Show d) => Int -> Either e d -> a -> String+prettyResultLine n (Left e) _ = prettyRecordLine n "Error:" e+prettyResultLine n (Right r) a = prettyRecordLine n a r+-}++{-+handleExitError :: (Show e) => IO (Either e a) -> IO a+handleExitError res+ = do+ res' <- res+ case res' of+ (Left e) ->+ do+ putStrLn $ "ERROR: " ++ show e+ exitFailure+ (Right a) -> return a+-} ++-- ----------------------------------------------------------------------------
+ source/Holumbus/Console/Console.hs view
@@ -0,0 +1,242 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Console.Console+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ This module provides a tiny and nice implementation of a little command + shell. It can be feed with individual commands and provides a simple but+ powerful way to interact with your program. The following functions are+ implemented by default:+ exit - exit the console loop+ help - print a nice help++ There was a little "bug" with the System.Console.Readline package. When+ we use this option, we make a foreign call... and the Haskell library+ documentation say this about concurrency and GHC:+ + "If you don't use the -threaded option, then the runtime does not make + use of multiple OS threads. Foreign calls will block all other running + Haskell threads until the call returns.+ The System.IO library still does multiplexing, so there can be multiple+ threads doing IO, and this is handled internally by the runtime using + select."+ + We make a foreign call, which is not in the System.IO library, so we+ have to work with -threaded when we want a fancy command history.++-}++-- ----------------------------------------------------------------------------+++module Holumbus.Console.Console+ (+ -- * Console datatype+ ConsoleData+ + -- * Operations+ , nextOption+ , parseOption+ , initializeConsole+ , addConsoleCommand+ , handleUserInput+ )+where++import Control.Concurrent+import Control.Monad++import Data.Char+import qualified Data.Map as Map++import System.IO+import System.Console.Editline.Readline++import Holumbus.Common.Utils ( handleAll )++-- ----------------------------------------------------------------------------+-- datatypes+-- ----------------------------------------------------------------------------+++-- | Map which contains all commands that the user can execute+type ConsoleData a = Map.Map String (ConsoleCommand a)+++-- | Console command, only a pair of a function which will be executed +-- and a description +type ConsoleCommand a = ( Maybe (ConsoleFunction a), String )+++-- | A console function. The string list represents the arguments+type ConsoleFunction a = (a -> [String] -> IO())++-- ----------------------------------------------------------------------------+-- operations +-- ----------------------------------------------------------------------------+++-- | Creates a new console datatype+initializeConsole :: ConsoleData a+initializeConsole = Map.fromList [(exitString, exitCommand), (helpString, helpCommand)]+++-- | Adds a new console command to the function, an existing command with the+-- same name will be overwritten+addConsoleCommand + :: String -- ^ command string (the word the user has to enter when he wants to execute the command)+ -> ConsoleFunction a -- ^ the function which should be executed+ -> String -- ^ the function description+ -> ConsoleData a -- ^ the old console data+ -> ConsoleData a+addConsoleCommand c f d m = Map.insert c (Just f, d) m+++-- | The exit function string.+exitString :: String+exitString = "exit"+++-- | A dummy exit function (Just to print the help description, the command +-- is handled in the main loop.+exitCommand :: ConsoleCommand a+exitCommand = ( Nothing, "exit the console")+++-- | The help function string.+helpString :: String+helpString = "help"+++-- | A dummy help function (Just to print the help description, the command +-- is handled in the main loop.+helpCommand :: ConsoleCommand a+helpCommand = (Nothing, "print this help")+++-- | The command-line prompt string.+shellString :: String+shellString = "command> "++-- | gets the next option from the command line as string+nextOption :: [String] -> IO (Maybe String, [String])+nextOption o+ = handleAll (\_ -> return (Nothing, o)) $+ do+ if ( null o ) then+ return (Nothing, o)+ else + return (Just $ head o, tail o) ++-- | Simple "parser" for the commandline...+parseOption :: Read a => [String] -> IO (Maybe a, [String])+parseOption o+ = handleAll (\_ -> return (Nothing, o)) $+ do+ if ( null o ) then+ return (Nothing, o)+ else + return (Just $ read $ head o, tail o) +++-- | Reads the input from the stdin.+getCommandLine :: IO (String)+getCommandLine +-- no commandline history+{- = do+ putStr shellString+ hFlush stdout+ maybeLine <- getLine --readline shellString+ return maybeLine+-}+-- with commandline history, please use -threaded option+ = do+ maybeLine <- readline shellString+ yield+ case maybeLine of+ Nothing -> return exitString -- EOF / control-d+ Just line -> + do + if (not $ null line) then do addHistory line else return ()+ return line+ ++-- | The main loop. You know... read stdin, parse the input, execute command.+-- You can quit it by the exit-command.+handleUserInput :: ConsoleData a -> a -> IO ()+handleUserInput cdata conf+ = do+ line <- getCommandLine+ input <- return (words line)+ cmd <- return (command input)+ args <- return (arguments input)+ if (cmd == exitString) + then do+ return ()+ else do+ if (not $ null cmd) then do handleCommand cdata conf cmd args else return ()+ handleUserInput cdata conf+ where+ command s = if (not $ null s) then head s else ""+ arguments s = tail s+++-- | Picks the command an execute the command function.+handleCommand :: ConsoleData a -> a -> String -> [String] -> IO ()+handleCommand cdata conf cmd args+ = do+ if (cmd == helpString)+ then do+ printHelp cdata+ else do+ handleCommand' (Map.lookup cmd cdata)+ where+ handleCommand' Nothing = do printError+ handleCommand' (Just (Nothing, _ )) = do printNoHandler+ handleCommand' (Just (Just f, _ )) = do f conf args+++-- | Is executed when the function has no handler+printNoHandler :: IO ()+printNoHandler + = do+ putStrLn "no function handler found"+++-- | Prints the "command-not-found" message.+printError :: IO ()+printError+ = do+ putStrLn "unknown command, try help for a list of available commands"+++-- | Prints the help text.+printHelp :: ConsoleData a -> IO ()+printHelp cdata+ = do+ putStrLn "available Commands:"+ printCommands (Map.toAscList cdata)+ where+ printCommands [] = do return ()+ printCommands (x:xs) = do+ printCommand x+ printCommands xs+ printCommand (c, (_, t)) = do+ putStrLn ((prettyCommandName 15 c) ++ " - " ++ t) +++-- | Does some pretty printing for the function descriptions+prettyCommandName :: Int -> String -> String +prettyCommandName n s+ | n <= 0 = s+ | (n > 0) && (null s) = ' ' : prettyCommandName (n-1) s+ | otherwise = x : prettyCommandName (n-1) xs+ where+ (x:xs) = s
+ source/Holumbus/Data/AccuMap.hs view
@@ -0,0 +1,122 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Data.AccuMap+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ A map of key-value-pairs. The values are hold in a list, so adding the same+ key-value-pair twice to the map, will result in storing the value twice.+ Even the sequence of adding the values will be saved.+ + The name AccuMap is from accumulation map. You can use this map to easily+ storing all you key-value-pairs. After that you can all values-lists by+ key. Unlike the MultiMap, you don't lose the information of identical values+ and their order of adding.+ + Most functions are inspired by the Data.Map type. +-}+-- ----------------------------------------------------------------------------++module Holumbus.Data.AccuMap+(+ AccuMap+, empty+, null+, insert+, insertList+, lookup+, member+, deleteKey+, union+, fromList+, fromTupleList+, toList+)+where++import Prelude hiding (null, lookup)++import qualified Data.Map as Map+++-- | the AccuMap datatype+data AccuMap k a = AM (Map.Map k [a])+ deriving (Eq, Ord)+++instance (Show k, Show a) => Show (AccuMap k a) where+ show (AM m) = msShow+ where+ ms = Map.toList m+ msShow = concat $ map (\(k,s) -> (show k) ++ "\n" ++ (showL s)) ms+ showL ls = concat $ map (\s -> show s ++ "\n") ls +++-- | Creates an empty AccuMap.+empty :: (Ord k) => AccuMap k a+empty = AM Map.empty+++-- | Test, if AccuMap is empty.+null :: (Ord k) => AccuMap k a -> Bool+null (AM m) = Map.null m+++-- | Insert a key-value-pair to the AccuMap.+insert :: (Ord k) => k -> a -> AccuMap k a -> AccuMap k a+insert k a (AM m) = AM $ Map.alter altering k m+ where+ altering Nothing = Just $ [a]+ altering (Just s) = Just $ a:s+++-- | Insert a key and a list of values to the AccuMap.+-- Faster than adding all pair one by one.+insertList :: (Ord k) => k -> [a] -> AccuMap k a -> AccuMap k a+insertList _ [] m = m+insertList k as (AM m) = AM $ Map.alter altering k m+ where+ altering Nothing = Just $ as+ altering (Just s) = Just $ as ++ s+++-- | Get the list of values for one key. If the key doesn't exists,+-- an empty list is returned.+lookup :: (Ord k) => k -> AccuMap k a -> [a]+lookup k (AM m) = maybe [] (id) (Map.lookup k m)+++-- | Test, if the key is in the AccuMap.+member :: (Ord k, Eq a) => k -> AccuMap k a -> Bool+member k m = [] /= lookup k m+++-- | Deletes a key and all its values from the AccuMap.+deleteKey :: (Ord k) => k -> AccuMap k a -> AccuMap k a+deleteKey k (AM m) = AM $ Map.delete k m+++-- | Combines two AccuMaps, the ordering of the two maps is+-- significant for the order of the values-elements.+union :: (Ord k) => AccuMap k a -> AccuMap k a -> AccuMap k a+union (AM m1) (AM m2) = AM $ Map.unionWith (\l1 l2 -> l1 ++ l2) m1 m2 +++-- | Creates an AccuMap from a list.+fromList :: (Ord k) => [(k,[a])] -> AccuMap k a+fromList ks = foldl (\m (k,as) -> insertList k as m) empty ks+++-- | Creates an AccuMap from a tuple list.+fromTupleList :: (Ord k) => [(k,a)] -> AccuMap k a+fromTupleList ts = fromList $ map (\(k,a) -> (k,[a])) ts+++-- | Transforms an AccuMap to a list.+toList :: (Ord k) => AccuMap k a -> [(k,[a])]+toList (AM m) = Map.toList m
+ source/Holumbus/Data/KeyMap.hs view
@@ -0,0 +1,127 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Data.KeyMap+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ The KeyMap is derived from the Data.Map type. The keys of the Map are+ strings and the values can be arbitrary data objects. But you don't have to+ specify the keys because every value-object is able to create it's own+ key via the "Key" typeclass.+ + From the functionality, the KeyMap stands between a set and a Map. If you+ want to insert an element to the map, it behaves like a set. You don't need + an additionnal key and it makes no different if you insert an object multiple+ times. If you want to access the objects in the KeyMap, you can lookup them+ via the key, so in this case this container behaves like an ordinary map.++ The functions for this container are named after the standard Map and Set+ functions.+-}+-- ----------------------------------------------------------------------------++module Holumbus.Data.KeyMap+ (+ KeyMap+ , Key(..)+ , empty+ , null+ , insert+ , lookup+ , keys+ , elems+ , memberKey+ , memberElem+ , deleteKey+ , deleteElem+ , fromList+ , toList+ , toAscList+ )+where++import Prelude hiding (null, lookup)+import Data.Maybe+import qualified Data.Map as Map++-- | Every element of this map has to implement a key-function. which+-- gives us the key of the element+class Key n where+ getKey :: n -> String+++-- | The KeyMap datatype.+data KeyMap a = NM (Map.Map String a)+ deriving (Show, Eq, Ord)+++-- | The empty KeyMap.+empty :: KeyMap a+empty = NM Map.empty+++-- | Test, if the MultiMap is empty.+null :: (Key a) => KeyMap a -> Bool+null (NM m) = Map.null m+++-- | Inserts an element in the KeyMap.+insert :: (Key a) => a -> KeyMap a -> KeyMap a+insert a (NM m) = NM $ Map.insert (getKey a) a m+ ++-- | Gets all different elements for one key or an empty set.+lookup :: (Key a, Monad m) => String -> KeyMap a -> m a+lookup k (NM mm)+ = do+ let Just r = Map.lookup k mm+ return r++-- | Get all different keys from the map.+keys :: (Key a) => KeyMap a -> [String]+keys (NM m) = Map.keys m+++-- | Get all different values in the map without regarding their keys.+elems :: (Key a) => KeyMap a -> [a]+elems (NM m) = Map.elems m+++-- | Test, if a key is in the KeyMap.+memberKey :: (Key a) => String -> KeyMap a -> Bool+memberKey k (NM m) = Map.member k m+++-- | Test, if a data object is in the KeyMap.+memberElem :: (Key a) => a -> KeyMap a -> Bool+memberElem a (NM m) = Map.member (getKey a) m+++-- | Deletes a whole key from the KeyMap.+deleteKey :: (Key a) => String -> KeyMap a -> KeyMap a+deleteKey k (NM m) = NM $ Map.delete k m+++-- | Deletes a single elemet from the KeyMap.+deleteElem :: (Key a) => a -> KeyMap a -> KeyMap a+deleteElem a (NM m) = NM $ Map.delete (getKey a) m+++-- | Creates a KeyMap from a list of keys.+fromList :: (Key a) => [a] -> KeyMap a+fromList as = foldl (\m a -> insert a m) empty as+++-- | Transforms a KeyMap to a list of keys.+toList :: (Key a) => KeyMap a -> [a]+toList (NM m) = map (snd) $ Map.toList m+++-- | The same as toList, but the keys are in ascending order.+toAscList :: (Key a) => KeyMap a -> [a]+toAscList (NM m) = map (snd) $ Map.toAscList m
+ source/Holumbus/Data/MultiMap.hs view
@@ -0,0 +1,178 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Data.MultiMap+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ This module provides a MultiMap, that means a Map, which can hold+ multiple values for one key, but every distinct value is only stores once.+ So adding the same key-value-pair twice will only create one new entry in+ the map.+ + This Map is helpfull to examine how many different key-values-pairs you+ have in your application.+ + Most of the functions are borrowed from Data.Map +-}+-- ----------------------------------------------------------------------------++module Holumbus.Data.MultiMap+(+ MultiMap+, empty+, null+, insert+, insertSet+, insertKeys+, lookup+, keys+, elems+, filterElements+, member+, delete+, deleteKey+, deleteElem+, deleteAllElems+, fromList+, fromTupleList+, toList+, toAscList+)+where++import Prelude hiding (null, lookup)++import qualified Data.Map as Map+import qualified Data.Set as Set+++-- | A MultiMap, it can hold more (different!!!) Elements for one key.+data MultiMap k a = MM (Map.Map k (Set.Set a))+ deriving (Show, Eq, Ord)++{-+instance (Show k, Show a) => Show (MultiMap k a) where+ show (MM m) = msShow+ where+ ms = map (\(k,s) -> (k, Set.toList s)) (Map.toList m)+ msShow = concat $ map (\(k,s) -> (show k) ++ "\n" ++ (showL s)) ms+ showL ls = concat $ map (\s -> show s ++ "\n") ls +-}++-- | The empty MultiMap.+empty :: (Ord k, Ord a) => MultiMap k a+empty = MM Map.empty+++-- | Test, if the MultiMap is empty.+null :: (Ord k, Ord a) => MultiMap k a -> Bool+null (MM m) = Map.null m+++-- | Inserts an element in the MultiMap.+insert :: (Ord k, Ord a) => k -> a -> MultiMap k a -> MultiMap k a+insert k a (MM m) = MM $ Map.alter altering k m+ where+ altering Nothing = Just $ Set.singleton a+ altering (Just s) = Just $ Set.insert a s+ ++-- | Inserts multiple elements in a set to the MultiMap.+insertSet :: (Ord k, Ord a) => k -> Set.Set a -> MultiMap k a -> MultiMap k a+insertSet k newSet mm@(MM m) = + if (Set.null newSet) then mm else MM $ Map.alter altering k m+ where+ altering Nothing = Just newSet+ altering (Just s) = Just $ Set.union newSet s+++-- | Inserts multiple keys with the same values.+insertKeys :: (Ord k, Ord a) => [k] -> Set.Set a -> MultiMap k a -> MultiMap k a+insertKeys ks a m = foldl (\m' k -> insertSet k a m') m ks +++-- | Gets all different elements for one key or an empty set.+lookup :: (Ord k, Ord a) => k -> MultiMap k a -> Set.Set a+lookup k (MM m) = maybe (Set.empty) (id) (Map.lookup k m)+++-- | Get all different elements from a list of keys.+lookupKeys :: (Ord k, Ord a) => [k] -> MultiMap k a -> Set.Set a+lookupKeys ks m = Set.unions $ map (\k -> lookup k m) ks+++-- | Get all different keys from the map.+keys :: (Ord k, Ord a) => MultiMap k a -> Set.Set k+keys (MM m) = Set.fromList $ Map.keys m+++-- | Get all different values in the map without regarding their keys.+elems :: (Ord k, Ord a) => MultiMap k a -> Set.Set a+elems (MM m) = Set.unions $ Map.elems m+++-- | Like lookup keys, but an empty input list will give all elements back,+-- not the empty set.+filterElements :: (Ord k, Ord a) => [k] -> MultiMap k a -> Set.Set a+filterElements [] m = elems m -- get all+filterElements ks m = lookupKeys ks m+++-- | Test, if a key is in the Map.+member :: (Ord k, Ord a) => k -> MultiMap k a -> Bool+member k m = Set.empty /= lookup k m+++-- | Deletes an Element from the Map, if the data in Nothing, the whole key is+-- deleted.+delete :: (Ord k, Ord a) => k -> Maybe a -> MultiMap k a -> MultiMap k a+delete k Nothing m = deleteKey k m+delete k (Just a) m = deleteElem k a m+++-- | Deletes a whole key from the map.+deleteKey :: (Ord k, Ord a) => k -> MultiMap k a -> MultiMap k a+deleteKey k (MM m) = MM $ Map.delete k m+++-- | Deletes a single Element from the map.+deleteElem :: (Ord k, Ord a) => k -> a -> MultiMap k a -> MultiMap k a+deleteElem k a (MM m) = MM $ Map.alter delSet k m+ where+ delSet Nothing = Nothing+ delSet (Just set) = filterEmpty $ Set.delete a set+ filterEmpty set+ | set == Set.empty = Nothing+ | otherwise = Just set+++-- | Deletes all Elements (*,a) (slow!!!).+deleteAllElems :: (Ord k, Ord a) => a -> MultiMap k a -> MultiMap k a+deleteAllElems a m = foldl (\m'' k -> deleteElem k a m'') m ks+ where+ ks = Set.toList $ keys m+++-- | Creates a MultiMap from a list of pairs (key,set value).+fromList :: (Ord k, Ord a) => [(k,Set.Set a)] -> MultiMap k a+fromList ks = foldl (\m (k,as) -> insertSet k as m) empty ks+++-- | Creates a MultiMap from a list of tuples.+fromTupleList :: (Ord k, Ord a) => [(k,a)] -> MultiMap k a+fromTupleList ks = foldl (\m (k,a) -> insert k a m) empty ks+++-- | Transforms a MultiMap to a list of pairs (key,set value).+toList :: (Ord k, Ord a) => MultiMap k a -> [(k,Set.Set a)]+toList (MM m) = Map.toList m+++-- | The same as toList, but the keys are in ascending order.+toAscList :: (Ord k, Ord a) => MultiMap k a -> [(k,Set.Set a)]+toAscList (MM m) = Map.toAscList m
+ source/Holumbus/Network/Communication.hs view
@@ -0,0 +1,959 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Network.Communication+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ This module implements an abstract client server model. The clients register+ at the server and check from time to time if the server still exists. If not,+ the client searches for a new server.+ The server on the other hand, keeps a list with all clients and checks, if+ each client is reachable. If not, the client is deleted from the list.+ + This abstract network model helps us to implement a basic distrubuted system+ with a central server and many attached clients which get little tasks from+ the server. Because this model alone would be very unfunctional, the user is+ able to define his own functions which will be handled by the server or the+ client. +-}++-- ----------------------------------------------------------------------------++{-# OPTIONS -fglasgow-exts #-}+module Holumbus.Network.Communication+(+ StreamName -- (reexport)+, SocketId -- (reexport)+, PortNumber -- (reexport)++-- time constants+, time30 -- (reexport)+, timeIndefinitely -- (reexport)++, IdType++, ClientInfo(..)++-- * server operations+, Server+, newServer+, closeServer+, ServerPort+, newServerPort+, sendRequestToServer++, getClientInfo+, getAllClientInfos++-- * client operations+, ClientClass(..)+, Client+, newClient+, closeClient+, ClientPort+, sendRequestToClient +)+where++import Control.Concurrent+import Data.Binary+import qualified Data.ByteString.Lazy as B+import qualified Data.Map as Map+import Data.Maybe+import Network+import System.Log.Logger++import Holumbus.Common.Debug+import Holumbus.Common.Threading+import qualified Holumbus.Data.MultiMap as MMap+import Holumbus.Network.Messages+import Holumbus.Network.Port+import Holumbus.Network.Site++++localLogger :: String+localLogger = "Holumbus.Network.Communication"+++-- ----------------------------------------------------------------------------+-- General Datatypes+-- ----------------------------------------------------------------------------++-- | The type of the client id.+type IdType = Int++++-- ----------------------------------------------------------------------------+-- Server-Messages+-- ----------------------------------------------------------------------------++-- | The requests, the server can handle.+data ServerRequestMessage+ = SReqRegisterClient SiteId (Port ClientRequestMessage)+ | SReqUnregisterClient IdType+ | SReqPing IdType+ | SReqServerAction B.ByteString+ | SReqUnknown+ deriving (Show)++instance Binary (ServerRequestMessage) where+ put (SReqRegisterClient sid po) = putWord8 1 >> put sid >> put po+ put (SReqUnregisterClient i) = putWord8 2 >> put i+ put (SReqPing i) = putWord8 3 >> put i+ put (SReqServerAction b) = putWord8 4 >> put b+ put (SReqUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> get >>= \sid -> get >>= \po -> return (SReqRegisterClient sid po)+ 2 -> get >>= \i -> return (SReqUnregisterClient i)+ 3 -> get >>= \i -> return (SReqPing i)+ 4 -> get >>= \b -> return (SReqServerAction b)+ _ -> return (SReqUnknown)+++-- | The responses the server gives.+data ServerResponseMessage+ = SRspSuccess+ | SRspRegisterClient IdType+ | SRspUnregisterClient+ | SRspPing Bool+ | SRspServerAction B.ByteString+ | SRspError String+ | SRspUnknown+ deriving (Show)++instance Binary (ServerResponseMessage) where+ put (SRspSuccess) = putWord8 1+ put (SRspRegisterClient i) = putWord8 2 >> put i+ put (SRspUnregisterClient) = putWord8 3+ put (SRspPing b) = putWord8 4 >> put b+ put (SRspServerAction b) = putWord8 5 >> put b+ put (SRspError e) = putWord8 6 >> put e+ put (SRspUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> return (SRspSuccess)+ 2 -> get >>= \i -> return (SRspRegisterClient i)+ 3 -> return (SRspUnregisterClient)+ 4 -> get >>= \b -> return (SRspPing b)+ 5 -> get >>= \b -> return (SRspServerAction b)+ 6 -> get >>= \e -> return (SRspError e)+ _ -> return (SRspUnknown)+ +instance RspMsg (ServerResponseMessage) where+ isError (SRspError _) = True+ isError _ = False+ + getErrorMsg (SRspError e) = e+ getErrorMsg _ = ""+ + isUnknown (SRspUnknown) = True+ isUnknown _ = False+ + mkErrorMsg e = SRspError e+ ++++-- ----------------------------------------------------------------------------+-- Server-TypeClass+-- ----------------------------------------------------------------------------++-- | The request-functions a server has to implement.+class ServerClass s where++ -- | Register a new client in the server database.+ registerClient :: SiteId -> Port ClientRequestMessage -> s -> IO IdType+ + -- | Delete a client from the server database.+ unregisterClient :: IdType -> s -> IO ()+ + -- | Check, if server is responding.+ pingServer :: IdType-> s -> IO Bool++++-- ----------------------------------------------------------------------------+-- Server-Data+-- ----------------------------------------------------------------------------+ +-- | The type of the functions which will be executed by registration and+-- unregistration.+type RegistrationAction = (IdType -> ClientPort -> IO ())+ + +-- The information of the client known by the server.+data ClientInfo = ClientInfo {+ ci_Id :: Int+ , ci_Site :: SiteId -- ^ SiteId (Hostname,PID) of the client process+ , ci_Port :: ClientPort -- ^ the port of the client+ , ci_PingThreadId :: Thread -- ^ the threadId of the ping-Process (needed to stop it)+ , ci_LifeValue :: Int+ }+ +instance Show ClientInfo where+ show (ClientInfo n s p _ i) = "{Id: " ++ show n ++ + " - Site: " ++ show s ++ + " - Port: " ++ show p ++ + " - LifeValue: " ++ show i ++ "}"+++-- | The data of the server needed to organise the clients.+data ServerData = ServerData {+ sd_ServerThreadId :: Thread -- ^ threadId of the streamDispatcher+ , sd_OwnStream :: Stream (ServerRequestMessage) -- ^ the stream the requestDispatcher reads from+ , sd_OwnPort :: Port (ServerRequestMessage) -- ^ the port the clients send messages to+ , sd_ClientMap :: Map.Map IdType ClientInfo -- ^ infomation of the the clients+ , sd_SiteToClientMap :: MMap.MultiMap SiteId IdType -- ^ needed to get the closest client+ , sd_SiteMap :: SiteMap -- ^ needed to get the closest site+ , sd_Register :: RegistrationAction+ , sd_Unregister :: RegistrationAction+ , sd_NextId :: IdType+ }+ + +-- | The server.+data Server = Server (MVar ServerData)+ + +maxLifeValue :: Int+maxLifeValue = 3+++-- | Creates a new server.+newServer+ :: (Binary a, Binary b)+ => StreamName -> Maybe PortNumber+ -> (a -> IO (Maybe b)) -- ^ handling own request+ -> Maybe RegistrationAction -- ^ for registration+ -> Maybe RegistrationAction -- ^ for unregistration + -> IO Server+newServer sn pn dispatch register unregister+ = do+ -- create a new server+ st <- (newStream STGlobal (Just sn) pn::IO (Stream ServerRequestMessage))+ po <- newPortFromStream st+ tid <- newThread+ let reg = maybe (\_ _ -> return ()) id register+ let unreg = maybe (\_ _ -> return ()) id unregister+ let sd = ServerData tid st po Map.empty MMap.empty Map.empty reg unreg 1+ s <- newMVar sd+ let server = Server s+ -- start the requestDispatcher to handle requests+ startRequestDispatcher tid st (dispatchServerRequest server dispatch)+ return server+++-- | Closes the server.+closeServer :: Server -> IO ()+closeServer s@(Server server)+ = do+ debugM localLogger "closeServer: start"+ (allIds,thread,stream) <- withMVar server $+ \sd ->+ do+ -- getAll ClientIds+ let allIds = Map.keys (sd_ClientMap sd)+ return (allIds, sd_ServerThreadId sd, sd_OwnStream sd) + debugM localLogger "closeServer: stopRequestDispatcher"+ -- shutdown the server thread+ stopRequestDispatcher thread+ -- close the stream+ debugM localLogger "closeServer: closeStream"+ closeStream stream+ debugM localLogger "closeServer: unregister clients"+ mapM (\i -> do unregisterClient i s) allIds+ debugM localLogger "closeServer: end"+ return ()+++-- | Handles the requests from the client.+dispatchServerRequest+ :: (Binary a, Binary b)+ => Server+ -> (a -> IO (Maybe b))+ -> ServerRequestMessage+ -> Port (ServerResponseMessage)+ -> IO ()+dispatchServerRequest server action msg replyPort+ = do+ debugM localLogger $ "dispatchServerRequest: " ++ show msg+ case msg of+ (SReqRegisterClient s po) ->+ do+ handleRequest replyPort (registerClient s po server) (\i -> SRspRegisterClient i)+ return ()+ (SReqUnregisterClient n) ->+ do+ handleRequest replyPort (unregisterClient n server) (\_ -> SRspUnregisterClient)+ return ()+ (SReqPing i) ->+ do+ handleRequest replyPort (pingServer i server) (\b -> SRspPing b)+ return ()+ (SReqServerAction b) ->+ do+ handleRequest replyPort+ (action $ decode b) + (\res -> maybe (SRspUnknown) (\r -> SRspServerAction $ encode r) res)+ _ -> + handleRequest replyPort (return ()) (\_ -> SRspUnknown)+++-- | Creates a new client id and updates the serverdata.+getNextId :: ServerData -> (IdType, ServerData)+getNextId sd + = (i, sd { sd_NextId = nid })+ where+ i = sd_NextId sd+ nid = i + 1+++-- | Adds a new client to the server datastructures,+-- the ping-thread will not be started.+addClientToServer+ :: IdType -> SiteId -> ClientPort -> Thread+ -> ServerData -> ServerData+addClientToServer i sid cp tid sd+ = sd { sd_ClientMap = nsm', sd_SiteToClientMap = snm', sd_SiteMap = sm' }+ where+ --update the ClientMap+ nsm = sd_ClientMap sd+ nsm' = Map.insert i (ClientInfo i sid cp tid maxLifeValue) nsm+ --update the SiteToClientMap+ snm = sd_SiteToClientMap sd+ snm' = MMap.insert sid i snm+ -- update the SiteMap+ sm = sd_SiteMap sd+ sm' = addIdToMap sid sm+ ++-- | Deletes a new client from the server datastructures,+-- the ping-thread will not be closed.+deleteClientFromServer :: IdType -> ServerData -> ServerData+deleteClientFromServer i sd + = sd { sd_ClientMap = nsm', sd_SiteToClientMap = snm', sd_SiteMap = sm' }+ where+ --update the ClientMap+ nsm = sd_ClientMap sd+ nsm' = Map.delete i nsm+ --update the SiteToClientMap and the SiteIdMap+ info = lookupClientInfo i sd+ snm = sd_SiteToClientMap sd+ sm = sd_SiteMap sd+ (snm', sm') = deleteSiteId info+ deleteSiteId Nothing = (snm, sm)+ deleteSiteId (Just info') = (MMap.deleteElem sid i snm , deleteIdFromMap sid sm)+ where+ sid = ci_Site info'+++-- | Gets the ClientPort from a ClientId (on the ServerData).+lookupClientInfo :: IdType -> ServerData -> Maybe ClientInfo+lookupClientInfo i sd = Map.lookup i (sd_ClientMap sd)+++-- | Gets a list with all registered clients (on the ServerData).+lookupAllClientInfos :: ServerData -> [ClientInfo]+lookupAllClientInfos sd = Map.elems (sd_ClientMap sd)+++-- | Gets the ClientPort from a ClientId (on the Server).+getClientInfo :: IdType -> Server -> IO (Maybe ClientInfo)+getClientInfo i (Server server)+ = withMVar server $ \sd -> return $ lookupClientInfo i sd +++-- | Gets a list with all registered clients (on the Server).+getAllClientInfos :: Server -> IO [ClientInfo]+getAllClientInfos (Server server)+ = withMVar server $ \sd -> return $ lookupAllClientInfos sd++-- | Sets the life value of a specific client.+setClientLife :: Int -> IdType -> Server -> IO ()+setClientLife v i (Server server)+ = modifyMVar server $+ \sd -> do+ let mbCi = lookupClientInfo i sd+ sd' <- case mbCi of+ (Just ci) -> do+ let ci' = ci {ci_LifeValue = v}+ nsm = sd_ClientMap sd+ nsm' = Map.insert i ci' nsm+ sd' = sd {sd_ClientMap = nsm'}+ return sd'+ (Nothing) -> return sd+ return (sd', ())++-- | Gets the life value of a specific client.+getClientLife :: IdType -> Server -> IO (Int)+getClientLife i (Server server)+ = withMVar server $+ \sd -> do+ let mbCi = lookupClientInfo i sd+ case mbCi of+ (Just ci) -> return $ ci_LifeValue ci+ (Nothing) -> return 0+++instance ServerClass Server where+ registerClient sid po s@(Server server)+ = do+ let cp = newClientPort po+ -- register the client at the server+ (ptid,i,register) <- modifyMVar server $+ \sd ->+ do+ -- create a new Id and a new Port+ let (i, sd') = getNextId sd+ let register = sd_Register sd+ -- add node to controller+ ptid <- newThread+ let sd'' = addClientToServer i sid cp ptid sd'+ return (sd'', (ptid, i, register))+ -- do general registration action+ register i cp+ -- startPingProcess for Client+ setThreadDelay 5000000 ptid+ setThreadAction (checkClient i cp s ptid) ptid+ setThreadErrorHandler (handleCheckClientError i s ptid) ptid+ startThread ptid+ return i+ + unregisterClient i (Server server)+ = do+ debugM localLogger "unregisterClient: start"+ (mbInfo, unregister) <- modifyMVar server $+ \sd ->+ do+ let unregister = sd_Unregister sd+ let mbInfo = lookupClientInfo i sd + let sd' = deleteClientFromServer i sd+ return (sd', (mbInfo,unregister))+ debugM localLogger "unregisterClient: client deleted"+ case mbInfo of+ (Just info) -> + do+ debugM localLogger "unregisterClient: killing the ping thread"+ -- kill the ping-thread+ stopThread (ci_PingThreadId info)+ debugM localLogger "unregisterClient: ping thread killed"+ debugM localLogger "unregisterClient: executing unregister-function"+ -- execute the unregister function+ unregister i (ci_Port info)+ debugM localLogger "unregisterClient: unregister-function executed"+ (Nothing) ->+ do+ debugM localLogger "unregisterClient: no client info found"+ return ()+ debugM localLogger "unregisterClient: end"+ + pingServer i (Server server)+ = withMVar server $+ \sd -> return $ isJust $ lookupClientInfo i sd+ +++instance Debug Server where+ printDebug (Server server)+ = withMVar server $+ \sd ->+ do+ putStrLn "printServer"+ putStrLn $ "OwnStream: " ++ show (sd_OwnStream sd)+ putStrLn $ "OwnPort: " ++ show (sd_OwnPort sd)+ putStrLn $ "ClientMap: " ++ show (sd_ClientMap sd)+ putStrLn $ "SiteToClientMap: " ++ show (sd_SiteToClientMap sd)+ putStrLn $ "SiteMap: " ++ show (sd_SiteMap sd)+ putStrLn $ "NextId: " ++ show (sd_NextId sd)+ +++-- ----------------------------------------------------------------------------+-- Server-Port+-- ----------------------------------------------------------------------------++-- | The ServerPort is only a wrapper for a Port-Datatype. +data ServerPort = ServerPort (Port ServerRequestMessage)+ deriving (Show)++instance Binary ServerPort where+ put (ServerPort p) = put p+ get+ = do+ p <- get+ return (ServerPort p)+ ++-- | Creates a new ServerPort.+newServerPort :: StreamName -> Maybe SocketId -> IO ServerPort+newServerPort sn soid+ = do+ p <- newPort sn soid+ return (ServerPort p)++ +instance ServerClass ServerPort where+ registerClient sid po (ServerPort p) + = do+ withStream $+ \s -> performPortAction p s time30 (SReqRegisterClient sid po) $+ \rsp ->+ do+ case rsp of+ (SRspRegisterClient n) -> return (Just n)+ _ -> return Nothing+ + unregisterClient i (ServerPort p)+ = do+ withStream $+ \s -> performPortAction p s time30 (SReqUnregisterClient i) $+ \rsp ->+ do+ case rsp of+ (SRspUnregisterClient) -> return (Just ())+ _ -> return Nothing+ + pingServer i (ServerPort p)+ = do+ withStream $+ \s -> performPortAction p s time30 (SReqPing i) $+ \rsp ->+ do+ case rsp of+ (SRspPing b) -> return (Just b)+ _ -> return Nothing + + +++-- ----------------------------------------------------------------------------+-- Client-Messages+-- ----------------------------------------------------------------------------+ +-- | Requests datatype, which is send to a filesystem node.+data ClientRequestMessage+ = CReqPing IdType+ | CReqClientAction B.ByteString+ | CReqClientId+ | CReqServerPort+ | CReqUnknown+ deriving (Show)++instance Binary ClientRequestMessage where+ put (CReqPing i) = putWord8 1 >> put i+ put (CReqClientAction b) = putWord8 2 >> put b+ put (CReqClientId) = putWord8 3+ put (CReqServerPort) = putWord8 4+ put (CReqUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> get >>= \i -> return (CReqPing i)+ 2 -> get >>= \b -> return (CReqClientAction b)+ 3 -> return (CReqClientId)+ 4 -> return (CReqServerPort)+ _ -> return (CReqUnknown)+++-- | Response datatype from a filesystem node.+data ClientResponseMessage+ = CRspPing Bool+ | CRspClientAction B.ByteString+ | CRspClientId (Maybe IdType)+ | CRspServerPort ServerPort+ | CRspError String+ | CRspUnknown+ deriving (Show) ++instance Binary ClientResponseMessage where+ put (CRspPing b) = putWord8 1 >> put b+ put (CRspClientAction b) = putWord8 2 >> put b+ put (CRspClientId i) = putWord8 3 >> put i+ put (CRspServerPort p) = putWord8 4 >> put p+ put (CRspError e) = putWord8 5 >> put e+ put (CRspUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> get >>= \b -> return (CRspPing b)+ 2 -> get >>= \b -> return (CRspClientAction b)+ 3 -> get >>= \i -> return (CRspClientId i)+ 4 -> get >>= \p -> return (CRspServerPort p)+ 5 -> get >>= \e -> return (CRspError e)+ _ -> return (CRspUnknown)++instance RspMsg ClientResponseMessage where+ isError (CRspError _) = True+ isError _ = False+ + getErrorMsg (CRspError e) = e+ getErrorMsg _ = ""+ + isUnknown (CRspUnknown) = True+ isUnknown _ = False+ + mkErrorMsg e = CRspError e++++-- ----------------------------------------------------------------------------+-- Client-TypeClass+-- ----------------------------------------------------------------------------++-- | The request-functions a client has to implement.+class ClientClass c where++ -- | Check, if the client is responding.+ pingClient :: IdType-> c -> IO Bool+ + -- | Get the ID of the client.+ getClientId :: c -> IO (Maybe IdType)+ + -- | Gets the server port the client wants to connect to.+ getServerPort :: c -> IO (ServerPort)+ +++ +-- ----------------------------------------------------------------------------+-- Client-Data+-- ----------------------------------------------------------------------------++ +-- | Client datatype.+data ClientData = ClientData {+ cd_ServerThreadId :: Thread+ , cd_PingThreadId :: Thread+ , cd_Id :: Maybe IdType+ , cd_LifeValue :: Int+ , cd_SiteId :: SiteId+ , cd_OwnStream :: Stream ClientRequestMessage+ , cd_OwnPort :: Port ClientRequestMessage+ , cd_ServerPort :: ServerPort+ }++ +-- | Only a wrapper around an MVar.+data Client = Client (MVar ClientData)+++-- | Creates a new client, it needs the StreamName and optional +-- the SocketId of the server.+newClient+ :: (Binary a, Binary b)+ => StreamName -> Maybe SocketId+ -> (a -> IO (Maybe b)) -- ^ the individual request dispatcher for the client+ -> IO Client+newClient sn soid action+ = do + sp <- newServerPort sn soid+ -- initialize values+ sid <- getSiteId+ stid <- newThread + st <- (newLocalStream Nothing::IO (Stream ClientRequestMessage))+ po <- newPortFromStream st+ ptid <- newThread+ let cd = (ClientData stid ptid Nothing maxLifeValue sid st po sp)+ c <- newMVar cd+ let client = Client c + -- first, we start the server, because we can't handle requests without it+ startRequestDispatcher stid st (dispatchClientRequest client action)+ -- then we try to register a the server+ setThreadDelay 5000000 ptid+ setThreadAction (checkServer sp sid po client) ptid+ setThreadErrorHandler (handleCheckServerError client) ptid+ startThread ptid+ return client+++-- | Closes the client.+closeClient :: Client -> IO ()+closeClient (Client client)+ = modifyMVar client $+ \cd ->+ do+ case (cd_Id cd) of+ (Just i) -> unregisterClient i (cd_ServerPort cd)+ (Nothing) -> return ()+ stopRequestDispatcher (cd_ServerThreadId cd)+ closeStream (cd_OwnStream cd)+ stopThread (cd_PingThreadId cd)+ return (cd, ())+++-- | Handles the requests from the server.+dispatchClientRequest+ :: (Binary a, Binary b)+ => Client+ -> (a -> IO (Maybe b))+ -> ClientRequestMessage + -> Port ClientResponseMessage+ -> IO ()+dispatchClientRequest client action msg replyPort+ = do+ debugM localLogger $ "dispatchClientRequest: " ++ show msg+ case msg of+ (CReqPing i) ->+ do+ handleRequest replyPort (pingClient i client) (\b -> CRspPing b)+ return ()+ (CReqClientAction b) ->+ do+ -- now, we have a specific client request+ handleRequest replyPort + (action $ decode b) + (\res -> maybe (CRspUnknown) (\r -> CRspClientAction $ encode r) res)+ _ -> + handleRequest replyPort (return ()) (\_ -> CRspUnknown)+++-- | Test, if the client is registered by a server. +isClientRegistered :: Client -> IO (Bool, Maybe IdType)+isClientRegistered (Client client)+ = withMVar client $ \cd -> return $ (isJust (cd_Id cd), (cd_Id cd))+++-- | Deletes the internal clientId, the client will then be in an +-- unregisterd state.+unsetClientId :: Client -> IO ()+unsetClientId (Client client)+ = modifyMVar client $ \cd -> return ( cd {cd_Id = Nothing}, ())++-- | Sets the life value of the server port.+setLifeValue :: Int -> Client -> IO ()+setLifeValue v (Client client)+ = modifyMVar client $ \cd -> return (cd {cd_LifeValue = v}, ())++-- | Gets the life value of the server port.+getLifeValue :: Client -> IO (Int)+getLifeValue (Client client)+ = withMVar client $ \cd -> return (cd_LifeValue cd)++-- | Assigns a new clientId to the client.+setClientId :: IdType -> Client -> IO ()+setClientId i (Client client)+ = modifyMVar client $ \cd -> return ( cd {cd_Id = Just i}, ()) +++instance ClientClass Client where+ pingClient i (Client client)+ = withMVar client $+ \cd -> return $ (cd_Id cd) == (Just i)+ + getClientId (Client client)+ = withMVar client $+ \cd -> return $ (cd_Id cd)+ + getServerPort (Client client)+ = withMVar client $+ \cd -> return $ (cd_ServerPort cd)+++instance Debug Client where+ printDebug (Client client)+ = withMVar client $+ \cd ->+ do+ putStrLn "printClient"+ putStrLn $ "Id: " ++ show (cd_Id cd)+ putStrLn $ "LifeValue " ++ show (cd_LifeValue cd)+ putStrLn $ "Site: " ++ show (cd_SiteId cd)+ putStrLn $ "OwnStream: " ++ show (cd_OwnStream cd)+ putStrLn $ "OwnPort: " ++ show (cd_OwnPort cd)+ putStrLn $ "ServerPort: " ++ show (cd_ServerPort cd)+++++-- ----------------------------------------------------------------------------+-- Client-Port+-- ----------------------------------------------------------------------------++-- | Just a wrapper around a port.+data ClientPort = ClientPort (Port ClientRequestMessage)+ deriving (Show)++instance Binary ClientPort where+ put (ClientPort p) = put p+ get+ = do+ p <- get+ return (ClientPort p)+++-- | Creates a new ClientPort.+newClientPort :: Port ClientRequestMessage -> ClientPort+newClientPort po = ClientPort po++++instance ClientClass ClientPort where+ pingClient i (ClientPort p)+ = do+ withStream $+ \s -> performPortAction p s time30 (CReqPing i) $+ \rsp ->+ do+ case rsp of+ (CRspPing b) -> return (Just b)+ _ -> return Nothing++ getClientId (ClientPort p)+ = do+ withStream $+ \s -> performPortAction p s time30 (CReqClientId) $+ \rsp ->+ do+ case rsp of+ (CRspClientId i) -> return (Just i)+ _ -> return Nothing+ + getServerPort (ClientPort p)+ = do+ withStream $+ \s -> performPortAction p s time30 (CReqServerPort) $+ \rsp ->+ do+ case rsp of+ (CRspServerPort sp) -> return (Just sp)+ _ -> return Nothing+++++-- ----------------------------------------------------------------------------+-- Communication-Functions+-- ----------------------------------------------------------------------------++-- | Sends a request from the server to the client an handles the response or+-- invokes a user-defined handler.+sendRequestToClient + :: (Show a, Binary a, Binary b)+ => ClientPort -> Int+ -> a+ -> (b -> IO (Maybe c)) -- ^ response handler+ -> IO c+sendRequestToClient (ClientPort p) timeout a handler+ = do+ debugM localLogger $ "sending request to Client: " ++ show a+ withStream $+ \s -> performPortAction p s timeout (CReqClientAction (encode a)) $+ \rsp ->+ do+ case rsp of+ (CRspClientAction b) -> + do+ handler (decode b)+ _ -> return Nothing+++-- | Sends a request from the client to the server an handles the response or+-- invokes a user-defined handler.+sendRequestToServer + :: (Show a, Binary a, Binary b)+ => ServerPort -> Int+ -> a+ -> (b -> IO (Maybe c)) -- ^ response handler+ -> IO c+sendRequestToServer (ServerPort p) timeout a handler+ = do+ debugM localLogger $ "sending message to Server: " ++ show a+ withStream $+ \s -> performPortAction p s timeout (SReqServerAction (encode a)) $+ \rsp ->+ do+ case rsp of+ (SRspServerAction b) -> + do+ handler (decode b)+ _ -> return Nothing++ +-- ----------------------------------------------------------------------------+-- Ping-Functions+-- ----------------------------------------------------------------------------++-- | Checks, if a client is still reachable, otherwise it will be deleted+-- from the server.+checkClient :: IdType -> ClientPort -> Server -> Thread -> IO ()+checkClient i po server thread+ = do+ debugM localLogger "pingClient"+ b <- pingClient i po+ case b of+ False -> + do+ warningM localLogger "pingCient: client is not reachable... delete him"+ handleCheckClientError i server thread+ True -> + do+ debugM localLogger "pingCient: client is ok"+ setClientLife maxLifeValue i server+ return ()+ ++-- | If a client does not respond to a ping, this function is invoked.+-- It deleted the client from the server and stops the ping thread.+handleCheckClientError :: IdType -> Server -> Thread -> IO ()+handleCheckClientError i server thread+ = do+ v <- getClientLife i server+ if (v > 0)+ then do+ setClientLife (v-1) i server+ else do+ unregisterClient i server+ stopThread thread+ return ()+++-- | Checks, if a server is still reachable, otherwise it will be +-- deleted from the client.+checkServer :: ServerPort -> SiteId -> Port ClientRequestMessage -> Client -> IO ()+checkServer sepo sid clpo c+ = do+ debugM localLogger "pingServer"+ (reg,i) <- isClientRegistered c+ if (reg)+ then do+ debugM localLogger "pingServer: client is registered, testing server" + b <- pingServer (fromJust i) sepo+ if (b)+ then do+ debugM localLogger "pingServer: server is ok"+ setLifeValue maxLifeValue c+ return () + else do+ warningM localLogger "pingServer: server is down" + handleCheckServerError c+ else do + debugM localLogger "pingServer: trying to register client"+ i' <- registerClient sid clpo sepo+ setClientId i' c +++-- | If the server does not respond to a ping, this function is invoked.+-- It sets the server to an unregistered state, so it will reconnect again.+handleCheckServerError :: Client -> IO ()+handleCheckServerError c+ = do+ v <- getLifeValue c+ if (v > 0)+ then do setLifeValue (v-1) c+ else do unsetClientId c
+ source/Holumbus/Network/Core.hs view
@@ -0,0 +1,266 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Network.Core+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ The Server-Module for the Holumbus framework.+ + It contains the lowlevel functions, like the socket handling (opening, + reading, writing, ...).+ +-}++-- ----------------------------------------------------------------------------++{-# OPTIONS -fglasgow-exts #-}+module Holumbus.Network.Core+ (+ -- * Socket-Descriptor+ SocketId(..)++ -- * Server-Operations+ , startSocket++ -- * Client-Operations+ , sendRequest++ -- * Handle-Operations+ , putMessage+ , getMessage++ , ThreadIdException(..)+ )+where++import Prelude hiding ( catch )++import Control.Concurrent+import Control.Exception ( Exception+ , bracket+ , catch+ )+import Control.Monad++import Data.Binary+import qualified Data.ByteString.Lazy as B+import Data.Typeable++import Network+import qualified Network.Socket as Socket++import System.Log.Logger+import System.CPUTime+import System.IO+import System.Posix++import Text.Printf+import Text.XML.HXT.Arrow++import Holumbus.Common.Utils ( handleAll )++-- | Logger+localLogger :: String+localLogger = "Holumbus.Network.Core"+++type ServerDispatcher = SocketId -> Handle -> HostName -> PortNumber -> IO ()++-- ------------------------------------------------------------+--+-- exception stuff++data ThreadIdException = ThreadIdException ThreadId+ deriving (Typeable, Show)++instance Exception ThreadIdException where++-- ----------------------------------------------------------------------------+-- Socket Descriptor+-- ----------------------------------------------------------------------------++-- | All data, that is needed to address a socket.+-- Contains the hostname and the portNumber.+data SocketId = SocketId HostName PortNumber + deriving (Show, Eq)++instance Binary (SocketId) where+ put (SocketId hn po)+ = put hn >> (put . toInteger) po+ get+ = do+ hn <- get+ poInt <- get+ return (SocketId hn (fromInteger poInt))++instance XmlPickler SocketId where+ xpickle = xpSocketId++xpSocketId :: PU SocketId+xpSocketId+ = xpElem "socket" $+ xpWrap(\(hn, po) -> SocketId hn (fromInteger po), \(SocketId hn po) -> (hn, (toInteger po))) $+ xpPair (xpAttr "hostname" xpText) (xpAttr "port" xpickle)+++-- ----------------------------------------------------------------------------+-- Server-Operations+-- ----------------------------------------------------------------------------++-- | Creates a new (unix-)socket and starts the listener in its own thread.+-- You'll get the threadId of the listener Thread, so you can kill it.+-- It is also possible to give a range of PortNumbers on which the socket+-- will be opened. The first portnumber available will be taken.+startSocket + :: ServerDispatcher -- ^ dispatcher function+ -> PortNumber -- ^ start port number+ -> PortNumber -- ^ end port number+ -> IO (Maybe (ThreadId, HostName, PortNumber))+startSocket f actPo maxPo+ = do+ s <- (getFirstSocket actPo maxPo)+ case s of+ Nothing -> + return Nothing+ (Just (so, po)) ->+ do+ hn <- getHostName+ tid <- forkIO $ + do+ handleAll+ (\e ->+ do+ putStrLn $ "ERROR - socket closed with exception: " ++ show e + sClose so+ ) $+ do+ {- 6.8+ catchDyn (waitForRequests f so (SocketId hn po)) (handler so)+ -}+ catch+ (waitForRequests f so (SocketId hn po))+ (handler so)+ return (Just (tid, hn, po))+ where+ handler :: Socket -> ThreadIdException -> IO ()+ handler so (ThreadIdException i)+ = do+ sClose so+ putStrLn $ "socket normally closed by thread " ++ show i +++-- | Gets the hostname of the computer of just "localhost".+getHostName :: IO (HostName)+getHostName+ = do+ (hn, _) <- Socket.getNameInfo [] True False (Socket.SockAddrUnix "localhost")+ return (maybe "localhost" id hn)+++-- | Gets the first free port number and creates of new socket on it.+getFirstSocket :: PortNumber -> PortNumber -> IO (Maybe (Socket, PortNumber))+getFirstSocket actPo maxPo+ | actPo > maxPo = return Nothing+ | otherwise + = do+ handleAll (return (getFirstSocket (actPo+1) maxPo)) $+ do+ socket <- getSocket (PortNumber actPo)+ return (Just (socket, actPo)) +++-- | Opens a socket on a port number.+getSocket :: PortID -> IO (Socket)+getSocket po =+ -- for MS-Windows Systems+ withSocketsDo $ do+ -- Don't let the server be terminated by sockets closed unexpectedly by the client.+ installHandler sigPIPE Ignore Nothing+ socket <- listenOn po+ return socket+++-- | Listens to a socket and opens a new dispatcher thread for every incomming+-- data.+waitForRequests :: ServerDispatcher -> Socket -> SocketId -> IO ()+waitForRequests f socket soid = + do+ client <- accept socket+ forkIO $ processRequest f soid client -- Spawn new thread to answer the current request.+ waitForRequests f socket soid -- Wait for more requests.+++-- | A wrapper around the user defined dispatcher function.+-- Mesures the time and catches unhandled exceptions.+processRequest :: ServerDispatcher -> SocketId -> (Handle, HostName, PortNumber) -> IO ()+processRequest f soid client = + bracket (return client) (\(hdl, _, _) -> hClose hdl) (\cl -> processRequest' cl)+ where+ processRequest' (hdl, hst, prt) = + do+ hSetBuffering hdl NoBuffering+ -- Dispatch the request and measure the processing time.+ t1 <- getCPUTime+ debugM localLogger "starting to dispatch request"+ handleAll (\e -> errorM localLogger $ "UnknownError: " ++ show e) $ do+ f soid hdl hst prt+ t2 <- getCPUTime+ d <- return ((fromIntegral (t2 - t1) / 1000000000000) :: Float)+ ds <- return (printf "%.4f" d)+ infoM localLogger ("request processed in " ++ ds ++ " sec")+++-- ----------------------------------------------------------------------------+-- Client-Operations+-- ----------------------------------------------------------------------------++ +-- | Send the query to a server and merge the result with the global result.+sendRequest :: (Handle -> IO a) -> HostName -> PortID -> IO a+sendRequest f n p = + withSocketsDo $ do + installHandler sigPIPE Ignore Nothing+ + --TODO exception handling+ --handle (\e -> do putStrLn $ show e return False) $+ bracket (connectTo n p) (hClose) (send)+ where + send hdl + = do+ hSetBuffering hdl NoBuffering+ f hdl+++-- ----------------------------------------------------------------------------+-- Handle-Operations+-- ----------------------------------------------------------------------------++-- | Puts a bytestring to a handle. But to make the reading easier, we write+-- the length of the data as a message-header to the handle, too. +putMessage :: B.ByteString -> Handle -> IO ()+putMessage msg hdl+ = do+ handleAll (\e -> do+ errorM localLogger $ "putMessage: " ++ show e+ errorM localLogger $ "message: " ++ show msg + ) $ do+ hPutStrLn hdl ((show $ B.length msg) ++ " ")+ B.hPut hdl msg+++-- | Reads data from a stream. We define, that the first line of the message+-- is the message header which tells us how much bytes we have to read.+getMessage :: Handle -> IO (B.ByteString)+getMessage hdl+ = do+ line <- hGetLine hdl+ let pkg = words line+ raw <- B.hGet hdl (read $ head pkg)+ return raw+
+ source/Holumbus/Network/Messages.hs view
@@ -0,0 +1,264 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Network.Messages+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ + General functions for the transmission of messages over the Holumbus-Ports.+ You don't need these functions, but they make your life much easier.+ + The mailbox concept doesn't deal with the request and response scheme+ very well, but with this module, all the boring stuff is solved. The function+ performPortAction will do everything for you. If you find it boring to write+ a seperate listener-thread for every mailbox you want to read from, you+ might look at the startRequestDispatcher and stopRequestDispatcher functions. ++-}++-- ----------------------------------------------------------------------------++{-# OPTIONS -fglasgow-exts #-}+module Holumbus.Network.Messages+(+-- * Message-Class+ RspMsg(..)++-- * Port-Handling+, performPortAction++-- * Stream-Handling+, startRequestDispatcher+, stopRequestDispatcher+, handleRequest+)+where++import Control.Concurrent+import Control.Exception ( Exception+ , throw+ )+{- 6.8+import qualified Control.Exception as E+-}++import Data.Binary+import Data.Maybe+import Data.Typeable++import System.Log.Logger++import Holumbus.Common.Threading+import Holumbus.Common.Utils+import qualified Holumbus.Network.Port as P+++localLogger :: String+localLogger = "Holumbus.Network.Messages"++++-- ----------------------------------------------------------------------------+-- Message-Class+-- ----------------------------------------------------------------------------++-- | The typeclass for the response messages. We want to react if an error+-- message is received an this interface helps us to detect and create such+-- a message. The unknown message will be send back, if the server doesn't+-- understand our request.+class RspMsg m where+ isError :: m -> Bool + getErrorMsg :: m -> String+ isUnknown :: m -> Bool+ mkErrorMsg :: String -> m++++-- ----------------------------------------------------------------------------+-- Port-Handling+-- ----------------------------------------------------------------------------++-- | Every request might raise an exception+data MessageException+ = TimeoutException -- ^ if the server takes too long to respond+ | UnknownRequest -- ^ if the server doesn't understand our request+ | FalseResponse String -- ^ when the server response doesn't match our definition+ | ErrorResponse String -- ^ if an error in the server occurred and he informs us about the error+ deriving (Show, Typeable)++instance Exception MessageException++-- | Sends a repest to the server (stream) and waits for a response.+-- If the response can't be received in a certain time, a TimeoutException+-- will be raised. If a response is received, an individual response handler+-- is executed.+talkWithServer+ :: (Show a, Binary a, Show b, Binary b, RspMsg b)+ => P.Port a -- ^ port to which the message will be send+ -> P.Stream b -- ^ the stream from which the response is read+ -> Int -- ^ timeout for the response in nanoseconds (1000000 = 1 sec) (0 = wait for ever)+ -> a -- ^ message to be send+ -> (b -> IO c) -- ^ handler function for the response + -> IO c+talkWithServer p respStream timeout m hdlFct+ = do+ respPort <- P.newPortFromStream respStream+ -- send the request to the node+ debugM localLogger $ "sending: " ++ show m+ P.sendWithGeneric p m (encode respPort)+ --wait for the response+ debugM localLogger $ "waiting for response for: " ++ show m + response <- P.tryWaitReadStream respStream timeout+ -- r' <- P.readStream respStream+ -- let response = Just r'+ debugM localLogger "response Message..."+ debugM localLogger $ show response+ res <- case response of+ -- if no response+ Nothing -> do+ warningM localLogger "talkWithServer: timeout"+ {- E.throwDyn TimeoutException -}+ throw TimeoutException+ -- handle the response+ (Just r) ->+ hdlFct r+ return res+ ++-- | A wrapper around the user defined response handler.+-- All error and unkown response will be catched, so you don't have to+-- deal with them. But you can't also throw an error in your response +-- function, if you want.+basicResponseHandler+ :: (Show b, Binary b, RspMsg b) + => (b -> IO (Maybe c)) + -> b + -> IO c+basicResponseHandler hdlFct rsp+ = do+ -- look for right message+ res <- hdlFct rsp+ case res of+ -- if right type... return result+ (Just r) -> + return r+ -- else handle error types+ Nothing ->+ handleError+ where+ handleError+ | (isError rsp) = do+ warningM localLogger $ "basicResponseHandler: error: " ++ show rsp+ {- 6.8 E.throwDyn $ ErrorResponse $ getErrorMsg rsp -}+ throw $ ErrorResponse $ getErrorMsg rsp+ | (isUnknown rsp) = do+ warningM localLogger $ "basicResponseHandler: unknown: " ++ show rsp+ {- 6.8 E.throwDyn UnknownRequest -}+ throw UnknownRequest+ | otherwise = do+ warningM localLogger $ "basicResponseHandler: false: " ++ show rsp+ {- E.throwDyn $ FalseResponse $ show rsp -}+ throw $ FalseResponse $ show rsp+ + +-- | Sends a request to the server (stream) and handles the response and all+-- error cases. Very helpful when simulating a request response scheme+-- with the mailboxes. +performPortAction+ :: (Show a, Binary a, Show b, Binary b, RspMsg b) + => P.Port a -- ^ request port+ -> P.Stream b -- ^ response Stream + -> Int -- ^ timeout for the response in mikroseconds (1000000 = 1 sec) (0 = wait for ever)+ -> a -- ^ request message+ -> (b -> IO (Maybe c)) -- ^ response handler+ -> IO c+performPortAction reqPo resStr timeout reqMsg rspHdl+ = do+ talkWithServer reqPo resStr timeout reqMsg $+ basicResponseHandler rspHdl+ ++-- ----------------------------------------------------------------------------+-- Stream-Handling+-- ----------------------------------------------------------------------------++-- | The server-side request dispatcher handles all incomming responses.+-- The dispatcher runs in its own thread and should not be killed by+-- any exceptions which will be raised in the handling process.+startRequestDispatcher+ :: (Binary a, Show a, Show b, Binary b, RspMsg b) + => Thread -- ^ threadId, to be filled+ -> P.Stream a -- ^ request-Stream (this is where the messages come in)+ -> (a -> P.Port b -> IO ()) -- ^ the dispatcher (create a reply message)+ -> IO ()+startRequestDispatcher thread reqS dispatcher+ = do+ setThreadAction (requestDispatcher reqS dispatcher) thread+ startThread thread+++-- | Stops the request dispatcher.+stopRequestDispatcher :: Thread -> IO ()+stopRequestDispatcher thread+ = do+ stopThread thread +++-- | Wrapper around the user-defined dispatching function. For every incomming+-- request a new thread will be created to be able to handle the next request. +requestDispatcher + :: (Binary a, Show a, Show b, Binary b, RspMsg b)+ => P.Stream a + -> (a -> P.Port b -> IO ())+ -> IO ()+requestDispatcher reqS dispatcher+ = do+ -- read the next message from the stream (block, if no message arrived)+ msg <- P.readStreamMsg reqS+ -- extract the data+ let dat = P.getMessageData msg+ debugM localLogger "dispatching new Message... "+ debugM localLogger $ show dat+ -- extract the (possible replyport)+ let responsePort = decodeMaybe $ P.getGenericData msg+ if (isNothing responsePort)+ then do+ warningM localLogger "no reply port in message"+ -- yield+ else do+ -- do the dispatching in a new process...+ _ <- forkIO $ dispatcher dat $ (fromJust responsePort)+ return ()+++-- | Execute a function and send its result to the specified port.+handleRequest+ :: (Show b, Binary b, RspMsg b)+ => P.Port b -- ^ the reply port (where the messages will be send to)+ -> IO c -- ^ the action which will generate the data to be send+ -> (c -> b) -- ^ create an output from the data+ -> IO ()+handleRequest po fhdl fres+ = do+ -- in case, we can't send the error...+ handleAll (\e -> do+ errorM localLogger $ "handleRequest: exeption raised and could not be send to controller" + errorM localLogger $ show e) $ do+ do+ -- in case our operation fails, we send a failure-response+ handleAll ( \e -> do+ errorM localLogger $ "handleRequest: exeption raised and reporting to controller" + errorM localLogger $ show e+ P.send po (mkErrorMsg $ show e)+ ) $+ do+ -- our action, might raise an exception+ r <- fhdl+ -- send the response+ P.send po $ fres r
+ source/Holumbus/Network/Port.hs view
@@ -0,0 +1,982 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Network.Port+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ Stream and Port datatype for internal an external process communication.+ Useful for communikation of distributed systems.+ +-}++-- ----------------------------------------------------------------------------++{-# OPTIONS_GHC -fno-warn-unused-binds #-} -- for unused record field selectors++module Holumbus.Network.Port+(+-- * Constants+ time1+, time10+, time30+, time60+, time120+, timeIndefinitely++-- * Datatypes+, SocketId(..) -- reexport from core+, MessageType+, Message+, StreamName+, StreamType(..)+, Stream+, Port++-- * Message-Operations+, getMessageType+, getMessageData+, getGenericData++-- * Global-Operations+, setPortRegistry++-- * Stream-Operations+, newGlobalStream+, newLocalStream+, newPrivateStream+, newStream+, closeStream++, isEmptyStream+, readStream+, readStreamMsg+, tryReadStream+, tryReadStreamMsg+, tryWaitReadStream+, tryWaitReadStreamMsg+, withStream++-- * Port-Operations+, newPortFromStream+, newPort+, newGlobalPort++, isPortLocal+, send+, sendWithGeneric+, sendWithMaybeGeneric ++, writePortToFile+, readPortFromFile++-- * Debug+, printStreamController+)+where++import Control.Concurrent+import Control.Exception+import Control.Monad++import Data.Binary+import qualified Data.ByteString.Lazy as B+import Data.Char+import qualified Data.Map as Map+import Data.Maybe+import qualified Data.Set as Set+import Data.Time+import Network++import System.IO+import System.IO.Unsafe+import System.Log.Logger+import System.Timeout++import Text.XML.HXT.Arrow++import Holumbus.Common.Utils ( handleAll )+import Holumbus.Network.Site+import Holumbus.Network.Core+import Holumbus.Network.PortRegistry+import qualified Holumbus.Data.MultiMap as MMap+++localLogger :: String+localLogger = "Holumbus.Network.Port"++++-- -----------------------------------------------------------------------------+-- Constants+-- -----------------------------------------------------------------------------+++-- | One second+time1 :: Int+time1 = 1000000++-- | 10 seconds+time10 :: Int+time10 = 10000000++-- | 30 seconds+time30 :: Int+time30 = 30000000+++-- | 60 seconds+time60 :: Int+time60 = 60000000+++-- | 120 seconds+time120 :: Int+time120 = 120000000+++-- | Wait how long it takes+timeIndefinitely :: Int+timeIndefinitely = -1+++-- | The default starting port number +defaultPort :: PortNumber+defaultPort = 9000+++-- | The default maximal port number+maxPort :: PortNumber+maxPort = 40000+++++-- ----------------------------------------------------------------------------+-- Message-Datatype+-- ----------------------------------------------------------------------------++-- | Message Type+-- Is it an internal Message or does it come from an external Node?+data MessageType = MTInternal | MTExternal + deriving (Show)++instance Binary MessageType where+ put MTInternal = putWord8 1+ put MTExternal = putWord8 2+ get+ = do+ t <- getWord8+ case t of+ 1 -> return MTInternal+ _ -> return MTExternal +++-- | Message Datatype.+-- We are sending additional information, to do debugging+data (Show a, Binary a) => Message a = Message {+ msg_Type :: ! MessageType -- ^ the message-type+ , msg_Receiver :: ! StreamName -- ^ the name of the destination stream+ , msg_Data :: ! a -- ^ the data + , msg_Generic :: ! (Maybe B.ByteString) -- ^ some generic data -- could be another port+ , msg_ReceiverSocket :: ! (Maybe SocketId) -- ^ socket to which the message is send (DEBUG)+ , msg_SenderSocket :: ! (Maybe SocketId) -- ^ socket from which the message was send (DEBUG)+ , msg_Send_time :: ! UTCTime -- ^ timestamp from the sender (DEBUG)+ , msg_Receive_time :: ! UTCTime -- ^ timestamp from the receiver (DEBUG)+ } deriving (Show)++instance (Show a, Binary a) => Binary (Message a) where+ put (Message t r d g rs ss t1 t2)+ = do+ put t+ put r+ put d+ put g+ put rs+ put ss+ put $ show t1+ put $ show t2+ get+ = do+ t <- get+ r <- get+ d <- get+ g <- get+ rs <- get+ ss <- get+ t1Str <- get+ t2Str <- get+ return $ (Message t r d g rs ss (read t1Str) (read t2Str))+++-- ----------------------------------------------------------------------------+-- Port-Datatype+-- ----------------------------------------------------------------------------++-- | The port datatype.+data Port a = Port { p_StreamName :: StreamName -- ^ the name of the destination stream+ , p_SocketId :: Maybe SocketId+ }+ deriving (Show)++instance (Show a, Binary a) => Binary (Port a) where+ put (Port sn soid) = put sn >> put soid+ get = do+ sn <- get+ soid <- get+ return (Port sn soid)++instance (Show a, Binary a) => XmlPickler (Port a) where+ xpickle = xpPort+ +xpPort :: PU (Port a)+xpPort = + xpElem "port" $+ xpWrap(\(sn, soid) -> Port sn soid, \(Port sn soid) -> (sn, soid)) $+ xpPair (xpAttr "name" xpText) (xpOption $ xpickle)+++++-- ----------------------------------------------------------------------------+-- Stream-Datatype+-- ----------------------------------------------------------------------------++-- | The name of a stream.+type StreamName = String+++-- | The stream type, determines the accessibility of a stream+data StreamType = STGlobal | STLocal | STPrivate+ deriving (Show, Eq, Ord)++-- | The stream datatype+data Stream a+ = Stream {+ s_StreamName :: StreamName+ , s_SocketId :: SocketId+ , s_Type :: StreamType+ , s_Channel :: BinaryChannel+ }++instance (Show a, Binary a) => Show (Stream a) where+ show (Stream sn soid st _)+ = "(Stream" +++ " - Name: " ++ show sn +++ " - SocketId: " ++ show soid +++ " - Type: " ++ show st +++ " )"+++ +-- ----------------------------------------------------------------------------+-- StreamController-Datatype+-- ----------------------------------------------------------------------------++-- | A chan datatype for binary messages+type BinaryChannel = (Chan (Message B.ByteString))+++-- | The stream controller datatype.+-- We need this to keep a log of all used streams in the program and to+-- use multiple streams per unix-socket. The access information for the+-- PortRegistry is also placed here. There should only be one stream+-- controller per program. To ensure this, we use a global reference to this+-- data object.+data StreamControllerData = StreamControllerData+ (Maybe SocketId)+ (Maybe GenericRegistry)+ Int+ (Map.Map StreamName (BinaryChannel, PortNumber, StreamType))+ (Map.Map PortNumber (ThreadId, HostName))+ (MMap.MultiMap PortNumber StreamName) + +instance Show StreamControllerData where+ show (StreamControllerData ds _ si stm sem pom)+ = "StreamControllerData:\n"+ ++ " DefaultSocket:\t" ++ show ds+ ++ " lastStreamId:\t" ++ show si+ ++ " Streams:\t" ++ show (Map.keys stm)+ ++ " ServerMap:\t" ++ show sem+ ++ " PortMap:\t" ++ show pom+++-- | The stream controller.+type StreamController = MVar StreamControllerData+++++-- ----------------------------------------------------------------------------+-- StreamController-Operations+-- ----------------------------------------------------------------------------++-- | A direct access to the program stream controller.+{-# NOINLINE streamController #-}+streamController :: StreamController+streamController+ = do+ unsafePerformIO $ newMVar emptyStreamControllerData+ where+ emptyStreamControllerData+ = StreamControllerData+ Nothing+ Nothing+ 0+ Map.empty+ Map.empty + MMap.empty +++-- | Sets the link to the PortRegistry in the stream controller +setPortRegistry :: (PortRegistry r) => r -> IO ()+setPortRegistry r+ = modifyMVar streamController $+ \(StreamControllerData s _ i sm pm pmm) ->+ return ((StreamControllerData s (Just $ mkGenericRegistry r) i sm pm pmm),())+++-- | Gets the next generated and program unique stream name.+getNextStreamName :: IO (StreamName)+getNextStreamName + = modifyMVar streamController $+ \(StreamControllerData s r i sm pm pmm) ->+ do+ let i' = i + 1+ let n = "$" ++ show i'+ let scd' = StreamControllerData s r i' sm pm pmm+ return (scd',n)+ ++-- | Test, if a user defined stream name is valid.+-- A stream name may only contain numbers and characters.+isValidStreamName :: StreamName -> Bool+isValidStreamName [] = False+isValidStreamName sn = null $ filter isForbiddenChar sn + where+ isForbiddenChar c = not $ isAlphaNum c+++-- | Test, if the stream name is valid and if it is not already used in the+-- program.+validateStreamName :: Maybe StreamName -> IO (StreamName)+validateStreamName (Nothing) = getNextStreamName+validateStreamName (Just sn)+ | isValidStreamName sn + = do+ taken <- withMVar streamController $+ \(StreamControllerData _ _ _ sm _ _) -> + return $ Map.member sn sm + if (taken) + then do error "stream name already exists"+ else do return sn + | otherwise + = error "invalid stream name"+++-- | Opens a unix-socket on the given port number. If no port number is +-- specified, the default port number will be used. It is not problem that +-- two ports share the same unix-socket. The stream controller will handle+-- the incomming messages to the right stream.+openSocket :: Maybe PortNumber -> IO (SocketId)+-- get/start the default socket+openSocket (Nothing)+ = modifyMVar streamController $+ \scd@(StreamControllerData s r i sm pm pmm) ->+ do+ case s of+ (Nothing) ->+ do+ res <- startStreamServer defaultPort maxPort+ case res of+ (Nothing) -> + error "Port: getDefaultPort: unable to open defaultsocket"+ (Just (soid@(SocketId hn pn), tid)) ->+ do+ let pm' = Map.insert pn (tid,hn) pm+ let s' = Just soid+ let scd' = StreamControllerData s' r i sm pm' pmm+ return (scd',soid)+ (Just soid) -> + return (scd,soid)+-- get/start the new socket+openSocket (Just pn)+ = modifyMVar streamController $+ \scd@(StreamControllerData s r i sm pm pmm) ->+ do+ let mp = Map.lookup pn pm+ case mp of+ (Nothing) ->+ do+ res <- startStreamServer pn pn+ case res of+ (Nothing) -> + error "Port: getDefaultPort: unable to open socket"+ (Just (soid@(SocketId hn _), tid)) ->+ do+ let pm' = Map.insert pn (tid,hn) pm+ let scd' = StreamControllerData s r i sm pm' pmm+ return (scd',soid)+ (Just (_,hn)) ->+ return (scd,SocketId hn pn)+ ++-- | Closes a socket, only possible, if the unix-socket was used by only one+-- stream.+closeSocket :: SocketId -> IO ()+closeSocket soid@(SocketId _ pn)+ = modifyMVar streamController $+ \scd@(StreamControllerData s r i sm pm pmm) ->+ do+ if (isNothing s || soid == fromJust s)+ -- don't delete the defaultSocket+ then do+ return (scd,())+ else do+ if (MMap.member pn pmm)+ -- if the port is still used by other streams, keep it+ then do+ return (scd,())+ -- close the socket and delete it from the controller+ else do+ let (tid, _) = fromJust $ Map.lookup pn pm+ stopStreamServer tid+ let pm' = Map.delete pn pm+ let scd' = StreamControllerData s r i sm pm' pmm+ return (scd',())+ ++-- | Registers a stream at the stream controller.+registerStream :: Stream a -> IO ()+registerStream st+ = modifyMVar streamController $+ \(StreamControllerData s r i sm pm pmm) ->+ do+ let sm' = Map.insert sn (ch,pn,ty) sm+ let pmm' = MMap.insert pn sn pmm + return ((StreamControllerData s r i sm' pm pmm'), ())+ where + (SocketId _ pn) = s_SocketId st+ ch = s_Channel st+ sn = s_StreamName st+ ty = s_Type st+++-- | Deletes a stream from the stream controller. +unregisterStream :: Stream a -> IO ()+unregisterStream st+ = modifyMVar streamController $+ \(StreamControllerData s r i sm pm pmm) ->+ do+ let sm' = Map.delete sn sm+ let pmm' = MMap.deleteElem pn sn pmm+ return ((StreamControllerData s r i sm' pm pmm'), ()) + where + (SocketId _ pn) = s_SocketId st+ sn = s_StreamName st+++-- | Registers a stream at the global PortRegistry.+registerGlobalPort :: Stream a -> IO ()+registerGlobalPort (Stream sn soid STGlobal _)+ = do+ r <- withMVar streamController $+ \(StreamControllerData _ r' _ _ _ _) -> return r'+ case r of+ (Just r') -> registerPort sn soid r'+ (Nothing) -> errorM localLogger $ "registerGlobalPort: no portregistry while handling port " ++ sn+registerGlobalPort _ = return ()+++-- | Deletes a stream from the global PortRegistry.+unregisterGlobalPort :: Stream a -> IO ()+unregisterGlobalPort (Stream sn _ STGlobal _)+ = do+ r <- withMVar streamController $+ \(StreamControllerData _ r' _ _ _ _) -> return r'+ case r of+ (Just r') -> unregisterPort sn r'+ (Nothing) -> errorM localLogger $ "unregisterGlobalPort: no portregistry while handling port " ++ sn+unregisterGlobalPort _ = return ()+++-- | Get the unix-socket of a stream to create a global port.+getGlobalPort :: StreamName -> IO (Maybe SocketId)+getGlobalPort sn+ = do+ r <- withMVar streamController $+ \(StreamControllerData _ r' _ _ _ _) -> return r'+ case r of+ (Just rp) -> + do+ handleAll (\e -> do+ errorM localLogger $ "getGlobalPort: error while getting port: " ++ sn ++ " exception: " ++ show e+ return Nothing+ ) $ do+ lookupPort sn rp + (Nothing) -> do + errorM localLogger $ "getGlobalPort: no portregistry found while getting port: " ++ sn+ return Nothing+++-- | Gets the names of all stream from a specified port number.+getStreamNamesForPort :: PortNumber -> IO (Set.Set StreamName)+getStreamNamesForPort pn+ = withMVar streamController $+ \(StreamControllerData _ _ _ _ _ pmm) ->+ return $ MMap.lookup pn pmm+++-- | Gets the data of a stream from the stream controller.+getStreamData :: StreamName -> IO (Maybe (BinaryChannel, PortNumber, StreamType))+getStreamData sn+ = withMVar streamController $+ \(StreamControllerData _ _ _ sm _ _) ->+ return $ Map.lookup sn sm++++-- -----------------------------------------------------------------------------+-- Message-Operations+-- -----------------------------------------------------------------------------++-- | Encodes a message into a bytestring.+encodeMessage :: (Show a, Binary a) => Message a -> Message B.ByteString+encodeMessage (Message t n d g r s t1 t2) = (Message t n (encode d) g r s t1 t2)+++-- | Decodes a message from a bytestring.+decodeMessage :: (Show a, Binary a) => Message B.ByteString -> Message a+decodeMessage (Message t n d g r s t1 t2) = (Message t n (decode d) g r s t1 t2)+++-- | Creates a new message with the current time.+newMessage :: (Show a, Binary a) => MessageType -> StreamName -> a -> Maybe B.ByteString -> IO (Message a)+newMessage t n d g+ = do+ time <- getCurrentTime+ return (Message t n d g Nothing Nothing time time)+++-- | Gets the type of a message.+getMessageType :: (Show a, Binary a) => Message a -> MessageType+getMessageType = msg_Type+++-- | Gets the data of a message.+getMessageData :: (Show a, Binary a) => Message a -> a+getMessageData = msg_Data+++-- | Gets the generic data (usually the return port) of a message.+getGenericData :: (Show a, Binary a) => Message a -> (Maybe B.ByteString)+getGenericData = msg_Generic+++-- | Gets the distination stream name of the message.+getMessageReceiver :: (Show a, Binary a) => Message a -> StreamName+getMessageReceiver = msg_Receiver+++-- | Sets the receive time of a message.+updateReceiveTime :: (Show a, Binary a) => Message a -> IO (Message a)+updateReceiveTime msg + = do+ time <- getCurrentTime+ return (msg {msg_Receive_time = time})++-- | Sets the receiver unix-socket of the message.+updateReceiverSocket :: (Show a, Binary a) => Message a -> SocketId -> Message a+updateReceiverSocket msg soId = msg { msg_ReceiverSocket = Just soId }+++-- | Sets the sender unix-socket of the message.+updateSenderSocket :: (Show a, Binary a) => Message a -> SocketId -> Message a+updateSenderSocket msg soId = msg { msg_SenderSocket = Just soId } +++++-- ----------------------------------------------------------------------------+-- Stream-Operations+-- ----------------------------------------------------------------------------+++-- | Creates a new global stream.+newGlobalStream :: (Show a, Binary a) => StreamName -> IO (Stream a)+newGlobalStream n = newStream STGlobal (Just n) Nothing+++-- | Creates a new local stream.+newLocalStream :: (Show a, Binary a) => Maybe StreamName -> IO (Stream a)+newLocalStream sn = newStream STLocal sn Nothing+++-- | Creates a new private stream.+newPrivateStream :: (Show a, Binary a) => Maybe StreamName -> IO (Stream a)+newPrivateStream sn = newStream STPrivate sn Nothing+++-- | General function for creating a new stream.+newStream + :: (Show a, Binary a) + => StreamType -> Maybe StreamName -> Maybe PortNumber + -> IO (Stream a)+newStream STGlobal Nothing _ = error "newStream: global ports always need a name"+newStream st n pn+ = do+ ch <- newChan+ sn <- validateStreamName n+ infoM localLogger $ "opening socket for " ++ sn+ soid <- openSocket pn+ let s = Stream sn soid st ch+ infoM localLogger $ "registering " ++ show sn ++ " at controller"+ registerStream s+ infoM localLogger $ "registering " ++ show sn ++ " at registry"+ registerGlobalPort s+ return s+++-- | Closes a stream.+closeStream :: (Show a, Binary a) => Stream a -> IO ()+closeStream s+ = do+ infoM localLogger $ "unregistering " ++ show sn ++ " at registry"+ unregisterGlobalPort s+ infoM localLogger $ "unregistering " ++ show sn ++ " at controller"+ unregisterStream s+ infoM localLogger $ "closing socket for " ++ sn+ closeSocket (s_SocketId s)+ where+ sn = s_StreamName s+++-- | Writes a message to the channel of the stream.+-- This function is not public, so you have to write to a stream throug its+-- port+writeChannel :: Chan (Message B.ByteString) -> Message B.ByteString -> IO ()+writeChannel ch msg+ = do+ newMsg <- updateReceiveTime msg+ writeChan ch newMsg+++-- | Test, if the stream contains new messages.+isEmptyStream :: Stream a -> IO Bool+isEmptyStream s+ = do+ isEmptyChan (s_Channel s)+++-- | Reads the data packet of the next message from a stream.+-- If stream is empty, this function will block until a new message arrives.+readStream :: (Show a, Binary a) => Stream a -> IO a+readStream s+ = do+ msg <- readStreamMsg s+ return (msg_Data msg)+++-- | Reads the next message from a stream (data packet + message header).+-- If stream is empty, this function will block until a new message arrives.+readStreamMsg :: (Show a, Binary a) => Stream a -> IO (Message a)+readStreamMsg s+ = do+ debugM localLogger "PORT: readStreamMsg 1"+ res <- readChan (s_Channel s)+ debugM localLogger "PORT: readStreamMsg 2"+ return $ decodeMessage res+++--TODO is there a better way for non-blocking?+-- | Helper function for doing a non blocking stream action.+tryStreamAction :: Stream a -> (Stream a -> IO (b)) -> IO (Maybe b)+tryStreamAction s f+ = do+ empty <- isEmptyStream s+ if (not empty) + then do timeout 1000 (f s)+ else do return Nothing+++-- | Helper function for doing a timed stream action.+tryWaitStreamAction :: Stream a -> (Stream a -> IO (b)) -> Int -> IO (Maybe b)+tryWaitStreamAction s f t+ = do+ debugM localLogger "tryWaitStreamAction: waiting..."+ r <- timeout t (f s)+ debugM localLogger "tryWaitStreamAction: ...finished"+ let debugResult = maybe "nothing received" (\_ -> "value found") r+ debugM localLogger $ "tryWaitStreamAction: " ++ debugResult+ return r+ ++-- | Reads the data packet of the next message from a stream.+-- If stream is empty, this function will immediately return with Nothing.+tryReadStream :: (Show a, Binary a) => Stream a -> IO (Maybe a)+tryReadStream s+ = do+ tryStreamAction s readStream+++-- | Reads the next message from a stream (data packet + message header).+-- If stream is empty, this function will immediately return with Nothing.+tryReadStreamMsg :: (Show a, Binary a) => Stream a -> IO (Maybe (Message a))+tryReadStreamMsg s+ = do+ tryStreamAction s readStreamMsg+++-- | Reads the data packet of the next message from a stream.+-- If stream is empty, this function will wait for new messages until the +-- time is up and if no message has arrived, return with Nothing.+tryWaitReadStream :: (Show a, Binary a) => Stream a -> Int -> IO (Maybe a)+tryWaitReadStream s t + = do+ tryWaitStreamAction s readStream t+++-- | Reads the next message from a stream (data packet + message header).+-- If stream is empty, this function will wait for new messages until the +-- time is up and if no message has arrived, return with Nothing.+tryWaitReadStreamMsg :: (Show a, Binary a) => Stream a -> Int -> IO (Maybe (Message a))+tryWaitReadStreamMsg s t+ = do+ tryWaitStreamAction s readStreamMsg t+++-- | Encapsulates a stream.+-- A new stream is created, then some user-action is done an after that the+-- stream is closed. +withStream :: (Show a, Binary a) => (Stream a -> IO b) -> IO b+withStream f+ = do+ debugM localLogger "withStream: creating new stream"+ s <- newStream STLocal Nothing Nothing+ debugM localLogger "withStream: new stream created"+ res <- f s+ closeStream s+ return res+++++-- ----------------------------------------------------------------------------+-- StreamServer-Operations+-- ----------------------------------------------------------------------------++-- | Starts a new thread which will listen on a unix-socket for new messages+-- and delegate them to their streams.+startStreamServer :: PortNumber -> PortNumber -> IO (Maybe (SocketId, ThreadId))+startStreamServer actPo maxPo+ = do+ res <- startSocket (streamDispatcher) actPo maxPo+ case res of+ Nothing ->+ return Nothing+ (Just (tid, hn, po)) ->+ return (Just (SocketId hn po, tid))+ ++-- | Stops a thread listening to a unix-socket.+stopStreamServer :: ThreadId -> IO ()+stopStreamServer sId + = do+ me <- myThreadId+ debugM localLogger $ "stopping server... with threadId: " ++ show sId ++ " - form threadId: " ++ show me+ {- 6.8 throwDynTo sId me -}+ throwTo sId (ThreadIdException me)+ yield+++-- | Delegates new incomming messages on a unix-socket to their streams.+streamDispatcher :: SocketId -> Handle -> HostName -> PortNumber -> IO ()+streamDispatcher (SocketId _ ownPo) hdl hn po+ = do+ debugM localLogger "streamDispatcher: getting message from handle"+ raw <- getMessage hdl+ let msg = (decode raw)::Message B.ByteString+ debugM localLogger $ "streamDispatcher: Message: " ++ show msg+ let sn = getMessageReceiver msg+ sns <- getStreamNamesForPort ownPo+ sd <- getStreamData sn+ if (Set.member sn sns)+ -- if the socket knows the stream + then do+ case sd of+ (Just (_,_,STPrivate)) ->+ warningM localLogger $ "streamDispatcher: received msg for private stream " ++ sn+ (Just (ch,_,_)) ->+ do+ debugM localLogger "streamDispatcher: writing message to channel"+ writeChannel ch $ updateSenderSocket msg (SocketId hn po)+ debugM localLogger "streamDispatcher: message written to channel"+ (Nothing) ->+ do+ errorM localLogger $ "streamDispatcher: no channel for stream " ++ sn+ -- if the stream is unknown, log this as error+ else do+ warningM localLogger $ "streamDispatcher: received msg for unknown stream " ++ sn+++++-- -----------------------------------------------------------------------------+-- Port Operations +-- -----------------------------------------------------------------------------+++-- | Creates a new Port, which is bound to a stream.+newPortFromStream :: Stream a -> IO (Port a)+newPortFromStream s+ = do+ let sn = s_StreamName s+ let soid = s_SocketId s+ return $ Port sn (Just soid)+++-- | Creates a new port from a streamname and its socketId.+newPort :: StreamName -> Maybe SocketId -> IO (Port a)+newPort sn soid = return $ Port sn soid+++-- | Creates a new port to a global stream, only its name is needed.+newGlobalPort :: StreamName -> IO (Port a)+newGlobalPort sn = return $ Port sn Nothing+++-- | Test, if a port is local.+isPortLocal :: Port a -> IO Bool+isPortLocal (Port sn mbSoid)+ = do+ sd <- getStreamData sn+ case sd of+ (Just (_,po,_)) ->+ do+ case mbSoid of+ (Just s1) ->+ do+ sid <- getSiteId+ let hn = getSiteHost sid+ let s2 = SocketId hn po + return (s1 == s2)+ (Nothing) ->+ do+ return False+ (Nothing) ->+ return False+ + +-- | Send data to the stream of the port.+-- The data is send via network, if the stream is located on an external+-- processor+send :: (Show a, Binary a) => Port a -> a -> IO ()+send p d = sendWithMaybeGeneric p d Nothing+++-- | Like "send", but here we can give some generic data (e.g. a port for reply +-- messages).+sendWithGeneric :: (Show a, Binary a) => Port a -> a -> B.ByteString -> IO ()+sendWithGeneric p d rp = sendWithMaybeGeneric p d (Just rp) +++-- | Like "sendWithGeneric", but the generic data is optional+sendWithMaybeGeneric :: (Show a, Binary a) => Port a -> a -> Maybe B.ByteString -> IO ()+sendWithMaybeGeneric p@(Port sn mbsoid) d rp+ = do+ local <- isPortLocal p+ if local+ -- we have a local stream+ then do+ msg <- newMessage MTInternal sn d rp+ sd <- getStreamData sn+ case sd of+ (Just (ch,_,_)) ->+ do+ writeChannel ch $ encodeMessage msg+ _ ->+ do+ errorM localLogger $ "sendWithMaybeGeneric: no channel found for stream " ++ sn+ else do+ -- we have an external stream+ case mbsoid of+ -- we know it's port+ (Just so@(SocketId hn po)) ->+ do+ msg <- newMessage MTExternal sn d rp+ let raw = encode $ encodeMessage $ updateReceiverSocket msg so+ sendRequest (putMessage raw) hn (PortNumber po)+ return ()+ -- we don't know it's port+ (Nothing) ->+ do+ extsoid <- getGlobalPort sn+ case extsoid of+ (Just so@(SocketId hn po)) ->+ do+ msg <- newMessage MTExternal sn d rp+ let raw = encode $ encodeMessage $ updateReceiverSocket msg so+ sendRequest (putMessage raw) hn (PortNumber po)+ return ()+ (Nothing) ->+ do+ errorM localLogger errorMsg+ error errorMsg+ where+ errorMsg = "sendWithMaybeGeneric: global port not found for stream " ++ sn+++-- | Writes a port-description to a file.+-- Quite useful fpr sharing ports between programs+writePortToFile :: (Show a, Binary a) => Port a -> FilePath -> IO ()+writePortToFile p fp+ = do+ bracket + (openFile fp WriteMode)+ (hClose) + (\hdl ->+ do + enc <- return (encode $ p)+ hPutStrLn hdl ((show $ B.length enc) ++ " ")+ B.hPut hdl enc+ )+++-- | Reads a port-description from a file.+-- Quite useful fpr sharing ports between programs+readPortFromFile :: (Show a, Binary a) => FilePath -> IO (Port a)+readPortFromFile fp+ = do+ bracket+ (openFile fp ReadMode)+ (hClose)+ (\hdl -> + do+ pkg <- liftM words $ hGetLine hdl+ raw <- B.hGet hdl (read $ head pkg)+ p <- return (decode raw)+ return p+ )+++++-- ----------------------------------------------------------------------------+-- Debugging+-- ----------------------------------------------------------------------------++-- | Prints the internal data of the stream controller to stdout,+-- useful for debugging.+printStreamController :: IO ()+printStreamController+ = withMVar streamController $+ \scd ->+ do+ putStrLn "StreamController:"+ putStrLn $ show scd+
+ source/Holumbus/Network/PortRegistry.hs view
@@ -0,0 +1,81 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Network.PortRegistry+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ The typeclass for the PortRegistry. Contains all PortRegistry functions which+ can accessed remotely by the clients.+ +-}+-- ----------------------------------------------------------------------------++{-# OPTIONS -fglasgow-exts #-}+module Holumbus.Network.PortRegistry+ (+ -- * Type-Classes+ PortRegistry(..)++ -- * GenericRegistry+ , GenericRegistry+ , mkGenericRegistry+ )+where++import Holumbus.Network.Core++-- ----------------------------------------------------------------------------+-- Type-Class+-- ----------------------------------------------------------------------------++-- | The Interface of the PortRegistry.+class PortRegistry pr where++ -- | Creates a new port entry in the registry.+ registerPort :: String -> SocketId -> pr -> IO ()++ -- | Deletes an entry from the registry.+ unregisterPort :: String -> pr -> IO ()++ -- | Get the socket of the port by its name.+ lookupPort :: String -> pr -> IO (Maybe SocketId)++ -- | Get a list of all registered ports.+ getPorts :: pr -> IO [(String, SocketId)]+ ++++-- ----------------------------------------------------------------------------+-- GenericRegistry+-- ----------------------------------------------------------------------------+ +-- | The generic registry object.+-- This is a wrapper around an PortRegistryData or PortRegistryPort object.+-- With this additional indirection, we eliminate the distinction between+-- the port or the data object in the datatypes using the PortRegistry.+-- Therefore it is easier to get access to the registry and the code gets +-- more readable.+-- This might be a good example of hiding network-access. To the caller it+-- makes no difference, if the PortRegistry is in the same address space of+-- on another computer in the network.++data GenericRegistry = forall r. (PortRegistry r) => GenericRegistry r+++-- | Creates a new generic PortRegistry.+mkGenericRegistry :: (PortRegistry r) => r -> GenericRegistry+mkGenericRegistry = GenericRegistry+++instance PortRegistry GenericRegistry where+ registerPort sn soid (GenericRegistry r) = registerPort sn soid r+ unregisterPort sn (GenericRegistry r) = unregisterPort sn r+ lookupPort sn (GenericRegistry r) = lookupPort sn r+ getPorts (GenericRegistry r) = getPorts r
+ source/Holumbus/Network/PortRegistry/Messages.hs view
@@ -0,0 +1,121 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Network.PortRegistry.Messages+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ This modules defines the messages from and to the PortRegistry.++-}+-- ----------------------------------------------------------------------------++module Holumbus.Network.PortRegistry.Messages+(+ PortRegistryRequestStream+, PortRegistryRequestPort++, PortRegistryResponseStream+, PortRegistryResponsePort++, PortRegistryRequestMessage(..)+, PortRegistryResponseMessage(..)+)+where++import Data.Binary++import Holumbus.Network.Port+import Holumbus.Network.Core+import Holumbus.Network.Messages+++-- ----------------------------------------------------------------------------+-- Ports+-- ----------------------------------------------------------------------------++-- | The Stream for the messages TO the PortRegistry.+type PortRegistryRequestStream = Stream PortRegistryRequestMessage++-- | The Port for the messages TO the PortRegistry.+type PortRegistryRequestPort = Port PortRegistryRequestMessage++-- | The Stream for the messages FROM the PortRegistry.+type PortRegistryResponseStream = Stream PortRegistryResponseMessage++-- | The Port for the messages FROM the PortRegistry.+type PortRegistryResponsePort = Port PortRegistryResponseMessage++++-- ----------------------------------------------------------------------------+-- Messages+-- ----------------------------------------------------------------------------++-- | The messages TO the PortRegistry.+data PortRegistryRequestMessage+ = PRReqRegister StreamName SocketId+ | PRReqUnregister StreamName+ | PRReqLookup StreamName+ | PRReqGetPorts+ | PRReqUnknown+ deriving (Show)++instance Binary PortRegistryRequestMessage where+ put (PRReqRegister sn soid) = putWord8 1 >> put sn >> put soid+ put (PRReqUnregister sn) = putWord8 2 >> put sn+ put (PRReqLookup sn) = putWord8 3 >> put sn+ put (PRReqGetPorts) = putWord8 4+ put (PRReqUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> get >>= \sn -> get >>= \soid -> return (PRReqRegister sn soid)+ 2 -> get >>= \sn -> return (PRReqUnregister sn)+ 3 -> get >>= \sn -> return (PRReqLookup sn)+ 4 -> return (PRReqGetPorts)+ _ -> return (PRReqUnknown)+++-- | The messages FROM the PortRegistry.+data PortRegistryResponseMessage+ = PRRspSuccess+ | PRRspLookup (Maybe SocketId)+ | PRRspGetPorts [(String,SocketId)]+ | PRRspError String+ | PRRspUnknown+ deriving (Show)++instance Binary PortRegistryResponseMessage where+ put (PRRspSuccess) = putWord8 1+ put (PRRspLookup soid) = putWord8 2 >> put soid+ put (PRRspGetPorts ls) = putWord8 3 >> put ls+ put (PRRspError e) = putWord8 4 >> put e+ put (PRRspUnknown) = putWord8 0+ get+ = do+ t <- getWord8+ case t of+ 1 -> return (PRRspSuccess)+ 2 -> get >>= \soid -> return (PRRspLookup soid)+ 3 -> get >>= \ls -> return (PRRspGetPorts ls)+ 4 -> get >>= \e -> return (PRRspError e)+ _ -> return (PRRspUnknown)++instance RspMsg PortRegistryResponseMessage where+ isError (PRRspError _) = True+ isError _ = False+ + getErrorMsg (PRRspError e) = e+ getErrorMsg _ = ""+ + isUnknown (PRRspUnknown) = True+ isUnknown _ = False + + mkErrorMsg e = PRRspError e
+ source/Holumbus/Network/PortRegistry/PortRegistryData.hs view
@@ -0,0 +1,163 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Network.PortRegistry.PortRegistryData+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1+++ This module contains the main datatype for the PortRegistry.+ +-}+-- ----------------------------------------------------------------------------++module Holumbus.Network.PortRegistry.PortRegistryData+(+-- * Datatypes+ PortRegistryData++-- * Creation and Destruction+, newPortRegistryData+, closePortRegistryData++, getPortRegistryRequestPort++-- * reexport+, setPortRegistry +)+where+++import Control.Concurrent+import qualified Data.Map as Map+import Network+import System.Log.Logger++import Holumbus.Common.Threading+import Holumbus.Network.Port+import Holumbus.Network.Messages+import Holumbus.Network.PortRegistry+import Holumbus.Network.PortRegistry.Messages+++localLogger :: String+localLogger = "Holumbus.Network.PortRegistry.PortRegistryData"++++-- ----------------------------------------------------------------------------+-- Datatypes+-- ----------------------------------------------------------------------------++-- | The data needed by the PortRegistry+data PortRegistryData = PortRegistryData {+ prd_ServerThreadId :: Thread -- ^ The thread-data of the message-dispatcher thread.+ , prd_OwnStream :: PortRegistryRequestStream -- ^ The Stream for all incomming messages.+ , prd_SocketMap :: MVar (Map.Map StreamName SocketId) -- ^ The map storing the port-data (the real registry).+ }+++-- ----------------------------------------------------------------------------+-- Creation and Destruction+-- ----------------------------------------------------------------------------+++-- | Creates a new PortRegistry.+newPortRegistryData :: StreamName -> Maybe PortNumber -> IO PortRegistryData+newPortRegistryData sn pn+ = do+ st <- (newStream STLocal (Just sn) pn)::IO PortRegistryRequestStream+ mMVar <- newMVar Map.empty+ sMVar <- newThread + let prd = PortRegistryData sMVar st mMVar+ startRequestDispatcher sMVar st (dispatch prd)+ return prd+++-- | Closes the PortRegistry with its streams and threads.+closePortRegistryData :: PortRegistryData -> IO ()+closePortRegistryData prd+ = do+ stopRequestDispatcher (prd_ServerThreadId prd)+ closeStream (prd_OwnStream prd)+ return ()+++-- | Get the RequestPort of the PortRegistry.+-- It can be used to give access to the PortRegistry, eg. you can serialize+-- this information and transfer it over the network to grant access to the+-- clients.+getPortRegistryRequestPort :: PortRegistryData -> IO PortRegistryRequestPort+getPortRegistryRequestPort prd = newPortFromStream (prd_OwnStream prd)+++-- | The main dispatch-function. It handles the incomming messages and reacts.+dispatch + :: PortRegistryData + -> PortRegistryRequestMessage + -> PortRegistryResponsePort+ -> IO ()+dispatch prd msg replyPort+ = do+ case msg of+ (PRReqRegister sn soid) ->+ do+ handleRequest replyPort (registerPort sn soid prd) (\_ -> PRRspSuccess)+ return ()+ (PRReqUnregister sn) -> + do+ handleRequest replyPort (unregisterPort sn prd) (\_ -> PRRspSuccess)+ return ()+ (PRReqLookup sn) ->+ do+ handleRequest replyPort (lookupPort sn prd) (\soid -> PRRspLookup soid)+ return ()+ (PRReqGetPorts) ->+ do+ handleRequest replyPort (getPorts prd) (\ls -> PRRspGetPorts ls)+ return ()+ _ -> + do+ infoM localLogger $ "dispatch: unknown request " ++ show msg+ handleRequest replyPort (return ()) (\_ -> PRRspUnknown)++++-- ----------------------------------------------------------------------------+-- Typeclass instanciation (PortRegistry)+-- ----------------------------------------------------------------------------+++-- The PortRegistry-typeclass instanciation for the PortRegistryData.+instance PortRegistry PortRegistryData where+ + registerPort sn soid prd+ = do+ infoM localLogger $ "register: " ++ sn ++ " - at: " ++ show soid + modifyMVar (prd_SocketMap prd) $+ \sm -> return (Map.insert sn soid sm, ())++ unregisterPort sn prd+ = do+ infoM localLogger $ "unregister: " ++ sn+ modifyMVar (prd_SocketMap prd) $+ \sm -> return (Map.delete sn sm, ())+ + lookupPort sn prd+ = do+ infoM localLogger $ "looking up: " ++ sn+ soid <- withMVar (prd_SocketMap prd) $+ \sm -> return (Map.lookup sn sm)+ infoM localLogger $ "result for: " ++ sn ++ " -> " ++ show soid+ return soid+ + getPorts prd+ = do+ infoM localLogger $ "getting all ports"+ withMVar (prd_SocketMap prd) $+ \sm -> return (Map.toList sm)+
+ source/Holumbus/Network/PortRegistry/PortRegistryPort.hs view
@@ -0,0 +1,144 @@+-- ----------------------------------------------------------------------------+{- |+ Module : Holumbus.Network.PortRegistry.PortRegistryPort+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ + This module contains the Interface for external access to the PortRegistry.+ +-}+-- ----------------------------------------------------------------------------++module Holumbus.Network.PortRegistry.PortRegistryPort+(+-- * Datatypes+ PortRegistryPort+ +-- * Creation+, newPortRegistryPort+, newPortRegistryFromData+, newPortRegistryFromXmlFile ++-- * reexport+, setPortRegistry +)+where++import System.Log.Logger++import Holumbus.Common.FileHandling+import Holumbus.Network.Port+import Holumbus.Network.Messages+import Holumbus.Network.PortRegistry+import Holumbus.Network.PortRegistry.Messages+++localLogger :: String+localLogger = "Holumbus.Network.PortRegistry.PortRegistryPort"+++-- ----------------------------------------------------------------------------+-- Datatypes+-- ----------------------------------------------------------------------------+ +-- | The datatype for the PortRegistry remote interface. +data PortRegistryPort = PortRegistryPort PortRegistryRequestPort+ deriving (Show)+++++-- ----------------------------------------------------------------------------+-- Creation+-- ----------------------------------------------------------------------------+++-- | Creates a new PortRegistryPort from a port-object.+newPortRegistryPort :: PortRegistryRequestPort -> PortRegistryPort+newPortRegistryPort p = PortRegistryPort p+++-- | Creates a new PortRegistryPort from the StreamName and SocketId.+newPortRegistryFromData :: StreamName -> SocketId -> IO PortRegistryPort+newPortRegistryFromData sn soid+ = do+ p <- newPort sn (Just soid)+ return $ newPortRegistryPort p+++-- | Creates a new PortRegistryPort from a XML-File.+newPortRegistryFromXmlFile :: FilePath -> IO PortRegistryPort+newPortRegistryFromXmlFile fp+ = do+ p <- loadFromXmlFile fp+ return $ newPortRegistryPort p++++-- ----------------------------------------------------------------------------+-- Typeclass instanciation for PortRegistryPort+-- ----------------------------------------------------------------------------++-- PortRegistry-typeclass instanciation for the PortRegistryPort.+instance PortRegistry PortRegistryPort where+ + registerPort sn soid (PortRegistryPort p)+ = do+ debugM localLogger "registerPort start"+ r <- withStream $+ \s -> performPortAction p s time30 (PRReqRegister sn soid) $+ \rsp ->+ do+ case rsp of+ (PRRspSuccess) -> return (Just $ ())+ _ -> return Nothing+ debugM localLogger "registerPort end"+ return r+++ unregisterPort sn (PortRegistryPort p)+ = do+ debugM localLogger "unregisterPort start"+ r <- withStream $+ \s -> performPortAction p s time30 (PRReqUnregister sn) $+ \rsp ->+ do+ case rsp of+ (PRRspSuccess) -> return (Just $ ())+ _ -> return Nothing+ debugM localLogger "unregisterPort end"+ return r+ + lookupPort sn (PortRegistryPort p)+ = do+ debugM localLogger "lookupPort start"+ r <- withStream $+ \s -> performPortAction p s time30 (PRReqLookup sn) $+ \rsp ->+ do+ case rsp of+ (PRRspLookup soid) -> return (Just $ soid)+ _ -> return Nothing+ debugM localLogger "lookupPort end"+ return r+ ++ getPorts (PortRegistryPort p)+ = do+ debugM localLogger "getPorts start"+ r <- withStream $+ \s -> performPortAction p s time30 (PRReqGetPorts) $+ \rsp ->+ do+ case rsp of+ (PRRspGetPorts ls) -> return (Just $ ls)+ _ -> return Nothing+ debugM localLogger "getPorts end"+ return r+
+ source/Holumbus/Network/Site.hs view
@@ -0,0 +1,219 @@+-- ----------------------------------------------------------------------------++{- |+ Module : Holumbus.Network.Site+ Copyright : Copyright (C) 2008 Stefan Schmidt+ License : MIT++ Maintainer : Stefan Schmidt (stefanschmidt@web.de)+ Stability : experimental+ Portability: portable+ Version : 0.1++ Just a little Id to help us to decide if two thread are running in the+ same program or just on the same machine or on differen machines. ++-}++-- ----------------------------------------------------------------------------++module Holumbus.Network.Site+(+-- * Datatypes+ SiteId+, SiteMap+ +-- * Operations on the SiteId+, getSiteId+, getSiteHost+, getSiteProcess+, isSameHost+, isSameProcess+, nearestId++-- * Operations on the SiteMap+, emptySiteMap+, addIdToMap+, deleteIdFromMap+, deleteHostFromMap+, isSiteIdMember+, getNeighbourSiteIds+)+where++import Data.Binary+import qualified Data.List as List+import Network.Socket+import System.Posix++import Text.XML.HXT.Arrow++import qualified Data.Map as Map+import qualified Data.Set as Set+++++-- ----------------------------------------------------------------------------+-- Datatypes+-- ----------------------------------------------------------------------------+++-- | The datatype of the SiteId, it contains the hostname and a processid,+-- so it is possible to decide if two site ids belong to the same process+-- or the the same computer or are on distinct computers.+data SiteId = SiteId HostName ProcessID deriving (Show, Eq, Ord)++instance Binary SiteId where+ put (SiteId hn pid) = put hn >> put (toInteger pid)+ get+ = do+ hn <- get+ pid <- get+ return (SiteId hn (fromInteger pid)) ++instance XmlPickler SiteId where+ xpickle = xpSiteId+ +xpSiteId :: PU SiteId+xpSiteId = + xpElem "siteId" $+ xpWrap (\(hn, pid) -> SiteId hn (fromInteger pid), \(SiteId hn pid) -> (hn, toInteger pid)) xpSite+ where+ xpSite =+ xpPair+ (xpElem "hostname" xpText)+ (xpElem "pid" xpickle)+++-- | Just a little Map to hold the SiteIds an to get the neighbout Ids.+type SiteMap = Map.Map HostName (Set.Set SiteId)+++++-- ----------------------------------------------------------------------------+-- Operations on the SiteId+-- ----------------------------------------------------------------------------+++-- | Little helper function.+getHostName :: IO (HostName)+getHostName+ = do+ (hn, _) <- getNameInfo [] True False (SockAddrUnix "localhost")+ return (maybe "localhost" id hn)+ ++-- | Gets the SiteId for the calling program. +getSiteId :: IO (SiteId)+getSiteId+ = do+ hn <- getHostName+ pid <- getProcessID+ return (SiteId hn pid)++ +-- | Extracts the Hostname from the SiteId.+getSiteHost :: SiteId -> HostName+getSiteHost (SiteId hn _) = hn+++-- | Extracts the ProcessID from the SiteId.+getSiteProcess :: SiteId -> ProcessID+getSiteProcess (SiteId _ pid) = pid+++-- | Test, if the two Ids are located on the same host.+isSameHost :: SiteId -> SiteId -> Bool+isSameHost (SiteId hn1 _) (SiteId hn2 _) = hn1 == hn2+++-- | Test, if the two Ids are located on the same host an in the same process.+isSameProcess :: SiteId -> SiteId -> Bool+isSameProcess = (==)+++-- | The filtering function.+-- Splits the given Id-list into three sublists:+-- the Ids from the same process,+-- the Ids from the same computer,+-- all other Ids.+-- This is some sort of distance function, a simple one, but for now it+-- should do its work.+filterSiteIds :: SiteId -> [SiteId] -> ([SiteId],[SiteId],[SiteId])+filterSiteIds _ [] = ([],[],[])+filterSiteIds i ls+ = (same, local, other)+ where+ (same, temp) = List.partition (\s -> isSameProcess i s) ls+ (local, other) = List.partition (\s -> isSameHost i s) temp+++-- | Gets the nearest item from an Id-list compared to a given Id.+nearestId :: SiteId -> [SiteId] -> Maybe SiteId+nearestId s l = nearestId' $ filterSiteIds s l+ where+ nearestId' ([], [], []) = Nothing+ nearestId' ([], [], x:_) = Just x+ nearestId' ([], x:_, _) = Just x+ nearestId' (x:_, _, _) = Just x+ + + + +-- ----------------------------------------------------------------------------+-- Operations on the SiteMap+-- ----------------------------------------------------------------------------++-- | Empty SiteId-Map.+emptySiteMap :: SiteMap+emptySiteMap = Map.empty+++-- | Adds an id to the map.+addIdToMap :: SiteId -> SiteMap -> SiteMap+addIdToMap i m + = Map.alter f hn m+ where+ hn = getSiteHost i+ f Nothing = (Just $ Set.singleton i)+ f (Just s) = (Just $ Set.insert i s)+++-- | Deletes an id from the map.+deleteIdFromMap :: SiteId -> SiteMap -> SiteMap+deleteIdFromMap i m+ = Map.alter f hn m+ where+ hn = getSiteHost i+ f Nothing = Nothing+ f (Just s) = filterEmpty $ Set.delete i s+ filterEmpty s+ | s == Set.empty = Nothing+ | otherwise = Just s+++-- | Delete a hostname an all its ids from the map.+deleteHostFromMap :: HostName -> SiteMap -> SiteMap+deleteHostFromMap hn m+ = Map.alter f hn m+ where+ f _ = Nothing+++-- | Test, if the site id is already in the list.+isSiteIdMember :: SiteId -> SiteMap -> Bool +isSiteIdMember i m+ = maybe False (\s -> Set.member i s) (Map.lookup hn m) + where+ hn = getSiteHost i+++-- | Gets all ids which are on the same host, but not+-- the original siteid itself.+getNeighbourSiteIds :: SiteId -> SiteMap -> Set.Set SiteId+getNeighbourSiteIds i m+ = maybe (Set.empty) (\s -> Set.delete i s) (Map.lookup hn m) + where+ hn = getSiteHost i