feat: add workspace fs-at seam and retained root lease
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{ "targets": [{ "target_name": "workspace_fs_at", "sources": ["workspace_fs_at.cc"], "cflags_cc": ["-std=c++17"], "defines": ["NAPI_VERSION=8"] }] }
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <node_api.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
struct Handle { uint64_t magic; int fd; bool directory; bool closed; };
|
||||
static const uint64_t MAGIC=0x5448545746534154ULL;
|
||||
static void finalize(napi_env env, void* data, void*) { Handle* h=(Handle*)data; if(h && h->magic==MAGIC && !h->closed){::close(h->fd);h->closed=true;} delete h; }
|
||||
static bool getHandle(napi_env env,napi_value v,Handle** out){ void* p=nullptr; if(napi_get_value_external(env,v,&p)!=napi_ok||!p)return false; auto*h=(Handle*)p; if(h->magic!=MAGIC||h->closed)return false;*out=h;return true; }
|
||||
static napi_value error(napi_env env,const char* syscall,int e){ napi_value msg,err,code,sys; napi_create_string_utf8(env,strerror(e),NAPI_AUTO_LENGTH,&msg); napi_create_error(env,nullptr,msg,&err); napi_create_string_utf8(env,syscall,NAPI_AUTO_LENGTH,&sys); napi_set_named_property(env,err,"syscall",sys); napi_create_string_utf8(env,strerror(e),NAPI_AUTO_LENGTH,&code); napi_set_named_property(env,err,"code",code); napi_value en; napi_create_int32(env,e,&en); napi_set_named_property(env,err,"errno",en); return err; }
|
||||
static napi_value fail(napi_env env,const char*s,int e){ napi_value x=error(env,s,e); napi_throw(env,x); return nullptr; }
|
||||
static bool str(napi_env env,napi_value v,std::string& out){ size_t n; if(napi_get_value_string_utf8(env,v,nullptr,0,&n)!=napi_ok)return false; out.resize(n); napi_get_value_string_utf8(env,v,out.data(),n+1,&n);return true; }
|
||||
static napi_value statObj(napi_env env,const struct stat& st){ napi_value o,n; napi_create_object(env,&o); napi_create_bigint_uint64(env,(uint64_t)st.st_dev,&n); napi_set_named_property(env,o,"device",n); napi_create_bigint_uint64(env,(uint64_t)st.st_ino,&n); napi_set_named_property(env,o,"inode",n); napi_create_uint32(env,(uint32_t)st.st_mode,&n); napi_set_named_property(env,o,"mode",n); napi_create_uint32(env,(uint32_t)st.st_uid,&n); napi_set_named_property(env,o,"uid",n); napi_create_uint32(env,(uint32_t)st.st_gid,&n); napi_set_named_property(env,o,"gid",n); napi_create_bigint_uint64(env,(uint64_t)st.st_nlink,&n); napi_set_named_property(env,o,"nlink",n); return o; }
|
||||
static napi_value result(napi_env env,int fd,bool dir,const struct stat&st){ auto*h=new Handle{MAGIC,fd,dir,false}; napi_value e,o,s; napi_create_external(env,h,finalize,nullptr,&e); napi_create_object(env,&o); napi_set_named_property(env,o,"handle",e); s=statObj(env,st); napi_set_named_property(env,o,"openedStat",s); return o; }
|
||||
static napi_value openatFn(napi_env env,napi_callback_info info){ size_t argc=1; napi_value a[1]; if(napi_get_cb_info(env,info,&argc,a,nullptr,nullptr)!=napi_ok||argc!=1)return fail(env,"openat",EINVAL); napi_value pv,nv,kv,mv; if(napi_get_named_property(env,a[0],"parent",&pv)!=napi_ok||napi_get_named_property(env,a[0],"name",&nv)!=napi_ok||napi_get_named_property(env,a[0],"kind",&kv)!=napi_ok||napi_get_named_property(env,a[0],"createMode",&mv)!=napi_ok)return fail(env,"openat",EINVAL); std::string name,kind; if(!str(env,nv,name)||!str(env,kv,kind))return fail(env,"openat",EINVAL); int32_t mode; if(napi_get_value_int32(env,mv,&mode)!=napi_ok)return fail(env,"openat",EINVAL); bool root=name=="/"; if(!root&&(name.empty()||name.size()>255||name=="."||name==".."||name.find('/')!=std::string::npos||name.find('\0')!=std::string::npos))return fail(env,"openat",EINVAL); Handle*ph=nullptr; if(root){if(kind!="directory"||mode!=0)return fail(env,"openat",EINVAL);} else if(!getHandle(env,pv,&ph)||!ph->directory)return fail(env,"openat",EBADF); int fd; if(root)fd=::open("/",O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); else if(kind=="directory"&&mode==0)fd=::openat(ph->fd,name.c_str(),O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); else if(kind=="regular_lock"&&mode==0600)fd=::openat(ph->fd,name.c_str(),O_RDWR|O_CREAT|O_CLOEXEC|O_NOFOLLOW,0600); else return fail(env,"openat",EINVAL); if(fd<0)return fail(env,"openat",errno); struct stat st; if(::fstat(fd,&st)<0){int e=errno;::close(fd);return fail(env,"fstat",e);} if(kind=="directory"&&!S_ISDIR(st.st_mode)){::close(fd);return fail(env,"openat",ENOTDIR);} if(kind=="regular_lock"&&(!S_ISREG(st.st_mode)||(st.st_mode&0777)!=0600)){::close(fd);return fail(env,"openat",EPERM);} return result(env,fd,kind=="directory",st); }
|
||||
static napi_value mkdiratFn(napi_env env,napi_callback_info info){size_t n=3; napi_value a[3]; napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;int32_t m;if(n!=3||!getHandle(env,a[0],&h)||!h->directory||!str(env,a[1],s)||napi_get_value_int32(env,a[2],&m)!=napi_ok||m!=0700)return fail(env,"mkdirat",EINVAL);if(s.empty()||s.size()>255||s=="."||s==".."||s.find('/')!=std::string::npos)return fail(env,"mkdirat",EINVAL);if(::mkdirat(h->fd,s.c_str(),0700)<0)return fail(env,"mkdirat",errno);return nullptr;}
|
||||
static napi_value fstatatFn(napi_env env,napi_callback_info info){size_t n=2;napi_value a[2];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;if(n!=2||!getHandle(env,a[0],&h)||!h->directory||!str(env,a[1],s)||s.empty()||s.size()>255||s=="."||s==".."||s.find('/')!=std::string::npos)return fail(env,"fstatat",EINVAL);struct stat st;if(::fstatat(h->fd,s.c_str(),&st,AT_SYMLINK_NOFOLLOW)<0)return fail(env,"fstatat",errno);return statObj(env,st);}
|
||||
static napi_value fsyncFn(napi_env env,napi_callback_info info){size_t n=1;napi_value a[1];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;if(n!=1||!getHandle(env,a[0],&h)||!h->directory)return fail(env,"fsync",EBADF);if(::fsync(h->fd)<0)return fail(env,"fsync",errno);return nullptr;}
|
||||
static napi_value closeFn(napi_env env,napi_callback_info info){size_t n=1;napi_value a[1];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;if(n!=1||!getHandle(env,a[0],&h))return fail(env,"close",EBADF);h->closed=true;int rc=::close(h->fd);if(rc<0)return fail(env,"close",errno);return nullptr;}
|
||||
static napi_value fdFn(napi_env env,napi_callback_info info){size_t n=1;napi_value a[1];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;if(n!=1||!getHandle(env,a[0],&h))return fail(env,"fcntl",EBADF);napi_value x;napi_create_int32(env,h->fd,&x);return x;}
|
||||
static napi_value init(napi_env env,napi_value exports){napi_property_descriptor p[]={{"openat",0,openatFn,0,0,0,napi_default,0},{"mkdirat",0,mkdiratFn,0,0,0,napi_default,0},{"fstatat",0,fstatatFn,0,0,0,napi_default,0},{"fsyncDirectory",0,fsyncFn,0,0,0,napi_default,0},{"close",0,closeFn,0,0,0,napi_default,0},{"fdNumberForSynchronousBorrow",0,fdFn,0,0,0,napi_default,0}};napi_define_properties(env,exports,6,p);return exports;} NAPI_MODULE(NODE_GYP_MODULE_NAME,init)
|
||||
Generated
+1212
File diff suppressed because it is too large
Load Diff
+10
-3
@@ -7,7 +7,9 @@
|
||||
"prebuild": "node scripts/clean-dist.mjs",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"test": "vitest run",
|
||||
"start": "node dist/server.js"
|
||||
"start": "node dist/server.js",
|
||||
"build:native": "node scripts/build-workspace-fs-at.mjs",
|
||||
"postinstall": "npm run build:native"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
@@ -18,7 +20,8 @@
|
||||
"yaml": "^2.9.0",
|
||||
"yauzl": "^3.4.0",
|
||||
"yazl": "^3.3.1",
|
||||
"zod": "^4.4.3"
|
||||
"zod": "^4.4.3",
|
||||
"fs-ext": "2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
@@ -26,6 +29,10 @@
|
||||
"@types/yazl": "^3.3.1",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vitest": "^2.1.0"
|
||||
"vitest": "^2.1.0",
|
||||
"node-gyp": "11.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22 <23"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../native/workspace-fs-at");
|
||||
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const result = spawnSync(npm, ["exec", "--", "node-gyp@11.2.0", "rebuild", "--offline"], { cwd: root, stdio: "inherit" });
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
@@ -0,0 +1,7 @@
|
||||
declare const nativeWorkspaceFsAtHandleBrand: unique symbol;
|
||||
declare const nativeWorkspaceFsAtComponentBrand: unique symbol;
|
||||
export interface NativeWorkspaceFsAtHandleV1 { readonly [nativeWorkspaceFsAtHandleBrand]: true; }
|
||||
export type NativeWorkspaceFsAtComponentV1 = string & { readonly [nativeWorkspaceFsAtComponentBrand]: true };
|
||||
export interface NativeWorkspaceFsAtStatV1 { readonly device: bigint; readonly inode: bigint; readonly mode: number; readonly uid: number; readonly gid: number; readonly nlink: bigint; }
|
||||
export interface NativeWorkspaceFsAtOpenResultV1 { readonly handle: NativeWorkspaceFsAtHandleV1; readonly openedStat: NativeWorkspaceFsAtStatV1; }
|
||||
export interface WorkspaceFsAtBindingV1 { openat(input: { readonly parent: NativeWorkspaceFsAtHandleV1|null; readonly name: "/"|NativeWorkspaceFsAtComponentV1; readonly kind: "directory"|"regular_lock"; readonly createMode: 0|0o600 }): NativeWorkspaceFsAtOpenResultV1; mkdirat(parent: NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1,mode:0o700):void; fstatat(parent:NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1):NativeWorkspaceFsAtStatV1; fsyncDirectory(handle:NativeWorkspaceFsAtHandleV1):void; close(handle:NativeWorkspaceFsAtHandleV1):void; fdNumberForSynchronousBorrow(handle:NativeWorkspaceFsAtHandleV1):number; }
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createRequire } from "node:module";
|
||||
import type { WorkspaceFsAtBindingV1, NativeWorkspaceFsAtHandleV1, NativeWorkspaceFsAtStatV1 } from "../native/workspace-fs-at-binding.js";
|
||||
const require = createRequire(import.meta.url);
|
||||
const binding = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node") as WorkspaceFsAtBindingV1;
|
||||
export interface WorkspaceFsAtStatV1 extends NativeWorkspaceFsAtStatV1 {}
|
||||
export type LockFileName = "writer.lock" | "session-readers.lock";
|
||||
export type WorkspaceFlockKindV1 = "shared" | "exclusive";
|
||||
export type WorkspaceFlockWaitV1 = "blocking" | "nonblocking";
|
||||
function component(value:string): string { if (typeof value!=="string" || value.length===0 || value.length>255 || value!==value.trim() || value==='.' || value==='..' || value.includes('/') || value.includes('\0')) throw new Error("invalid path component"); return value; }
|
||||
function normalizeError(error: unknown): Error { if (error instanceof Error) return error; return new Error(String(error)); }
|
||||
class Owned {
|
||||
protected live=true;
|
||||
constructor(protected readonly raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1) {}
|
||||
stat(): WorkspaceFsAtStatV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
|
||||
close(): void { if(!this.live)return; this.live=false; try { binding.close(this.raw); } catch(e){ throw normalizeError(e); } }
|
||||
_raw(): NativeWorkspaceFsAtHandleV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.raw; }
|
||||
}
|
||||
export class OwnedWorkspaceFsAtDirectory extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtDirectory(raw,stat);} }
|
||||
export class OwnedWorkspaceFsAtRegularFile extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtRegularFile(raw,stat);} }
|
||||
function rawDirectory(value:OwnedWorkspaceFsAtDirectory){ return value._raw(); }
|
||||
export class WorkspaceFsAtV1 {
|
||||
openRoot(): OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:null,name:"/",kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
|
||||
openDirectoryAt(parent:OwnedWorkspaceFsAtDirectory, name:string):OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:rawDirectory(parent),name:component(name) as never,kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
|
||||
openOrCreateLockAt(parent:OwnedWorkspaceFsAtDirectory,name:LockFileName,mode:0o600):OwnedWorkspaceFsAtRegularFile { if(name!=="writer.lock"&&name!=="session-readers.lock"||mode!==0o600)throw new Error("invalid lock"); const r=binding.openat({parent:rawDirectory(parent),name:name as never,kind:"regular_lock",createMode:0o600}); return OwnedWorkspaceFsAtRegularFile.from(r.handle,r.openedStat); }
|
||||
mkdirAt(parent:OwnedWorkspaceFsAtDirectory,name:string,mode:0o700):void { binding.mkdirat(rawDirectory(parent),component(name) as never,mode); }
|
||||
statAtNoFollow(parent:OwnedWorkspaceFsAtDirectory,name:string):WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent),component(name) as never); }
|
||||
fsyncDirectory(directory:OwnedWorkspaceFsAtDirectory):void { binding.fsyncDirectory(rawDirectory(directory)); }
|
||||
flockOwnedLock(owned:OwnedWorkspaceFsAtRegularFile,kind:WorkspaceFlockKindV1,wait:WorkspaceFlockWaitV1):void { const fd=binding.fdNumberForSynchronousBorrow(owned._raw()); const fsExt=require("fs-ext") as {flockSync(fd:number,operation:string):void}; const operation=kind==="shared"?(wait==="blocking"?"sh":"shnb"):(wait==="blocking"?"ex":"exnb"); fsExt.flockSync(fd,operation); }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, type OwnedWorkspaceFsAtRegularFile, type WorkspaceFsAtStatV1 } from "./workspace-fs-at.js";
|
||||
export type CanonicalWorkspaceId = string & { readonly __workspaceId: unique symbol };
|
||||
export interface WorkspaceLockRootIdentityV1 { readonly schemaVersion:1; readonly workspaceId:CanonicalWorkspaceId; readonly device:bigint; readonly inode:bigint; }
|
||||
export class CanonicalWorkspaceLockRootInput { private constructor(readonly workspaceId:CanonicalWorkspaceId, readonly owner:symbol){} static make(id:CanonicalWorkspaceId,owner:symbol){return new CanonicalWorkspaceLockRootInput(id,owner);} }
|
||||
export class BorrowedVerifiedWorkspaceLockRootLease { private constructor(readonly identity:WorkspaceLockRootIdentityV1, private readonly check:()=>void){} static make(i:WorkspaceLockRootIdentityV1,c:()=>void){return new BorrowedVerifiedWorkspaceLockRootLease(i,c);} assertLive(){this.check();} }
|
||||
function conflict(message="preprocessing conflict"):Error { const e=new Error(message); e.name="PreprocessingConflictError"; return e; }
|
||||
function exactStat(stat:WorkspaceFsAtStatV1,uid:number):boolean { return (stat.mode&0o170000)===0o040000 && (stat.mode&0o777)===0o700 && stat.uid===uid && stat.nlink===1n; }
|
||||
export class WorkspaceSessionReadersLockLease {
|
||||
private live=true; private constructor(private readonly lock:OwnedWorkspaceFsAtRegularFile, private readonly root:OwnedWorkspaceFsAtDirectory, readonly rootIdentity:WorkspaceLockRootIdentityV1){}
|
||||
static make(lock:OwnedWorkspaceFsAtRegularFile,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new WorkspaceSessionReadersLockLease(lock,root,id);}
|
||||
transfer():WorkspaceSessionReadersLockLease { if(!this.live)throw conflict(); this.live=false; return WorkspaceSessionReadersLockLease.make(this.lock,this.root,this.rootIdentity); }
|
||||
async close():Promise<void>{if(!this.live)return;this.live=false;let failure:unknown;try{this.lock.close();}catch(e){failure=e;}try{this.root.close();}catch(e){failure??=e;}if(failure)throw conflict();}
|
||||
}
|
||||
export class VerifiedWorkspaceLockRootLease {
|
||||
private live=true; private borrowed=0; private constructor(private readonly owner:symbol,private readonly fs:WorkspaceFsAtV1,private readonly root:OwnedWorkspaceFsAtDirectory,readonly identity:WorkspaceLockRootIdentityV1){}
|
||||
static make(owner:symbol,fs:WorkspaceFsAtV1,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new VerifiedWorkspaceLockRootLease(owner,fs,root,id);}
|
||||
private check(){if(!this.live)throw conflict(); const s=this.root.stat();if(s.device!==this.identity.device||s.inode!==this.identity.inode)throw conflict("workspace root identity changed");}
|
||||
async borrow<T>(action:(b:BorrowedVerifiedWorkspaceLockRootLease)=>Promise<T>):Promise<T>{this.check();this.borrowed++;try{return await action(BorrowedVerifiedWorkspaceLockRootLease.make(this.identity,()=>this.check()));}finally{this.borrowed--;}}
|
||||
transfer():VerifiedWorkspaceLockRootLease{this.check();if(this.borrowed)throw conflict("workspace root is borrowed");this.live=false;return VerifiedWorkspaceLockRootLease.make(this.owner,this.fs,this.root,this.identity);}
|
||||
async acquireSessionReadersShared():Promise<WorkspaceSessionReadersLockLease>{this.check();let lock:OwnedWorkspaceFsAtRegularFile|undefined;try{lock=this.fs.openOrCreateLockAt(this.root,"session-readers.lock",0o600);this.fs.flockOwnedLock(lock,"shared","nonblocking");this.check();this.live=false;return WorkspaceSessionReadersLockLease.make(lock,this.root,this.identity);}catch(e){try{lock?.close();}catch{this.live=false;try{this.root.close();}catch{}}throw conflict();}}
|
||||
async close():Promise<void>{if(!this.live)return;if(this.borrowed)await new Promise<void>(resolve=>{const tick=()=>this.borrowed?setTimeout(tick,1):resolve();tick();});this.live=false;try{this.root.close();}catch{throw conflict();}}
|
||||
// package-private operation used by the writer capability; never returns the underlying handle.
|
||||
async _withRoot<T>(action:(root:OwnedWorkspaceFsAtDirectory,fs:WorkspaceFsAtV1)=>Promise<T>):Promise<T>{this.check();return action(this.root,this.fs);}
|
||||
}
|
||||
export class VerifiedWorkspaceLockRootLeaseFactory {
|
||||
private readonly owner=Symbol(); private readonly parent:OwnedWorkspaceFsAtDirectory;
|
||||
constructor(private readonly input:{readonly workspaceFsAt:WorkspaceFsAtV1;readonly installationId:string;readonly sessionsRootFromValidatedInstallationConfig:string;readonly serviceUid:number;readonly provisionedWorkspaceMode:0o700}) { if(!Number.isInteger(input.serviceUid)||input.serviceUid<0)throw new Error("invalid service uid");if(input.provisionedWorkspaceMode!==0o700)throw new Error("invalid workspace mode");if(!input.sessionsRootFromValidatedInstallationConfig.startsWith("/"))throw new Error("sessions root must be absolute");let d=input.workspaceFsAt.openRoot();try{for(const c of input.sessionsRootFromValidatedInstallationConfig.split("/").filter(Boolean)) {const n=input.workspaceFsAt.openDirectoryAt(d,c);d.close();d=n;}this.parent=d;}catch(e){try{d.close();}catch{}throw e;}}
|
||||
canonicalInput(workspaceId:string):CanonicalWorkspaceLockRootInput{if(!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId))throw conflict();return CanonicalWorkspaceLockRootInput.make(workspaceId as CanonicalWorkspaceId,this.owner);}
|
||||
private async open(input:CanonicalWorkspaceLockRootInput):Promise<VerifiedWorkspaceLockRootLease>{if(input.owner!==this.owner)throw conflict();const root=this.input.workspaceFsAt.openDirectoryAt(this.parent,input.workspaceId);if(!exactStat(root.stat(),this.input.serviceUid)){root.close();throw conflict();}return VerifiedWorkspaceLockRootLease.make(this.owner,this.input.workspaceFsAt,root,{schemaVersion:1,workspaceId:input.workspaceId,device:root.stat().device,inode:root.stat().inode});}
|
||||
acquire(input:CanonicalWorkspaceLockRootInput){return this.open(input);}
|
||||
async acquireOrProvision(input:CanonicalWorkspaceLockRootInput){if(input.owner!==this.owner)throw conflict();try{return await this.open(input);}catch{try{this.input.workspaceFsAt.mkdirAt(this.parent,input.workspaceId,0o700);}catch(e){/* EEXIST is the concurrent winner; all other errors are rechecked below. */}this.input.workspaceFsAt.fsyncDirectory(this.parent);return this.open(input);}}
|
||||
}
|
||||
Reference in New Issue
Block a user