119 lines
4.3 KiB
Haskell
119 lines
4.3 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|||
|
|
|
||
|
|
module ChuSQL.Web.StorageProcess (
|
||
|
|
StorageProcess (..),
|
||
|
|
findStorageServer,
|
||
|
|
startStorageProcess,
|
||
|
|
stopStorageProcess,
|
||
|
|
waitForStorage,
|
||
|
|
) where
|
||
|
|
|
||
|
|
import ChuSQL.Storage.IPC (Request (..), Response (..), closeConnection, sendRequest)
|
||
|
|
import Control.Concurrent (threadDelay)
|
||
|
|
import Control.Exception (SomeException, try)
|
||
|
|
import Control.Monad (filterM)
|
||
|
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||
|
|
import System.Directory (createDirectoryIfMissing, doesFileExist, getTemporaryDirectory)
|
||
|
|
import System.Environment (getEnvironment, setEnv)
|
||
|
|
import System.Exit (ExitCode)
|
||
|
|
import System.FilePath ((</>))
|
||
|
|
import System.IO (Handle)
|
||
|
|
import System.Process (
|
||
|
|
CreateProcess (..),
|
||
|
|
ProcessHandle,
|
||
|
|
StdStream (..),
|
||
|
|
createProcess,
|
||
|
|
proc,
|
||
|
|
terminateProcess,
|
||
|
|
waitForProcess,
|
||
|
|
)
|
||
|
|
|
||
|
|
-- 拉起并管理 Rust 存储进程,起来后等管道应答 ping 再继续。
|
||
|
|
|
||
|
|
data StorageProcess = StorageProcess
|
||
|
|
{ spProcess :: ProcessHandle
|
||
|
|
, spPipe :: String
|
||
|
|
, spDataDir :: FilePath
|
||
|
|
}
|
||
|
|
|
||
|
|
-- | 找存储进程,没有就给路径提示
|
||
|
|
findStorageServer :: Maybe FilePath -> IO (Either String FilePath)
|
||
|
|
findStorageServer (Just p) = do
|
||
|
|
ok <- doesFileExist p
|
||
|
|
pure (if ok then Right p else Left ("storage server not found: " ++ p))
|
||
|
|
findStorageServer Nothing = do
|
||
|
|
let dirs =
|
||
|
|
[ "chusql-storage/target"
|
||
|
|
, "../chusql-storage/target"
|
||
|
|
, "../../chusql-storage/target"
|
||
|
|
, "target"
|
||
|
|
]
|
||
|
|
modes = ["release", "debug"]
|
||
|
|
exes = ["chusql-storage.exe", "chusql-storage"]
|
||
|
|
candidates = [d </> m </> e | d <- dirs, m <- modes, e <- exes]
|
||
|
|
found <- filterM doesFileExist candidates
|
||
|
|
pure $ case found of
|
||
|
|
(p : _) -> Right p
|
||
|
|
[] -> Left "no Rust storage server found; run `cargo build --release` in chusql-storage, or pass --storage <path>"
|
||
|
|
-- | 起存储进程并等它就绪
|
||
|
|
startStorageProcess :: FilePath -> IO (Either String StorageProcess)
|
||
|
|
startStorageProcess bin = do
|
||
|
|
stamp <- round . (* 1000000) <$> getPOSIXTime :: IO Integer
|
||
|
|
tmp <- getTemporaryDirectory
|
||
|
|
let pipe = "chusql-web-" ++ show stamp
|
||
|
|
dir = tmp </> pipe
|
||
|
|
createDirectoryIfMissing True dir
|
||
|
|
envs <-
|
||
|
|
childEnv
|
||
|
|
[ ("CHUSQL_PIPE", pipe)
|
||
|
|
, ("CHUSQL_DATA_DIR", dir)
|
||
|
|
]
|
||
|
|
started <-
|
||
|
|
try (createProcess (proc bin []){env = Just envs, std_out = NoStream, std_err = Inherit}) ::
|
||
|
|
IO (Either SomeException (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle))
|
||
|
|
case started of
|
||
|
|
Left e -> pure (Left ("failed to start the storage process: " ++ show e))
|
||
|
|
Right (_, _, _, ph) -> do
|
||
|
|
setEnv "CHUSQL_PIPE" pipe
|
||
|
|
setEnv "CHUSQL_DATA_DIR" dir
|
||
|
|
ready <- waitForStorage (200 :: Int)
|
||
|
|
if ready
|
||
|
|
then pure (Right (StorageProcess ph pipe dir))
|
||
|
|
else do
|
||
|
|
_ <- try (terminateProcess ph) :: IO (Either SomeException ())
|
||
|
|
pure (Left "storage process did not become ready within 5 seconds (no ping reply on the pipe)")
|
||
|
|
|
||
|
|
-- | 停掉存储进程,保留数据目录
|
||
|
|
stopStorageProcess :: StorageProcess -> IO ()
|
||
|
|
stopStorageProcess sp = do
|
||
|
|
closeConnection
|
||
|
|
_ <- try (terminateProcess (spProcess sp)) :: IO (Either SomeException ())
|
||
|
|
_ <- try (waitForProcess (spProcess sp)) :: IO (Either SomeException ExitCode)
|
||
|
|
pure ()
|
||
|
|
|
||
|
|
-- | 轮询 ping 直到对面应答
|
||
|
|
waitForStorage :: Int -> IO Bool
|
||
|
|
waitForStorage 0 = pure False
|
||
|
|
waitForStorage n = do
|
||
|
|
r <- try (sendRequest ReqPing) :: IO (Either SomeException Response)
|
||
|
|
case r of
|
||
|
|
Right RespPong -> pure True
|
||
|
|
_ -> threadDelay 25000 >> waitForStorage (n - 1)
|
||
|
|
|
||
|
|
-- | 子进程环境:继承并覆盖两个专用变量
|
||
|
|
childEnv :: [(String, String)] -> IO [(String, String)]
|
||
|
|
childEnv extra = do
|
||
|
|
base <- getEnvironment
|
||
|
|
let ours = ["CHUSQL_PIPE", "CHUSQL_DATA_DIR"]
|
||
|
|
kept = filter (\(k, _) -> k `notElem` ours) base
|
||
|
|
missing (k, _) = not (any ((== k) . fst) kept)
|
||
|
|
defaults = filter missing storageDefaults
|
||
|
|
pure (kept ++ defaults ++ extra)
|
||
|
|
|
||
|
|
-- | 存储进程的调参默认值(可以被用户环境里的同名变量覆盖)
|
||
|
|
storageDefaults :: [(String, String)]
|
||
|
|
storageDefaults =
|
||
|
|
[ ("CHUSQL_PAGE_SIZE", "4096")
|
||
|
|
, ("CHUSQL_BTREE_ORDER", "4")
|
||
|
|
, ("CHUSQL_LOG", "info")
|
||
|
|
]
|