gargoyle 0.1 → 0.1.1.0
raw patch · 5 files changed
+85/−26 lines, 5 filesdep +unixdep ~basedep ~directorydep ~filelocksetup-changednew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: unix
Dependency ranges changed: base, directory, filelock, filepath, network, process
API changes (from Hackage documentation)
Files
- ChangeLog.md +10/−0
- README.md +34/−0
- Setup.hs +0/−2
- gargoyle.cabal +30/−21
- src/Gargoyle.hs +11/−3
+ ChangeLog.md view
@@ -0,0 +1,10 @@+# Changelog for gargoyle++## 0.1.1++* Bugfix: Error cases on opening file were non-exhaustive+* Loosen version bounds++## 0.1++* Initial release
+ README.md view
@@ -0,0 +1,34 @@+# gargoyle++Gargoyle is a framework for managing daemons from Haskell. Currently, the only requirement is that the daemon be able to communicate over a Unix domain socket. See [gargoyle-postgresql](https://hackage.haskell.org/package/gargoyle-postgresql) for an example that uses gargoyle to manage postgresql.++To use Gargoyle the client must:+* Define a value of the 'Gargoyle' type which specifies how to administer the daemon.+* Create an executable whose `main` is `gargoyleMain`. The name of this executable should match the executable name specified in the `_gargoyle_exec` field of the `Gargoyle`.+* The client will run their code with `withGargoyle` to gain access to the daemon.++# Importing into Haskell package set++```nix+haskellPackages.override {+ overrides = self: super:+ let gargoylePkgs = import ./path/to/gargoyle-repo { haskellPackages = self; };+ in gargoylePkgs // {+ # .. your overrides+ };+}+```++By default `gargoyle-postgresql-nix` will use the `postgresql` of the `pkgs` used by your `haskellPackages`. To override this, pass `postgresql` by changing the above line to look more like++```nix+gargoylePkgs = import ./path/to/gargoyle-repo { haskellPackages = self; postgresql = myCustomVersion; }+```++# Hacking++Do something like this:++```shell+nix-shell -A gargoyle-postgresql.env --run 'cd gargoyle-postgresql && cabal new-repl'+```
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
gargoyle.cabal view
@@ -1,14 +1,14 @@-name: gargoyle-version: 0.1-license: BSD3-license-file: LICENSE-author: Obsidian Systems LLC-maintainer: maintainer@obsidian.systems-copyright: Copyright (C) Obsidian Systems LLC 2017-category: System-build-type: Simple-cabal-version: >=1.10-synopsis: Automatically spin up and spin down local daemons+name: gargoyle+version: 0.1.1.0+license: BSD3+license-file: LICENSE+author: Obsidian Systems LLC+maintainer: maintainer@obsidian.systems+copyright: Copyright (C) Obsidian Systems LLC 2017+category: System+build-type: Simple+cabal-version: >=1.10+synopsis: Automatically spin up and spin down local daemons description: Gargoyle is a framework for managing daemons from Haskell. In the current release the only requirement is that the daemon be able to communicate over a Unix domain socket. See <https://hackage.haskell.org/package/gargoyle-postgresql gargoyle-postgresql> for a fully worked out example. .@@ -18,15 +18,24 @@ 2. Create an executable whose `main` is 'gargoyleMain'. The name of this executable should match the executable name specified in the '_gargoyle_exec' field of the 'Gargoyle'. 3. The client will run their code with 'withGargoyle' to gain access to the daemon. +extra-source-files:+ ChangeLog.md+ README.md++tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2+ library- exposed-modules: Gargoyle- ghc-options: -Wall- ghc-prof-options: -fprof-auto- build-depends: base >= 4.9 && < 4.11- , directory == 1.3.*- , filepath == 1.4.*- , filelock == 0.1.*- , network == 2.6.*- , process == 1.4.*- hs-source-dirs: src+ exposed-modules: Gargoyle+ ghc-options: -Wall+ ghc-prof-options: -fprof-auto-exported+ build-depends:+ base >=4.12.0 && <4.15+ , directory >=1.3.3 && <1.4+ , filelock >=0.1.1 && <0.2+ , filepath >=1.4.2 && <1.5+ , network >=2.6.0 && <3.2+ , process >=1.5.0 && <1.7+ , unix >=2.7.2 && <2.8++ hs-source-dirs: src default-language: Haskell2010
src/Gargoyle.hs view
@@ -20,6 +20,7 @@ import System.IO.Error import System.Environment import System.FileLock+import System.Posix.Process import System.Process import Debug.Trace@@ -92,7 +93,10 @@ let monProc = (proc (_gargoyle_exec g) [daemonDir]) { std_in = CreatePipe , std_out = CreatePipe- , std_err = Inherit }+ , std_err = Inherit+ , close_fds = True+ , new_session = True+ } (Just monIn, Just monOut, Nothing, monHnd) <- createProcess monProc void $ forkOS $ void $ waitForProcess monHnd hClose monIn@@ -102,7 +106,7 @@ threadDelay 500000 -- These are expensive ops so don't try too hard acquire -- Try again "ready" -> acquire- _ -> fail "Unexpected gargoyle message from monitor process"+ _ -> fail $ "Unexpected gargoyle message from monitor process: " <> r | otherwise -> throwIO e bracket_ acquire (shutdown s ShutdownBoth >> close s) $ b =<< _gargoyle_getInfo g (gWorkDir daemonDir)@@ -115,7 +119,7 @@ gargoyleMain :: Gargoyle pid a -- ^ Description of how to initialize, spin up, and spin down a daemon. -> IO () -- ^ Returns only when all clients have disconnected.-gargoyleMain g = do+gargoyleMain g = void $ forkProcess $ do checkThreadedRuntime [daemonDir] <- getArgs >>= \case x@[_] -> return x@@ -126,6 +130,7 @@ | isAlreadyInUseError e -> return () | isDoesNotExistError e -> throwIO e -- this means it's not a file but it exists | isPermissionError e -> throwIO e -- the daemon directory is in a bad state+ | otherwise -> throwIO e -- The daemon tries to hold on to the lock file for its lifetime, signaling that it is -- accepting connections. lock <- tryLockFile lockPath Exclusive >>= \case@@ -166,6 +171,7 @@ shutdownVar <- newEmptyMVar void $ forkOS $ forever $ do (s, _) <- accept controlSocket+ acceptThread <- myThreadId --TODO: What happens if we decide we're shutting down here? modifyMVar_ numClientsVar $ \n -> do return $ succ n@@ -182,6 +188,8 @@ case pred n of 0 -> do shutdown controlSocket ShutdownBoth+ -- We have to explicitly kill the accept thread, because otherwise (sometimes?) 'accept' will begin returning EAGAIN, and ghc will continuously retry it. This busywait consumes 100% of CPU, prevents the monitor from actually exiting, and leaves the control socket and lock file in a state where another monitor can't be started.+ killThread acceptThread putMVar shutdownVar () n' -> putMVar numClientsVar n' bracket (_gargoyle_start g (gWorkDir daemonDir)) (_gargoyle_stop g) $ \_ -> do