From e4e3c3cae0f58a68fd38d7eefcbdd96a124eb4e9 Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Mon, 13 Nov 2023 13:57:11 -0700 Subject: [PATCH 1/4] Make "take a lock" a member of "file entry" --- index.bs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 881fb19..43da40f 100644 --- a/index.bs +++ b/index.bs @@ -128,14 +128,14 @@ A file entry additionally consists of binary data (a [=byte sequence=]), a modification timestamp (a number representing the number of milliseconds since the Unix Epoch), a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") -and a shared lock count (a number representing the number shared locks that are taken at a given point in time). +and a shared lock count (a number representing the number of shared locks that are taken at a given point in time). A user agent has an associated file system queue which is the result of [=starting a new parallel queue=]. This queue is to be used for all file system operations.
-To take a [=file entry/lock=] with a |value| of +To take a lock with a |value| of "`exclusive`" or "`shared`" on a given [=file entry=] |file|: 1. Let |lock| be the |file|'s [=file entry/lock=]. @@ -538,7 +538,7 @@ 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/lock/take|takes a shared lock=] on the + 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. @@ -575,7 +575,7 @@ The createWritable(|options|) method |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] + 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] with "`shared`" on |entry|. 1. [=Queue a storage task=] with |global| to run these steps: @@ -603,7 +603,7 @@ 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/lock/take|takes an exclusive lock=] on the + 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}} @@ -645,7 +645,7 @@ The createSyncAccessHandle() method s |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] + 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] with "`exclusive`" on |entry|. 1. [=Queue a storage task=] with |global| to run these steps: From f56615cbf06ecb7c4d692232ef1023a6a396de16 Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Tue, 19 Dec 2023 15:29:53 -0700 Subject: [PATCH 2/4] Simplify take lock and release lock. --- index.bs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/index.bs b/index.bs index 43da40f..6ea488a 100644 --- a/index.bs +++ b/index.bs @@ -128,7 +128,7 @@ A file entry additionally consists of binary data (a [=byte sequence=]), a modification timestamp (a number representing the number of milliseconds since the Unix Epoch), a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") -and a shared lock count (a number representing the number of shared locks that are taken at a given point in time). +and a lock count (a number representing the number of locks that are taken at a given point in time). A user agent has an associated file system queue which is the result of [=starting a new parallel queue=]. This queue is to be used for all @@ -139,20 +139,18 @@ To take a lock with a |value "`exclusive`" or "`shared`" on a given [=file entry=] |file|: 1. Let |lock| be the |file|'s [=file entry/lock=]. -1. Let |count| be the |file|'s [=file entry/shared lock count=]. +1. Let |count| be the |file|'s [=file entry/lock count=]. +1. If |lock| is not "`open`": + 1. If |value| is "`exclusive`" or |lock| is "`taken-exclusive`": + 1. Return "`failure`". 1. If |value| is "`exclusive`": - 1. If |lock| is "`open`": - 1. Set lock to "`taken-exclusive`". - 1. Return "`success`". -1. If |value| is "`shared`": - 1. If |lock| is "`open`": - 1. Set |lock| to "`taken-shared`". - 1. Set |count| to 1. - 1. Return "`success`". - 1. Otherwise, if |lock| is "`taken-shared`": - 1. Increase |count| by 1. - 1. Return "`success`". -1. Return "`failure`". + 1. [=Assert=]: |lock| is "`open`". + 1. [=Assert=]: |count| is 0. + 1. Set |lock| to "`taken-exclusive`". +1. Otherwise: + 1. Set |lock| to "`taken-shared`". +1. Increase |count| by 1. +1. Return "`success`". Note: These steps have to be run on the [=file system queue=]. @@ -160,14 +158,14 @@ Note: These steps have to be run on the [=file system queue=].
To release a [=file entry/lock=] on a given -[=file entry=] |file|: +[=/file entry=] |file|: 1. Let |lock| be the |file|'s associated [=file entry/lock=]. -1. Let |count| be the |file|'s [=file entry/shared lock count=]. -1. If |lock| is "`taken-shared`": - 1. Decrease |count| by 1. - 1. If |count| is 0, set |lock| to "`open`". -1. Otherwise, set |lock| to "`open`". +1. [=Assert=]: |lock| is not "`open`". +1. Let |count| be the |file|'s [=file entry/lock count=]. +1. [=Assert=]: |count| is greater than 0. +1. Decrease |count| by 1. +1. If |count| is 0, set |lock| to "`open`". Note: These steps have to be run on the [=file system queue=]. From 41b97ba142ed0e8f0731e33264fea4f30d85bd51 Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Tue, 19 Dec 2023 16:17:52 -0700 Subject: [PATCH 3/4] Create lock type --- index.bs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index 6ea488a..586b918 100644 --- a/index.bs +++ b/index.bs @@ -124,10 +124,13 @@ Issue: We should consider having further normative restrictions on file names th never be allowed using this API, rather than leaving it entirely up to underlying file systems. +A lock type is a [=string=] that may exclusively be "`open`", +"`exclusive`", or "`shared`". + A file entry additionally consists of binary data (a [=byte sequence=]), a modification timestamp (a number representing the number of milliseconds since the Unix Epoch), -a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") +a lock (a [=lock type=]), and a lock count (a number representing the number of locks that are taken at a given point in time). A user agent has an associated file system queue which is the @@ -135,20 +138,16 @@ result of [=starting a new parallel queue=]. This queue is to be used for all file system operations.
-To take a lock with a |value| of -"`exclusive`" or "`shared`" on a given [=file entry=] |file|: +To take a lock with a |lockType| (a [=lock type=]) +on a given [=file entry=] |file|: +1. [=Assert=]: |lockType| is not "`open`". 1. Let |lock| be the |file|'s [=file entry/lock=]. 1. Let |count| be the |file|'s [=file entry/lock count=]. 1. If |lock| is not "`open`": - 1. If |value| is "`exclusive`" or |lock| is "`taken-exclusive`": + 1. If |lockType| is "`exclusive`" or |lock| is not equal to |lockType|: 1. Return "`failure`". -1. If |value| is "`exclusive`": - 1. [=Assert=]: |lock| is "`open`". - 1. [=Assert=]: |count| is 0. - 1. Set |lock| to "`taken-exclusive`". -1. Otherwise: - 1. Set |lock| to "`taken-shared`". +1. Set |lock| to |lockType|. 1. Increase |count| by 1. 1. Return "`success`". From 46d0186706159700c677f577751fa91c42dbc9ce Mon Sep 17 00:00:00 2001 From: Nathan Memmott Date: Tue, 19 Dec 2023 15:51:29 -0700 Subject: [PATCH 4/4] Define file locking in inactive pages Instead of immediately failing, "take a lock" will attempt to evict the pages that hold a lock if they are all inactive. --- index.bs | 142 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 46 deletions(-) diff --git a/index.bs b/index.bs index 586b918..8e81aec 100644 --- a/index.bs +++ b/index.bs @@ -127,44 +127,94 @@ systems. A lock type is a [=string=] that may exclusively be "`open`", "`exclusive`", or "`shared`". +A file lock is a [=struct=] with the following [=struct/items=]: + +: type +:: A [=lock type=]. +: globals +:: A [=/list=] of [=realm/global objects=] representing the current holders of the lock. + A file entry additionally consists of binary data (a [=byte sequence=]), a modification timestamp (a number representing the number of milliseconds since the Unix Epoch), -a lock (a [=lock type=]), -and a lock count (a number representing the number of locks that are taken at a given point in time). +a lock (a [=file lock=]), a pending lock (a [=file lock=]), +and pending steps (a [=/list=] of sets of steps). A user agent has an associated file system queue which is the result of [=starting a new parallel queue=]. This queue is to be used for all file system operations.
-To take a lock with a |lockType| (a [=lock type=]) -on a given [=file entry=] |file|: +To take a lock on a given [=file entry=] |file| +with a |lockType| (a [=lock type=]), a set of steps |resultSteps|, a [=realm/global object=] |global|, +and a |lockResult| (a [=string=]): 1. [=Assert=]: |lockType| is not "`open`". -1. Let |lock| be the |file|'s [=file entry/lock=]. -1. Let |count| be the |file|'s [=file entry/lock count=]. -1. If |lock| is not "`open`": - 1. If |lockType| is "`exclusive`" or |lock| is not equal to |lockType|: - 1. Return "`failure`". -1. Set |lock| to |lockType|. -1. Increase |count| by 1. -1. Return "`success`". +1. Let |pendingLock| be the |file|'s [=file entry/pending lock=]. +1. Let |pendingSteps| be the |file|'s [=file entry/pending steps=]. +1. Let |activeLock| be the |file|'s [=file entry/lock=]. +1. If |pendingLock|'s [=file lock/type=] is not "`open`": + 1. Set |activeLock| to |pendingLock|. +1. Let |activeLockType| be the |activeLock|'s [=file lock/type=]. +1. If |activeLockType| is not "`open`": + 1. If |activeLockType| is not equal to |lockType| or is "`exclusive`": + 1. Let |evictedStatus| be the result of [=file lock/evicting all globals=] on |activeLock|. + 1. If |evictedStatus| is "`failure`": + 1. Set |lockResult| to `failure`. + 1. Run |resultSteps|. + 1. Return. + 1. [=list/Empty=] |pendingLock|'s [=file lock/globals=]. + 1. [=list/Empty=] |pendingSteps|. + 1. Set |activeLock| to |pendingLock|. +1. Set |lockResult| to `success`. +1. Set the |activeLock|'s [=file lock/type=] to |lockType|. +1. [=list/Append=] |global| to |activeLock|'s [=file lock/globals=]. +1. If |activeLock| is the |pendingLock|: + 1. [=list/Append=] |resultSteps| to |pendingSteps|. +1. Otherwise: + 1. Run |resultSteps|. Note: These steps have to be run on the [=file system queue=].
+
+To evict all globals for a [=file entry/lock=] |lock|. + +1. [=set/For each=] |global| of |lock|'s [=file lock/globals=]: + 1. If |global| is a {{Window}} object whose [=associated Document=] is [=Document/fully active=]: + 1. Return "`failure`". + 1. If |global| is a {{WorkerGlobalScope}} object whose [=WorkerGlobalScope/closing=] flag is false and whose {{worker}} is not a [[html/workers#the-worker's-lifetime|suspendable worker]]: + 1. Return "`failure`". +1. [=set/For each=] |global| of |lock|'s [=file lock/globals=]: + 1. The user agent must initiate [[html/document-lifecycle#destroying-documents|destroying]] the {{Window}}'s [=associated Document=]. +1. Return "`success`". + +
+
To release a [=file entry/lock=] on a given -[=/file entry=] |file|: +[=/file entry=] |file| with a [=realm/global object=] |global|: -1. Let |lock| be the |file|'s associated [=file entry/lock=]. -1. [=Assert=]: |lock| is not "`open`". -1. Let |count| be the |file|'s [=file entry/lock count=]. -1. [=Assert=]: |count| is greater than 0. -1. Decrease |count| by 1. -1. If |count| is 0, set |lock| to "`open`". +1. Let |lock| be the |file|'s [=file entry/lock=]. +1. Let |lockGlobals| be the |lock|'s [=file lock/globals=]. +1. [=Assert=]: |lock|'s [=file lock/type=] is not "`open`". +1. [=Assert=]: |lockGlobals|'s [=list/contains=] |global|. +1. [=list/Remove=] the first [=list/item=] in |lockGlobals| equal to |global|. +1. If |lockGlobals| [=list/is empty=]: + 1. Let |pendingLock| be the |file|'s [=file entry/pending lock=]. + 1. Let |pendingLockGlobals| be the |pendingLock|'s [=file lock/globals=]. + 1. If |pendingLock|'s [=file lock/type=] is "`open`": + 1. [=Assert=]: |pendingLockGlobals| [=list/is empty=]. + 1. Set |lock|'s [=file lock/type=] to "`open`". + 1. Return. + 1. [=Assert=]: |pendingLockGlobals|'s [=list/size=] is greater than 0. + 1. Set |file|'s [=file entry/lock=] to |pendingLock|. + 1. Set |pendingLock|'s [=file lock/type=] to "`open`". + 1. [=list/Empty=] |pendingLock|'s [=file lock/globals=]. + 1. [=set/For each=] |resultSteps| of |file|'s [=file entry/pending steps=]: + 1. Run |resultSteps|. + 1. [=list/Empty=] |file|'s [=file entry/pending steps=]. Note: These steps have to be run on the [=file system queue=]. @@ -572,20 +622,20 @@ The createWritable(|options|) method |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] - with "`shared`" 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 |stream| be the result of creating a new `FileSystemWritableFileStream` - for |entry| in |realm|. - 1. If |options|["{{FileSystemCreateWritableOptions/keepExistingData}}"] - is true: - 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s - [=file entry/binary data=]. - 1. [=/Resolve=] |result| with |stream|. + 1. Let |lockResult| be the empty string. + 1. [=file entry/Take a lock=] on |entry| with lock type "`shared`", global |global|, + lock result |lockResult|, and the following result steps: + 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 |stream| be the result of creating a new `FileSystemWritableFileStream` + for |entry| in |realm| and global |global|. + 1. If |options|["{{FileSystemCreateWritableOptions/keepExistingData}}"] + is true: + 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s + [=file entry/binary data=]. + 1. [=/Resolve=] |result| with |stream|. 1. Return |result|. @@ -642,16 +692,16 @@ The createSyncAccessHandle() method s |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResult| be the result of [=file entry/take a lock|taking a lock=] - with "`exclusive`" 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 |lockResult| be the empty string. + 1. [=file entry/Take a lock=] on |entry| with lock type "`exclusive`", global |global|, + lock result |lockResult|, and the following result steps: + 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|. - 1. [=/Resolve=] |result| with |handle|. + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` + for |entry| in |realm|. + 1. [=/Resolve=] |result| with |handle|. 1. Return |result|. @@ -1148,7 +1198,7 @@ Similarly, when piping a {{ReadableStream}} into a {{FileSystemWritableFileStrea
To create a new `FileSystemWritableFileStream` -given a [=file entry=] |file| in a [=/Realm=] |realm|: +given a [=file entry=] |file| in a [=/Realm=] |realm| with [=realm/global object=] |global|: 1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|. 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=] to |file|. @@ -1179,7 +1229,7 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. [=file entry/lock/release|Release the lock=] on - |stream|'s [=FileSystemWritableFileStream/[[file]]=]. + |stream|'s [=FileSystemWritableFileStream/[[file]]=] with global |global|. 1. [=Queue a storage task=] with |file|'s [=relevant global object=] to [=/resolve=] |closeResult| with `undefined`. @@ -1187,7 +1237,7 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: 1. Let |abortAlgorithm| be these steps: 1. [=enqueue steps|Enqueue this step=] to the [=file system queue=]: 1. [=file entry/lock/release|Release the lock=] on - |stream|'s [=FileSystemWritableFileStream/[[file]]=]. + |stream|'s [=FileSystemWritableFileStream/[[file]]=] with global |global|. 1. Let |highWaterMark| be 1. 1. Let |sizeAlgorithm| be an algorithm that returns `1`. 1. [=WritableStream/Set up=] |stream| with close() method steps are: 1. Set |lockReleased| to false. 1. Let |file| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. [=file entry/lock/release|Release the lock=] on |file|. + 1. [=file entry/lock/release|Release the lock=] on |file| with [=this=]'s [=relevant global object=]. 1. Set |lockReleased| to true. 1. [=Pause=] until |lockReleased| is true.