PE Haskell after part 2
This is part 2 of a series describing how a Haskell program by my hand evolved.
The program is called after
, for an introduction see part 1.
Previously
Previously I tried to use inotify
on /proc
, which does not work. Now we have to use a fork-join kind of model with polling threads to see if the process is still alive.
Fork-join polling threads
Fork-join is as simple as it sounds: you start multiple threads and at one point, wait for them all to finish.
We start with a simple Thread.sleep
(if you speak Java) approach:
module After where
import Control.Concurrent
import System.Directory
halfASecondInMicroseconds = 500000
afterPid :: String -> IO ()
afterPid pid = do
fileExists <- doesDirectoryExist ("/proc/" ++ pid)
if fileExists
then do
threadDelay halfASecondInMicroseconds
afterPid pid
else return ()
As you can see, the function only returns if the /proc/
pid directory nolonger exists.
Using this in a single threaded environment would just work by blocking on each pid.
We could start the function in a thread and use MVar
to block on each of the threads.
That would also mean that we would have to do some error handling and management, which is why I opted to use the parallel-io package next.
The main function now parses the arguments and uses the global thread pool of parallel-io to implement a simple fork-join:
main :: IO ()
main = runCommand $ \opts args -> do
if optQuiet opts
then return ()
else do
parallel_ (map afterPid args)
stopGlobalPool
The parallel_
function implements the complete fork/join and map creates a list of [IO()]
actions to run. stopGlobalPool
is only there for clean-up.
Configure continuous integration
Now that we have some basic functionality, we need to introduce a build-server to make sure we keep sharing a working version with the world. The easiest way I know is using Travis CI
After signing up for an account and connecting it with github, you can start using it by adding a configuration (.travis.yml
) file to your repository:
ghc: 7.6
install:
- cabal install --only-dependencies
Add some tests
There are multiple ways to add tests, starting with different test executors for Cabal.
The most basic is exitcode-stdio-1.0
and I just used github:scvalex/ltc as an example.
The only test I could come up with is making sure afterPid
does not wait for the process itself:
main :: IO ()
main = defaultMain [
testCase "neverSelf" testSelf
]
testSelf = do
self <- getProcessID
afterPid (show self)
To compile and run the test, I added the tets-suite to cabal:
test-suite afterTests
hs-source-dirs: test src
main-is: AfterTest.hs
type: exitcode-stdio-1.0
build-depends: base >=4.7 && <4.8,
unix ==2.7.*,
directory ==1.2.*,
filepath ==1.3.*,
test-framework,
test-framework-hunit,
HUnit
default-language: Haskell2010
As you can see, most of the dependencies of the main program are repeated. The only best way around repeating dependencies for a test is splitting of the functionality of the executable into a library. This I will do later on in the series.