IODriver.hs (62105B)
1 {-# LANGUAGE ScopedTypeVariables #-} 2 3 module IODriver 4 ( IOPermissions(..) 5 , defaultPerms 6 , unsafePerms 7 , checkIOSentinel 8 , runIO 9 , runIOWithEnv 10 , runIOWith 11 , runIOWithYieldHook 12 , YieldStats(..) 13 , runIOWithYieldStatsHook 14 ) where 15 16 import Runtime (T(..), EvaluatedForm(Decode), formatT, apply, toString, toNumber, ofString, ofNumber, ofBytes, toBytes, ofList) 17 import ContentStore.Arboricx (treeTermDomain, decodeTreeTermShared, encodeTreeTerm) 18 import ContentStore.Object (hashObject, validateObjectHash) 19 import ContentStore.Indexed (indexedTreeDomain, decodeIndexedTree, encodeIndexedTree) 20 import qualified Data.ByteString as BS 21 import System.IO (putStr, getLine) 22 import System.Posix.Files (createLink) 23 import System.Posix.IO (openFd, closeFd, defaultFileFlags, OpenMode(ReadOnly)) 24 import System.Posix.Unistd (fileSynchronise) 25 import qualified System.IO as IO 26 import Control.Exception (try, catch, bracket, bracketOnError, evaluate, IOException, SomeException, 27 SomeAsyncException, fromException, throwIO, mask_, finally) 28 import System.IO.Error (isDoesNotExistError, isPermissionError, isAlreadyExistsError) 29 import Data.List (isPrefixOf, isInfixOf) 30 import System.FilePath (addTrailingPathSeparator, takeDirectory) 31 import System.Directory (canonicalizePath, doesPathExist, listDirectory, createDirectory, renameFile, removeFile, doesDirectoryExist) 32 import Data.Time.Clock.POSIX (getPOSIXTime) 33 import Crypto.Hash (hash, SHA256, Digest) 34 import Data.ByteArray (convert) 35 import Data.ByteString.Base16 (encode) 36 import Data.Text.Encoding (decodeUtf8, decodeUtf8', encodeUtf8) 37 import qualified Data.Text as T 38 import Data.Char (toLower) 39 import qualified Data.Map.Strict as Map 40 import Data.Map.Strict (Map) 41 import qualified Data.Sequence as Seq 42 import Data.Sequence (Seq, (|>), ViewL(..)) 43 import Data.Time.Clock (UTCTime, getCurrentTime, addUTCTime, diffUTCTime) 44 import Control.Concurrent (threadDelay, forkIOWithUnmask, killThread, ThreadId, MVar, newEmptyMVar, putMVar, readMVar) 45 import Control.Concurrent.STM (TVar, newTVarIO, atomically, readTVar, writeTVar, modifyTVar', retry) 46 import qualified Data.Set as Set 47 import Data.Set (Set) 48 import qualified Data.Foldable as Fold 49 import qualified Network.Socket as NS 50 import qualified Network.Socket.ByteString as NSB 51 52 -- --------------------------------------------------------------------------- 53 -- Permissions 54 -- --------------------------------------------------------------------------- 55 56 data IOPermissions = IOPermissions 57 { allowRead :: [FilePath] 58 , allowWrite :: [FilePath] 59 , allowReadAll :: Bool 60 , allowWriteAll :: Bool 61 } 62 deriving (Show) 63 64 defaultPerms :: IOPermissions 65 defaultPerms = IOPermissions [] [] False False 66 67 unsafePerms :: IOPermissions 68 unsafePerms = IOPermissions [] [] True True 69 70 checkIOSentinel :: T -> Either String (Integer, T) 71 checkIOSentinel tree = 72 case tree of 73 Fork sentinel (Fork version action) -> do 74 s <- toString sentinel 75 case s of 76 "tricuIO" -> do 77 v <- toNumber version 78 return (v, action) 79 _ -> Left "sentinel mismatch (expected \"tricuIO\")" 80 _ -> Left "root is not an IO sentinel pair" 81 82 -- --------------------------------------------------------------------------- 83 -- Runtime, Frames, and Machine 84 -- --------------------------------------------------------------------------- 85 86 data Runtime = Runtime 87 { rtPerms :: IOPermissions 88 , rtEnv :: T 89 , rtState :: T 90 } 91 deriving (Show) 92 93 data Frame 94 = BindFrame T 95 | LocalFrame T 96 deriving (Show) 97 98 data Machine = Machine 99 { machineRuntime :: Runtime 100 , machineCurrent :: T 101 , machineFrames :: [Frame] 102 } 103 deriving (Show) 104 105 -- --------------------------------------------------------------------------- 106 -- Result convention 107 -- --------------------------------------------------------------------------- 108 -- Direct-return actions pass the raw value to the continuation: 109 -- pure, bind, putStr, getLine, ask, local, get, put, 110 -- fork, await, yield, sleep 111 -- 112 -- Result-return actions wrap the outcome as an ok/err pair: 113 -- ok val = Fork (Stem Leaf) (Fork val Leaf) -- (t t) val t 114 -- err code = Fork Leaf (Fork code Leaf) -- t code t 115 -- readFile, writeFile 116 -- 117 -- Runtime protocol errors are returned as direct values via errResult. 118 119 okResult :: T -> T 120 okResult val = Fork (Stem Leaf) (Fork val Leaf) 121 122 errResult :: String -> T 123 errResult msg = Fork Leaf (Fork (ofString msg) Leaf) 124 125 pureAction :: T -> T 126 pureAction x = Fork (ofNumber 0) x 127 128 invalidAsyncHandleResult :: T 129 invalidAsyncHandleResult = errResult "invalid task handle" 130 131 invalidSocketHandleResult :: T 132 invalidSocketHandleResult = errResult "invalid socket handle" 133 134 selfAwaitResult :: T 135 selfAwaitResult = errResult "self await" 136 137 deadlockResult :: T 138 deadlockResult = errResult "deadlock" 139 140 invalidSleepResult :: T 141 invalidSleepResult = errResult "invalid sleep" 142 143 ioErrorString :: IOException -> String 144 ioErrorString e 145 | isDoesNotExistError e = "does not exist" 146 | isPermissionError e = "permission denied" 147 | isAlreadyExistsError e = "already exists" 148 | otherwise = "io error" 149 150 -- --------------------------------------------------------------------------- 151 -- Task identity and handles 152 -- --------------------------------------------------------------------------- 153 154 newtype TaskId = TaskId Integer 155 deriving (Eq, Ord, Show) 156 157 taskHandle :: TaskId -> T 158 taskHandle (TaskId n) = 159 Fork (ofString "task") (ofNumber n) 160 161 decodeTaskHandle :: T -> Either String TaskId 162 decodeTaskHandle tree = 163 case tree of 164 Fork tag nTree -> do 165 tagString <- toString tag 166 if tagString == "task" 167 then TaskId <$> toNumber nTree 168 else Left "invalid task handle tag" 169 _ -> 170 Left "invalid task handle" 171 172 -- --------------------------------------------------------------------------- 173 -- Socket identity and handles 174 -- --------------------------------------------------------------------------- 175 176 newtype SockId = SockId Integer 177 deriving (Eq, Ord, Show) 178 179 sockHandle :: SockId -> T 180 sockHandle (SockId n) = 181 Fork (ofString "sock") (ofNumber n) 182 183 decodeSockHandle :: T -> Either String SockId 184 decodeSockHandle tree = 185 case tree of 186 Fork tag nTree -> do 187 tagString <- toString tag 188 if tagString == "sock" 189 then SockId <$> toNumber nTree 190 else Left "invalid socket handle tag" 191 _ -> 192 Left "invalid socket handle" 193 194 getSocketPort :: NS.Socket -> IO (Maybe Integer) 195 getSocketPort sock = do 196 addr <- NS.getSocketName sock 197 case addr of 198 NS.SockAddrInet p _ -> return (Just (fromIntegral p)) 199 NS.SockAddrInet6 p _ _ _ -> return (Just (fromIntegral p)) 200 _ -> return Nothing 201 202 -- --------------------------------------------------------------------------- 203 -- Socket registry 204 -- --------------------------------------------------------------------------- 205 206 data SocketRegistry = SocketRegistry 207 { sockMap :: Map SockId NS.Socket 208 , sockNextId :: Integer 209 } 210 211 -- Call with async exceptions masked from acquisition through registration. 212 registerSocket :: TVar SocketRegistry -> NS.Socket -> IO SockId 213 registerSocket registry sock = atomically $ do 214 SocketRegistry sockets next <- readTVar registry 215 let sid = SockId next 216 writeTVar registry (SocketRegistry (Map.insert sid sock sockets) (next + 1)) 217 pure sid 218 219 -- All OS workers belong to one run. Completed workers unregister immediately; 220 -- cleanup cancels and joins the remaining workers before closing their sockets. 221 type Workers = TVar (Map ThreadId (MVar ())) 222 223 startWorker :: Workers -> IO () -> IO () 224 startWorker workers action = mask_ $ do 225 start <- newEmptyMVar 226 done <- newEmptyMVar 227 tid <- forkIOWithUnmask $ \unmask -> do 228 self <- readMVar start 229 unmask action `finally` do 230 atomically $ modifyTVar' workers (Map.delete self) 231 putMVar done () 232 atomically $ modifyTVar' workers (Map.insert tid done) 233 putMVar start tid 234 235 cleanupRun :: (TVar (Map TaskId T), TVar SocketRegistry, Workers) -> IO () 236 cleanupRun (_, sockets, workers) = do 237 pending <- atomically $ readTVar workers 238 mapM_ killThread (Map.keys pending) 239 mapM_ readMVar (Map.elems pending) 240 owned <- atomically $ do 241 SocketRegistry owned next <- readTVar sockets 242 writeTVar sockets (SocketRegistry Map.empty next) 243 pure owned 244 mapM_ (\sock -> NS.close sock `catch` (\(_ :: IOException) -> pure ())) owned 245 246 -- --------------------------------------------------------------------------- 247 -- Free-monad action AST 248 -- --------------------------------------------------------------------------- 249 250 data Action 251 = APure T 252 | ABind T T 253 | AAbort T 254 | APutStr T 255 | APutBytes T 256 | AGetLine 257 | AReadSourceFile T 258 | AEncodeSourceText T 259 | AReadFile T 260 | AReadIndexedTree T T 261 | AHashIndexedTree T 262 | AWriteIndexedTreeIfAbsent T T T 263 | AReadTreeTerm T T 264 | AHashTreeTerm T 265 | AWriteTreeTermIfAbsent T T T 266 | AWriteFile T T 267 | AWriteBytes T T 268 | AWriteBytesIfAbsent T T 269 | AAtomicWriteBytes T T 270 | AListDirectory T 271 | ARenameFile T T 272 | ACreateDirectory T 273 | ADeleteFile T 274 | AFileExists T 275 | ASha256Hex T 276 | ACurrentTime 277 | AAsk 278 | ALocal T T 279 | AGet 280 | APut T 281 | AFork T 282 | AAwait T 283 | AYield 284 | ASleep T 285 | ASocket 286 | ACloseSocket T 287 | ABindSocket T T T 288 | AListen T T 289 | AAccept T 290 | AConnect T T T 291 | ARecv T T 292 | ASend T T 293 | AGetSocketName T 294 deriving (Show) 295 296 -- --------------------------------------------------------------------------- 297 -- Action tag constants 298 -- --------------------------------------------------------------------------- 299 300 tagPure, tagBind, tagAbort :: Integer 301 tagPure = 0 302 tagBind = 1 303 tagAbort = 2 304 305 tagPutStr, tagPutBytes, tagGetLine :: Integer 306 tagPutStr = 10 307 tagPutBytes = 12 308 tagGetLine = 11 309 310 tagReadFile, tagWriteFile, tagWriteBytes :: Integer 311 tagReadFile = 20 312 tagWriteFile = 21 313 tagWriteBytes = 22 314 315 tagListDirectory, tagRenameFile, tagCreateDirectory, tagDeleteFile, tagFileExists :: Integer 316 tagListDirectory = 23 317 tagRenameFile = 24 318 tagCreateDirectory = 25 319 tagDeleteFile = 26 320 tagFileExists = 27 321 322 tagSha256Hex, tagCurrentTime, tagWriteBytesIfAbsent, tagAtomicWriteBytes :: Integer 323 tagSha256Hex = 28 324 tagCurrentTime = 29 325 tagWriteBytesIfAbsent = 32 326 tagAtomicWriteBytes = 33 327 328 tagReadTreeTerm :: Integer 329 tagReadTreeTerm = 34 330 331 tagHashTreeTerm, tagWriteTreeTermIfAbsent :: Integer 332 tagHashTreeTerm = 35 333 tagWriteTreeTermIfAbsent = 36 334 335 tagReadSourceFile, tagEncodeSourceText :: Integer 336 tagReadSourceFile = 37 337 tagEncodeSourceText = 38 338 -- 37/38 are source UTF-8 operations; 40/41 are state operations. 339 tagReadIndexedTree, tagHashIndexedTree, tagWriteIndexedTreeIfAbsent :: Integer 340 tagReadIndexedTree = 50 341 tagHashIndexedTree = 51 342 tagWriteIndexedTreeIfAbsent = 52 343 344 tagAsk, tagLocal :: Integer 345 tagAsk = 30 346 tagLocal = 31 347 348 tagGet, tagPut :: Integer 349 tagGet = 40 350 tagPut = 41 351 352 tagFork, tagAwait, tagYield, tagSleep :: Integer 353 tagFork = 60 354 tagAwait = 61 355 tagYield = 62 356 tagSleep = 63 357 358 tagSocket, tagCloseSocket, tagBindSocket, tagListen, tagAccept :: Integer 359 tagSocket = 70 360 tagCloseSocket = 71 361 tagBindSocket = 72 362 tagListen = 73 363 tagAccept = 74 364 365 tagConnect, tagRecv, tagSend, tagGetSocketName :: Integer 366 tagConnect = 75 367 tagRecv = 76 368 tagSend = 77 369 tagGetSocketName = 78 370 371 data Step 372 = Halt Runtime T 373 | ExecutionFailed String 374 | Continue Machine 375 | ForkRequested T Machine 376 | AwaitRequested TaskId Machine 377 | YieldRequested Machine 378 | SleepRequested Integer Machine 379 | AsyncAction (IO T) Machine 380 381 instance Show Step where 382 show (Halt _ v) = "Halt _ (" ++ show v ++ ")" 383 show (ExecutionFailed message) = "ExecutionFailed " ++ show message 384 show (Continue m) = "Continue (" ++ show m ++ ")" 385 show (ForkRequested t m) = "ForkRequested (" ++ show t ++ ") (" ++ show m ++ ")" 386 show (AwaitRequested tid m) = "AwaitRequested " ++ show tid ++ " (" ++ show m ++ ")" 387 show (YieldRequested m) = "YieldRequested (" ++ show m ++ ")" 388 show (SleepRequested n m) = "SleepRequested " ++ show n ++ " (" ++ show m ++ ")" 389 show (AsyncAction _ m) = "AsyncAction <io> (" ++ show m ++ ")" 390 391 decodeAction :: T -> Either String Action 392 decodeAction tree = 393 case tree of 394 Fork tag payload -> 395 case toNumber tag of 396 Right n | n == tagPure -> 397 Right (APure payload) 398 399 Right n | n == tagAbort -> 400 Right (AAbort payload) 401 402 Right n | n == tagBind -> 403 case payload of 404 Fork left k -> Right (ABind left k) 405 _ -> Left "Invalid Bind: expected pair action continuation" 406 407 Right n | n == tagPutStr -> 408 Right (APutStr payload) 409 410 Right n | n == tagPutBytes -> 411 Right (APutBytes payload) 412 413 Right n | n == tagGetLine -> 414 Right AGetLine 415 416 Right n | n == tagReadSourceFile -> 417 Right (AReadSourceFile payload) 418 419 Right n | n == tagEncodeSourceText -> 420 Right (AEncodeSourceText payload) 421 422 Right n | n == tagReadFile -> 423 Right (AReadFile payload) 424 425 Right n | n == tagReadTreeTerm -> 426 case payload of 427 Fork path expected -> Right (AReadTreeTerm path expected) 428 _ -> Left "Invalid ReadTreeTerm: expected pair path digest" 429 430 Right n | n == tagHashTreeTerm -> 431 Right (AHashTreeTerm payload) 432 433 Right n | n == tagWriteTreeTermIfAbsent -> 434 case payload of 435 Fork path (Fork expected value) -> Right (AWriteTreeTermIfAbsent path expected value) 436 _ -> Left "Invalid WriteTreeTermIfAbsent: expected path digest tree" 437 438 Right n | n == tagReadIndexedTree -> 439 case payload of 440 Fork path expected -> Right (AReadIndexedTree path expected) 441 _ -> Left "Invalid ReadIndexedTree: expected pair path digest" 442 443 Right n | n == tagHashIndexedTree -> 444 Right (AHashIndexedTree payload) 445 446 Right n | n == tagWriteIndexedTreeIfAbsent -> 447 case payload of 448 Fork path (Fork expected value) -> Right (AWriteIndexedTreeIfAbsent path expected value) 449 _ -> Left "Invalid WriteIndexedTreeIfAbsent: expected path digest tree" 450 451 Right n | n == tagWriteFile -> 452 case payload of 453 Fork path contents -> Right (AWriteFile path contents) 454 _ -> Left "Invalid WriteFile: expected pair path contents" 455 456 Right n | n == tagWriteBytes -> 457 case payload of 458 Fork path contents -> Right (AWriteBytes path contents) 459 _ -> Left "Invalid WriteBytes: expected pair path contents" 460 461 Right n | n == tagWriteBytesIfAbsent -> 462 case payload of 463 Fork path contents -> Right (AWriteBytesIfAbsent path contents) 464 _ -> Left "Invalid WriteBytesIfAbsent: expected pair path contents" 465 466 Right n | n == tagAtomicWriteBytes -> 467 case payload of 468 Fork path contents -> Right (AAtomicWriteBytes path contents) 469 _ -> Left "Invalid AtomicWriteBytes: expected pair path contents" 470 471 Right n | n == tagListDirectory -> 472 Right (AListDirectory payload) 473 474 Right n | n == tagRenameFile -> 475 case payload of 476 Fork old new -> Right (ARenameFile old new) 477 _ -> Left "Invalid RenameFile: expected pair oldPath newPath" 478 479 Right n | n == tagCreateDirectory -> 480 Right (ACreateDirectory payload) 481 482 Right n | n == tagDeleteFile -> 483 Right (ADeleteFile payload) 484 485 Right n | n == tagFileExists -> 486 Right (AFileExists payload) 487 488 Right n | n == tagSha256Hex -> 489 Right (ASha256Hex payload) 490 491 Right n | n == tagCurrentTime -> 492 Right ACurrentTime 493 494 Right n | n == tagAsk -> 495 Right AAsk 496 497 Right n | n == tagLocal -> 498 case payload of 499 Fork f action -> Right (ALocal f action) 500 _ -> Left "Invalid Local: expected pair function action" 501 502 Right n | n == tagGet -> 503 Right AGet 504 505 Right n | n == tagPut -> 506 Right (APut payload) 507 508 Right n | n == tagFork -> 509 Right (AFork payload) 510 511 Right n | n == tagAwait -> 512 Right (AAwait payload) 513 514 Right n | n == tagYield -> 515 Right AYield 516 517 Right n | n == tagSleep -> 518 Right (ASleep payload) 519 520 Right n | n == tagSocket -> 521 Right ASocket 522 523 Right n | n == tagCloseSocket -> 524 Right (ACloseSocket payload) 525 526 Right n | n == tagBindSocket -> 527 case payload of 528 Fork sock (Fork addr port) -> Right (ABindSocket sock addr port) 529 _ -> Left "Invalid BindSocket: expected pair sock (pair addr port)" 530 531 Right n | n == tagListen -> 532 case payload of 533 Fork sock backlog -> Right (AListen sock backlog) 534 _ -> Left "Invalid Listen: expected pair sock backlog" 535 536 Right n | n == tagAccept -> 537 Right (AAccept payload) 538 539 Right n | n == tagConnect -> 540 case payload of 541 Fork sock (Fork addr port) -> Right (AConnect sock addr port) 542 _ -> Left "Invalid Connect: expected pair sock (pair addr port)" 543 544 Right n | n == tagRecv -> 545 case payload of 546 Fork sock maxBytes -> Right (ARecv sock maxBytes) 547 _ -> Left "Invalid Recv: expected pair sock maxBytes" 548 549 Right n | n == tagSend -> 550 case payload of 551 Fork sock bytes -> Right (ASend sock bytes) 552 _ -> Left "Invalid Send: expected pair sock bytes" 553 554 Right n | n == tagGetSocketName -> 555 Right (AGetSocketName payload) 556 557 Right n -> 558 Left $ "Unknown IO action tag: " ++ show n 559 560 Left err -> 561 Left $ "Invalid action tag: " ++ err 562 563 _ -> 564 Left $ "Invalid action tree: expected pair tag payload, got " ++ show tree 565 566 -- --------------------------------------------------------------------------- 567 -- Small-step IO machine 568 -- --------------------------------------------------------------------------- 569 570 finishValue :: Machine -> T -> IO Step 571 finishValue machine value = 572 case machineFrames machine of 573 [] -> 574 pure (Halt (machineRuntime machine) value) 575 576 BindFrame k : rest -> 577 pure (Continue machine 578 { machineCurrent = apply k value 579 , machineFrames = rest 580 }) 581 582 LocalFrame oldEnv : rest -> 583 let runtime' = (machineRuntime machine) { rtEnv = oldEnv } 584 in pure (Continue machine 585 { machineRuntime = runtime' 586 , machineCurrent = pureAction value 587 , machineFrames = rest 588 }) 589 590 stepMachine :: TVar SocketRegistry -> Machine -> IO Step 591 stepMachine sockVar machine = 592 case decodeAction (machineCurrent machine) of 593 Right action -> dispatch action 594 Left _ -> finishValue machine (errResult "invalid action") 595 where 596 dispatch action = case action of 597 APure val -> 598 finishValue machine val 599 600 AAbort diagnostic -> 601 pure (ExecutionFailed 602 ("execution aborted: " ++ either (const $ formatT Decode diagnostic) id (toString diagnostic))) 603 604 ABind left k -> 605 pure (Continue machine 606 { machineCurrent = left 607 , machineFrames = BindFrame k : machineFrames machine 608 }) 609 610 APutStr str -> 611 case decodeString str "PutStr" of 612 Right s -> 613 pure (AsyncAction (putStr s >> pure Leaf) machine) 614 Left _ -> 615 finishValue machine (errResult "invalid string") 616 617 APutBytes bs -> 618 case decodeBytes bs "PutBytes" of 619 Right b -> 620 pure (AsyncAction (BS.putStr b >> pure Leaf) machine) 621 Left _ -> 622 finishValue machine (errResult "invalid bytes") 623 624 AGetLine -> 625 pure (AsyncAction (ofString <$> getLine) machine) 626 627 AReadSourceFile path -> dispatchFileRead tryReadSourceFile path 628 629 AEncodeSourceText text -> 630 case decodeString text "EncodeSourceText" of 631 Right contents | text == ofString contents -> finishValue machine 632 (okResult (ofBytes (encodeUtf8 (T.pack contents)))) 633 _ -> finishValue machine (errResult "invalid source scalar list") 634 635 AReadFile path -> dispatchFileRead tryReadFile path 636 637 AReadTreeTerm path expected -> 638 dispatchTreeRead tryReadTreeTerm path expected 639 640 AHashTreeTerm tree -> 641 pure (AsyncAction (do 642 digest <- evaluate (hashObject treeTermDomain (encodeTreeTerm tree)) 643 return (okResult (ofString (T.unpack digest)))) machine) 644 645 AWriteTreeTermIfAbsent path expected tree -> 646 dispatchTreeWrite tryWriteTreeTerm path expected tree 647 648 AReadIndexedTree path expected -> 649 dispatchTreeRead tryReadIndexedTree path expected 650 651 AHashIndexedTree tree -> 652 pure (AsyncAction (do 653 payload <- encodeIndexedTree tree 654 digest <- evaluate (hashObject indexedTreeDomain payload) 655 return (okResult (ofString (T.unpack digest)))) machine) 656 657 AWriteIndexedTreeIfAbsent path expected tree -> 658 dispatchTreeWrite tryWriteIndexedTree path expected tree 659 660 AWriteFile path contents -> 661 case decodeString path "WriteFile" of 662 Right p -> 663 case decodeString contents "WriteFile" of 664 Right c -> do 665 mDeny <- checkWritePerm p 666 case mDeny of 667 Just denied -> finishValue machine denied 668 Nothing -> pure (AsyncAction (tryWriteFile p c) machine) 669 Left _ -> finishValue machine (errResult "invalid string") 670 Left _ -> finishValue machine (errResult "invalid string") 671 672 AWriteBytes path contents -> 673 case decodeString path "WriteBytes" of 674 Right p -> 675 case decodeBytes contents "WriteBytes" of 676 Right c -> do 677 mDeny <- checkWritePerm p 678 case mDeny of 679 Just denied -> finishValue machine denied 680 Nothing -> pure (AsyncAction (tryWriteFileBytes p c) machine) 681 Left _ -> finishValue machine (errResult "invalid bytes") 682 Left _ -> finishValue machine (errResult "invalid string") 683 684 AWriteBytesIfAbsent path contents -> 685 dispatchAtomicBytes path contents True 686 687 AAtomicWriteBytes path contents -> 688 dispatchAtomicBytes path contents False 689 690 AListDirectory pathTree -> 691 case decodeString pathTree "ListDirectory" of 692 Right p -> do 693 mDeny <- checkReadPerm p 694 case mDeny of 695 Just denied -> finishValue machine denied 696 Nothing -> pure (AsyncAction (tryListDirectory p) machine) 697 Left _ -> finishValue machine (errResult "invalid string") 698 699 ARenameFile oldTree newTree -> 700 case decodeString oldTree "RenameFile" of 701 Right old -> 702 case decodeString newTree "RenameFile" of 703 Right new -> do 704 mDenyOld <- checkWritePerm old 705 mDenyNew <- checkWritePerm new 706 case (mDenyOld, mDenyNew) of 707 (Just denied, _) -> finishValue machine denied 708 (_, Just denied) -> finishValue machine denied 709 (Nothing, Nothing) -> pure (AsyncAction (tryRenameFile old new) machine) 710 Left _ -> finishValue machine (errResult "invalid string") 711 Left _ -> finishValue machine (errResult "invalid string") 712 713 ACreateDirectory pathTree -> 714 case decodeString pathTree "CreateDirectory" of 715 Right p -> do 716 mDeny <- checkWritePerm p 717 case mDeny of 718 Just denied -> finishValue machine denied 719 Nothing -> pure (AsyncAction (tryCreateDirectory p) machine) 720 Left _ -> finishValue machine (errResult "invalid string") 721 722 ADeleteFile pathTree -> 723 case decodeString pathTree "DeleteFile" of 724 Right p -> do 725 mDeny <- checkWritePerm p 726 case mDeny of 727 Just denied -> finishValue machine denied 728 Nothing -> pure (AsyncAction (tryDeleteFile p) machine) 729 Left _ -> finishValue machine (errResult "invalid string") 730 731 AFileExists pathTree -> 732 case decodeString pathTree "FileExists" of 733 Right p -> do 734 mDeny <- checkReadPerm p 735 case mDeny of 736 Just denied -> finishValue machine denied 737 Nothing -> pure (AsyncAction (tryFileExists p) machine) 738 Left _ -> finishValue machine (errResult "invalid string") 739 740 ASha256Hex bytesTree -> 741 case decodeBytes bytesTree "Sha256Hex" of 742 Right bs -> pure (AsyncAction (pure $ trySha256Hex bs) machine) 743 Left _ -> finishValue machine (errResult "invalid bytes") 744 745 ACurrentTime -> 746 pure (AsyncAction (tryCurrentTime) machine) 747 748 AAsk -> 749 finishValue machine (rtEnv (machineRuntime machine)) 750 751 ALocal f action' -> 752 let runtime = machineRuntime machine 753 oldEnv = rtEnv runtime 754 newEnv = apply f oldEnv 755 runtime' = runtime { rtEnv = newEnv } 756 in pure (Continue machine 757 { machineRuntime = runtime' 758 , machineCurrent = action' 759 , machineFrames = LocalFrame oldEnv : machineFrames machine 760 }) 761 762 AGet -> 763 finishValue machine (rtState (machineRuntime machine)) 764 765 APut newState -> 766 let runtime' = (machineRuntime machine) { rtState = newState } 767 in finishValue (machine { machineRuntime = runtime' }) Leaf 768 769 AFork childAction -> 770 pure (ForkRequested childAction machine) 771 772 AAwait handleTree -> 773 case decodeTaskHandle handleTree of 774 Right taskId -> 775 pure (AwaitRequested taskId machine) 776 Left _ -> 777 finishValue machine invalidAsyncHandleResult 778 779 AYield -> 780 pure (YieldRequested machine) 781 782 ASleep msTree -> 783 case toNumber msTree of 784 Right ms | ms >= 0 -> 785 pure (SleepRequested ms machine) 786 _ -> 787 finishValue machine invalidSleepResult 788 789 ASocket -> mask_ $ do 790 -- Acquisition through registry insertion is one ownership transfer. 791 result <- try $ bracketOnError 792 (NS.socket NS.AF_INET NS.Stream NS.defaultProtocol) NS.close $ \sock -> do 793 NS.setSocketOption sock NS.ReuseAddr 1 794 registerSocket sockVar sock 795 case result of 796 Left (e :: IOException) -> finishValue machine (errResult ("io error: " ++ show e)) 797 Right sid -> finishValue machine (okResult (sockHandle sid)) 798 799 ACloseSocket sockTree -> mask_ $ 800 case decodeSockHandle sockTree of 801 Left _ -> finishValue machine invalidSocketHandleResult 802 Right sid -> do 803 mSock <- atomically $ do 804 SocketRegistry m next <- readTVar sockVar 805 case Map.lookup sid m of 806 Nothing -> return Nothing 807 Just sock -> do 808 writeTVar sockVar (SocketRegistry (Map.delete sid m) next) 809 return (Just sock) 810 case mSock of 811 Nothing -> finishValue machine invalidSocketHandleResult 812 Just sock -> do 813 NS.close sock 814 finishValue machine (okResult Leaf) 815 816 ABindSocket sockTree addrTree portTree -> 817 case decodeSockHandle sockTree of 818 Left _ -> finishValue machine invalidSocketHandleResult 819 Right sid -> 820 case decodeString addrTree "BindSocket" of 821 Left _ -> finishValue machine (errResult "invalid address") 822 Right addrStr -> 823 case toNumber portTree of 824 Left _ -> finishValue machine (errResult "invalid port") 825 Right port -> do 826 mSock <- atomically $ do 827 SocketRegistry m _ <- readTVar sockVar 828 return (Map.lookup sid m) 829 case mSock of 830 Nothing -> finishValue machine invalidSocketHandleResult 831 Just sock -> do 832 result <- try (do 833 addrInfo <- NS.getAddrInfo (Just $ NS.defaultHints { NS.addrSocketType = NS.Stream }) 834 (Just addrStr) 835 (Just (show port)) 836 let serverAddr = head addrInfo 837 NS.bind sock (NS.addrAddress serverAddr) 838 ) :: IO (Either IOException ()) 839 case result of 840 Left e -> 841 finishValue machine (errResult ("io error: " ++ show e)) 842 Right () -> 843 finishValue machine (okResult Leaf) 844 845 AListen sockTree backlogTree -> 846 case decodeSockHandle sockTree of 847 Left _ -> finishValue machine invalidSocketHandleResult 848 Right sid -> 849 case toNumber backlogTree of 850 Left _ -> finishValue machine (errResult "invalid backlog") 851 Right backlog -> do 852 mSock <- atomically $ do 853 SocketRegistry m _ <- readTVar sockVar 854 return (Map.lookup sid m) 855 case mSock of 856 Nothing -> finishValue machine invalidSocketHandleResult 857 Just sock -> do 858 result <- try (NS.listen sock (fromIntegral backlog)) :: IO (Either IOException ()) 859 case result of 860 Left e -> 861 finishValue machine (errResult ("io error: " ++ show e)) 862 Right () -> 863 finishValue machine (okResult Leaf) 864 865 AAccept listenTree -> 866 case decodeSockHandle listenTree of 867 Left _ -> finishValue machine invalidSocketHandleResult 868 Right listenSid -> 869 pure (AsyncAction (mask_ $ do 870 mListenSock <- atomically $ do 871 SocketRegistry m _ <- readTVar sockVar 872 return (Map.lookup listenSid m) 873 case mListenSock of 874 Nothing -> return (errResult "invalid socket handle") 875 Just listenSock -> do 876 result <- try (NS.accept listenSock) :: IO (Either IOException (NS.Socket, NS.SockAddr)) 877 case result of 878 Left e -> 879 return (errResult ("io error: " ++ show e)) 880 Right (clientSock, addr) -> do 881 clientSid <- registerSocket sockVar clientSock 882 let addrStr = case addr of 883 NS.SockAddrInet p h -> 884 let (a,b,c,d) = NS.hostAddressToTuple h 885 in show a ++ "." ++ show b ++ "." ++ show c ++ "." ++ show d ++ ":" ++ show p 886 _ -> show addr 887 return (okResult (Fork (sockHandle clientSid) (ofString addrStr))) 888 ) machine) 889 890 AConnect sockTree addrTree portTree -> 891 case decodeSockHandle sockTree of 892 Left _ -> finishValue machine invalidSocketHandleResult 893 Right sid -> 894 case decodeString addrTree "Connect" of 895 Left _ -> finishValue machine (errResult "invalid address") 896 Right addrStr -> 897 case toNumber portTree of 898 Left _ -> finishValue machine (errResult "invalid port") 899 Right port -> do 900 mSock <- atomically $ do 901 SocketRegistry m _ <- readTVar sockVar 902 return (Map.lookup sid m) 903 case mSock of 904 Nothing -> finishValue machine invalidSocketHandleResult 905 Just sock -> 906 pure (AsyncAction (do 907 result <- try (do 908 addrInfo <- NS.getAddrInfo (Just $ NS.defaultHints { NS.addrSocketType = NS.Stream }) 909 (Just addrStr) 910 (Just (show port)) 911 let serverAddr = head addrInfo 912 NS.connect sock (NS.addrAddress serverAddr) 913 ) :: IO (Either IOException ()) 914 case result of 915 Left e -> 916 return (errResult ("io error: " ++ show e)) 917 Right () -> 918 return (okResult Leaf) 919 ) machine) 920 921 ARecv sockTree maxBytesTree -> 922 case decodeSockHandle sockTree of 923 Left _ -> finishValue machine invalidSocketHandleResult 924 Right sid -> 925 case toNumber maxBytesTree of 926 Left _ -> finishValue machine (errResult "invalid maxBytes") 927 Right maxBytes -> do 928 mSock <- atomically $ do 929 SocketRegistry m _ <- readTVar sockVar 930 return (Map.lookup sid m) 931 case mSock of 932 Nothing -> finishValue machine invalidSocketHandleResult 933 Just sock -> 934 pure (AsyncAction (do 935 result <- try (NSB.recv sock (fromIntegral maxBytes)) :: IO (Either IOException BS.ByteString) 936 case result of 937 Left e -> 938 return (errResult ("io error: " ++ show e)) 939 Right bs -> 940 if BS.null bs 941 then return (errResult "connection closed") 942 else return (okResult (ofBytes bs)) 943 ) machine) 944 945 AGetSocketName sockTree -> 946 case decodeSockHandle sockTree of 947 Left _ -> finishValue machine invalidSocketHandleResult 948 Right sid -> do 949 mSock <- atomically $ do 950 SocketRegistry m _ <- readTVar sockVar 951 return (Map.lookup sid m) 952 case mSock of 953 Nothing -> finishValue machine invalidSocketHandleResult 954 Just sock -> do 955 mPort <- getSocketPort sock 956 case mPort of 957 Just port -> finishValue machine (okResult (ofNumber port)) 958 Nothing -> finishValue machine (errResult "io error: could not get socket name") 959 960 ASend sockTree bytesTree -> 961 case decodeSockHandle sockTree of 962 Left _ -> finishValue machine invalidSocketHandleResult 963 Right sid -> 964 case decodeBytes bytesTree "Send" of 965 Left _ -> finishValue machine (errResult "invalid bytes") 966 Right bs -> do 967 mSock <- atomically $ do 968 SocketRegistry m _ <- readTVar sockVar 969 return (Map.lookup sid m) 970 case mSock of 971 Nothing -> finishValue machine invalidSocketHandleResult 972 Just sock -> 973 pure (AsyncAction (do 974 result <- try (NSB.send sock bs) :: IO (Either IOException Int) 975 case result of 976 Left e -> 977 return (errResult ("io error: " ++ show e)) 978 Right sent -> 979 return (okResult (ofNumber (fromIntegral sent))) 980 ) machine) 981 982 dispatchFileRead reader path = 983 case toString path of 984 Left _ -> finishValue machine (errResult "invalid string") 985 Right p -> permittedRead reader p 986 987 permittedRead reader path = do 988 denied <- checkReadPerm path 989 case denied of 990 Just result -> finishValue machine result 991 Nothing -> pure (AsyncAction (reader path) machine) 992 993 -- Keep validation before permissions, and permissions before encoding or OS 994 -- work. In particular, denied tree publication must not force the tree. 995 withTreeIdentity path expected next = 996 case (toString path, toString expected) of 997 (Right p, Right digest) -> case validateObjectHash (T.pack digest) of 998 Left _ -> finishValue machine (errResult "invalid CAS digest") 999 Right () -> next p (T.pack digest) 1000 _ -> finishValue machine (errResult "invalid string") 1001 1002 dispatchTreeRead reader path expected = withTreeIdentity path expected $ \p digest -> 1003 permittedRead (\file -> reader file digest) p 1004 1005 dispatchTreeWrite writer path expected tree = withTreeIdentity path expected $ \p digest -> do 1006 -- Verifying a pre-existing/racing winner requires read permission too. 1007 writeDenied <- checkWritePerm p 1008 readDenied <- checkReadPerm p 1009 case (writeDenied, readDenied) of 1010 (Just result, _) -> finishValue machine result 1011 (_, Just result) -> finishValue machine result 1012 _ -> pure (AsyncAction (writer p digest tree) machine) 1013 1014 dispatchAtomicBytes pathTree contentsTree exclusive = 1015 case decodeString pathTree "AtomicWriteBytes" of 1016 Right p -> 1017 case decodeBytes contentsTree "AtomicWriteBytes" of 1018 Right bs -> do 1019 mDeny <- checkWritePerm p 1020 case mDeny of 1021 Just denied -> finishValue machine denied 1022 Nothing -> pure (AsyncAction 1023 (tryAtomicBytes p bs exclusive) machine) 1024 Left _ -> finishValue machine (errResult "invalid bytes") 1025 Left _ -> finishValue machine (errResult "invalid string") 1026 1027 -- Resolve existing symlinked ancestors even when the final name is new. 1028 -- Canonicalization failures deny access; they must never fall back to a 1029 -- syntactic prefix. This is a path policy, not a race-free filesystem sandbox. 1030 checkReadPerm = checkPathPerm allowReadAll allowRead 1031 checkWritePerm = checkPathPerm allowWriteAll allowWrite 1032 1033 checkPathPerm unrestricted roots path 1034 | unrestricted perms = pure Nothing 1035 | otherwise = do 1036 checked <- try $ do 1037 canonical <- canonicalizePath path 1038 prefixes <- mapM canonicalizePath (filter (not . null) (roots perms)) 1039 pure (any (isPathPrefixOf canonical) prefixes) 1040 pure $ case checked of 1041 Right True -> Nothing 1042 Right False -> Just policyErrResult 1043 Left (_ :: IOException) -> Just policyErrResult 1044 where perms = rtPerms (machineRuntime machine) 1045 1046 policyErrResult = errResult "permission denied" 1047 1048 isPathPrefixOf :: FilePath -> FilePath -> Bool 1049 isPathPrefixOf path prefix = 1050 let prefix' = addTrailingPathSeparator prefix 1051 in path == prefix || prefix' `isPrefixOf` path 1052 1053 -- Source-only boundary: preserve the exact octets alongside decoded scalars. 1054 -- No BOM stripping, replacement decoding, newline folding or normalization. 1055 tryReadSourceFile path = do 1056 result <- try (BS.readFile path) :: IO (Either IOException BS.ByteString) 1057 return $ case result of 1058 Left e -> errResult (ioErrorString e) 1059 Right bytes -> case decodeUtf8' bytes of 1060 Left problem -> errResult (path ++ ": invalid UTF-8 source: " ++ show problem) 1061 Right text 1062 | T.isPrefixOf (T.singleton '\xfeff') text -> 1063 errResult (path ++ ": UTF-8 source BOM is not permitted") 1064 | otherwise -> okResult (Fork (ofBytes bytes) (ofString (T.unpack text))) 1065 1066 tryReadFile path = do 1067 result <- try (BS.readFile path) :: IO (Either IOException BS.ByteString) 1068 case result of 1069 Right content -> return $ okResult (ofBytes content) 1070 Left e -> return $ errResult (ioErrorString e) 1071 1072 -- Fixed canonical domain, no source evaluation and no byte-list roundtrip. 1073 -- Verify before decoding so malformed bytes cannot bypass integrity checks. 1074 tryReadTreeTerm = tryReadTreeObject treeTermDomain decodeTreeTermShared "invalid canonical tree-term" 1075 tryReadIndexedTree = tryReadTreeObject indexedTreeDomain decodeIndexedTree "invalid indexed tree" 1076 1077 tryReadTreeObject domain decode invalid path expected = do 1078 result <- try (BS.readFile path) :: IO (Either IOException BS.ByteString) 1079 case result of 1080 Left e -> return $ errResult (ioErrorString e) 1081 Right payload 1082 | hashObject domain payload /= expected -> 1083 return $ errResult "CAS digest mismatch" 1084 | otherwise -> case decode payload of 1085 Left _ -> return $ errResult invalid 1086 Right tree -> return $ okResult tree 1087 1088 tryWriteTreeTerm path expected tree = 1089 tryWriteTreeObject treeTermDomain path expected (encodeTreeTerm tree) 1090 tryWriteIndexedTree path expected tree = do 1091 payload <- encodeIndexedTree tree 1092 tryWriteTreeObject indexedTreeDomain path expected payload 1093 1094 tryWriteTreeObject domain path expected payload = do 1095 if hashObject domain payload /= expected 1096 then return $ errResult "CAS digest mismatch" 1097 else do 1098 published <- tryAtomicBytes path payload True 1099 case published of 1100 Fork (Stem Leaf) (Fork _ Leaf) -> do 1101 -- Preserve byte-for-byte winner checking, not merely hash checking. 1102 winner <- try (BS.readFile path) :: IO (Either IOException BS.ByteString) 1103 case winner of 1104 Left e -> return $ errResult (ioErrorString e) 1105 Right bytes 1106 | bytes == payload -> return published 1107 | otherwise -> return $ errResult "CAS object exists with different bytes" 1108 _ -> return published 1109 1110 tryWriteFile path contents = tryWriteUnit (IO.writeFile path contents) 1111 tryWriteFileBytes path contents = tryWriteUnit (BS.writeFile path contents) 1112 1113 tryWriteUnit action = do 1114 result <- try action :: IO (Either IOException ()) 1115 pure $ either (errResult . ioErrorString) (const (okResult Leaf)) result 1116 1117 -- Sync file contents before exposing the name and its parent directory 1118 -- after publication. A losing writer also syncs the winning object so a 1119 -- following lock/alias cannot outlive the object on a power loss. 1120 syncPath path = bracket 1121 (openFd path ReadOnly defaultFileFlags) 1122 closeFd fileSynchronise 1123 1124 syncParent path = syncPath (takeDirectory path) 1125 1126 tryAtomicBytes path contents exclusive = do 1127 let tmpDir = takeDirectory path 1128 cleanup tmp h = do 1129 IO.hClose h `catch` (\(_ :: IOException) -> return ()) 1130 removeFile tmp `catch` (\(_ :: IOException) -> return ()) 1131 result <- try $ bracket 1132 (IO.openBinaryTempFile tmpDir ".tricu-write-") 1133 (\(tmp, h) -> cleanup tmp h) 1134 (\(tmp, h) -> do 1135 BS.hPut h contents 1136 IO.hClose h 1137 syncPath tmp 1138 if exclusive 1139 then do 1140 linked <- try (createLink tmp path) :: IO (Either IOException ()) 1141 case linked of 1142 Right () -> syncParent path >> return True 1143 Left e | isAlreadyExistsError e -> do 1144 syncPath path 1145 syncParent path 1146 return False 1147 | otherwise -> ioError e 1148 else renameFile tmp path >> syncParent path >> return True) 1149 case result of 1150 Right newlyWritten -> return $ okResult 1151 (if newlyWritten then Stem Leaf else Leaf) 1152 Left (e :: IOException) -> return $ errResult (ioErrorString e) 1153 1154 tryListDirectory path = do 1155 exists <- doesPathExist path 1156 if not exists 1157 then return $ errResult "does not exist" 1158 else do 1159 isDir <- doesDirectoryExist path 1160 if not isDir 1161 then return $ errResult "not a directory" 1162 else do 1163 result <- try (listDirectory path) :: IO (Either IOException [FilePath]) 1164 case result of 1165 Right entries -> 1166 let filtered = filter (`notElem` [".", ".."]) entries 1167 in return $ okResult (ofList (map ofString filtered)) 1168 Left e -> return $ errResult (ioErrorString e) 1169 1170 tryRenameFile old new = do 1171 oldExists <- doesPathExist old 1172 if not oldExists 1173 then return $ errResult "does not exist" 1174 else do 1175 result <- try (renameFile old new) :: IO (Either IOException ()) 1176 case result of 1177 Right () -> return $ okResult Leaf 1178 Left e 1179 | isDoesNotExistError e -> return $ errResult "does not exist" 1180 | isPermissionError e -> return $ errResult "permission denied" 1181 | "cross-device" `isInfixOf` map toLower (show e) || "exdev" `isInfixOf` map toLower (show e) -> 1182 return $ errResult "cross-device rename" 1183 | otherwise -> return $ errResult (ioErrorString e) 1184 1185 tryCreateDirectory path = do 1186 exists <- doesPathExist path 1187 if exists 1188 then do 1189 isDir <- doesDirectoryExist path 1190 if isDir 1191 then durableDirectoryResult path 1192 else return $ errResult "already exists" 1193 else do 1194 let parent = takeDirectory path 1195 parentExists <- doesPathExist parent 1196 if parentExists 1197 then do 1198 parentIsDir <- doesDirectoryExist parent 1199 if parentIsDir 1200 then do 1201 result <- try (createDirectory path) :: IO (Either IOException ()) 1202 case result of 1203 Right () -> durableDirectoryResult path 1204 Left e 1205 | isDoesNotExistError e -> return $ errResult "does not exist" 1206 | isPermissionError e -> return $ errResult "permission denied" 1207 | isAlreadyExistsError e -> existingDirectoryResult path 1208 | otherwise -> return $ errResult (ioErrorString e) 1209 else return $ errResult "not a directory" 1210 else do 1211 result <- try (createDirectory path) :: IO (Either IOException ()) 1212 case result of 1213 Right () -> durableDirectoryResult path 1214 Left e 1215 | isDoesNotExistError e -> return $ errResult "does not exist" 1216 | isPermissionError e -> return $ errResult "permission denied" 1217 | isAlreadyExistsError e -> existingDirectoryResult path 1218 | otherwise -> return $ errResult (ioErrorString e) 1219 1220 durableDirectoryResult path = do 1221 result <- try (syncPath path >> syncParent path) :: IO (Either IOException ()) 1222 return $ either (errResult . ioErrorString) (const (okResult Leaf)) result 1223 1224 existingDirectoryResult path = do 1225 isDir <- doesDirectoryExist path 1226 if isDir then durableDirectoryResult path 1227 else return $ errResult "already exists" 1228 1229 tryDeleteFile path = do 1230 exists <- doesPathExist path 1231 if not exists 1232 then return $ okResult Leaf 1233 else do 1234 isDir <- doesDirectoryExist path 1235 if isDir 1236 then return $ errResult "is a directory" 1237 else do 1238 result <- try (removeFile path) :: IO (Either IOException ()) 1239 case result of 1240 Right () -> return $ okResult Leaf 1241 Left e 1242 | isDoesNotExistError e -> return $ okResult Leaf 1243 | isPermissionError e -> return $ errResult "permission denied" 1244 | otherwise -> return $ errResult (ioErrorString e) 1245 1246 tryFileExists path = do 1247 result <- try (doesPathExist path) :: IO (Either IOException Bool) 1248 case result of 1249 Right exists -> return $ okResult (if exists then Stem Leaf else Leaf) 1250 Left e 1251 | isPermissionError e -> return $ errResult "permission denied" 1252 | otherwise -> return $ errResult (ioErrorString e) 1253 1254 trySha256Hex bs = 1255 let digest = hash bs :: Digest SHA256 1256 hexBs = encode (convert digest) 1257 hexStr = T.unpack (decodeUtf8 hexBs) 1258 in okResult (ofString hexStr) 1259 1260 tryCurrentTime = do 1261 now <- getPOSIXTime 1262 return $ okResult (ofNumber (floor now)) 1263 1264 decodeString t ctx = 1265 case toString t of 1266 Right s -> Right s 1267 Left _ -> Left $ "Invalid " ++ ctx ++ " string" 1268 1269 decodeBytes t ctx = 1270 case toBytes t of 1271 Right b -> Right b 1272 Left _ -> Left $ "Invalid " ++ ctx ++ " bytes" 1273 1274 -- --------------------------------------------------------------------------- 1275 -- Scheduler 1276 -- --------------------------------------------------------------------------- 1277 1278 data TaskStatus 1279 = Runnable Machine 1280 | BlockedOn TaskId Machine 1281 | Sleeping UTCTime Machine 1282 | AsyncWaiting Machine 1283 deriving (Show) 1284 1285 data Scheduler = Scheduler 1286 { schedulerNextTaskId :: Integer 1287 , schedulerRunnable :: Seq TaskId 1288 , schedulerTasks :: Map TaskId TaskStatus 1289 , schedulerWaiters :: Map TaskId (Seq TaskId) 1290 , schedulerSleepQueue :: Map UTCTime (Set TaskId) 1291 , schedulerAsyncCompleted :: TVar (Map TaskId T) 1292 , schedulerCompleted :: Map TaskId (T, T) 1293 , schedulerSockets :: TVar SocketRegistry 1294 , schedulerWorkers :: Workers 1295 , schedulerFailure :: Maybe String 1296 , schedulerYieldHook :: YieldStats -> IO () 1297 } 1298 1299 instance Show Scheduler where 1300 show s = "Scheduler { schedulerNextTaskId = " ++ show (schedulerNextTaskId s) 1301 ++ ", schedulerRunnable = " ++ show (schedulerRunnable s) 1302 ++ ", schedulerTasks = " ++ show (schedulerTasks s) 1303 ++ ", schedulerWaiters = " ++ show (schedulerWaiters s) 1304 ++ ", schedulerSleepQueue = " ++ show (schedulerSleepQueue s) 1305 ++ ", schedulerAsyncCompleted = <tvar>" 1306 ++ ", schedulerCompleted = " ++ show (schedulerCompleted s) 1307 ++ ", schedulerSockets = <tvar>" 1308 ++ ", schedulerFailure = " ++ show (schedulerFailure s) 1309 ++ " }" 1310 1311 -- Counts describe the scheduler at a yield without exposing retained trees. 1312 data YieldStats = YieldStats 1313 { yieldBindFrames :: !Int 1314 , yieldLocalFrames :: !Int 1315 , yieldTasks :: !Int 1316 } deriving (Eq, Show) 1317 1318 initialScheduler :: (YieldStats -> IO ()) -> TVar (Map TaskId T) -> TVar SocketRegistry -> Workers -> Machine -> Scheduler 1319 initialScheduler yieldHook asyncVar sockVar workers mainMachine = 1320 Scheduler 1321 { schedulerNextTaskId = 1 1322 , schedulerRunnable = Seq.singleton (TaskId 0) 1323 , schedulerTasks = Map.singleton (TaskId 0) (Runnable mainMachine) 1324 , schedulerWaiters = Map.empty 1325 , schedulerSleepQueue = Map.empty 1326 , schedulerAsyncCompleted = asyncVar 1327 , schedulerCompleted = Map.empty 1328 , schedulerSockets = sockVar 1329 , schedulerWorkers = workers 1330 , schedulerFailure = Nothing 1331 , schedulerYieldHook = yieldHook 1332 } 1333 1334 runtimeOfStatus :: TaskStatus -> Maybe Runtime 1335 runtimeOfStatus (Runnable machine) = Just (machineRuntime machine) 1336 runtimeOfStatus (BlockedOn _ machine) = Just (machineRuntime machine) 1337 runtimeOfStatus (Sleeping _ machine) = Just (machineRuntime machine) 1338 runtimeOfStatus (AsyncWaiting machine) = Just (machineRuntime machine) 1339 1340 wakeAwaiters :: TaskId -> T -> Scheduler -> Scheduler 1341 wakeAwaiters targetId value scheduler = 1342 case Map.lookup targetId (schedulerWaiters scheduler) of 1343 Nothing -> scheduler 1344 Just waiters -> 1345 let (tasks', queue') = Fold.foldl' (wakeOne targetId value) 1346 (schedulerTasks scheduler, schedulerRunnable scheduler) 1347 waiters 1348 in scheduler 1349 { schedulerTasks = tasks' 1350 , schedulerRunnable = queue' 1351 , schedulerWaiters = Map.delete targetId (schedulerWaiters scheduler) 1352 } 1353 where 1354 wakeOne _ _ (tasks, queue) waiterId = 1355 case Map.lookup waiterId tasks of 1356 Just (BlockedOn _ machine) -> 1357 let machine' = machine { machineCurrent = pureAction value } 1358 in (Map.insert waiterId (Runnable machine') tasks, queue |> waiterId) 1359 _ -> (tasks, queue) 1360 1361 wakeDueSleepers :: Scheduler -> IO Scheduler 1362 wakeDueSleepers scheduler = do 1363 now <- getCurrentTime 1364 let go sq accTasks accQueue = 1365 case Map.lookupMin sq of 1366 Nothing -> (accTasks, accQueue, sq) 1367 Just (t, taskSet) 1368 | t <= now -> 1369 let tasks' = Fold.foldl' (\m tid -> 1370 case Map.lookup tid m of 1371 Just (Sleeping _ machine) -> Map.insert tid (Runnable machine) m 1372 _ -> m 1373 ) accTasks (Set.toList taskSet) 1374 queue' = Fold.foldl' (|>) accQueue (Set.toList taskSet) 1375 in go (Map.deleteMin sq) tasks' queue' 1376 | otherwise -> (accTasks, accQueue, sq) 1377 (tasks', queue', sq') = go (schedulerSleepQueue scheduler) 1378 (schedulerTasks scheduler) 1379 (schedulerRunnable scheduler) 1380 pure scheduler 1381 { schedulerTasks = tasks' 1382 , schedulerRunnable = queue' 1383 , schedulerSleepQueue = sq' 1384 } 1385 1386 nearestSleepTime :: Scheduler -> Maybe UTCTime 1387 nearestSleepTime = fmap fst . Map.lookupMin . schedulerSleepQueue 1388 1389 hasAsyncWaiters :: Scheduler -> Bool 1390 hasAsyncWaiters = any isAsync . Map.elems . schedulerTasks 1391 where 1392 isAsync (AsyncWaiting _) = True 1393 isAsync _ = False 1394 1395 resumeCurrentWith :: TaskId -> T -> Machine -> Scheduler -> IO Scheduler 1396 resumeCurrentWith taskId value machine scheduler = 1397 let machine' = machine { machineCurrent = pureAction value } 1398 in pure scheduler 1399 { schedulerTasks = Map.insert taskId (Runnable machine') (schedulerTasks scheduler) 1400 , schedulerRunnable = schedulerRunnable scheduler |> taskId 1401 } 1402 1403 wouldCycle :: TaskId -> TaskId -> Map TaskId TaskStatus -> Bool 1404 wouldCycle target current tasks = 1405 case Map.lookup target tasks of 1406 Just (BlockedOn next _) -> 1407 next == current || wouldCycle next current tasks 1408 _ -> False 1409 1410 handleStep :: TaskId -> Step -> Scheduler -> IO Scheduler 1411 handleStep taskId (Continue machine) scheduler = 1412 pure scheduler 1413 { schedulerTasks = Map.insert taskId (Runnable machine) (schedulerTasks scheduler) 1414 , schedulerRunnable = schedulerRunnable scheduler |> taskId 1415 } 1416 1417 handleStep taskId (Halt runtime value) scheduler = 1418 let scheduler' = wakeAwaiters taskId value scheduler 1419 in pure scheduler' 1420 { schedulerTasks = Map.delete taskId (schedulerTasks scheduler') 1421 , schedulerCompleted = Map.insert taskId (value, rtState runtime) (schedulerCompleted scheduler') 1422 } 1423 1424 handleStep parentId (ForkRequested childAction parentMachine) scheduler = 1425 let childId = TaskId (schedulerNextTaskId scheduler) 1426 handle = taskHandle childId 1427 1428 parentMachine' = 1429 parentMachine { machineCurrent = pureAction handle } 1430 1431 childMachine = 1432 Machine 1433 { machineRuntime = machineRuntime parentMachine 1434 , machineCurrent = childAction 1435 , machineFrames = [] 1436 } 1437 1438 tasks' = 1439 Map.insert parentId (Runnable parentMachine') $ 1440 Map.insert childId (Runnable childMachine) $ 1441 schedulerTasks scheduler 1442 1443 queue' = 1444 schedulerRunnable scheduler |> parentId |> childId 1445 1446 in pure scheduler 1447 { schedulerNextTaskId = schedulerNextTaskId scheduler + 1 1448 , schedulerTasks = tasks' 1449 , schedulerRunnable = queue' 1450 } 1451 1452 handleStep currentId (AwaitRequested targetId machine) scheduler 1453 | currentId == targetId = 1454 resumeCurrentWith currentId selfAwaitResult machine scheduler 1455 1456 | otherwise = 1457 case Map.lookup targetId (schedulerTasks scheduler) of 1458 Nothing -> 1459 case Map.lookup targetId (schedulerCompleted scheduler) of 1460 Just (value, _) -> resumeCurrentWith currentId value machine scheduler 1461 Nothing -> resumeCurrentWith currentId invalidAsyncHandleResult machine scheduler 1462 1463 Just (BlockedOn nextId _) -> 1464 if wouldCycle targetId currentId (schedulerTasks scheduler) 1465 then resumeCurrentWith currentId (errResult "cyclic await") machine scheduler 1466 else block 1467 1468 Just _ -> block 1469 where 1470 block = pure scheduler 1471 { schedulerTasks = Map.insert currentId (BlockedOn targetId machine) (schedulerTasks scheduler) 1472 , schedulerWaiters = Map.alter addWaiter targetId (schedulerWaiters scheduler) 1473 } 1474 addWaiter Nothing = Just (Seq.singleton currentId) 1475 addWaiter (Just sq) = Just (sq |> currentId) 1476 1477 handleStep taskId (YieldRequested machine) scheduler = do 1478 let binds = length [() | BindFrame _ <- machineFrames machine] 1479 locals = length [() | LocalFrame _ <- machineFrames machine] 1480 schedulerYieldHook scheduler (YieldStats binds locals (Map.size (schedulerTasks scheduler))) 1481 resumeCurrentWith taskId Leaf machine scheduler 1482 1483 handleStep taskId (SleepRequested ms machine) scheduler = do 1484 now <- getCurrentTime 1485 let seconds = fromIntegral ms / 1000 1486 wakeTime = addUTCTime seconds now 1487 machine' = machine { machineCurrent = pureAction Leaf } 1488 pure scheduler 1489 { schedulerTasks = Map.insert taskId (Sleeping wakeTime machine') (schedulerTasks scheduler) 1490 , schedulerSleepQueue = Map.alter (Just . maybe (Set.singleton taskId) (Set.insert taskId)) wakeTime (schedulerSleepQueue scheduler) 1491 } 1492 1493 handleStep _ (ExecutionFailed message) scheduler = 1494 pure scheduler { schedulerFailure = Just message } 1495 1496 handleStep taskId (AsyncAction ioAction machine) scheduler = do 1497 startWorker (schedulerWorkers scheduler) $ do 1498 result <- (Right <$> ioAction) `catch` \(e :: SomeException) -> 1499 case fromException e :: Maybe SomeAsyncException of 1500 Just _ -> throwIO e 1501 Nothing -> pure (Left (show e)) 1502 atomically $ modifyTVar' (schedulerAsyncCompleted scheduler) (Map.insert taskId $ 1503 case result of 1504 Right val -> val 1505 Left msg -> errResult msg) 1506 pure scheduler 1507 { schedulerTasks = Map.insert taskId (AsyncWaiting machine) (schedulerTasks scheduler) 1508 } 1509 1510 handleNoRunnable :: Scheduler -> IO Scheduler 1511 handleNoRunnable scheduler = 1512 case nearestSleepTime scheduler of 1513 Just wakeTime -> do 1514 now <- getCurrentTime 1515 let micros = max 0 (floor (diffUTCTime wakeTime now * 1000000)) 1516 threadDelay micros 1517 wakeDueSleepers scheduler 1518 1519 Nothing -> 1520 if hasAsyncWaiters scheduler 1521 then do 1522 -- Block efficiently until at least one async operation completes. 1523 atomically $ do 1524 m <- readTVar (schedulerAsyncCompleted scheduler) 1525 if Map.null m then retry else return () 1526 pure scheduler 1527 else 1528 case Map.lookup (TaskId 0) (schedulerTasks scheduler) of 1529 Just status -> 1530 case runtimeOfStatus status of 1531 Just runtime -> 1532 let scheduler' = wakeAwaiters (TaskId 0) deadlockResult scheduler 1533 in pure scheduler' 1534 { schedulerTasks = Map.delete (TaskId 0) (schedulerTasks scheduler') 1535 , schedulerCompleted = Map.insert (TaskId 0) (deadlockResult, rtState runtime) (schedulerCompleted scheduler') 1536 } 1537 Nothing -> pure scheduler 1538 Nothing -> pure scheduler 1539 1540 schedulerStep :: Scheduler -> IO Scheduler 1541 schedulerStep scheduler = do 1542 -- Poll completed async operations and resume their tasks. 1543 completed <- atomically $ do 1544 m <- readTVar (schedulerAsyncCompleted scheduler) 1545 writeTVar (schedulerAsyncCompleted scheduler) Map.empty 1546 return m 1547 schedulerAfterAsync <- Fold.foldlM 1548 (\s (tid, val) -> 1549 case Map.lookup tid (schedulerTasks s) of 1550 Just (AsyncWaiting machine) -> resumeCurrentWith tid val machine s 1551 _ -> pure s) 1552 scheduler 1553 (Map.toList completed) 1554 1555 scheduler1 <- wakeDueSleepers schedulerAfterAsync 1556 case Seq.viewl (schedulerRunnable scheduler1) of 1557 EmptyL -> 1558 handleNoRunnable scheduler1 1559 1560 taskId :< restQueue -> 1561 case Map.lookup taskId (schedulerTasks scheduler1) of 1562 Just (Runnable machine) -> do 1563 step <- stepMachine (schedulerSockets scheduler1) machine 1564 handleStep taskId step scheduler1 { schedulerRunnable = restQueue } 1565 1566 _ -> 1567 pure scheduler1 { schedulerRunnable = restQueue } 1568 1569 runScheduler :: Scheduler -> IO (Either String (T, T)) 1570 runScheduler scheduler = 1571 case schedulerFailure scheduler of 1572 Just message -> pure (Left message) 1573 Nothing -> case Map.lookup (TaskId 0) (schedulerCompleted scheduler) of 1574 Just (value, finalState) -> pure (Right (value, finalState)) 1575 Nothing -> schedulerStep scheduler >>= runScheduler 1576 1577 -- --------------------------------------------------------------------------- 1578 -- Public API 1579 -- --------------------------------------------------------------------------- 1580 1581 runIOWith :: IOPermissions -> T -> T -> T -> IO (Either String (T, T)) 1582 runIOWith = runIOWithYieldHook (pure ()) 1583 1584 -- Profiling observes a yield while its pending continuations remain live. 1585 runIOWithYieldHook :: IO () -> IOPermissions -> T -> T -> T -> IO (Either String (T, T)) 1586 runIOWithYieldHook yieldHook = runIOWithYieldStatsHook (\_ -> yieldHook) 1587 1588 runIOWithYieldStatsHook :: (YieldStats -> IO ()) -> IOPermissions -> T -> T -> T -> IO (Either String (T, T)) 1589 runIOWithYieldStatsHook yieldHook perms env initialState action = 1590 case checkIOSentinel action of 1591 Left err -> pure (Left err) 1592 Right (_, action') -> bracket acquire cleanupRun $ \(asyncVar, sockVar, workers) -> do 1593 let initialMachine = Machine 1594 { machineRuntime = Runtime 1595 { rtPerms = perms 1596 , rtEnv = env 1597 , rtState = initialState 1598 } 1599 , machineCurrent = action' 1600 , machineFrames = [] 1601 } 1602 runScheduler (initialScheduler yieldHook asyncVar sockVar workers initialMachine) 1603 1604 where 1605 acquire = do 1606 asyncVar <- newTVarIO Map.empty 1607 sockVar <- newTVarIO (SocketRegistry Map.empty 0) 1608 workers <- newTVarIO Map.empty 1609 pure (asyncVar, sockVar, workers) 1610 1611 runIOWithEnv :: IOPermissions -> T -> T -> IO (Either String T) 1612 runIOWithEnv perms env action = do 1613 result <- runIOWith perms env Leaf action 1614 pure (fmap fst result) 1615 1616 runIO :: IOPermissions -> T -> IO (Either String T) 1617 runIO perms action = do 1618 result <- runIOWith perms Leaf Leaf action 1619 pure (fmap fst result)