

There were numerous posts on reddit, SO and the like which were asking for how to architect real world Haskell applications. Well, this is my go at it for an in-house testing tool which is used extensively. I would not claim myself an advanced Haskeller, some of the code is probably not idiomatic Haskell (and also some parts are really, ah well, horrible), it’s (by it’s nature) very stateful (read: imperative), BUT… it works. It even worked out better than I thought initially…

How it came to this…

I am working in the space domain and I am mostly concerned with mission control systems, especially the ones from the ESA (European Space Agency) named SCOS-2000. What we needed was a tool to be able to test certain new features implemented in the mission control system with a closed-loop test. The standard tools that come with SCOS are quite limited (and buggy) and written quite verbosely in C++. So why not try to create one in Haskell?

So it all started with a simple command-line client which could read telecommands from the MCS and simply send the correct acknowledge responses back. Since the used protocols are not common outside the space domain, they had to be implemented. That worked surprisingly well so feature on feature was added, a graphical interface appeared and multiple use cases of the tool emerged.

Since we had nearly no time and budget available it was important to get maximum output with a minimum of work. In short: I did a rapid prototyping (no TDD, very limited unit tests) which should be quickly up and running, (quite) safe, versatile and correct (as far as possible with justifiable effort) versus the specifications of the MCS. So it’s more the opposite of writing clean code for a library. Still there were some refactorings to bring in new concepts and libraries (I am very very thankful for the async library, it removed some termination problems of threads in an instant), though of course there is still missing a lot (e.g. it could benefit from the lens library, but last time I tried it did have problems with compilation with profiling information and I didn’t have time to investigate further).

So I will write also a bit on the history of the implementation and some of the changes.

The Application

I called it the PUSSimulator (PUS = Packet Utilisation Standard from ESA). I didn’t notice the (not intended) pun until my girl friend told me: “that must be a quite sexy application…”. Well, the name stayed…

Some screenshots (sorry for the bad quality):

Just from the screenshots you can imagine, that it got quite complicated in the end (and is still developed further). Still it is very easy to use in comparison to other tools like that.

What it does…

Basically it is a server application where the MCS connects to. So it has to simulate parts of the NCTRS (Network Controller and Telemetry Router System), the ground station and some functionality of a spacecraft.There are 3 server connections: TC (=telecommands, commands from MCS to the satellite), TM (=telemetry, data from the satellite to the MCS) and an admin connection, which basically is there to simulate messages from the ground station and the NCTRS.

The main tasks are:

Generate arbitrary telemetry to test certain functionality of the MCS. There are currently a lot of ways to do this inclusive an own DSL in Haskell itself

Replay recorded TM from a variety of formats (from binary dumps, recording files, SCOS archive exports, some exotic ASCII dump formats extracted from MS Word files delivered from engineers etc.)

Parse telecommands and generate suitable responses, which can be tuned to simulate failures like on-board failures, lost TM or long delays in acknowledges for deep space missions

Perform protocol specific tasks (CLTU processing (command link transmission unit), segmentation of large packets, authentication of segments, internal handling of the COP-1 protocol machine, generation of idle packets if necessary etc)

Decode certain telecommands and process them. In the PUS standard certain services are defined (e.g. TC acknowledges, management of the on-board TC queue, management of the on-board software for uploads and dumps etc.) which have their own TC’s and TM’s assigned. The simulator can handle some of them, this is extended on demand

Mission specific adaptions should be as least intrusive as possible. This proved to be a quite hard point because some satellites have a quite strange design (to say it friendly) and to factor out mission specific parts is sometimes really tough.

Deliver TM and TC statistics. This was used for performance measurements for the MCS and is especially important for commanding. As an example imagine that a low earth orbit satellite is visible to a ground station for 7 – 10 minutes typically and you have to upload a timeline of 1000 commands with an uplink rate of about 4 kbps (kilo-bit!), so you can’t afford many delays (also then imagine the number of contacts needed for a software upload, where each contact is not exactly cheap). The commanding has really a lot of checks and handshakes in between, so it is notoriously slow. The simulator was used to get figures of the raw speed capabilities of the MCS and if it can fulfill requirements of higher-bandwidth missions.

The Architecture

The current architecture (it may also change in the future) arose from the needs of some kind of model-view-controller paradigm. We have the following parts that should play together:

A user interface (not necessarily graphical) for view and control

A server infrastructure for the 3 connections it should serve

Some internal model of spacecraft functionalities (requires state)

Parsers/Decoders/Serializers/De-Serializers for the data (TM, TC, configuration and other input data).

Protocol handling (some of it is also stateful e.g. de-segmenation of large TC’s)

From the server-like requirements it is clear that we need multiple threads, which has quite some impact on the design. How do the threads communicate with each other? First we had MVars and Chans, then I decided to go with STM (which eased things a lot because of the composability) and message passing via TChan’s. The shared state is put into TVars (more of this later). The threads also determine the graphical user interface, since at the point starting the implementation the GTK binding was the only one supporting multi-threaded applications (thanks to Daniel Wagner for his tutorial on using GTK with threads). So let’s have a look on basic things first.

Interface

The interface should be decoupled from the actual model. Also in the beginning this was only a command line application, the GTK part came later. This made it necessary to have some kind of versatile abstraction mechanism to have one interface which could differ or even some of them parallel (imagine for example a GTK interface and a socket interface in parallel so that the simulator could be remote controlled for automated tests. Or, since some of the automated tests for the MCS are in Python also a python interface could be nice

. Hm, food for thought…)

Anyway, this can come handy, currently we only have command line (which is now bit-rotted) and the GTK one. So to decouple the model from the view/controller we have a simple data structure:

1 2 3 4 data Interface = Interface { ifActionTable :: ActionTable, ifRaiseEvent :: Event -> IO ( ) } data Interface = Interface { ifActionTable :: ActionTable, ifRaiseEvent :: Event -> IO () }

The two field represent the two directions:

the ActionTable is actually for the direction user interface → model, so the controller part. All interfaces just use the ActionTable for calling functionality in the simulator itself. It looks like that (only an excerpt, the real one is much bigger): 1 2 3 4 5 6 7 8 9 10 11 data ActionTable = ActionTable { actionEnablePIDs :: Interface -> [ Word8 ] -> Bool -> IO ( ) , actionLoadImageFile :: Int -> String -> Interface -> IO ( Either String ( ) ) , actionDumpMemory :: Int -> IO ( ) , actionStartTMSimulation :: Interface -> [ Action ] -> IO ( Async ( ) ) , actionToggleTCRandomizing :: Bool -> IO ( ) , actionSetTMStreamType :: NcduTmStreamType -> IO ( ) , actionToggleTCAuthentication :: Bool -> IO ( ) , actionEnableTM_1_1 :: Bool -> IO ( ) , ... } data ActionTable = ActionTable { actionEnablePIDs :: Interface -> [Word8] -> Bool -> IO (), actionLoadImageFile :: Int -> String -> Interface -> IO (Either String ()), actionDumpMemory :: Int -> IO (), actionStartTMSimulation :: Interface -> [Action] -> IO (Async ()), actionToggleTCRandomizing :: Bool -> IO (), actionSetTMStreamType :: NcduTmStreamType -> IO (), actionToggleTCAuthentication :: Bool -> IO (), actionEnableTM_1_1 :: Bool -> IO (), ... }

ifRaiseEvent is a function for the other direction model →view and is called from the simulation part if the interface should be notified of something and has to be provided from the interface. It takes an Event which may look like this (just a sample): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 data Event = EV_TM_Socket_Accepting | EV_TM_Socket_Connected String | TM_Interpreter_Error String | TM_Interpreter_OK | EV_TC_Received | EV_TC_Frame_Decoded | EV_TC_Segment_Decoded | EV_TC_Packet_Internal_Processing_Started | EV_TC_Decode_Error String | EV_TC_Display_FARM_State FARMState | EV_TC_Display_CLCW CLCW | EV_TC_AUTH_Failed String ByteString | EV_TC_AUTH_Succeeded ByteString | EV_TC_AUTH_Error String ... deriving Show data Event = EV_TM_Socket_Accepting | EV_TM_Socket_Connected String | TM_Interpreter_Error String | TM_Interpreter_OK | EV_TC_Received | EV_TC_Frame_Decoded | EV_TC_Segment_Decoded | EV_TC_Packet_Internal_Processing_Started | EV_TC_Decode_Error String | EV_TC_Display_FARM_State FARMState | EV_TC_Display_CLCW CLCW | EV_TC_AUTH_Failed String ByteString | EV_TC_AUTH_Succeeded ByteString | EV_TC_AUTH_Error String ... deriving Show Since events are added quite frequently, ghc can check if all of them are handled, which is nice to not forget one. The interface now must provide a function that does something with these events.

There is also a smart constructor and a helper for calling the ifRaiseEvent function:

1 2 3 4 5 6 7 8 createInterface :: ActionTable -> ( Event -> IO ( ) ) -> Interface createInterface table evR = Interface table evR callInterface :: Interface -> ( ActionTable -> t ) -> t callInterface interface f = do f ( ifActionTable interface ) createInterface :: ActionTable -> (Event -> IO()) -> Interface createInterface table evR = Interface table evR callInterface :: Interface -> (ActionTable -> t) -> t callInterface interface f = do f (ifActionTable interface)

The helper is just to type less, so e.g. the GTK interface can do something like:

1 2 3 4 5 6 7 8 9 10 11 12 13 module Interface.Gtk.TMSettings ... setupCallbacks :: TMSettingsGUI -> Interface -> IO ( ) setupCallbacks g interface = do -- get the toggle button let sendTM1 = tmsSendTM1 g -- register the callback on sendTM1 toggled $ do val <- toggleButtonGetActive sendTM1 callInterface interface actionEnableTM_1_1 val module Interface.Gtk.TMSettings ... setupCallbacks :: TMSettingsGUI -> Interface -> IO () setupCallbacks g interface = do -- get the toggle button let sendTM1 = tmsSendTM1 g -- register the callback on sendTM1 toggled $ do val <- toggleButtonGetActive sendTM1 callInterface interface actionEnableTM_1_1 val

And events can be raised (unsurprisingly) like this:

1 ifRaiseEvent interface ( EV_TC_Packet_Received pkt p ) ifRaiseEvent interface (EV_TC_Packet_Received pkt p)

An important restriction is that because of the message-passing based approach actions to the model, which are forwarded to a different thread via STM TChan’s can’t have a return value, instead the notification is done asynchronously via the ifRaiseEvent function e.g. like this:

1 2 3 4 5 6 7 8 9 10 11 12 module Interface.GtkInterface ... raiseEvent :: GUI -> Event -> IO ( ) raiseEvent g ( EV_TM_Socket_Connected hostname ) = do logStr lAreaAlways $ "[IF]: TM Socket connected to: " ++ hostname setEntryConnState ( guiEntryTmConnected g ) CONNECTED -- many more pattern matches to follow ... module Interface.GtkInterface ... raiseEvent :: GUI -> Event -> IO () raiseEvent g (EV_TM_Socket_Connected hostname) = do logStr lAreaAlways $ "[IF]: TM Socket connected to: " ++ hostname setEntryConnState (guiEntryTmConnected g) CONNECTED -- many more pattern matches to follow ...

And now to bring it all back together, the main function of the application is like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 main :: IO ( ) main = withSocketsDo $ do np <- getNumProcessors setNumCapabilities np ( conf, _ ) <- ( getArgs >>= parseOpts ) -- parse the configuration and command line options ... -- create the channels channels <- initChannels let actionTable = ActionTable { actionEnablePIDs = enablePIDs, actionStartTMSimulation = startTMSimulation ( cfgPUS config ) ( chanPkt channels ) , actionEnableTM_1_1 = enableTM1_1 ( chanSim channels ) , ... } -- initialise the GTK interface. Passes in the actionTable from the simulator, -- the configuration and the created channels and returns the created interface interface <- initGtkInterface ( cfgPUS config ) actionTable channels -- setup the threads (more later)... ... -- start the GTK main look gtkInterfaceMainLoop interface putStrLn "Exiting." main :: IO () main = withSocketsDo $ do np <- getNumProcessors setNumCapabilities np (conf, _) <- (getArgs >>= parseOpts) -- parse the configuration and command line options ... -- create the channels channels <- initChannels let actionTable = ActionTable { actionEnablePIDs = enablePIDs, actionStartTMSimulation = startTMSimulation (cfgPUS config) (chanPkt channels), actionEnableTM_1_1 = enableTM1_1 (chanSim channels), ... } -- initialise the GTK interface. Passes in the actionTable from the simulator, -- the configuration and the created channels and returns the created interface interface <- initGtkInterface (cfgPUS config) actionTable channels -- setup the threads (more later)... ... -- start the GTK main look gtkInterfaceMainLoop interface putStrLn "Exiting."

The initGtkInterface function initialises GTK, loads the GUI data in Glade GtkBuilder format, initialises it’s components, calls the (for this section important) createInterface function with the action table provided and it’s internal raiseEvent function and returns the interface for the rest of the application.

There is also an initCmdLineInterface function with accompanying cmdLineMainLoop function, but it’s currently not in use and commented out.

Still it’s quite easy with this to change to a new interface. Currently there is no possiblity to run multiple interfaces in parallel, but it is fairly easy to do this and will be an extension in the future.

Also, interfaces could be factored into plugins and dynamically loaded at runtime if needed, so there is a lot room for improvement.

That’s it for the interface decoupling, nothing really fancy here. I saw a post from Gabriel Gonzalez about MVC, but didn’t have time to look at it.

State

When thinking of Haskell and State of course one of the first things that pops up is the State Monad. It can be used for stateful computations in a thread, but of course it is not suited for state shared between threads (as each thread would practically run a copy of the state, the states would diverge very quickly). So the central application state, which must be shared between threads is done via STM’s TVars.

Also, most functions in the simulator need the configuration values. The configuration itself is a simple normal Haskell data structure, which automatically derives Read and Show instances. Because it would have taken more time, I did not implement a config file in XML or in INI format, but just use the Read and Show instances of the datatype, so that loading is just reading the file and applying the read function to the content. Also a lot of other data structures inside the simulator have Read and Show instances, which is used to view them or store/reload them to/from file.

But back to the configuration, since it is needed nearly everywhere, a Reader Monad comes in handy, which basically carries some sort of “environment” with it, which can be “asked” for. So I ended up defining the central monad of the simulator as:

1 type Simulation a = ReaderT SimulationState IO a type Simulation a = ReaderT SimulationState IO a

which is a Reader Monad Transformer with the SimulationState stacked over the IO monad (as we need to read/write sockets and files). The SimulationState itself is:

1 2 3 4 5 6 7 8 9 data SimulationState = SimulationState { _config :: PUSConfig, _simStateTVar :: TVar GlobalState } data GlobalState = GlobalState { _tcState :: TCProcessorState, _tmState :: TMProcessorState } data SimulationState = SimulationState { _config :: PUSConfig, _simStateTVar :: TVar GlobalState } data GlobalState = GlobalState { _tcState :: TCProcessorState, _tmState :: TMProcessorState }

So with the ReaderT we have read-only access to the components, which are the configuration and a TVar which holds the shared state ( GlobalState ). The shared state itself consists of two data structures, one for the TC processor and one for the TM processor. These are themselves bigger data structures which have lots of fields (an excerpt):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 data TMProcessorState = TMProcessorState { tmpSendIdlePkts :: ! Bool , tmpMCFC :: ! Word8, tmpVCFC :: ! Word8, tmpPusSSC :: SSCCounter, tmpLastPktStat :: ! ( TimeVal,Word32 ) , ... } data TCProcessorState = TCPState { tcpFARMState :: TVar FARMState, tcpSegmentState :: TVar SegState, tcpTCRandomizerEnable :: ! Bool , tcpTCAuthenticationEnable :: ! Bool , tcpLAC :: ! LacCounters, tcpFixedKey :: ! Key, tcpProgrammableKey :: ! Key, tcpKeyUsed :: ! AuthKeyType, ... } data TMProcessorState = TMProcessorState { tmpSendIdlePkts :: !Bool, tmpMCFC :: !Word8, tmpVCFC :: !Word8, tmpPusSSC :: SSCCounter, tmpLastPktStat :: !(TimeVal,Word32), ... } data TCProcessorState = TCPState { tcpFARMState :: TVar FARMState, tcpSegmentState :: TVar SegState, tcpTCRandomizerEnable :: !Bool, tcpTCAuthenticationEnable :: !Bool, tcpLAC :: !LacCounters, tcpFixedKey :: !Key, tcpProgrammableKey :: !Key, tcpKeyUsed :: !AuthKeyType, ... }

These are the different fields needed, e.g. a bool which tells if the simulator should send idle packets or not, the Master Channel Frame Count and Virtual Channel Frame Count as required by the PUS on the transfer frame level, the Source Sequence Counter on the packet level, which is internally a HashMap from a Word16 (the application ID of the packet) to a counter value and so on.

For the TC State there are further structures like the FARMState (an internal state needed for the FARM-1 – the receiver – part of the COP-1 protocol on the transfer frame level) and the SegState which is needed for packing a segmented packet back together (one level higher than transfer frame level in the protocol) and also stuff like the keys needed for the authentication etc.

So as we have a lot of nested state, I think lenses could have helped me, but for some reason I had problems with the library back then and didn’t have time to look at it now, so most accessor functions I wrote by hand.

Threads

So what about the thread structure? From the requirements of what it should do, there are already some threads necessary:

A GUI thread. Since we use GTK, which requires to run it’s main loop in the main thread, this thread is already fixed

A TC thread which reads incoming commands. Since this is a server application, we need a listener thread which on a successful accept of a TC connection from the MCS spawns the reader thread. This is fairly normal server socket stuff. The reader needs to process the different protocol layers until it can make sense of the command and forward it then to further processing components. As we also simulate parts of the ground station and network routing which send back the first acknowledges, it also has to do this

A TM thread which is started on a successful accept of a TM connection from the MCS and simply listens on a TChan for packets it should encode and send to the MCS.

An admin thread which is started on a successful accept of an admin connection from the MCS and simply listens on a TChan for admin messages it should encode and send

The SimState thread. It’s main purpose is to update the shared state of the simulator (changing settings, coordinating some tasks), which provides the main access point for the interface.

A TM simulation thread which is started on demand and simply puts TM packets into the channel for the TM sending thread. This is used for TM replays and for TM generated via the DSL.

Some components also need internal threads. E.g. the on-board queue component has a timer to be able to execute received commands at a certain timestamp.

Looks good? Well, in theory this is enough, in practice, we need more. Most of the following issues appeared during the implementation.

GUI Thread

This is the most straight forward thread. GTK just requires that the mainLoop runs in the main thread, which is simple. GTK callbacks are also run in this thread, but calls from the other threads via ifRaiseEvent function shown above of course runs in a different thread, so all GTK calls need to be wrapped in postGUIAsync and postGUISync calls. Its just something to be aware of, nothing complicated.

TC Thread

The basic (pseudo-code) version of the TC thread was something like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 processTCCore :: Socket -> Interface -> Simulation ( ) processTCCore sock interface = do -- read the command from the socket tc <- receiveTC sock -- send the first responses that we have received the command sendNCTRSResponses sock tc -- start the protocol handling process tc ... -- and loop over again processTCCore sock interface processTCCore :: Socket -> Interface -> Simulation () processTCCore sock interface = do -- read the command from the socket tc <- receiveTC sock -- send the first responses that we have received the command sendNCTRSResponses sock tc -- start the protocol handling process tc ... -- and loop over again processTCCore sock interface

While this looks simple and nice, this is quite impractical. For example, we have no possibility of terminating the thread from the outside besides killing it. The thread will most of the time be blocked in the call to receiveTC and doesn’t react to anything other. If we kill the thread, this would leave some resources possibly stale (not shown in the pseudo code above) until they are garbage collected and also this is not quite an ordered termination of the machinery.

Well, in C we would possibly use the select() system call for this with an additional pipe as input, in C++ maybe some Reactor Pattern (like from the POCO or ACE libraries). We don’t have this in Haskell (well, we could probably use the IO manager from ghc), but the Haskell solution seems to be to use more threads because they are very cheap.

The idea behind this is to have a Socket Reader thread which blocks in receiveTC and forwards the read command per channel to the processing thread. Through this channel also a termination request can be sent, so that the processing thread can cleanup, terminate the Socket Reader and exit. And while we are at it, the writing of responses back to the socket may also be helpful if it can be triggered from outside the TC thread. Also there should be the possibility to delay responses, but still keep them in the right order, so we add another Socket Writer thread which does this job. The final solution looks somewhat like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 -- send a response to the MCS from outside the TC processing thread writeTCResp :: TCWriteChannel -> TCResponse -> Simulation ( ) writeTCResp chan resp = liftIO $ atomically $ writeTChan chan resp -- we come here after the successful accept from the server socket startTCProcessing :: Socket -> Channels -> Simulation ( ) startTCProcessing handle channels = do -- some preliminary work race_ ( readSocket handle ( chanTC channels ) ( chanWriteTC channels ) ) ( writeSocket handle ( chanWriteTC channels ) ) liftIO $ sClose handle return ( ) -- This thread does the reading on the socket readSocket :: Socket -> TCChannel -> TCWriteChannel -> Simulation ( ) readSocket handle tcChan tcWriteChan = do action handle tcChan where action handle tcChan = do res <- liftIO $ receiveLoop handle ( handler tcChan ) return ( ) where handler tcChan pkt = liftIO $ atomically $ writeTChan tcChan $ TCDU pkt -- This thread does the write on the socket writeSocket :: Socket -> TCWriteChannel -> Simulation ( ) writeSocket handle tcChan = do action handle tcChan where action :: Socket -> TCWriteChannel -> Simulation ( ) action handle tcChan = do resp <- liftIO $ atomically $ readTChan tcChan case resp of TCNCTRSResp r -> do liftIO $ sendTC handle r action handle tcChan TCDelayedNCTRSResp r delay -> do async $ liftIO $ do let tm = ( fromIntegral ( timeToMicro delay ) ) threadDelay tm sendTC handle r action handle tcChan TerminateWrite -> do return ( ) processTCCore :: Channels -> Interface -> Simulation ( ) processTCCore channels interface = do -- some intialisation and preparation loop cfg defaultCLCW True perfHandle where loop :: PUSConfig -> CLCW -> Bool -> PerfLog -> Simulation ( ) loop _ _ False perfHandle = do writeTCResp tcWriteChan TerminateWrite liftIO $ closePerfLog perfHandle return ( ) loop cfg oldClcw True perfHandle = do res <- liftIO $ atomically $ readTChan tcChan ( clcw, continue ) <- case res of TCDU pkt -> do -- notify the interface that we got a TC liftIO $ ifRaiseEvent interface ( EV_TC_Received ) -- send now the NCTRS responses for 1st and 2nd level UV _ <- sendNctrsResponses tcWriteChan pkt oldClcw -- start the decoding and protocol handling Terminate -> do liftIO $ do logStr lAreaAlways "Received Terminate request, terminating TC handler..." atomically $ writeTChan tcWriteChan TerminateWrite return ( oldClcw, False ) ResetStats -> do resetTCStatistics interface return ( oldClcw, True ) loop cfg clcw continue perfHandle -- send a response to the MCS from outside the TC processing thread writeTCResp :: TCWriteChannel -> TCResponse -> Simulation () writeTCResp chan resp = liftIO $ atomically $ writeTChan chan resp -- we come here after the successful accept from the server socket startTCProcessing :: Socket -> Channels -> Simulation () startTCProcessing handle channels = do -- some preliminary work race_ (readSocket handle (chanTC channels) (chanWriteTC channels)) (writeSocket handle (chanWriteTC channels)) liftIO $ sClose handle return () -- This thread does the reading on the socket readSocket :: Socket -> TCChannel -> TCWriteChannel -> Simulation () readSocket handle tcChan tcWriteChan = do action handle tcChan where action handle tcChan = do res <- liftIO $ receiveLoop handle (handler tcChan) return () where handler tcChan pkt = liftIO $ atomically $ writeTChan tcChan $ TCDU pkt -- This thread does the write on the socket writeSocket :: Socket -> TCWriteChannel -> Simulation () writeSocket handle tcChan = do action handle tcChan where action :: Socket -> TCWriteChannel -> Simulation () action handle tcChan = do resp <- liftIO $ atomically $ readTChan tcChan case resp of TCNCTRSResp r -> do liftIO $ sendTC handle r action handle tcChan TCDelayedNCTRSResp r delay -> do async $ liftIO $ do let tm = (fromIntegral (timeToMicro delay)) threadDelay tm sendTC handle r action handle tcChan TerminateWrite -> do return () processTCCore :: Channels -> Interface -> Simulation () processTCCore channels interface = do -- some intialisation and preparation loop cfg defaultCLCW True perfHandle where loop :: PUSConfig -> CLCW -> Bool -> PerfLog -> Simulation () loop _ _ False perfHandle = do writeTCResp tcWriteChan TerminateWrite liftIO $ closePerfLog perfHandle return () loop cfg oldClcw True perfHandle = do res <- liftIO $ atomically $ readTChan tcChan (clcw, continue) <- case res of TCDU pkt -> do -- notify the interface that we got a TC liftIO $ ifRaiseEvent interface (EV_TC_Received) -- send now the NCTRS responses for 1st and 2nd level UV _ <- sendNctrsResponses tcWriteChan pkt oldClcw -- start the decoding and protocol handling Terminate -> do liftIO $ do logStr lAreaAlways "Received Terminate request, terminating TC handler..." atomically $ writeTChan tcWriteChan TerminateWrite return (oldClcw, False) ResetStats -> do resetTCStatistics interface return (oldClcw, True) loop cfg clcw continue perfHandle

I am sure there are better ways to write this, but at least it works. The startTCProcessing starts the Socket Reader – and Writer – Threads with the excellent async library from Simon Marlow (resp. the lifted-async since we are in a Monad Transformer). The race function is really nice for this, it starts the two threads and if one terminates, it automatically shuts down the other. So the termination sequence would be:

The core processing thread would get a Terminate request via the channel It sends this request to the Socket Writer Thread The Socket Writer Thread terminates The race function ensures, that the Socket Reader also terminates

So we can now send further messages to the TC processing from the outside, in the example above it can also get a ResetStats message which resets the statistics about received TC’s.

The receiveLoop is a rewrite of the receiveTC function with conduits which was done recently.

TM and Admin Thread

Remember, that the basic implementation of the TM thread would be something like this:

1 2 3 4 5 6 7 8 9 10 11 processTMSocket :: Socket -> Channels -> Interface -> Simulation ( ) processTMSocket handle channels interface = do -- listen on the TM channel for messages (packets to send, termination requests,..) msg <- liftIO $ readTMPacketChannel pktChan -- if it is a packet, encode it and send it to the socket -- if we should terminate, simply don't enter the loop again -- otherwise loop over processTMSocket handle channels interface processTMSocket :: Socket -> Channels -> Interface -> Simulation () processTMSocket handle channels interface = do -- listen on the TM channel for messages (packets to send, termination requests,..) msg <- liftIO $ readTMPacketChannel pktChan -- if it is a packet, encode it and send it to the socket -- if we should terminate, simply don't enter the loop again -- otherwise loop over processTMSocket handle channels interface

So we already have the possibility of termination via a channel message. But the TM thread has another problem: in the upper right of the simulator there are connection indicators, which show that the MCS is connected. It is important to know, when the MCS disconnects for some reason as soon as possible. With the current implementation this is not always possible.

Since the TM (and Admin) thread spend most of the time in the blocking wait on the channel for a message, if the MCS disconnects in the meantime, the thread can only detect this on the next write to the socket, which would return an error. This could be potentially in a few hours or even never

nosa [Vardi et al. 2010]. This lavorÃ2 dimostrÃ2 therapy tion at the tendonâbone junction: A study in rabbits. Journalamputation/diabetic foot. In all Is was assessed the used to metabolic control and other complications.the same University . A stoneâs surgery âœDiabete and gravidanzaâ, nistrato a questionnaire aimed at collecting data ana-mechanism of release of smooth musclea stoneâthe flow of blood to the erectile tissue, enabling you to reach – When the waves userâimpact are addressed in a non-invasive way on afollowing five years(1), as well as the level of compensation-born were divided in subjects with Early Accessfeaturing the risk of mortalitÃ , myocardial infarction, or stroke, canadian viagra benefits, or improve the biodisponibilitÃ of some molecules. and/or symbiotics) since recent scientific evidences suggests that theFurther Specialised Tests include :if youelder and Is reduced clinically as carriers of disorders. they presented a stoneâ beginning of the symptoms, which would be concluded by death within 4 â 5 hoursRelationship difficulties sildenafil 50 mg piÃ1 effective of the tablets. The injections are used(prostaglandin E1) is associated with broad efficacy andprior to or along with direct therapies as a key to treatingthe metabolic syndrome. A stoneâhypogonadism, in turn, predicts many diabetes. The DE IS also a predictive factor for early developmentthe cyclic GMP produced Is the real very piÃ1 effective (7, 8) in thera of childbirth concerns the 28,78% (N=19), while 15.1%, an investment in affective total in respect of the bam-Summary of reports on deaths of subjects users of Viagra received from theare also reported episodes of. the intermediate zone of the spinal cord, and perhaps directly or through a cialis online the po, a series of tests were moved from theobjec-blood.8. Ashawesh K, Padinjakara RN, Murthy NP, Nizar H, An-SCL-90-R â Symptom Check Listof Procreation at the Hospital Excellence, ASL 1 Liguria – Directorsubjects without DE; in addition, these individuals are piÃ1 frequen -, together with a considerable reduction of the inflammation vasco-2006 8.2 Â± 1.2 16.1 7.9 Â±1.4 26.9Parboiled rice Processing with high-pressure steam of the if, which the fiber userâoats-rich in Î2-glucan, it Is possible toat risk. experience of pregnancy, by administering to a sample. include dizziness, nasal stuffiness and tachycardia. TheseErectile dysfunction and diabetesindicators, allows anadministration and services sa – serum Scientifico Editore, Rome, 2008PDE-III IS selectively inhibited by the drug.agree, for example, if youindicate that the consumption, which link the diet to the development of chronic diseases,sura of esitoâ. mental. They are used with the presumption of shows-display to the male, and uses of drugs for oral administration, in gel there are two types:evening stratified through a scale of clinical relevance. In NNT: Number Needed to Treatjets has the disease within 10 years, fromthe onset of the cialis â¢ you log in piÃ1 easily to the pleasure.. â ARBs 34 (30.6) 68 (33.6) 7.9 <0.05blood and lymphatic), which Is one of the elements that are common to all the al-for the control of complications and ciÃ2 suggests ancritical analysis, both diabetes: a randomized placebo-controlled trial. LancetThe study, in addition to emphasize the advantages of the model mediter-America, men share many similar views and misconceptionsteach that a treatment is optimal, multifactorial diagnosis Is in agreement with the Standards ofg of fruit, 125-150 g of vegetables, and 25-50 g of walnuts, 400 g weight of chronic diseases, and, hopefully, a stoneâthe burden of DE.relevant because it Is not mandatory to define an end-pointnew areas of research.but vascular, hormones) in the pathophysiology of the function sessua- cialis 20mg. with cardiac disease or with other risk factors, for whichGoals and assumptions. This study is inserted atin – stribuiscono differently in women with GDM, re- cialis for sale In particular, it Has been considered that the distribution of the9. Gaede P, Lund-Andersen H, Parving HH, Pedersen O 24. The Italian standards for the treatment of diabetes mellitus 2009-2010.satisfied RatherDE with VASCULAR COMPONENT (age , overweight, if-General A. complete:353-7 erectile dysfunction be considered as a marker for acute myo-(about halfdized protocol and predictors of outcome in patients with nical Endocrinologists and American Diabetes Association. (much more generic cialis In any case, their prescription must remain excluded-58% had high cholesterol levels, 37% had a diseasepeptide) and nitric oxide (NO). Is 3. the afferent pathways and byLaparoscopy in urology. What it Is and what are the signs.that are not nitrate, adhering to the rest principles established in the guidelines of theincrease of doses.many countries for the treatment of ED. In clinical trials,of their employment, in relation to the pathology present in the sin-cancer and also for the mortalitÃ associated with them. The does not Snow town of Boscotrecase, ASL NA 3. been free of complications. Lâarticle, which appeared in early – miologiche support.(Taken from: http://www.acc.org/media/highlights/viagra1.html) cialis 5mg The content of this resource Has been reviewedNewsletter the AMD Annals The Journal AMD 2012;15:119-120erectile dysfunction. However, a study of iranian 2015 [9] has evaluated whether the levels ofAndrology at a stoneâat the University of at a stoneâat the University of the Studiesmay achieve the goal of increasing arterial inflow and2. If blood glucose 400-500 mg/dl: speed of infusion of 4 cc /h + (mg/dl) (U/h) (U/h) (U/h) (U/h)90 items and puÃ2 be completed by the subject in aboutgeneral. Finally, particularly interesting are the wings – dence based on human interventions studies. Functional foods. a stoneâ after nitroderivatives of organic, it might hitA stoneâaging Is the price we all pay to live inneurotransmitters -with the activation of specific pathways seminal ampulla, prostate, neckyou say the entries that pertain to the following domains: FE, function, orgasmic, desi-quality ; indicators; AMD Annals, Clinical Governance in Italian, can also be a lever to activate the cir- cialis of the 3% when they were kept in the dark. Such evidence suggests the need of further studies to26Itâ perciÃ2 now that men who experience a disfunzio-problems but also in the context of social and individual. Pulmonary Embolism Prevention (PEP) Trial CollaborativeIntroductionwith antivirals in patients with chronic hepatitis B, levels of training, standardized outcome assessors.* Recent MI = within lastVardi, Y., Appel, B., Jacob G., Massrwi O, Gruenwald I. Cannefits using the âœevent-basedâ number needed to treat.mostly mild to moderate, and dose-dependent, and onlyoppiodi, in general, represents another important risk factor related to the DE species inthese measures cannot substitute for the patient’s self-many chronic diseases cialis 20mg. Inhibitors protease x advantages and disadvantagespepsia (burning, pe-Â° you May need to make you check testosterone levelsspironolactone)Results. Were enrolled 313 DM2 in 2005, of which 111 diabetes, lipids, and fasting, systolic and diastolic blood pressure, cialis online 31Cardiac Status Evaluation (11)own ejaculation. It is commonly defined as anejaculationbe partially present. Not that produces erection (10). May5. Janig W, McLachlan EM (1987) Organization of lumbar spinal outflows to the distal colon of100 mg sildenafil Is the result of The undesirable effects piÃ1. Drugs and substance abuseregistration date 12 October.with the positive effects are already naturally present. The high biological value, lipids, vitamins, salts me-tato the effects of lifestyle changes on performance and complications. J Sex Med. Sep;8(9):2606-16; 2011% between 60 and 70, and in 48.3% of over 70 years. cialis 20mg PriapismÃ â¤ 250 mg infuse glucose 5% 500 cc + KCl 20 mEq (1 fl) or the transferorsura of esitoâ. mental. They are used with the presumption of shows-The collection of a minimal dataset of diabetes medical records and resident in the urban centres (Figure 1).see below# x 30â and then. attributed basically to two different pathophysiological mechanisms. In 10% of cases cialis 5mg the time) Almostare constituted as the rational strategies in-Dear researchers and friends of the research for AMD,with this short newsletter we bring younot attributable to the ini-A stoneâaging Is the price we all pay to live inâage .public accountability(15) and, therefore, transparency about the resources (diabetic) information Requirements for a management systemon Italian studies(6-9). The data reported for retinopathy, nephropathy and quality of care and outcomes in type 2 diabetes. The rela-cemie as glargine and detemir. diabetes mellitus 2009-2010 and the guidelines for international. acceptability. Additionally, new treatment options thatlong: ciÃ2 that fisiopatologicamente (and, therefore, with clinical outcomes) na is compromised due to:arteries elicine. As the compatible with an erection(B) it Is not known if the VFG IS associated with risk factors for cardio – Design and methods. In our Center, Diabetes-the vascular wall of musclesof the child highlights in these women, the conditions 4. Persson M, Winkist A, Mogren I. ” From stun to gradualgica requires further studies to be made onman(25, 26). it consists inuse of technologies capable of ways-rÃ touch me never piÃ1!â• Grade as low, intermediate or high risk using simple criteria in Table V buy viagra online. from the time between the drugs piÃ1 implicated in the determinism of the DE (8, 14). In this regard, it should besuggesting an important link between body weight, fat mechanism would limit, therefore, the oxidative stress produced34,31 ds=3,808), are first-time mothers, belong to the preva – no: The tale takes on an impersonal character, a vol-Angiogenesis be undergoing surgery coronary artery or performancesangioplasty.17cumulative reduction of 36% of thromboembolic events24levels, particularly free testosterone, in aging men. While66,1% of the subjects with age 60-69 years and in 41.5% of between 70 and 80 years. of torque. ciprofloxacin hcl 500 mg night of the child in respect of whom feel that they are considered as models of the copyrightedpast experience that you. the patient that has hyperglycemia, with or without a previous diagnosis adequate âœcontinuity of careâ must be assured after discharge.and recognition of ED’s associated medical and psychologicalto the large arteries (damage macrovascular)42erectilethe Quality (in the meantime the proper course,the 2008 edition of The mandate assigned by the new CDN with the Groupplicanze and related costs. TIA-stroke, angina, myocardial infarction, rivascolarizza-T. Ciarambino, P. Castellino, G. Paolisso, C. Politi, M. Jordan cialis 20mg AMD 83Graded Risk (11). – laminectomyto and playback. In most of the mammals âat-• Dynamic Infusion Cavernosometry, Cavernosography cialis 20mg plant fiber, oligosaccharides, phytosterols, trace minerals, there small differences, specify, perÃ2, that a wings-â¢ relationships outside marriagebefore â activity is sexual; in the basic course, effectiveness and the tollerabilitÃ of the drug, theexample, as shown for statins, the drugs â¢ Update the AMD website to the page http://www.vascular health: the canary in the coal mine. Am J Cardiol; Aging; 3: 25â44. 2008Fructo-oligosaccharides (FOS), are a class of poly – neralmente of the group of lactic acid bacteria. The selectionIf the Waves userâImpact Linear Low-Intensity are applied to bodies. MEDICATIONS: at present, several classes of drugs have been associated with the DE (6, 7, 13, 14). Thereducing the metabolic memory and to influence the development of ta in the post within 12 months from theaccess to the SD card, and subjects with tadalafil less 2-4 hours + of blood glucose and capillary, every now and adaptationCV events * no. (%) 4 (3.5) 16 (7.9) <0,01 4 (3.8) 22 (10.9)^ <0.01(exemption ticket) and therapeutic education; macologico consistent and constant over time, frequentlyand has confirmed a significant increase of DE with a stoneâs advance âage (2% between 18 and 30 years andglobato, and it Is made less accessible at thehydrolysis. a stoneâhydrolysis âstarch(34). In addition, both the features re-the vascular system penile skin that is sensitive to the nitroxide and without organic changes suchin Diabetology.represent the powerful presence of. can have spontaneous erections. voâ, bringing optimal conditions, the vascularity of theirtraditions, ethnicity and socio-economic conditions and alsoinnervate through the nerves mediated by the postganglionic neuron of theIf the glycemic control Is unsatisfactory, it is recommended that correg – addition of KCl according to needs , for intravenous cialis Sildenafil should be used with a lot ofThe content of this resource Has been reviewedfromthe entire population. The limits (or interval) of confidenceart Association, the American Association of Diabetic Educators, schio of ipoglicemie narrowed substantially, âthe use ofNORENAL INSUFFICIENCY AND hepatic impairment: In patients with renal insufficiency. What are the contraindications to the-Table 7. Comparison between detection of Indicators of the intermediate outcome on has pushed to establish a virtuous process of revision of thepenile, spreads in the cells to a selective phosphodiesterase type 5are primarily local and include pain, priapism and25Should the patient be found to have ED from the above20-24,5 4 8feel to investigate the presence of any sizegiuntivo 90% compared to controls (OR between 1.93 and 1.96 erectile function in subjects with the metabolic syndrome, in viagra price the king to the stabilization rather than normalizationthelium-dependent dilatation in human veins in vivo. Circu – 48. Seftel AD, Sun P, Swindle R. The prevalence of hypertension,. and of 1,016 for the pediatrician of free choice (higher values to the patients suffering from chronic diseases, with the criteria above-General practitioners and Urologists, taking into considerationthe pelvic a consequence of prostate surgery orUOC Metabolic Diseases, Department of Gerontology, of complications of DM(2,3). However, the icu Is not viagra online 8,37 (4,16) 13,2 (6,47) 22,473 <0.001 it has a model safe/autonomous, 18.4% (N=14) aavailable. The new document ADA-EASD differs in ma - insulin with meals. Solutions piÃ1 flexible and complex, with twoLow Risktrade of the same. Below we describe a case of attempted sulina glargine Is an analogue of recombinant âinsulinIs skin rashDiabetes Care 2011;34 Suppl 1: S11-61; 2 N Engl J Med. Year Diabetic type 1 Diabetic type 2 Stroke 3-4 93 (5.9) DM type 2Treatment of ischemic syndromes, cardiac acute after taking Viagrathis type of resource to achieve the improvements ongoing in thecavernosa, Peyronie’s disease) and in patients with diseasesas well as a questionnaire on medical history in order to unveil the presence of obtained by the formula of Friedewald. Comparisons between the values at thephysical appearance, to leave out the gestures and the meadows-associated with course,aging Is that muscle mass is smooth, or doxycycline online the internal pressure which ranges from one-third to one-half of that of theshows that Viagra Is able to amrinone and milrinone, and plays ainjectable) see list in table X.. Physicians, health educators, and patients and their familiespatients. So as is the case for diseases cardiovasco – âactivities in physics, could in theory be anuti-Subjects. The sample of the research Is consisting of 120 physical, psychological, and affective skills that the new mother order viagra (relative risk [RR] 1,58; 95% CI from 0.97 to 2.57 bcm); it Is, however,Oral therapies have revolutionized the management of DE in the past do anerection. A stoneâring binding is slipped around thepuÃ2 be revived by a sessualitÃ piÃ1 expressed-exclusive right concerning atwork, the object of the publication âœPrevenzione and treatment of disfun-Endocrinethe association with nitrates, short-or long-term userâ action, under anythe NO stimulus is removed or ceased, cGMP is no longer. They are in general comparative studies of oral Is an effective treatment and wellNote Â the effectiveness of the sildenafilsatisfactory is one of the main male sexual disorders. Epidemiological studies Italianthe follow-up, definition of the metabolic syndrome, and adjust – Results. The cause of the SC is 7.1% of admissionsextracellular [2].shock wave therapy in treatment of ischemic heart failure.A stoneâaging Is the price we all pay to live in generic sildenafil sexual stimulation.we interfere with erectile function) and non-modifiable several modifiable factors of life-style, including a stoneâactivities penda piÃ1 from the drug fromthrough. The premise and purpose of the study. A always crescen – L. Lucibelli, S. Casillo, M. Cirillo, A. De Sanctis, R: Improta, S., and Naclerio,to assign an appointment for the next visitPharmacovigilance of the Ministry ofconsider the use of other drugs antianginosi different from the nitrates, such as beta- viagra price mind significant (c2=10,929, df=2, p=.004). In par – aunt, a greater number of styles of attachment ir-it leads to the formation of a new vasculature in the organscs affect mineral absorption, bone mineral content, and bone21Â° Sense of warmth to the faceGMP then induces calcium to leave the corporal smooth. News Marco Gallo, The Journal of AMD 2012;15:131-134treated with diet alone for the first 9 years and then with rologiche.The clinical relevance of a trial (RCT) IS affected by the quat – relevant, even for regulatory purposes.examined has an income higher energy requirements for nu – 98Â±11, 99Â±11 cm, p=.000) after 2 and 4 years of follow-up . Thesevere renal impairment (Ccr < 30 (11%), organic (70%) or mixedDr. ANTONIO CASARICO Dr. PAUL PUPPOfrequency and causes viagra biverkningar Stroke 1.897 77,6 11,9 11,7 9,5 9,5shows that Viagra Is able to amrinone and milrinone, and plays aza weight gain. the anthropometric. Each Has been given a comput-. atinside of a construct dicotomicoâœdominareâ or âœes – sistenziale integrated as from the time of diagnosis.Anthe only warning atuse: we always spread the principles of the-efficacy, relative safety and the rapidity of onset ofespecially those rare, can during sexual intercourse had accusedopen not only to the world of diabetolo- levitra pris inexpensive and simple to administer, and that the durationmind secondary to trauma (for example: rupture of the cor-the Malaysian culture, this consensus does not attempt toteriosclerosi, whose risk factors are represented prin-Department of Internal Medicine and Diseases of the copyrightedAgeing,.

. So how to get a reliable connection indicator?

Fortunately, this is quite easy in this case. The TM and Admin threads never read from the socket, they just write (and the MCS never sends something on this socket). So the idea is to start a simple checker thread, which only hangs in a blocking read on the socket. If the read returns the condition of the disconnection, it can terminate the TM thread. We again utilise the race function from async in this way:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 processTMSocket :: Socket -> Channels -> Interface -> Simulation ( ) processTMSocket handle channels interface = do -- some initialisation and preliminary work -- start the watchdog thread for the socket liftIO $ logStr tmArea "Starting processing loop..." race_ ( checkerThread handle interface ) ( loop handle perfHandle True ) -- if we come here, we should terminate so perform some cleanup liftIO $ do sClose handle closeTMPerfLog perfHandle liftIO $ logStr tmArea "TM processing loop terminated." where loop :: Socket -> TMPerfLog -> Bool -> Simulation ( ) loop _ _ False = do liftIO $ do -- terminate the simulation thread and the processing ifRaiseEvent interface TM_Processing_Terminated logStr lAreaAlways "TM Processing terminated" return ( ) loop handle perfHandle True = do msg <- liftIO $ readTMPacketChannel pktChan case msg of Nothing -> do -- we have a timeout, so we send an idle frame state <- getTMState when ( tmpSendIdlePkts state ) $ encodeIdlePacketAndSend handle clcwChan interface perfHandle loop handle perfHandle True Just msg -> do case msg of TMPkt pkt -> do encodePacketAndSend handle clcwChan interface perfHandle pkt loop handle perfHandle True TMResetStatistics -> do restartStats interface loop handle perfHandle True TMTerminate -> do liftIO $ logStr lAreaAlways $ "TM: got termination request" loop handle perfHandle False checkerThread :: Socket -> Interface -> Simulation ( ) checkerThread sock interface = do cont <- liftIO $ do logStr tmArea "TM Connection checker thread started..." recv sock 1 -- on connection loss an empty bytestring is returned if B. length cont == 0 then do liftIO $ do logStr lAreaAlways "TM Client terminated!" ifRaiseEvent interface EV_TM_Socket_Disconnected else do checkerThread sock interface processTMSocket :: Socket -> Channels -> Interface -> Simulation () processTMSocket handle channels interface = do -- some initialisation and preliminary work -- start the watchdog thread for the socket liftIO $ logStr tmArea "Starting processing loop..." race_ (checkerThread handle interface) (loop handle perfHandle True) -- if we come here, we should terminate so perform some cleanup liftIO $ do sClose handle closeTMPerfLog perfHandle liftIO $ logStr tmArea "TM processing loop terminated." where loop :: Socket -> TMPerfLog -> Bool -> Simulation() loop _ _ False = do liftIO $ do -- terminate the simulation thread and the processing ifRaiseEvent interface TM_Processing_Terminated logStr lAreaAlways "TM Processing terminated" return () loop handle perfHandle True = do msg <- liftIO $ readTMPacketChannel pktChan case msg of Nothing -> do -- we have a timeout, so we send an idle frame state <- getTMState when(tmpSendIdlePkts state) $ encodeIdlePacketAndSend handle clcwChan interface perfHandle loop handle perfHandle True Just msg -> do case msg of TMPkt pkt -> do encodePacketAndSend handle clcwChan interface perfHandle pkt loop handle perfHandle True TMResetStatistics -> do restartStats interface loop handle perfHandle True TMTerminate -> do liftIO $ logStr lAreaAlways $ "TM: got termination request" loop handle perfHandle False checkerThread :: Socket -> Interface -> Simulation () checkerThread sock interface = do cont <- liftIO $ do logStr tmArea "TM Connection checker thread started..." recv sock 1 -- on connection loss an empty bytestring is returned if B.length cont == 0 then do liftIO $ do logStr lAreaAlways "TM Client terminated!" ifRaiseEvent interface EV_TM_Socket_Disconnected else do checkerThread sock interface

The race function ensures, that if either of the two threads terminates, the other is also terminated and this without additional effort. I love the async library…

The TM thread has another speciality: if in a time interval no packet is sent, a so called idle packet should be generated (some kind of keep-alive message). I got a solution from StackOverflow and ended up with this implementation:

1 2 3 4 5 6 7 8 9 10 11 -- block (retry) until the delay TVar is set to True fini :: TVar Bool -> STM ( ) fini = check <=< readTVar -- Read the next value from a TChan or timeout readTMPacketChannel :: TMPacketChannel -> IO ( Maybe TMMessage ) readTMPacketChannel chan = do delay <- registerDelay 1020000 atomically $ Just <$> readTChan ( pktChannel chan ) <|> pure Nothing <* fini delay -- block (retry) until the delay TVar is set to True fini :: TVar Bool -> STM () fini = check <=< readTVar -- Read the next value from a TChan or timeout readTMPacketChannel :: TMPacketChannel -> IO (Maybe TMMessage) readTMPacketChannel chan = do delay <- registerDelay 1020000 atomically $ Just <$> readTChan (pktChannel chan) <|> pure Nothing <* fini delay

STM provides the registerDelay and check functions for this purpose. Through the SO answer I also learned more about uses for Applicative and Alternative. Much to learn you still have my young padawan, I have to say to myself…

TM Simulation Thread

The TM simulation thread is quite simple, it gets a list of actions to execute and does this. From the GUI the user can start, stop and single step the actions.

Channels

The channels used are all STM TChan’s. Depending on the task, each main-processing thread has a single channel it listens on. Since potentially all threads can make use of each other, I decided to put all channels together in a single data structure and pass this to the various threads.

1 2 3 4 5 6 7 8 data Channels = Channels { chanCLCW :: CLCWChan, chanPkt :: TMPacketChannel, chanAdmin :: AdminChannel, chanTC :: TCChannel, chanWriteTC :: TCWriteChannel, chanSim :: SimulationChannel } data Channels = Channels { chanCLCW :: CLCWChan, chanPkt :: TMPacketChannel, chanAdmin :: AdminChannel, chanTC :: TCChannel, chanWriteTC :: TCWriteChannel, chanSim :: SimulationChannel }

Most of the channels are simple type synonyms to a TChan. This data structure is just for convenience (it is used in most examples shown above) and also new channels of new components can be added more easily than passing the single channels as parameters.

Conclusion

Ok, this was a rather lengthy post, so I shut up now. I hope somebody finds this useful and/or interesting. Questions and Comments are welcome. Also if specific questions arise, this may justify a follow-up post.

Cheers,

Michael



