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 I tried to use
/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
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
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.