Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
[GH-339] Add the lock file and the support for the lock file.
Browse files Browse the repository at this point in the history
  • Loading branch information
ksaric committed Oct 31, 2019
1 parent 27c929f commit c8d65a0
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 98 deletions.
9 changes: 8 additions & 1 deletion cardano-launcher/app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ import Cardano.Shell.Launcher (LoggingDependencies (..), TLSError,
TLSPath (..), WalletRunner (..),
generateTlsCertificates, runLauncher,
walletRunnerProcess)
import Cardano.Shell.Application (checkIfApplicationIsRunning)
import Cardano.Shell.Update.Lib (UpdaterData (..),
runDefaultUpdateProcess)
import Control.Exception.Safe (throwM)


--------------------------------------------------------------------------------
-- Main
--------------------------------------------------------------------------------
Expand All @@ -51,6 +51,13 @@ import Control.Exception.Safe (throwM)
main :: IO ()
main = silence $ do

-- The name of the lock file
let lockFile = "./lockfile"

-- Check if it's locked or not. Will throw an exception if the
-- application is already running.
_ <- checkIfApplicationIsRunning lockFile

defaultConfigPath <- getDefaultConfigPath

-- The execution of the launcher CLI
Expand Down
21 changes: 3 additions & 18 deletions cardano-launcher/cardano-launcher.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ source-repository head
library
exposed-modules:
Cardano.Shell.Launcher
, Cardano.Shell.Launcher.Types
-- Update system
, Cardano.Shell.Update.Lib
, Cardano.Shell.Update.Types
-- Other subsystems
, Cardano.Shell.Application
, Cardano.Shell.Template
, Cardano.Shell.Environment
, Cardano.Shell.CLI
, Cardano.Shell.Configuration
, Cardano.Shell.Launcher.Types
hs-source-dirs:
src
build-depends:
Expand Down Expand Up @@ -72,9 +74,6 @@ executable cardano-launcher
base >=4.7 && <5
, cardano-prelude
, cardano-launcher
-- directory
, directory
, filepath
-- formatting
, formatting
-- logging
Expand Down Expand Up @@ -110,13 +109,6 @@ executable mock-daedalus-frontend
build-depends:
base >=4.7 && <5
, cardano-prelude
-- directory
, directory
, filepath
-- formatting
, formatting
-- exception handling
, safe-exceptions

default-language: Haskell2010
default-extensions: NoImplicitPrelude
Expand All @@ -138,13 +130,6 @@ executable mock-installer
build-depends:
base >=4.7 && <5
, cardano-prelude
-- directory
, directory
, filepath
-- formatting
, formatting
-- exception handling
, safe-exceptions

default-language: Haskell2010
default-extensions: NoImplicitPrelude
Expand Down
69 changes: 69 additions & 0 deletions cardano-launcher/src/Cardano/Shell/Application.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module Cardano.Shell.Application
( checkIfApplicationIsRunning
) where

import Cardano.Prelude

import Control.Exception.Safe (throwM)

import GHC.IO.Handle.Lock (LockMode (..), hTryLock)

import System.Directory (doesFileExist)

--------------------------------------------------------------------------------
-- Types
--------------------------------------------------------------------------------

data ApplicationError
= ApplicationAlreadyRunningException
| LockFileDoesNotExist FilePath
deriving (Eq, Show)

instance Exception ApplicationError

--------------------------------------------------------------------------------
-- Functions
--------------------------------------------------------------------------------

-- | Use the GHC.IO.Handle.Lock API. It needs an application lock file,
-- but the lock is not just that the file exists, it's a proper OS level API
-- that automatically unlocks the file if the process terminates.
-- This is based on the problem that we observe with the existing code
-- that we suspect many of the upgrade problems are due to the old version still running.
-- The point here would be to make that diagnosis clear and reliable, to help reduce user confusion.
-- For example, somebody has two installations, one in /home/user/cardano-sl-2.0 and
-- the other in /home/user/cardano-sl-1.33. Each instance of the program that runs knows
-- which dir is its app state dir, and it uses the lock file in that dir.
-- So, in this case, there will be two lock files, and you can run both
-- versions concurrently, but not two instances of the same version.
-- I guess it's better to think about it as the lock file protecting the
-- application state, rather than about preventing multiple instances of
-- the application from running.
-- Another example, somebody has two installations,
-- one in /home/user/cardano-sl-2.0-installation-1 and one
-- in /home/user/cardano-sl-2.0-installation-2. Perhaps ports will still conflict.
-- But it catches the primary issue with upgrades, where we don't change the state dir.
-- This fits in as one of the modular features in the framework, that we provide as optional
-- bundled features. It does a check on initialization (and can fail synchronously),
-- it can find the app state dir by config, or from the server environment. It can release
-- the lock file on shutdown (ok, that's actually automatic, but it fits
-- into the framework as a nice example).
checkIfApplicationIsRunning :: FilePath -> IO ()
checkIfApplicationIsRunning lockFilePath = do

fileExist <- doesFileExist lockFilePath

-- If it doesn't exist, create it?
-- We might add some checks for permissions?
when (not fileExist) $
writeFile lockFilePath ""

lockfileHandle <- openFile lockFilePath ReadMode
isAlreadyRunning <- hTryLock lockfileHandle ExclusiveLock

-- We need to inform the user if the application version is already running.
when (not isAlreadyRunning) $
throwM ApplicationAlreadyRunningException

-- Otherwise, all is good.
return ()
3 changes: 0 additions & 3 deletions cardano-shell/cardano-shell.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ library
, cardano-prelude
, concurrency
, containers
, directory
, formatting
, process
, QuickCheck
Expand Down Expand Up @@ -107,8 +106,6 @@ test-suite cardano-shell-test
, base >=4.7 && <5
, cardano-shell
, cardano-prelude
, process
, yaml
-- quickcheck
, QuickCheck
-- SM
Expand Down
55 changes: 0 additions & 55 deletions cardano-shell/src/Cardano/Shell/Lib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,17 @@ module Cardano.Shell.Lib
( GeneralException (..)
, CardanoApplication (..)
, runCardanoApplicationWithFeatures
-- * configuration for running
, checkIfApplicationIsRunning
-- * additional re-exports
, doesFileExist
) where

import Cardano.Prelude hiding (async, cancel, (%))
import Prelude (Show (..))

import Control.Exception.Safe (throwM)

import Control.Concurrent.Classy.Async (async, cancel)
import qualified Control.Concurrent.Classy.Async as CA

import GHC.IO.Handle.Lock (LockMode (..), hTryLock)

import Formatting (bprint, build, formatToString, stext, (%))
import Formatting.Buildable (Buildable (..))

import System.Directory (doesFileExist)

import Cardano.Shell.Types (CardanoApplication (..),
CardanoFeature (..))

Expand All @@ -36,8 +26,6 @@ import Cardano.Shell.Types (CardanoApplication (..),
data GeneralException
= UnknownFailureException -- the "catch-all"
| FileNotFoundException FilePath
| ApplicationAlreadyRunningException
| LockFileDoesNotExist FilePath
| ConfigurationError Text
deriving (Eq)

Expand All @@ -46,8 +34,6 @@ instance Exception GeneralException
instance Buildable GeneralException where
build UnknownFailureException = bprint ("Something went wrong and we don't know what.")
build (FileNotFoundException filePath) = bprint ("File not found on path '"%stext%"'.") (strConv Lenient filePath)
build ApplicationAlreadyRunningException = bprint "Application is already running. Please shut down the application first."
build (LockFileDoesNotExist filePath) = bprint ("Lock file not found on path '"%stext%"'.") (strConv Lenient filePath)
build (ConfigurationError etext) = bprint ("Configuration error: "%stext%".") etext

-- | Instance so we can see helpful error messages when something goes wrong.
Expand All @@ -58,47 +44,6 @@ instance Show GeneralException where
-- Feature initialization
--------------------------------------------------------------------------------

-- | Use the GHC.IO.Handle.Lock API. It needs an application lock file,
-- but the lock is not just that the file exists, it's a proper OS level API
-- that automatically unlocks the file if the process terminates.
-- This is based on the problem that we observe with the existing code
-- that we suspect many of the upgrade problems are due to the old version still running.
-- The point here would be to make that diagnosis clear and reliable, to help reduce user confusion.
-- For example, somebody has two installations, one in /home/user/cardano-sl-2.0 and
-- the other in /home/user/cardano-sl-1.33. Each instance of the program that runs knows
-- which dir is its app state dir, and it uses the lock file in that dir.
-- So, in this case, there will be two lock files, and you can run both
-- versions concurrently, but not two instances of the same version.
-- I guess it's better to think about it as the lock file protecting the
-- application state, rather than about preventing multiple instances of
-- the application from running.
-- Another example, somebody has two installations,
-- one in /home/user/cardano-sl-2.0-installation-1 and one
-- in /home/user/cardano-sl-2.0-installation-2. Perhaps ports will still conflict.
-- But it catches the primary issue with upgrades, where we don't change the state dir.
-- This fits in as one of the modular features in the framework, that we provide as optional
-- bundled features. It does a check on initialization (and can fail synchronously),
-- it can find the app state dir by config, or from the server environment. It can release
-- the lock file on shutdown (ok, that's actually automatic, but it fits
-- into the framework as a nice example).
checkIfApplicationIsRunning :: FilePath -> IO ()
checkIfApplicationIsRunning lockFilePath = do

-- If the lock file doesn't exist, throw an exception.
-- We want to differentiate between different exceptional situations.
whenM (not <$> doesFileExist lockFilePath) $ do
throwM $ LockFileDoesNotExist lockFilePath

lockfileHandle <- openFile lockFilePath ReadMode
isAlreadyRunning <- hTryLock lockfileHandle ExclusiveLock

-- We need to inform the user if the application version is already running.
when (not isAlreadyRunning) $
throwM ApplicationAlreadyRunningException

-- Otherwise, all is good.
pure ()

-- Here we run all the features.
-- A general pattern. The dependency is always in a new thread, and we depend on it,
-- so when that dependency gets shut down all the other features that depend on it get
Expand Down
20 changes: 2 additions & 18 deletions nix/.stack.nix/cardano-launcher.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions nix/.stack.nix/cardano-shell.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nix/.stack.nix/default.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ extra-deps:
- time-units-1.0.0
- silently-1.2.5.1

- Win32-2.5.4.1@sha256:e623a1058bd8134ec14d62759f76cac52eee3576711cb2c4981f398f1ec44b85

- git: https://github.com/input-output-hk/cardano-prelude
commit: 12ab51e27539c9cce042ded0c89efc0ccae6137a
subdirs:
Expand Down

0 comments on commit c8d65a0

Please sign in to comment.