Make push-to-repo SSH events sequential via the Repo actor
Until now, `Vervis.Ssh` was freely running git/darcs commands, regardless of what the `Repo` actor is doing at the same time. It means that in the `Repo` actor implementation, every repo manipulation must take into account the possibility for a simultaneous push. This commit gives more control, clarity and safety to the `Repo` actor. Since moving the push logic itself to the `Repo` actor would be cumbersome and complicated, the push logic remains in `Vervis.Ssh`, but some thingss happen differently: 1. Before running the git/darcs command that handles the push, it waits for the `Repo` actor to be available (as if pushing is one of the `Repo` actor's methods) 2. In the method handler, the `Repo` actor waits for `Vervis.Ssh` to say that the push handler is done 3. When the git/darcs command returns, `Vervis.Ssh` sends the notification to release the `Repo` actor from the waiting So, from now on, `Repo` and `Loom` code, in particular code that handles PRs, can be sure no simultaneous pushing will happen.
This commit is contained in:
parent
ea463703b5
commit
a74b24f61a
4 changed files with 69 additions and 32 deletions
|
@ -42,7 +42,7 @@ module Control.Concurrent.Actor
|
||||||
, startTheater
|
, startTheater
|
||||||
, callIO
|
, callIO
|
||||||
, call
|
, call
|
||||||
--, sendIO
|
, sendIO
|
||||||
, send
|
, send
|
||||||
, sendManyIO
|
, sendManyIO
|
||||||
, sendMany
|
, sendMany
|
||||||
|
|
|
@ -502,7 +502,7 @@ instance Actor Repo where
|
||||||
type ActorStage Repo = Staje
|
type ActorStage Repo = Staje
|
||||||
type ActorKey Repo = RepoId
|
type ActorKey Repo = RepoId
|
||||||
type ActorReturn Repo = Either Text Text
|
type ActorReturn Repo = Either Text Text
|
||||||
data ActorMessage Repo = MsgR Verse
|
data ActorMessage Repo = MsgR (Either Verse (IO ()))
|
||||||
instance Actor Project where
|
instance Actor Project where
|
||||||
type ActorStage Project = Staje
|
type ActorStage Project = Staje
|
||||||
type ActorKey Project = ProjectId
|
type ActorKey Project = ProjectId
|
||||||
|
@ -533,8 +533,11 @@ instance VervisActor Loom where
|
||||||
actorVerse = MsgL
|
actorVerse = MsgL
|
||||||
toVerse (MsgL v) = Just v
|
toVerse (MsgL v) = Just v
|
||||||
instance VervisActor Repo where
|
instance VervisActor Repo where
|
||||||
actorVerse = MsgR
|
actorVerse = MsgR . Left
|
||||||
toVerse (MsgR v) = Just v
|
toVerse (MsgR e) =
|
||||||
|
case e of
|
||||||
|
Left v -> Just v
|
||||||
|
Right _ -> Nothing
|
||||||
|
|
||||||
instance Stage Staje where
|
instance Stage Staje where
|
||||||
data StageEnv Staje = forall y. (Typeable y, Yesod y) => Env
|
data StageEnv Staje = forall y. (Typeable y, Yesod y) => Env
|
||||||
|
@ -575,8 +578,10 @@ instance Message (ActorMessage Loom) where
|
||||||
summarize (MsgL verse) = summarizeVerse verse
|
summarize (MsgL verse) = summarizeVerse verse
|
||||||
refer (MsgL verse) = referVerse verse
|
refer (MsgL verse) = referVerse verse
|
||||||
instance Message (ActorMessage Repo) where
|
instance Message (ActorMessage Repo) where
|
||||||
summarize (MsgR verse) = summarizeVerse verse
|
summarize (MsgR (Left verse)) = summarizeVerse verse
|
||||||
refer (MsgR verse) = referVerse verse
|
summarize (MsgR (Right _)) = "WaitPushCompletion"
|
||||||
|
refer (MsgR (Left verse)) = referVerse verse
|
||||||
|
refer (MsgR (Right _)) = "WaitPushCompletion"
|
||||||
instance Message (ActorMessage Project) where
|
instance Message (ActorMessage Project) where
|
||||||
summarize (MsgJ verse) = summarizeVerse verse
|
summarize (MsgJ verse) = summarizeVerse verse
|
||||||
refer (MsgJ verse) = referVerse verse
|
refer (MsgJ verse) = referVerse verse
|
||||||
|
|
|
@ -54,9 +54,12 @@ import Vervis.Persist.Discussion
|
||||||
import Vervis.Ticket
|
import Vervis.Ticket
|
||||||
|
|
||||||
repoBehavior :: UTCTime -> RepoId -> ActorMessage Repo -> ActE (Text, Act (), Next)
|
repoBehavior :: UTCTime -> RepoId -> ActorMessage Repo -> ActE (Text, Act (), Next)
|
||||||
repoBehavior now repoID (MsgR _verse@(Verse _authorIdMsig body)) =
|
repoBehavior now repoID (MsgR (Left _verse@(Verse _authorIdMsig body))) =
|
||||||
case AP.activitySpecific $ actbActivity body of
|
case AP.activitySpecific $ actbActivity body of
|
||||||
_ -> throwE "Unsupported activity type for Repo"
|
_ -> throwE "Unsupported activity type for Repo"
|
||||||
|
repoBehavior _now _repoID (MsgR (Right waitValue)) = do
|
||||||
|
liftIO waitValue
|
||||||
|
done "Waited for push to complete"
|
||||||
|
|
||||||
instance VervisActorLaunch Repo where
|
instance VervisActorLaunch Repo where
|
||||||
actorBehavior' now repoID ve = do
|
actorBehavior' now repoID ve = do
|
||||||
|
|
|
@ -24,7 +24,7 @@ import Control.Monad.IO.Class (liftIO)
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT))
|
import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT))
|
||||||
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
|
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask, asks)
|
||||||
import Data.Attoparsec.Text
|
import Data.Attoparsec.Text
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import Data.ByteString.Lazy (fromStrict)
|
import Data.ByteString.Lazy (fromStrict)
|
||||||
|
@ -50,6 +50,8 @@ import Yesod.Core.Dispatch
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Formatting as F
|
import qualified Formatting as F
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Control.Concurrent.Return
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
|
|
||||||
import Data.Git.Local
|
import Data.Git.Local
|
||||||
|
@ -66,12 +68,12 @@ import Vervis.Settings
|
||||||
-- Types
|
-- Types
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
type ChannelBase = LoggingT (ReaderT ConnectionPool IO)
|
type ChannelBase = LoggingT (ReaderT (ConnectionPool, Theater) IO)
|
||||||
type SessionBase = LoggingT (ReaderT ConnectionPool IO)
|
type SessionBase = LoggingT (ReaderT ConnectionPool IO)
|
||||||
type UserAuthId = PersonId
|
type UserAuthId = PersonId
|
||||||
|
|
||||||
type Channel = ChannelT UserAuthId ChannelBase
|
type Channel = ChannelT UserAuthId ChannelBase
|
||||||
type Session = SessionT SessionBase UserAuthId ChannelBase
|
type Session = SessionT ChannelBase UserAuthId SessionBase
|
||||||
type SshChanDB = SqlPersistT Channel
|
type SshChanDB = SqlPersistT Channel
|
||||||
type SshSessDB = SqlPersistT Session
|
type SshSessDB = SqlPersistT Session
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ src = "SSH"
|
||||||
|
|
||||||
runChanDB :: SshChanDB a -> Channel a
|
runChanDB :: SshChanDB a -> Channel a
|
||||||
runChanDB action = do
|
runChanDB action = do
|
||||||
pool <- lift . lift $ ask
|
pool <- lift . lift $ asks fst
|
||||||
runSqlPool action pool
|
runSqlPool action pool
|
||||||
|
|
||||||
runSessDB :: SshSessDB a -> Session a
|
runSessDB :: SshSessDB a -> Session a
|
||||||
|
@ -178,8 +180,8 @@ detectAction (Execute s) =
|
||||||
Right action -> Right action
|
Right action -> Right action
|
||||||
detectAction _ = Left "Unsupported channel request"
|
detectAction _ = Left "Unsupported channel request"
|
||||||
|
|
||||||
execute :: FilePath -> [String] -> Channel ()
|
execute' :: Bool -> FilePath -> [String] -> Channel ()
|
||||||
execute cmd args = do
|
execute' wait cmd args = do
|
||||||
lift $ $logDebugS src $
|
lift $ $logDebugS src $
|
||||||
F.sformat ("Executing " % F.string % " " % F.shown) cmd args
|
F.sformat ("Executing " % F.string % " " % F.shown) cmd args
|
||||||
let config = (proc cmd args)
|
let config = (proc cmd args)
|
||||||
|
@ -191,7 +193,15 @@ execute cmd args = do
|
||||||
verifyPipe (Just h) = h
|
verifyPipe (Just h) = h
|
||||||
verifyPipes (mIn, mOut, mErr, ph) =
|
verifyPipes (mIn, mOut, mErr, ph) =
|
||||||
(verifyPipe mIn, verifyPipe mOut, verifyPipe mErr, ph)
|
(verifyPipe mIn, verifyPipe mOut, verifyPipe mErr, ph)
|
||||||
spawnProcess $ verifyPipes <$> createProcess config
|
if wait
|
||||||
|
then spawnProcessWait $ verifyPipes <$> createProcess config
|
||||||
|
else spawnProcess $ verifyPipes <$> createProcess config
|
||||||
|
|
||||||
|
execute :: FilePath -> [String] -> Channel ()
|
||||||
|
execute = execute' False
|
||||||
|
|
||||||
|
executeWait :: FilePath -> [String] -> Channel ()
|
||||||
|
executeWait = execute' True
|
||||||
|
|
||||||
whenRepoExists
|
whenRepoExists
|
||||||
:: Text
|
:: Text
|
||||||
|
@ -242,17 +252,26 @@ runAction decodeRepoHash root _wantReply action =
|
||||||
return ARProcess
|
return ARProcess
|
||||||
DarcsApply repoHash -> do
|
DarcsApply repoHash -> do
|
||||||
let repoPath = repoDir root repoHash
|
let repoPath = repoDir root repoHash
|
||||||
can <-
|
maybeCan <-
|
||||||
case decodeRepoHash repoHash of
|
case decodeRepoHash repoHash of
|
||||||
Nothing -> return False
|
Nothing -> return Nothing
|
||||||
Just repoID -> canPushTo repoID
|
Just repoID -> do
|
||||||
if can
|
can <- canPushTo repoID
|
||||||
then whenDarcsRepoExists True repoPath $ do
|
return $
|
||||||
|
if can
|
||||||
|
then Just repoID
|
||||||
|
else Nothing
|
||||||
|
case maybeCan of
|
||||||
|
Just repoID -> whenDarcsRepoExists True repoPath $ do
|
||||||
pid <- authId <$> askAuthDetails
|
pid <- authId <$> askAuthDetails
|
||||||
liftIO $ setEnv "VERVIS_SSH_USER" (show $ fromSqlKey pid)
|
liftIO $ setEnv "VERVIS_SSH_USER" (show $ fromSqlKey pid)
|
||||||
execute "darcs" ["apply", "--all", "--repodir", repoPath]
|
theater <- lift . lift $ asks snd
|
||||||
|
(sendValue, waitValue) <- liftIO newReturn
|
||||||
|
_ <- liftIO $ sendIO theater repoID $ MsgR $ Right waitValue
|
||||||
|
executeWait "darcs" ["apply", "--all", "--repodir", repoPath]
|
||||||
|
liftIO $ sendValue ()
|
||||||
return ARProcess
|
return ARProcess
|
||||||
else return $ ARFail "You can't push to this repository"
|
Nothing -> return $ ARFail "You can't push to this repository"
|
||||||
GitUploadPack repoHash -> do
|
GitUploadPack repoHash -> do
|
||||||
let repoPath = repoDir root repoHash
|
let repoPath = repoDir root repoHash
|
||||||
whenGitRepoExists False repoPath $ do
|
whenGitRepoExists False repoPath $ do
|
||||||
|
@ -260,17 +279,26 @@ runAction decodeRepoHash root _wantReply action =
|
||||||
return ARProcess
|
return ARProcess
|
||||||
GitReceivePack repoHash -> do
|
GitReceivePack repoHash -> do
|
||||||
let repoPath = repoDir root repoHash
|
let repoPath = repoDir root repoHash
|
||||||
can <-
|
maybeCan <-
|
||||||
case decodeRepoHash repoHash of
|
case decodeRepoHash repoHash of
|
||||||
Nothing -> return False
|
Nothing -> return Nothing
|
||||||
Just repoID -> canPushTo repoID
|
Just repoID -> do
|
||||||
if can
|
can <- canPushTo repoID
|
||||||
then whenGitRepoExists True repoPath $ do
|
return $
|
||||||
|
if can
|
||||||
|
then Just repoID
|
||||||
|
else Nothing
|
||||||
|
case maybeCan of
|
||||||
|
Just repoID -> whenGitRepoExists True repoPath $ do
|
||||||
pid <- authId <$> askAuthDetails
|
pid <- authId <$> askAuthDetails
|
||||||
liftIO $ setEnv "VERVIS_SSH_USER" (show $ fromSqlKey pid)
|
liftIO $ setEnv "VERVIS_SSH_USER" (show $ fromSqlKey pid)
|
||||||
execute "git-receive-pack" [repoPath]
|
theater <- lift . lift $ asks snd
|
||||||
|
(sendValue, waitValue) <- liftIO newReturn
|
||||||
|
_ <- liftIO $ sendIO theater repoID $ MsgR $ Right waitValue
|
||||||
|
executeWait "git-receive-pack" [repoPath]
|
||||||
|
liftIO $ sendValue ()
|
||||||
return ARProcess
|
return ARProcess
|
||||||
else return $ ARFail "You can't push to this repository"
|
Nothing -> return $ ARFail "You can't push to this repository"
|
||||||
|
|
||||||
handle
|
handle
|
||||||
:: (KeyHashid Repo -> Maybe RepoId)
|
:: (KeyHashid Repo -> Maybe RepoId)
|
||||||
|
@ -315,8 +343,9 @@ mkConfig
|
||||||
-> HashidsContext
|
-> HashidsContext
|
||||||
-> ConnectionPool
|
-> ConnectionPool
|
||||||
-> LogFunc
|
-> LogFunc
|
||||||
|
-> Theater
|
||||||
-> IO (Config SessionBase ChannelBase UserAuthId)
|
-> IO (Config SessionBase ChannelBase UserAuthId)
|
||||||
mkConfig settings ctx pool logFunc = do
|
mkConfig settings ctx pool logFunc theater = do
|
||||||
keyPair <- keyPairFromFile $ appSshKeyFile settings
|
keyPair <- keyPairFromFile $ appSshKeyFile settings
|
||||||
return $ Config
|
return $ Config
|
||||||
{ cSession = SessionConfig
|
{ cSession = SessionConfig
|
||||||
|
@ -329,13 +358,13 @@ mkConfig settings ctx pool logFunc = do
|
||||||
, cChannel = ChannelConfig
|
, cChannel = ChannelConfig
|
||||||
{ ccRequestHandler = handle (decodeKeyHashidPure ctx) (appRepoDir settings)
|
{ ccRequestHandler = handle (decodeKeyHashidPure ctx) (appRepoDir settings)
|
||||||
, ccRunBaseMonad =
|
, ccRunBaseMonad =
|
||||||
flip runReaderT pool . flip runLoggingT logFunc
|
flip runReaderT (pool, theater) . flip runLoggingT logFunc
|
||||||
}
|
}
|
||||||
, cPort = fromIntegral $ appSshPort settings
|
, cPort = fromIntegral $ appSshPort settings
|
||||||
, cReadyAction = ready logFunc
|
, cReadyAction = ready logFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
runSsh :: AppSettings -> HashidsContext -> ConnectionPool -> LogFunc -> Theater -> IO ()
|
runSsh :: AppSettings -> HashidsContext -> ConnectionPool -> LogFunc -> Theater -> IO ()
|
||||||
runSsh settings ctx pool logFunc _theater = do
|
runSsh settings ctx pool logFunc theater = do
|
||||||
config <- mkConfig settings ctx pool logFunc
|
config <- mkConfig settings ctx pool logFunc theater
|
||||||
startConfig config
|
startConfig config
|
||||||
|
|
Loading…
Reference in a new issue