tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

IODriver.hs (49893B)


      1 module IODriver
      2   ( IOPermissions(..)
      3   , defaultPerms
      4   , unsafePerms
      5   , checkIOSentinel
      6   , runIO
      7   , runIOWithEnv
      8   , runIOWith
      9   ) where
     10 
     11 import Research (T(..), apply, toString, toNumber, ofString, ofNumber, ofBytes, toBytes, ofList)
     12 import qualified Data.ByteString as BS
     13 import System.IO (putStr, getLine)
     14 import qualified System.IO as IO
     15 import Control.Exception (try, catch, IOException, SomeException)
     16 import System.IO.Error (isDoesNotExistError, isPermissionError, isAlreadyExistsError)
     17 import Data.List (isPrefixOf, isInfixOf)
     18 import System.FilePath (normalise, isRelative, (</>), addTrailingPathSeparator, splitDirectories, takeDirectory)
     19 import System.Directory (canonicalizePath, doesPathExist, getCurrentDirectory, listDirectory, createDirectory, renameFile, removeFile, doesDirectoryExist)
     20 import Data.Time.Clock.POSIX (getPOSIXTime)
     21 import Crypto.Hash (hash, SHA256, Digest)
     22 import Data.ByteArray (convert)
     23 import Data.ByteString.Base16 (encode)
     24 import Data.Text.Encoding (decodeUtf8)
     25 import qualified Data.Text as T
     26 import Data.Char (toLower)
     27 import qualified Data.Map.Strict as Map
     28 import Data.Map.Strict (Map)
     29 import qualified Data.Sequence as Seq
     30 import Data.Sequence (Seq, (|>), ViewL(..))
     31 import Data.Time.Clock (UTCTime, getCurrentTime, addUTCTime, diffUTCTime)
     32 import Control.Concurrent (threadDelay, forkIO)
     33 import Control.Concurrent.STM (TVar, newTVarIO, atomically, readTVar, writeTVar, modifyTVar', retry)
     34 import qualified Data.Set as Set
     35 import Data.Set (Set)
     36 import qualified Data.Foldable as Fold
     37 import qualified Network.Socket as NS
     38 import qualified Network.Socket.ByteString as NSB
     39 
     40 -- ---------------------------------------------------------------------------
     41 -- Permissions
     42 -- ---------------------------------------------------------------------------
     43 
     44 data IOPermissions = IOPermissions
     45   { allowRead     :: [FilePath]
     46   , allowWrite    :: [FilePath]
     47   , allowReadAll  :: Bool
     48   , allowWriteAll :: Bool
     49   }
     50   deriving (Show)
     51 
     52 defaultPerms :: IOPermissions
     53 defaultPerms = IOPermissions [] [] False False
     54 
     55 unsafePerms :: IOPermissions
     56 unsafePerms = IOPermissions [] [] True True
     57 
     58 checkIOSentinel :: T -> Either String (Integer, T)
     59 checkIOSentinel tree =
     60   case tree of
     61     Fork sentinel (Fork version action) -> do
     62       s <- toString sentinel
     63       case s of
     64         "tricuIO" -> do
     65           v <- toNumber version
     66           return (v, action)
     67         _ -> Left "sentinel mismatch (expected \"tricuIO\")"
     68     _ -> Left "root is not an IO sentinel pair"
     69 
     70 -- ---------------------------------------------------------------------------
     71 -- Runtime, Frames, and Machine
     72 -- ---------------------------------------------------------------------------
     73 
     74 data Runtime = Runtime
     75   { rtPerms :: IOPermissions
     76   , rtEnv   :: T
     77   , rtState :: T
     78   }
     79   deriving (Show)
     80 
     81 data Frame
     82   = BindFrame T
     83   | LocalFrame T
     84   deriving (Show)
     85 
     86 data Machine = Machine
     87   { machineRuntime :: Runtime
     88   , machineCurrent :: T
     89   , machineFrames  :: [Frame]
     90   }
     91   deriving (Show)
     92 
     93 -- ---------------------------------------------------------------------------
     94 -- Result convention
     95 -- ---------------------------------------------------------------------------
     96 -- Direct-return actions pass the raw value to the continuation:
     97 --   pure, bind, putStr, getLine, ask, local, get, put,
     98 --   fork, await, yield, sleep
     99 --
    100 -- Result-return actions wrap the outcome as an ok/err pair:
    101 --   ok val  = Fork (Stem Leaf) (Fork val Leaf)   -- (t t) val t
    102 --   err code = Fork Leaf (Fork code Leaf)        -- t code t
    103 --   readFile, writeFile
    104 --
    105 -- Runtime protocol errors are returned as direct values via errResult.
    106 
    107 okResult :: T -> T
    108 okResult val = Fork (Stem Leaf) (Fork val Leaf)
    109 
    110 errResult :: String -> T
    111 errResult msg = Fork Leaf (Fork (ofString msg) Leaf)
    112 
    113 pureAction :: T -> T
    114 pureAction x = Fork (ofNumber 0) x
    115 
    116 invalidAsyncHandleResult :: T
    117 invalidAsyncHandleResult = errResult "invalid task handle"
    118 
    119 invalidSocketHandleResult :: T
    120 invalidSocketHandleResult = errResult "invalid socket handle"
    121 
    122 selfAwaitResult :: T
    123 selfAwaitResult = errResult "self await"
    124 
    125 deadlockResult :: T
    126 deadlockResult = errResult "deadlock"
    127 
    128 invalidSleepResult :: T
    129 invalidSleepResult = errResult "invalid sleep"
    130 
    131 ioErrorString :: IOException -> String
    132 ioErrorString e
    133   | isDoesNotExistError  e = "does not exist"
    134   | isPermissionError    e = "permission denied"
    135   | isAlreadyExistsError e = "already exists"
    136   | otherwise              = "io error"
    137 
    138 -- ---------------------------------------------------------------------------
    139 -- Task identity and handles
    140 -- ---------------------------------------------------------------------------
    141 
    142 newtype TaskId = TaskId Integer
    143   deriving (Eq, Ord, Show)
    144 
    145 taskHandle :: TaskId -> T
    146 taskHandle (TaskId n) =
    147   Fork (ofString "task") (ofNumber n)
    148 
    149 decodeTaskHandle :: T -> Either String TaskId
    150 decodeTaskHandle tree =
    151   case tree of
    152     Fork tag nTree -> do
    153       tagString <- toString tag
    154       if tagString == "task"
    155         then TaskId <$> toNumber nTree
    156         else Left "invalid task handle tag"
    157     _ ->
    158       Left "invalid task handle"
    159 
    160 -- ---------------------------------------------------------------------------
    161 -- Socket identity and handles
    162 -- ---------------------------------------------------------------------------
    163 
    164 newtype SockId = SockId Integer
    165   deriving (Eq, Ord, Show)
    166 
    167 sockHandle :: SockId -> T
    168 sockHandle (SockId n) =
    169   Fork (ofString "sock") (ofNumber n)
    170 
    171 decodeSockHandle :: T -> Either String SockId
    172 decodeSockHandle tree =
    173   case tree of
    174     Fork tag nTree -> do
    175       tagString <- toString tag
    176       if tagString == "sock"
    177         then SockId <$> toNumber nTree
    178         else Left "invalid socket handle tag"
    179     _ ->
    180       Left "invalid socket handle"
    181 
    182 getSocketPort :: NS.Socket -> IO (Maybe Integer)
    183 getSocketPort sock = do
    184   addr <- NS.getSocketName sock
    185   case addr of
    186     NS.SockAddrInet p _      -> return (Just (fromIntegral p))
    187     NS.SockAddrInet6 p _ _ _ -> return (Just (fromIntegral p))
    188     _                        -> return Nothing
    189 
    190 -- ---------------------------------------------------------------------------
    191 -- Socket registry
    192 -- ---------------------------------------------------------------------------
    193 
    194 data SocketRegistry = SocketRegistry
    195   { sockMap    :: Map SockId NS.Socket
    196   , sockNextId :: Integer
    197   }
    198 
    199 -- ---------------------------------------------------------------------------
    200 -- Free-monad action AST
    201 -- ---------------------------------------------------------------------------
    202 
    203 data Action
    204   = APure T
    205   | ABind T T
    206   | APutStr T
    207   | APutBytes T
    208   | AGetLine
    209   | AReadFile T
    210   | AWriteFile T T
    211   | AWriteBytes T T
    212   | AListDirectory T
    213   | ARenameFile T T
    214   | ACreateDirectory T
    215   | ADeleteFile T
    216   | AFileExists T
    217   | ASha256Hex T
    218   | ACurrentTime
    219   | AAsk
    220   | ALocal T T
    221   | AGet
    222   | APut T
    223   | AFork T
    224   | AAwait T
    225   | AYield
    226   | ASleep T
    227   | ASocket
    228   | ACloseSocket T
    229   | ABindSocket T T T
    230   | AListen T T
    231   | AAccept T
    232   | AConnect T T T
    233   | ARecv T T
    234   | ASend T T
    235   | AGetSocketName T
    236   deriving (Show)
    237 
    238 -- ---------------------------------------------------------------------------
    239 -- Action tag constants
    240 -- ---------------------------------------------------------------------------
    241 
    242 tagPure, tagBind :: Integer
    243 tagPure = 0
    244 tagBind = 1
    245 
    246 tagPutStr, tagPutBytes, tagGetLine :: Integer
    247 tagPutStr = 10
    248 tagPutBytes = 12
    249 tagGetLine = 11
    250 
    251 tagReadFile, tagWriteFile, tagWriteBytes :: Integer
    252 tagReadFile = 20
    253 tagWriteFile = 21
    254 tagWriteBytes = 22
    255 
    256 tagListDirectory, tagRenameFile, tagCreateDirectory, tagDeleteFile, tagFileExists :: Integer
    257 tagListDirectory = 23
    258 tagRenameFile = 24
    259 tagCreateDirectory = 25
    260 tagDeleteFile = 26
    261 tagFileExists = 27
    262 
    263 tagSha256Hex, tagCurrentTime :: Integer
    264 tagSha256Hex = 28
    265 tagCurrentTime = 29
    266 
    267 tagAsk, tagLocal :: Integer
    268 tagAsk = 30
    269 tagLocal = 31
    270 
    271 tagGet, tagPut :: Integer
    272 tagGet = 40
    273 tagPut = 41
    274 
    275 tagFork, tagAwait, tagYield, tagSleep :: Integer
    276 tagFork = 60
    277 tagAwait = 61
    278 tagYield = 62
    279 tagSleep = 63
    280 
    281 tagSocket, tagCloseSocket, tagBindSocket, tagListen, tagAccept :: Integer
    282 tagSocket = 70
    283 tagCloseSocket = 71
    284 tagBindSocket = 72
    285 tagListen = 73
    286 tagAccept = 74
    287 
    288 tagConnect, tagRecv, tagSend, tagGetSocketName :: Integer
    289 tagConnect = 75
    290 tagRecv = 76
    291 tagSend = 77
    292 tagGetSocketName = 78
    293 
    294 data Step
    295   = Halt Runtime T
    296   | Continue Machine
    297   | ForkRequested T Machine
    298   | AwaitRequested TaskId Machine
    299   | YieldRequested Machine
    300   | SleepRequested Integer Machine
    301   | AsyncAction (IO T) Machine
    302 
    303 instance Show Step where
    304   show (Halt _ v) = "Halt _ (" ++ show v ++ ")"
    305   show (Continue m) = "Continue (" ++ show m ++ ")"
    306   show (ForkRequested t m) = "ForkRequested (" ++ show t ++ ") (" ++ show m ++ ")"
    307   show (AwaitRequested tid m) = "AwaitRequested " ++ show tid ++ " (" ++ show m ++ ")"
    308   show (YieldRequested m) = "YieldRequested (" ++ show m ++ ")"
    309   show (SleepRequested n m) = "SleepRequested " ++ show n ++ " (" ++ show m ++ ")"
    310   show (AsyncAction _ m) = "AsyncAction <io> (" ++ show m ++ ")"
    311 
    312 decodeAction :: T -> Either String Action
    313 decodeAction tree =
    314   case tree of
    315     Fork tag payload ->
    316       case toNumber tag of
    317         Right n | n == tagPure ->
    318           Right (APure payload)
    319 
    320         Right n | n == tagBind ->
    321           case payload of
    322             Fork left k -> Right (ABind left k)
    323             _ -> Left "Invalid Bind: expected pair action continuation"
    324 
    325         Right n | n == tagPutStr ->
    326           Right (APutStr payload)
    327 
    328         Right n | n == tagPutBytes ->
    329           Right (APutBytes payload)
    330 
    331         Right n | n == tagGetLine ->
    332           Right AGetLine
    333 
    334         Right n | n == tagReadFile ->
    335           Right (AReadFile payload)
    336 
    337         Right n | n == tagWriteFile ->
    338           case payload of
    339             Fork path contents -> Right (AWriteFile path contents)
    340             _ -> Left "Invalid WriteFile: expected pair path contents"
    341 
    342         Right n | n == tagWriteBytes ->
    343           case payload of
    344             Fork path contents -> Right (AWriteBytes path contents)
    345             _ -> Left "Invalid WriteBytes: expected pair path contents"
    346 
    347         Right n | n == tagListDirectory ->
    348           Right (AListDirectory payload)
    349 
    350         Right n | n == tagRenameFile ->
    351           case payload of
    352             Fork old new -> Right (ARenameFile old new)
    353             _ -> Left "Invalid RenameFile: expected pair oldPath newPath"
    354 
    355         Right n | n == tagCreateDirectory ->
    356           Right (ACreateDirectory payload)
    357 
    358         Right n | n == tagDeleteFile ->
    359           Right (ADeleteFile payload)
    360 
    361         Right n | n == tagFileExists ->
    362           Right (AFileExists payload)
    363 
    364         Right n | n == tagSha256Hex ->
    365           Right (ASha256Hex payload)
    366 
    367         Right n | n == tagCurrentTime ->
    368           Right ACurrentTime
    369 
    370         Right n | n == tagAsk ->
    371           Right AAsk
    372 
    373         Right n | n == tagLocal ->
    374           case payload of
    375             Fork f action -> Right (ALocal f action)
    376             _ -> Left "Invalid Local: expected pair function action"
    377 
    378         Right n | n == tagGet ->
    379           Right AGet
    380 
    381         Right n | n == tagPut ->
    382           Right (APut payload)
    383 
    384         Right n | n == tagFork ->
    385           Right (AFork payload)
    386 
    387         Right n | n == tagAwait ->
    388           Right (AAwait payload)
    389 
    390         Right n | n == tagYield ->
    391           Right AYield
    392 
    393         Right n | n == tagSleep ->
    394           Right (ASleep payload)
    395 
    396         Right n | n == tagSocket ->
    397           Right ASocket
    398 
    399         Right n | n == tagCloseSocket ->
    400           Right (ACloseSocket payload)
    401 
    402         Right n | n == tagBindSocket ->
    403           case payload of
    404             Fork sock (Fork addr port) -> Right (ABindSocket sock addr port)
    405             _ -> Left "Invalid BindSocket: expected pair sock (pair addr port)"
    406 
    407         Right n | n == tagListen ->
    408           case payload of
    409             Fork sock backlog -> Right (AListen sock backlog)
    410             _ -> Left "Invalid Listen: expected pair sock backlog"
    411 
    412         Right n | n == tagAccept ->
    413           Right (AAccept payload)
    414 
    415         Right n | n == tagConnect ->
    416           case payload of
    417             Fork sock (Fork addr port) -> Right (AConnect sock addr port)
    418             _ -> Left "Invalid Connect: expected pair sock (pair addr port)"
    419 
    420         Right n | n == tagRecv ->
    421           case payload of
    422             Fork sock maxBytes -> Right (ARecv sock maxBytes)
    423             _ -> Left "Invalid Recv: expected pair sock maxBytes"
    424 
    425         Right n | n == tagSend ->
    426           case payload of
    427             Fork sock bytes -> Right (ASend sock bytes)
    428             _ -> Left "Invalid Send: expected pair sock bytes"
    429 
    430         Right n | n == tagGetSocketName ->
    431           Right (AGetSocketName payload)
    432 
    433         Right n ->
    434           Left $ "Unknown IO action tag: " ++ show n
    435 
    436         Left err ->
    437           Left $ "Invalid action tag: " ++ err
    438 
    439     _ ->
    440       Left $ "Invalid action tree: expected pair tag payload, got " ++ show tree
    441 
    442 -- ---------------------------------------------------------------------------
    443 -- Small-step IO machine
    444 -- ---------------------------------------------------------------------------
    445 
    446 finishValue :: Machine -> T -> IO Step
    447 finishValue machine value =
    448   case machineFrames machine of
    449     [] ->
    450       pure (Halt (machineRuntime machine) value)
    451 
    452     BindFrame k : rest ->
    453       pure (Continue machine
    454         { machineCurrent = apply k value
    455         , machineFrames = rest
    456         })
    457 
    458     LocalFrame oldEnv : rest ->
    459       let runtime' = (machineRuntime machine) { rtEnv = oldEnv }
    460       in pure (Continue machine
    461           { machineRuntime = runtime'
    462           , machineCurrent = pureAction value
    463           , machineFrames = rest
    464           })
    465 
    466 stepMachine :: TVar SocketRegistry -> Machine -> IO Step
    467 stepMachine sockVar machine =
    468   case decodeAction (machineCurrent machine) of
    469     Right action -> dispatch action
    470     Left _       -> finishValue machine (errResult "invalid action")
    471   where
    472     dispatch action = case action of
    473       APure val ->
    474         finishValue machine val
    475 
    476       ABind left k ->
    477         pure (Continue machine
    478           { machineCurrent = left
    479           , machineFrames = BindFrame k : machineFrames machine
    480           })
    481 
    482       APutStr str ->
    483         case decodeString str "PutStr" of
    484           Right s ->
    485             pure (AsyncAction (putStr s >> pure Leaf) machine)
    486           Left _ ->
    487             finishValue machine (errResult "invalid string")
    488 
    489       APutBytes bs ->
    490         case decodeBytes bs "PutBytes" of
    491           Right b ->
    492             pure (AsyncAction (BS.putStr b >> pure Leaf) machine)
    493           Left _ ->
    494             finishValue machine (errResult "invalid bytes")
    495 
    496       AGetLine ->
    497         pure (AsyncAction (ofString <$> getLine) machine)
    498 
    499       AReadFile path ->
    500         case decodeString path "ReadFile" of
    501           Right p -> do
    502             mDeny <- checkReadPerm p
    503             case mDeny of
    504               Just denied -> finishValue machine denied
    505               Nothing     -> pure (AsyncAction (tryReadFile p) machine)
    506           Left _ -> finishValue machine (errResult "invalid string")
    507 
    508       AWriteFile path contents ->
    509         case decodeString path "WriteFile" of
    510           Right p ->
    511             case decodeString contents "WriteFile" of
    512               Right c -> do
    513                 mDeny <- checkWritePerm p
    514                 case mDeny of
    515                   Just denied -> finishValue machine denied
    516                   Nothing     -> pure (AsyncAction (tryWriteFile p c) machine)
    517               Left _ -> finishValue machine (errResult "invalid string")
    518           Left _ -> finishValue machine (errResult "invalid string")
    519 
    520       AWriteBytes path contents ->
    521         case decodeString path "WriteBytes" of
    522           Right p ->
    523             case decodeBytes contents "WriteBytes" of
    524               Right c -> do
    525                 mDeny <- checkWritePerm p
    526                 case mDeny of
    527                   Just denied -> finishValue machine denied
    528                   Nothing     -> pure (AsyncAction (tryWriteFileBytes p c) machine)
    529               Left _ -> finishValue machine (errResult "invalid bytes")
    530           Left _ -> finishValue machine (errResult "invalid string")
    531 
    532       AListDirectory pathTree ->
    533         case decodeString pathTree "ListDirectory" of
    534           Right p -> do
    535             mDeny <- checkReadPerm p
    536             case mDeny of
    537               Just denied -> finishValue machine denied
    538               Nothing     -> pure (AsyncAction (tryListDirectory p) machine)
    539           Left _ -> finishValue machine (errResult "invalid string")
    540 
    541       ARenameFile oldTree newTree ->
    542         case decodeString oldTree "RenameFile" of
    543           Right old ->
    544             case decodeString newTree "RenameFile" of
    545               Right new -> do
    546                 mDenyOld <- checkWritePerm old
    547                 mDenyNew <- checkWritePerm new
    548                 case (mDenyOld, mDenyNew) of
    549                   (Just denied, _) -> finishValue machine denied
    550                   (_, Just denied) -> finishValue machine denied
    551                   (Nothing, Nothing) -> pure (AsyncAction (tryRenameFile old new) machine)
    552               Left _ -> finishValue machine (errResult "invalid string")
    553           Left _ -> finishValue machine (errResult "invalid string")
    554 
    555       ACreateDirectory pathTree ->
    556         case decodeString pathTree "CreateDirectory" of
    557           Right p -> do
    558             mDeny <- checkWritePerm p
    559             case mDeny of
    560               Just denied -> finishValue machine denied
    561               Nothing     -> pure (AsyncAction (tryCreateDirectory p) machine)
    562           Left _ -> finishValue machine (errResult "invalid string")
    563 
    564       ADeleteFile pathTree ->
    565         case decodeString pathTree "DeleteFile" of
    566           Right p -> do
    567             mDeny <- checkWritePerm p
    568             case mDeny of
    569               Just denied -> finishValue machine denied
    570               Nothing     -> pure (AsyncAction (tryDeleteFile p) machine)
    571           Left _ -> finishValue machine (errResult "invalid string")
    572 
    573       AFileExists pathTree ->
    574         case decodeString pathTree "FileExists" of
    575           Right p -> do
    576             mDeny <- checkReadPerm p
    577             case mDeny of
    578               Just denied -> finishValue machine denied
    579               Nothing     -> pure (AsyncAction (tryFileExists p) machine)
    580           Left _ -> finishValue machine (errResult "invalid string")
    581 
    582       ASha256Hex bytesTree ->
    583         case decodeBytes bytesTree "Sha256Hex" of
    584           Right bs -> pure (AsyncAction (pure $ trySha256Hex bs) machine)
    585           Left _ -> finishValue machine (errResult "invalid bytes")
    586 
    587       ACurrentTime ->
    588         pure (AsyncAction (tryCurrentTime) machine)
    589 
    590       AAsk ->
    591         finishValue machine (rtEnv (machineRuntime machine))
    592 
    593       ALocal f action' ->
    594         let runtime = machineRuntime machine
    595             oldEnv  = rtEnv runtime
    596             newEnv  = apply f oldEnv
    597             runtime' = runtime { rtEnv = newEnv }
    598         in pure (Continue machine
    599             { machineRuntime = runtime'
    600             , machineCurrent = action'
    601             , machineFrames = LocalFrame oldEnv : machineFrames machine
    602             })
    603 
    604       AGet ->
    605         finishValue machine (rtState (machineRuntime machine))
    606 
    607       APut newState ->
    608         let runtime' = (machineRuntime machine) { rtState = newState }
    609         in finishValue (machine { machineRuntime = runtime' }) Leaf
    610 
    611       AFork childAction ->
    612         pure (ForkRequested childAction machine)
    613 
    614       AAwait handleTree ->
    615         case decodeTaskHandle handleTree of
    616           Right taskId ->
    617             pure (AwaitRequested taskId machine)
    618           Left _ ->
    619             finishValue machine invalidAsyncHandleResult
    620 
    621       AYield ->
    622         pure (YieldRequested machine)
    623 
    624       ASleep msTree ->
    625         case toNumber msTree of
    626           Right ms | ms >= 0 ->
    627             pure (SleepRequested ms machine)
    628           _ ->
    629             finishValue machine invalidSleepResult
    630 
    631       ASocket -> do
    632         result <- try (NS.socket NS.AF_INET NS.Stream NS.defaultProtocol) :: IO (Either SomeException NS.Socket)
    633         case result of
    634           Left e ->
    635             finishValue machine (errResult ("io error: " ++ show e))
    636           Right sock -> do
    637             NS.setSocketOption sock NS.ReuseAddr 1
    638             sid <- atomically $ do
    639               SocketRegistry m next <- readTVar sockVar
    640               let sid = SockId next
    641               writeTVar sockVar (SocketRegistry (Map.insert sid sock m) (next + 1))
    642               return sid
    643             finishValue machine (okResult (sockHandle sid))
    644 
    645       ACloseSocket sockTree ->
    646         case decodeSockHandle sockTree of
    647           Left _ -> finishValue machine invalidSocketHandleResult
    648           Right sid -> do
    649             mSock <- atomically $ do
    650               SocketRegistry m next <- readTVar sockVar
    651               case Map.lookup sid m of
    652                 Nothing -> return Nothing
    653                 Just sock -> do
    654                   writeTVar sockVar (SocketRegistry (Map.delete sid m) next)
    655                   return (Just sock)
    656             case mSock of
    657               Nothing -> finishValue machine invalidSocketHandleResult
    658               Just sock -> do
    659                 NS.close sock
    660                 finishValue machine (okResult Leaf)
    661 
    662       ABindSocket sockTree addrTree portTree ->
    663         case decodeSockHandle sockTree of
    664           Left _ -> finishValue machine invalidSocketHandleResult
    665           Right sid ->
    666             case decodeString addrTree "BindSocket" of
    667               Left _ -> finishValue machine (errResult "invalid address")
    668               Right addrStr ->
    669                 case toNumber portTree of
    670                   Left _ -> finishValue machine (errResult "invalid port")
    671                   Right port -> do
    672                     mSock <- atomically $ do
    673                       SocketRegistry m _ <- readTVar sockVar
    674                       return (Map.lookup sid m)
    675                     case mSock of
    676                       Nothing -> finishValue machine invalidSocketHandleResult
    677                       Just sock -> do
    678                         result <- try (do
    679                           addrInfo <- NS.getAddrInfo (Just $ NS.defaultHints { NS.addrSocketType = NS.Stream })
    680                                                      (Just addrStr)
    681                                                      (Just (show port))
    682                           let serverAddr = head addrInfo
    683                           NS.bind sock (NS.addrAddress serverAddr)
    684                           ) :: IO (Either SomeException ())
    685                         case result of
    686                           Left e ->
    687                             finishValue machine (errResult ("io error: " ++ show e))
    688                           Right () ->
    689                             finishValue machine (okResult Leaf)
    690 
    691       AListen sockTree backlogTree ->
    692         case decodeSockHandle sockTree of
    693           Left _ -> finishValue machine invalidSocketHandleResult
    694           Right sid ->
    695             case toNumber backlogTree of
    696               Left _ -> finishValue machine (errResult "invalid backlog")
    697               Right backlog -> do
    698                 mSock <- atomically $ do
    699                   SocketRegistry m _ <- readTVar sockVar
    700                   return (Map.lookup sid m)
    701                 case mSock of
    702                   Nothing -> finishValue machine invalidSocketHandleResult
    703                   Just sock -> do
    704                     result <- try (NS.listen sock (fromIntegral backlog)) :: IO (Either SomeException ())
    705                     case result of
    706                       Left e ->
    707                         finishValue machine (errResult ("io error: " ++ show e))
    708                       Right () ->
    709                         finishValue machine (okResult Leaf)
    710 
    711       AAccept listenTree ->
    712         case decodeSockHandle listenTree of
    713           Left _ -> finishValue machine invalidSocketHandleResult
    714           Right listenSid ->
    715             pure (AsyncAction (do
    716               mListenSock <- atomically $ do
    717                 SocketRegistry m _ <- readTVar sockVar
    718                 return (Map.lookup listenSid m)
    719               case mListenSock of
    720                 Nothing -> return (errResult "invalid socket handle")
    721                 Just listenSock -> do
    722                   result <- try (NS.accept listenSock) :: IO (Either SomeException (NS.Socket, NS.SockAddr))
    723                   case result of
    724                     Left e ->
    725                       return (errResult ("io error: " ++ show e))
    726                     Right (clientSock, addr) -> do
    727                       clientSid <- atomically $ do
    728                         SocketRegistry m next <- readTVar sockVar
    729                         let sid = SockId next
    730                         writeTVar sockVar (SocketRegistry (Map.insert sid clientSock m) (next + 1))
    731                         return sid
    732                       let addrStr = case addr of
    733                             NS.SockAddrInet p h ->
    734                               let (a,b,c,d) = NS.hostAddressToTuple h
    735                               in show a ++ "." ++ show b ++ "." ++ show c ++ "." ++ show d ++ ":" ++ show p
    736                             _ -> show addr
    737                       return (okResult (Fork (sockHandle clientSid) (ofString addrStr)))
    738               ) machine)
    739 
    740       AConnect sockTree addrTree portTree ->
    741         case decodeSockHandle sockTree of
    742           Left _ -> finishValue machine invalidSocketHandleResult
    743           Right sid ->
    744             case decodeString addrTree "Connect" of
    745               Left _ -> finishValue machine (errResult "invalid address")
    746               Right addrStr ->
    747                 case toNumber portTree of
    748                   Left _ -> finishValue machine (errResult "invalid port")
    749                   Right port -> do
    750                     mSock <- atomically $ do
    751                       SocketRegistry m _ <- readTVar sockVar
    752                       return (Map.lookup sid m)
    753                     case mSock of
    754                       Nothing -> finishValue machine invalidSocketHandleResult
    755                       Just sock ->
    756                         pure (AsyncAction (do
    757                           result <- try (do
    758                             addrInfo <- NS.getAddrInfo (Just $ NS.defaultHints { NS.addrSocketType = NS.Stream })
    759                                                        (Just addrStr)
    760                                                        (Just (show port))
    761                             let serverAddr = head addrInfo
    762                             NS.connect sock (NS.addrAddress serverAddr)
    763                             ) :: IO (Either SomeException ())
    764                           case result of
    765                             Left e ->
    766                               return (errResult ("io error: " ++ show e))
    767                             Right () ->
    768                               return (okResult Leaf)
    769                           ) machine)
    770 
    771       ARecv sockTree maxBytesTree ->
    772         case decodeSockHandle sockTree of
    773           Left _ -> finishValue machine invalidSocketHandleResult
    774           Right sid ->
    775             case toNumber maxBytesTree of
    776               Left _ -> finishValue machine (errResult "invalid maxBytes")
    777               Right maxBytes -> do
    778                 mSock <- atomically $ do
    779                   SocketRegistry m _ <- readTVar sockVar
    780                   return (Map.lookup sid m)
    781                 case mSock of
    782                   Nothing -> finishValue machine invalidSocketHandleResult
    783                   Just sock ->
    784                     pure (AsyncAction (do
    785                       result <- try (NSB.recv sock (fromIntegral maxBytes)) :: IO (Either SomeException BS.ByteString)
    786                       case result of
    787                         Left e ->
    788                           return (errResult ("io error: " ++ show e))
    789                         Right bs ->
    790                           if BS.null bs
    791                             then return (errResult "connection closed")
    792                             else return (okResult (ofBytes bs))
    793                       ) machine)
    794 
    795       AGetSocketName sockTree ->
    796         case decodeSockHandle sockTree of
    797           Left _ -> finishValue machine invalidSocketHandleResult
    798           Right sid -> do
    799             mSock <- atomically $ do
    800               SocketRegistry m _ <- readTVar sockVar
    801               return (Map.lookup sid m)
    802             case mSock of
    803               Nothing -> finishValue machine invalidSocketHandleResult
    804               Just sock -> do
    805                 mPort <- getSocketPort sock
    806                 case mPort of
    807                   Just port -> finishValue machine (okResult (ofNumber port))
    808                   Nothing   -> finishValue machine (errResult "io error: could not get socket name")
    809 
    810       ASend sockTree bytesTree ->
    811         case decodeSockHandle sockTree of
    812           Left _ -> finishValue machine invalidSocketHandleResult
    813           Right sid ->
    814             case decodeBytes bytesTree "Send" of
    815               Left _ -> finishValue machine (errResult "invalid bytes")
    816               Right bs -> do
    817                 mSock <- atomically $ do
    818                   SocketRegistry m _ <- readTVar sockVar
    819                   return (Map.lookup sid m)
    820                 case mSock of
    821                   Nothing -> finishValue machine invalidSocketHandleResult
    822                   Just sock ->
    823                     pure (AsyncAction (do
    824                       result <- try (NSB.send sock bs) :: IO (Either SomeException Int)
    825                       case result of
    826                         Left e ->
    827                           return (errResult ("io error: " ++ show e))
    828                         Right sent ->
    829                           return (okResult (ofNumber (fromIntegral sent)))
    830                       ) machine)
    831 
    832     -- Permission and IO helpers
    833     checkReadPerm p =
    834       if allowReadAll (rtPerms (machineRuntime machine))
    835         then return Nothing
    836         else do
    837           mp <- canonicalizeSafe p
    838           case mp of
    839             Left _     -> return $ Just policyErrResult
    840             Right path -> do
    841               allowed <- pathAllowed path (allowRead (rtPerms (machineRuntime machine)))
    842               if allowed
    843                 then return Nothing
    844                 else return $ Just policyErrResult
    845 
    846     checkWritePerm p =
    847       if allowWriteAll (rtPerms (machineRuntime machine))
    848         then return Nothing
    849         else do
    850           mp <- canonicalizeSafe p
    851           case mp of
    852             Left _     -> return $ Just policyErrResult
    853             Right path -> do
    854               allowed <- pathAllowed path (allowWrite (rtPerms (machineRuntime machine)))
    855               if allowed
    856                 then return Nothing
    857                 else return $ Just policyErrResult
    858 
    859     policyErrResult = errResult "permission denied"
    860 
    861     canonicalizeSafe :: FilePath -> IO (Either String FilePath)
    862     canonicalizeSafe p = do
    863       exists <- doesPathExist p
    864       if exists
    865         then do
    866           result <- try (canonicalizePath p) :: IO (Either SomeException FilePath)
    867           case result of
    868             Right canon -> return $ Right canon
    869             Left _      -> normalizeSyntactic p
    870         else normalizeSyntactic p
    871 
    872     normalizeSyntactic :: FilePath -> IO (Either String FilePath)
    873     normalizeSyntactic p = do
    874       absPath <- if isRelative p then (</> p) <$> getCurrentDirectory else return p
    875       let norm = normalise absPath
    876           dirs = splitDirectories norm
    877       if ".." `elem` dirs
    878         then return $ Left "Path contains unresolved parent-directory references"
    879         else return $ Right norm
    880 
    881     pathAllowed :: FilePath -> [FilePath] -> IO Bool
    882     pathAllowed _ [] = return False
    883     pathAllowed p prefixes = do
    884       let validPrefixes = filter (not . null) prefixes
    885       if null validPrefixes
    886         then return False
    887         else do
    888           absPrefixes <- mapM resolvePrefix validPrefixes
    889           return $ any (isPathPrefixOf p) absPrefixes
    890 
    891     resolvePrefix :: FilePath -> IO FilePath
    892     resolvePrefix p = do
    893       let norm = normalise p
    894       absPath <- if isRelative norm then (</> norm) <$> getCurrentDirectory else return norm
    895       exists <- doesPathExist absPath
    896       if exists
    897         then do
    898           result <- try (canonicalizePath absPath) :: IO (Either SomeException FilePath)
    899           case result of
    900             Right canon -> return canon
    901             Left _      -> return absPath
    902         else return absPath
    903 
    904     isPathPrefixOf :: FilePath -> FilePath -> Bool
    905     isPathPrefixOf path prefix =
    906       let prefix' = addTrailingPathSeparator prefix
    907       in path == prefix || prefix' `isPrefixOf` path
    908 
    909     tryReadFile path = do
    910       result <- try (BS.readFile path) :: IO (Either IOException BS.ByteString)
    911       case result of
    912         Right content -> return $ okResult (ofBytes content)
    913         Left e        -> return $ errResult (ioErrorString e)
    914 
    915     tryWriteFile path contents = do
    916       result <- try (IO.writeFile path contents) :: IO (Either IOException ())
    917       case result of
    918         Right () -> return $ okResult Leaf
    919         Left e   -> return $ errResult (ioErrorString e)
    920 
    921     tryWriteFileBytes path contents = do
    922       result <- try (BS.writeFile path contents) :: IO (Either IOException ())
    923       case result of
    924         Right () -> return $ okResult Leaf
    925         Left e   -> return $ errResult (ioErrorString e)
    926 
    927     tryListDirectory path = do
    928       exists <- doesPathExist path
    929       if not exists
    930         then return $ errResult "does not exist"
    931         else do
    932           isDir <- doesDirectoryExist path
    933           if not isDir
    934             then return $ errResult "not a directory"
    935             else do
    936               result <- try (listDirectory path) :: IO (Either IOException [FilePath])
    937               case result of
    938                 Right entries ->
    939                   let filtered = filter (`notElem` [".", ".."]) entries
    940                   in return $ okResult (ofList (map ofString filtered))
    941                 Left e -> return $ errResult (ioErrorString e)
    942 
    943     tryRenameFile old new = do
    944       oldExists <- doesPathExist old
    945       if not oldExists
    946         then return $ errResult "does not exist"
    947         else do
    948           result <- try (renameFile old new) :: IO (Either IOException ())
    949           case result of
    950             Right () -> return $ okResult Leaf
    951             Left e
    952               | isDoesNotExistError e -> return $ errResult "does not exist"
    953               | isPermissionError e -> return $ errResult "permission denied"
    954               | "cross-device" `isInfixOf` map toLower (show e) || "exdev" `isInfixOf` map toLower (show e) ->
    955                   return $ errResult "cross-device rename"
    956               | otherwise -> return $ errResult (ioErrorString e)
    957 
    958     tryCreateDirectory path = do
    959       exists <- doesPathExist path
    960       if exists
    961         then do
    962           isDir <- doesDirectoryExist path
    963           if isDir
    964             then return $ okResult Leaf
    965             else return $ errResult "already exists"
    966         else do
    967           let parent = takeDirectory path
    968           parentExists <- doesPathExist parent
    969           if parentExists
    970             then do
    971               parentIsDir <- doesDirectoryExist parent
    972               if parentIsDir
    973                 then do
    974                   result <- try (createDirectory path) :: IO (Either IOException ())
    975                   case result of
    976                     Right () -> return $ okResult Leaf
    977                     Left e
    978                       | isDoesNotExistError e -> return $ errResult "does not exist"
    979                       | isPermissionError e -> return $ errResult "permission denied"
    980                       | isAlreadyExistsError e -> return $ errResult "already exists"
    981                       | otherwise -> return $ errResult (ioErrorString e)
    982                 else return $ errResult "not a directory"
    983             else do
    984               result <- try (createDirectory path) :: IO (Either IOException ())
    985               case result of
    986                 Right () -> return $ okResult Leaf
    987                 Left e
    988                   | isDoesNotExistError e -> return $ errResult "does not exist"
    989                   | isPermissionError e -> return $ errResult "permission denied"
    990                   | isAlreadyExistsError e -> return $ errResult "already exists"
    991                   | otherwise -> return $ errResult (ioErrorString e)
    992 
    993     tryDeleteFile path = do
    994       exists <- doesPathExist path
    995       if not exists
    996         then return $ okResult Leaf
    997         else do
    998           isDir <- doesDirectoryExist path
    999           if isDir
   1000             then return $ errResult "is a directory"
   1001             else do
   1002               result <- try (removeFile path) :: IO (Either IOException ())
   1003               case result of
   1004                 Right () -> return $ okResult Leaf
   1005                 Left e
   1006                   | isDoesNotExistError e -> return $ okResult Leaf
   1007                   | isPermissionError e -> return $ errResult "permission denied"
   1008                   | otherwise -> return $ errResult (ioErrorString e)
   1009 
   1010     tryFileExists path = do
   1011       result <- try (doesPathExist path) :: IO (Either IOException Bool)
   1012       case result of
   1013         Right exists -> return $ okResult (if exists then Stem Leaf else Leaf)
   1014         Left e
   1015           | isPermissionError e -> return $ errResult "permission denied"
   1016           | otherwise -> return $ errResult (ioErrorString e)
   1017 
   1018     trySha256Hex bs =
   1019       let digest = hash bs :: Digest SHA256
   1020           hexBs = encode (convert digest)
   1021           hexStr = T.unpack (decodeUtf8 hexBs)
   1022       in okResult (ofString hexStr)
   1023 
   1024     tryCurrentTime = do
   1025       now <- getPOSIXTime
   1026       return $ okResult (ofNumber (floor now))
   1027 
   1028     decodeString t ctx =
   1029       case toString t of
   1030         Right s  -> Right s
   1031         Left _ -> Left $ "Invalid " ++ ctx ++ " string"
   1032 
   1033     decodeBytes t ctx =
   1034       case toBytes t of
   1035         Right b  -> Right b
   1036         Left _ -> Left $ "Invalid " ++ ctx ++ " bytes"
   1037 
   1038 -- ---------------------------------------------------------------------------
   1039 -- Scheduler
   1040 -- ---------------------------------------------------------------------------
   1041 
   1042 data TaskStatus
   1043   = Runnable Machine
   1044   | BlockedOn TaskId Machine
   1045   | Sleeping UTCTime Machine
   1046   | AsyncWaiting Machine
   1047   deriving (Show)
   1048 
   1049 data Scheduler = Scheduler
   1050   { schedulerNextTaskId :: Integer
   1051   , schedulerRunnable   :: Seq TaskId
   1052   , schedulerTasks      :: Map TaskId TaskStatus
   1053   , schedulerWaiters    :: Map TaskId (Seq TaskId)
   1054   , schedulerSleepQueue :: Map UTCTime (Set TaskId)
   1055   , schedulerAsyncCompleted :: TVar (Map TaskId T)
   1056   , schedulerCompleted  :: Map TaskId (T, T)
   1057   , schedulerSockets    :: TVar SocketRegistry
   1058   , schedulerNextSockId :: Integer
   1059   }
   1060 
   1061 instance Show Scheduler where
   1062   show s = "Scheduler { schedulerNextTaskId = " ++ show (schedulerNextTaskId s)
   1063     ++ ", schedulerRunnable = " ++ show (schedulerRunnable s)
   1064     ++ ", schedulerTasks = " ++ show (schedulerTasks s)
   1065     ++ ", schedulerWaiters = " ++ show (schedulerWaiters s)
   1066     ++ ", schedulerSleepQueue = " ++ show (schedulerSleepQueue s)
   1067     ++ ", schedulerAsyncCompleted = <tvar>"
   1068     ++ ", schedulerCompleted = " ++ show (schedulerCompleted s)
   1069     ++ ", schedulerSockets = <tvar>"
   1070     ++ ", schedulerNextSockId = " ++ show (schedulerNextSockId s)
   1071     ++ " }"
   1072 
   1073 initialScheduler :: TVar (Map TaskId T) -> TVar SocketRegistry -> Machine -> Scheduler
   1074 initialScheduler asyncVar sockVar mainMachine =
   1075   Scheduler
   1076     { schedulerNextTaskId = 1
   1077     , schedulerRunnable = Seq.singleton (TaskId 0)
   1078     , schedulerTasks = Map.singleton (TaskId 0) (Runnable mainMachine)
   1079     , schedulerWaiters = Map.empty
   1080     , schedulerSleepQueue = Map.empty
   1081     , schedulerAsyncCompleted = asyncVar
   1082     , schedulerCompleted = Map.empty
   1083     , schedulerSockets = sockVar
   1084     , schedulerNextSockId = 0
   1085     }
   1086 
   1087 runtimeOfStatus :: TaskStatus -> Maybe Runtime
   1088 runtimeOfStatus (Runnable machine) = Just (machineRuntime machine)
   1089 runtimeOfStatus (BlockedOn _ machine) = Just (machineRuntime machine)
   1090 runtimeOfStatus (Sleeping _ machine) = Just (machineRuntime machine)
   1091 runtimeOfStatus (AsyncWaiting machine) = Just (machineRuntime machine)
   1092 
   1093 wakeAwaiters :: TaskId -> T -> Scheduler -> Scheduler
   1094 wakeAwaiters targetId value scheduler =
   1095   case Map.lookup targetId (schedulerWaiters scheduler) of
   1096     Nothing -> scheduler
   1097     Just waiters ->
   1098       let (tasks', queue') = Fold.foldl' (wakeOne targetId value)
   1099                               (schedulerTasks scheduler, schedulerRunnable scheduler)
   1100                               waiters
   1101       in scheduler
   1102            { schedulerTasks = tasks'
   1103            , schedulerRunnable = queue'
   1104            , schedulerWaiters = Map.delete targetId (schedulerWaiters scheduler)
   1105            }
   1106   where
   1107     wakeOne _ _ (tasks, queue) waiterId =
   1108       case Map.lookup waiterId tasks of
   1109         Just (BlockedOn _ machine) ->
   1110           let machine' = machine { machineCurrent = pureAction value }
   1111           in (Map.insert waiterId (Runnable machine') tasks, queue |> waiterId)
   1112         _ -> (tasks, queue)
   1113 
   1114 wakeDueSleepers :: Scheduler -> IO Scheduler
   1115 wakeDueSleepers scheduler = do
   1116   now <- getCurrentTime
   1117   let go sq accTasks accQueue =
   1118         case Map.lookupMin sq of
   1119           Nothing -> (accTasks, accQueue, sq)
   1120           Just (t, taskSet)
   1121             | t <= now ->
   1122                 let tasks' = Fold.foldl' (\m tid ->
   1123                       case Map.lookup tid m of
   1124                         Just (Sleeping _ machine) -> Map.insert tid (Runnable machine) m
   1125                         _ -> m
   1126                       ) accTasks (Set.toList taskSet)
   1127                     queue' = Fold.foldl' (|>) accQueue (Set.toList taskSet)
   1128                 in go (Map.deleteMin sq) tasks' queue'
   1129             | otherwise -> (accTasks, accQueue, sq)
   1130       (tasks', queue', sq') = go (schedulerSleepQueue scheduler)
   1131                                    (schedulerTasks scheduler)
   1132                                    (schedulerRunnable scheduler)
   1133   pure scheduler
   1134     { schedulerTasks = tasks'
   1135     , schedulerRunnable = queue'
   1136     , schedulerSleepQueue = sq'
   1137     }
   1138 
   1139 nearestSleepTime :: Scheduler -> Maybe UTCTime
   1140 nearestSleepTime = fmap fst . Map.lookupMin . schedulerSleepQueue
   1141 
   1142 hasAsyncWaiters :: Scheduler -> Bool
   1143 hasAsyncWaiters = any isAsync . Map.elems . schedulerTasks
   1144   where
   1145     isAsync (AsyncWaiting _) = True
   1146     isAsync _ = False
   1147 
   1148 resumeCurrentWith :: TaskId -> T -> Machine -> Scheduler -> IO Scheduler
   1149 resumeCurrentWith taskId value machine scheduler =
   1150   let machine' = machine { machineCurrent = pureAction value }
   1151   in pure scheduler
   1152       { schedulerTasks = Map.insert taskId (Runnable machine') (schedulerTasks scheduler)
   1153       , schedulerRunnable = schedulerRunnable scheduler |> taskId
   1154       }
   1155 
   1156 wouldCycle :: TaskId -> TaskId -> Map TaskId TaskStatus -> Bool
   1157 wouldCycle target current tasks =
   1158   case Map.lookup target tasks of
   1159     Just (BlockedOn next _) ->
   1160       next == current || wouldCycle next current tasks
   1161     _ -> False
   1162 
   1163 handleStep :: TaskId -> Step -> Scheduler -> IO Scheduler
   1164 handleStep taskId (Continue machine) scheduler =
   1165   pure scheduler
   1166     { schedulerTasks = Map.insert taskId (Runnable machine) (schedulerTasks scheduler)
   1167     , schedulerRunnable = schedulerRunnable scheduler |> taskId
   1168     }
   1169 
   1170 handleStep taskId (Halt runtime value) scheduler =
   1171   let scheduler' = wakeAwaiters taskId value scheduler
   1172   in pure scheduler'
   1173        { schedulerTasks = Map.delete taskId (schedulerTasks scheduler')
   1174        , schedulerCompleted = Map.insert taskId (value, rtState runtime) (schedulerCompleted scheduler')
   1175        }
   1176 
   1177 handleStep parentId (ForkRequested childAction parentMachine) scheduler =
   1178   let childId = TaskId (schedulerNextTaskId scheduler)
   1179       handle = taskHandle childId
   1180 
   1181       parentMachine' =
   1182         parentMachine { machineCurrent = pureAction handle }
   1183 
   1184       childMachine =
   1185         Machine
   1186           { machineRuntime = machineRuntime parentMachine
   1187           , machineCurrent = childAction
   1188           , machineFrames = []
   1189           }
   1190 
   1191       tasks' =
   1192         Map.insert parentId (Runnable parentMachine') $
   1193         Map.insert childId (Runnable childMachine) $
   1194         schedulerTasks scheduler
   1195 
   1196       queue' =
   1197         schedulerRunnable scheduler |> parentId |> childId
   1198 
   1199   in pure scheduler
   1200       { schedulerNextTaskId = schedulerNextTaskId scheduler + 1
   1201       , schedulerTasks = tasks'
   1202       , schedulerRunnable = queue'
   1203       }
   1204 
   1205 handleStep currentId (AwaitRequested targetId machine) scheduler
   1206   | currentId == targetId =
   1207       resumeCurrentWith currentId selfAwaitResult machine scheduler
   1208 
   1209   | otherwise =
   1210       case Map.lookup targetId (schedulerTasks scheduler) of
   1211         Nothing ->
   1212           case Map.lookup targetId (schedulerCompleted scheduler) of
   1213             Just (value, _) -> resumeCurrentWith currentId value machine scheduler
   1214             Nothing -> resumeCurrentWith currentId invalidAsyncHandleResult machine scheduler
   1215 
   1216         Just (BlockedOn nextId _) ->
   1217           if wouldCycle targetId currentId (schedulerTasks scheduler)
   1218             then resumeCurrentWith currentId (errResult "cyclic await") machine scheduler
   1219             else block
   1220 
   1221         Just _ -> block
   1222   where
   1223     block = pure scheduler
   1224       { schedulerTasks = Map.insert currentId (BlockedOn targetId machine) (schedulerTasks scheduler)
   1225       , schedulerWaiters = Map.alter addWaiter targetId (schedulerWaiters scheduler)
   1226       }
   1227     addWaiter Nothing = Just (Seq.singleton currentId)
   1228     addWaiter (Just sq) = Just (sq |> currentId)
   1229 
   1230 handleStep taskId (YieldRequested machine) scheduler =
   1231   resumeCurrentWith taskId Leaf machine scheduler
   1232 
   1233 handleStep taskId (SleepRequested ms machine) scheduler = do
   1234   now <- getCurrentTime
   1235   let seconds = fromIntegral ms / 1000
   1236       wakeTime = addUTCTime seconds now
   1237       machine' = machine { machineCurrent = pureAction Leaf }
   1238   pure scheduler
   1239     { schedulerTasks = Map.insert taskId (Sleeping wakeTime machine') (schedulerTasks scheduler)
   1240     , schedulerSleepQueue = Map.alter (Just . maybe (Set.singleton taskId) (Set.insert taskId)) wakeTime (schedulerSleepQueue scheduler)
   1241     }
   1242 
   1243 handleStep taskId (AsyncAction ioAction machine) scheduler = do
   1244   _ <- forkIO $ do
   1245     result <- (Right <$> ioAction) `catch` \(e :: SomeException) -> pure (Left (show e))
   1246     atomically $ modifyTVar' (schedulerAsyncCompleted scheduler) (Map.insert taskId $
   1247       case result of
   1248         Right val -> val
   1249         Left msg  -> errResult msg)
   1250   pure scheduler
   1251     { schedulerTasks = Map.insert taskId (AsyncWaiting machine) (schedulerTasks scheduler)
   1252     }
   1253 
   1254 handleNoRunnable :: Scheduler -> IO Scheduler
   1255 handleNoRunnable scheduler =
   1256   case nearestSleepTime scheduler of
   1257     Just wakeTime -> do
   1258       now <- getCurrentTime
   1259       let micros = max 0 (floor (diffUTCTime wakeTime now * 1000000))
   1260       threadDelay micros
   1261       wakeDueSleepers scheduler
   1262 
   1263     Nothing ->
   1264       if hasAsyncWaiters scheduler
   1265         then do
   1266           -- Block efficiently until at least one async operation completes.
   1267           atomically $ do
   1268             m <- readTVar (schedulerAsyncCompleted scheduler)
   1269             if Map.null m then retry else return ()
   1270           pure scheduler
   1271         else
   1272           case Map.lookup (TaskId 0) (schedulerTasks scheduler) of
   1273             Just status ->
   1274               case runtimeOfStatus status of
   1275                 Just runtime ->
   1276                   let scheduler' = wakeAwaiters (TaskId 0) deadlockResult scheduler
   1277                   in pure scheduler'
   1278                        { schedulerTasks = Map.delete (TaskId 0) (schedulerTasks scheduler')
   1279                        , schedulerCompleted = Map.insert (TaskId 0) (deadlockResult, rtState runtime) (schedulerCompleted scheduler')
   1280                        }
   1281                 Nothing -> pure scheduler
   1282             Nothing -> pure scheduler
   1283 
   1284 schedulerStep :: Scheduler -> IO Scheduler
   1285 schedulerStep scheduler = do
   1286   -- Poll completed async operations and resume their tasks.
   1287   completed <- atomically $ do
   1288     m <- readTVar (schedulerAsyncCompleted scheduler)
   1289     writeTVar (schedulerAsyncCompleted scheduler) Map.empty
   1290     return m
   1291   schedulerAfterAsync <- Fold.foldlM
   1292     (\s (tid, val) ->
   1293       case Map.lookup tid (schedulerTasks s) of
   1294         Just (AsyncWaiting machine) -> resumeCurrentWith tid val machine s
   1295         _ -> pure s)
   1296     scheduler
   1297     (Map.toList completed)
   1298 
   1299   scheduler1 <- wakeDueSleepers schedulerAfterAsync
   1300   case Seq.viewl (schedulerRunnable scheduler1) of
   1301     EmptyL ->
   1302       handleNoRunnable scheduler1
   1303 
   1304     taskId :< restQueue ->
   1305       case Map.lookup taskId (schedulerTasks scheduler1) of
   1306         Just (Runnable machine) -> do
   1307           step <- stepMachine (schedulerSockets scheduler1) machine
   1308           handleStep taskId step scheduler1 { schedulerRunnable = restQueue }
   1309 
   1310         _ ->
   1311           pure scheduler1 { schedulerRunnable = restQueue }
   1312 
   1313 runScheduler :: Scheduler -> IO (T, T)
   1314 runScheduler scheduler =
   1315   case Map.lookup (TaskId 0) (schedulerCompleted scheduler) of
   1316     Just (value, finalState) ->
   1317       pure (value, finalState)
   1318 
   1319     _ ->
   1320       schedulerStep scheduler >>= runScheduler
   1321 
   1322 -- ---------------------------------------------------------------------------
   1323 -- Public API
   1324 -- ---------------------------------------------------------------------------
   1325 
   1326 runIOWith :: IOPermissions -> T -> T -> T -> IO (Either String (T, T))
   1327 runIOWith perms env initialState action =
   1328   case checkIOSentinel action of
   1329     Left err -> pure (Left err)
   1330     Right (_, action') -> do
   1331       asyncVar <- newTVarIO Map.empty
   1332       sockVar  <- newTVarIO (SocketRegistry Map.empty 0)
   1333       let initialMachine = Machine
   1334             { machineRuntime = Runtime
   1335                 { rtPerms = perms
   1336                 , rtEnv = env
   1337                 , rtState = initialState
   1338                 }
   1339             , machineCurrent = action'
   1340             , machineFrames = []
   1341             }
   1342       Right <$> runScheduler (initialScheduler asyncVar sockVar initialMachine)
   1343 
   1344 runIOWithEnv :: IOPermissions -> T -> T -> IO (Either String T)
   1345 runIOWithEnv perms env action = do
   1346   result <- runIOWith perms env Leaf action
   1347   pure (fmap fst result)
   1348 
   1349 runIO :: IOPermissions -> T -> IO (Either String T)
   1350 runIO perms action = do
   1351   result <- runIOWith perms Leaf Leaf action
   1352   pure (fmap fst result)