1 import Base 2 import ./DurableError.bend as DurableError 3 import ./DbLockPolicy.bend as Policy 4 5 # Owns the open LOCK file until release. 6 type Lock is Type: 7 Lock{file: File} 8 9 # Builds the stable LOCK path for a database directory. 10 def lock_path(path: String) -> String: 11 path ++ "/LOCK" 12 13 def DbLock.try_lock(file: File) -> IO(File & Result<&1, &1, U32 & String, Unit>): 14 import "./effs/db_lock.c" 15 import "./effs/db_lock.js" 16 17 def DbLock.try_unlock(file: File) -> IO(File & Result<&1, &1, U32 & String, Unit>): 18 import "./effs/db_unlock.c" 19 import "./effs/db_unlock.js" 20 21 # Classifies a failure to open the LOCK file. 22 def open_failed(error: U32 & String, operation: String, path: String) -> IO(Result<&1, &1, DurableError.Error, Lock>): 23 match error: 24 case (code, message): 25 IO.pure(Result<&1, &1, DurableError.Error, Lock>, 26 Fail{Policy.classify_lock_error(code, message, operation, path)}) 27 28 # Closes the file and classifies a failed lock attempt. 29 def lock_failed( 30 file: File, 31 error: U32 & String, 32 operation: String, 33 path: String 34 ) -> IO(Result<&1, &1, DurableError.Error, Lock>): 35 match error: 36 case (code, message): 37 do IO>: 38 _closed : Unit <- File.close(file) 39 return Fail{Policy.classify_lock_error(code, message, operation, path)} 40 41 # Returns an owned lock on success or closes the failed handle. 42 def lock_result( 43 pair: File & Result<&1, &1, U32 & String, Unit>, 44 operation: String, 45 path: String 46 ) -> IO(Result<&1, &1, DurableError.Error, Lock>): 47 match pair: 48 case (file, Fail{error}): 49 lock_failed(file, error, operation, path) 50 case (file, Done{Unit{}}): 51 IO.pure(Result<&1, &1, DurableError.Error, Lock>, Done{Lock{file}}) 52 53 # Attempts the nonblocking OS lock on the open file. 54 def lock_file(file: File, operation: String, path: String) -> IO(Result<&1, &1, DurableError.Error, Lock>): 55 do IO>: 56 pair : File & Result<&1, &1, U32 & String, Unit> <- DbLock.try_lock(file) 57 lock_result(pair, operation, path) 58 59 # Locks the opened file or classifies its open error. 60 def opened( 61 result: Result<&1, &1, U32 & String, File>, 62 operation: String, 63 path: String 64 ) -> IO(Result<&1, &1, DurableError.Error, Lock>): 65 match result: 66 case Fail{error}: 67 open_failed(error, operation, path) 68 case Done{file}: 69 lock_file(file, operation, path) 70 71 # Opens the stable LOCK file and acquires its OS lock. 72 def acquire(+path: String, operation: String) -> IO(Result<&1, &1, DurableError.Error, Lock>): 73 do IO>: 74 result : Result<&1, &1, U32 & String, File> <- File.open(lock_path(path), "a") 75 opened(result, operation, path) 76 77 # Closes the file and preserves the unlock error. 78 def release_failed( 79 file: File, 80 error: U32 & String, 81 operation: String, 82 path: String 83 ) -> IO(Result<&1, &1, DurableError.Error, Unit>): 84 match error: 85 case (code, message): 86 do IO>: 87 _closed : Unit <- File.close(file) 88 return Fail{DurableError.classify_open(DurableError.OpenHostFailure{code, message}, operation, path)} 89 90 # Closes the file after a successful unlock. 91 def release_succeeded(file: File) -> IO(Result<&1, &1, DurableError.Error, Unit>): 92 do IO>: 93 _closed : Unit <- File.close(file) 94 return Done{Unit{}} 95 96 # Closes the file after either unlock result. 97 def released( 98 pair: File & Result<&1, &1, U32 & String, Unit>, 99 operation: String, 100 path: String 101 ) -> IO(Result<&1, &1, DurableError.Error, Unit>): 102 match pair: 103 case (file, Fail{error}): 104 release_failed(file, error, operation, path) 105 case (file, Done{Unit{}}): 106 release_succeeded(file) 107 108 # Unlocks the file and closes it after the result. 109 def unlock_file(file: File, operation: String, path: String) -> IO(Result<&1, &1, DurableError.Error, Unit>): 110 do IO>: 111 pair : File & Result<&1, &1, U32 & String, Unit> <- DbLock.try_unlock(file) 112 released(pair, operation, path) 113 114 # Consumes the lock and releases its file handle. 115 def release(lock: Lock, operation: String, path: String) -> IO(Result<&1, &1, DurableError.Error, Unit>): 116 match lock: 117 case Lock{file}: 118 unlock_file(file, operation, path)