From 564df68be04ef24b79c6116bff7047762a02b22c Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Mon, 13 Nov 2023 14:50:54 -0700 Subject: [PATCH] Add new SAH and WFS locking modes Adds new locking modes for sync access handles and writable file streams. Updates "file entry/take a lock" and "file entry/lock/release" to support these new modes. --- index.bs | 86 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index 586b918..c2dbfd5 100644 --- a/index.bs +++ b/index.bs @@ -125,7 +125,8 @@ never be allowed using this API, rather than leaving it entirely up to underlyin systems. A lock type is a [=string=] that may exclusively be "`open`", -"`exclusive`", or "`shared`". +"`exclusive`", "`writable-siloed`", "`sync-access-handle-read-only`", +"`sync-access-handle-read-write-unsafe`". A file entry additionally consists of binary data (a [=byte sequence=]), a @@ -170,8 +171,7 @@ Note: These steps have to be run on the [=file system queue=]. -Note: Locks help prevent concurrent modifications to a file. A {{FileSystemWritableFileStream}} -requires a shared lock, while a {{FileSystemSyncAccessHandle}} requires an exclusive one. +Note: Locks help prevent concurrent modifications to a file that are incompatible. A directory entry additionally consists of a [=/set=] of children, which are themselves [=/file system entries=]. @@ -417,8 +417,24 @@ The isSameEntry(|other|) method steps are ## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle} +enum FileSystemWritableFileStreamMode { + "exclusive", + "siloed", +}; + dictionary FileSystemCreateWritableOptions { boolean keepExistingData = false; + FileSystemWritableFileStreamMode mode = "siloed"; +}; + +enum FileSystemSyncAccessHandleMode { + "readwrite", + "read-only", + "readwrite-unsafe", +}; + +dictionary FileSystemCreateSyncAccessHandleOptions { + FileSystemSyncAccessHandleMode mode = "readwrite"; }; [Exposed=(Window,Worker), SecureContext, Serializable] @@ -426,7 +442,7 @@ interface FileSystemFileHandle : FileSystemHandle { Promise<File> getFile(); Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {}); [Exposed=DedicatedWorker] - Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(); + Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(optional FileSystemCreateSyncAccessHandleOptions options = {}); }; @@ -535,10 +551,12 @@ The getFile() method steps are: the temporary file starts out empty, otherwise the existing file is first copied to this temporary file. - Creating a {{FileSystemWritableFileStream}} [=file entry/take a lock|takes a shared lock=] on the - [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=]. - This prevents the creation of {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}} - for the entry, until the stream is closed. + Creating a {{FileSystemWritableFileStream}} [=file entry/take a lock|takes a lock=] on the + [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=] + and a |lockType| determined by {{FileSystemCreateWritableOptions/mode}}. Until the stream is + closed, both {{FileSystemCreateWritableOptions/mode|modes}} prevent any file operation or the + creation of a file primitive on the [=file entry=], but "`siloed`" will allow the creation of other + {{FileSystemWritableFileStream}} in "`siloed`" {{FileSystemCreateWritableOptions/mode}}.

See WICG/file-system-access issue #67 @@ -572,8 +590,15 @@ The createWritable(|options|) method |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. + 1. Let |lockType| be the empty string. + 1. Let |mode| be |options|["{{FileSystemCreateWritableOptions/mode}}"]. + 1. If |mode| is "`exclusive`": + 1. Set |lockType| to "`exclusive`". + 1. Otherwise: + 1. [=Assert=]: |mode| is "`siloed`". + 1. Set |lockType| to "`writable-siloed`". 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] - with "`shared`" on |entry|. + with |lockType| on |entry|. 1. [=Queue a storage task=] with |global| to run these steps: 1. If |lockResult| is "`failure`", [=/reject=] |result| with a @@ -600,11 +625,12 @@ The createWritable(|options|) method [=file entry=] [=locate an entry|locatable=] by |fileHandle|'s [=FileSystemHandle/locator=]. To ensure the changes are reflected in this file, the handle can be flushed. - Creating a {{FileSystemSyncAccessHandle}} [=file entry/take a lock|takes an exclusive lock=] on the - [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=]. - This prevents the creation of further {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}} - or {{FileSystemWritableFileStream|FileSystemWritableFileStreams}} - for the entry, until the access handle is closed. + Creating a {{FileSystemSyncAccessHandle}} [=file entry/take a lock|takes a lock=] on the + [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=] + and a |lockType| determined by {{FileSystemCreateSyncAccessHandleOptions/mode}}. Until the access handle is + closed, all {{FileSystemCreateSyncAccessHandleOptions/mode|modes}} prevent any file operation or the + creation of a file primitive on the [=file entry=], but "`read-only`" and "`readwrite-unsafe`" will allow the creation of other + {{FileSystemSyncAccessHandle}} in their respective {{FileSystemCreateSyncAccessHandleOptions/mode|modes}}. The returned {{FileSystemSyncAccessHandle}} offers synchronous methods. This allows for higher performance on contexts where asynchronous operations come with high overhead, e.g., WebAssembly. @@ -614,7 +640,7 @@ The createWritable(|options|) method

-The createSyncAccessHandle() method steps are: +The createSyncAccessHandle(|options|) method steps are: 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. @@ -642,15 +668,28 @@ The createSyncAccessHandle() method s |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. + 1. Let |lockType| be the empty string. + 1. Let |writeAccess| be the empty string. + 1. Let |mode| be |options|["{{FileSystemCreateSyncAccessHandleOptions/mode}}"]. + 1. If |mode| is "`readwrite`": + 1. Set |lockType| to "`exclusive`". + 1. Set |writeAccess| to "`writable`". + 1. Otherwise, if |mode| is "`read-only`": + 1. Set |lockType| to "`sync-access-handle-read-only`". + 1. Set |writeAccess| to "`not-writable`". + 1. Otherwise: + 1. [=Assert=]: |mode| is "`readwrite-unsafe`". + 1. Set |lockType| to "`sync-access-handle-read-write-unsafe`". + 1. Set |writeAccess| to "`writable`". 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] - with "`exclusive`" on |entry|. + with |lockType| on |entry|. 1. [=Queue a storage task=] with |global| to run these steps: 1. If |lockResult| is "`failure`", [=/reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` - for |entry| in |realm|. + with |entry| and |writeAccess| in |realm|. 1. [=/Resolve=] |result| with |handle|. 1. Return |result|. @@ -1437,6 +1476,9 @@ A {{FileSystemSyncAccessHandle}} has an associated \[[state]], a string that may exclusively be "`open`" or "`closed`". +A {{FileSystemSyncAccessHandle}} has an associated \[[writeAccess]], +a [=string=] that may exclusively be "`writable`" or "`not-writable`". + A {{FileSystemSyncAccessHandle}} is an object that is capable of reading from/writing to, as well as obtaining and changing the size of, a single file. @@ -1448,11 +1490,13 @@ A {{FileSystemSyncAccessHandle}} has a fil
To create a new `FileSystemSyncAccessHandle` -given a [=file entry=] |file| in a [=/Realm=] |realm|: +given a [=file entry=] |file| and a [=string=] |writeAccess| in a [=/Realm=] |realm|: +1. [=Assert=]: |writeAccess| is "`writable`" or "`not-writable`". 1. Let |handle| be a [=new=] {{FileSystemSyncAccessHandle}} in |realm|. 1. Set |handle|'s [=FileSystemSyncAccessHandle/[[file]]=] to |file|. 1. Set |handle|'s [=FileSystemSyncAccessHandle/[[state]]=] to "`open`". +1. Set |handle|'s [=FileSystemSyncAccessHandle/[[writeAccess]]=] to |writeAccess|. 1. Return |handle|.
@@ -1515,6 +1559,8 @@ The write(|buffer|, {{FileSystemReadW 1. If [=this=]'s [=[[state]]=] is "`closed`", [=throw=] an "{{InvalidStateError}}" {{DOMException}}. +1. If [=this=]'s [=[[writeAccess]]=]' is "`not-writable`", + [=throw=] a "{{NoModificationAllowedError}}" {{DOMException}}. 1. Let |writePosition| be |options|["{{FileSystemReadWriteOptions/at}}"] if |options|["{{FileSystemReadWriteOptions/at}}"] [=map/exists=]; otherwise [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=]. @@ -1575,6 +1621,8 @@ The truncate(|newSize|) method 1. If [=this=]'s [=[[state]]=] is "`closed`", [=throw=] an "{{InvalidStateError}}" {{DOMException}}. +1. If [=this=]'s [=[[writeAccess]]=]' is "`not-writable`", + [=throw=] a "{{NoModificationAllowedError}}" {{DOMException}}. 1. Let |fileContents| be a copy of [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=]. 1. Let |oldSize| be the [=byte sequence/length=] of [=this=]'s @@ -1631,6 +1679,8 @@ The flush() method steps are: 1. If [=this=]'s [=[[state]]=] is "`closed`", [=throw=] an "{{InvalidStateError}}" {{DOMException}}. +1. If [=this=]'s [=[[writeAccess]]=]' is "`not-writable`", + [=throw=] a "{{NoModificationAllowedError}}" {{DOMException}}. 1. Attempt to transfer all cached modifications of the file's content to the file system's underlying storage device.