Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Access Handles to spec #21

Merged
merged 15 commits into from
Sep 29, 2022
Merged

Add Access Handles to spec #21

merged 15 commits into from
Sep 29, 2022

Conversation

fivedots
Copy link
Contributor

@fivedots fivedots commented Apr 11, 2022

This PR adds Access Handles to the spec. The Access Handles surface differs from existing ones by offering in-place and exclusive write access to a file’s content. This change, along with the ability to consistently read unflushed modifications and the availability of a synchronous variant on dedicated workers, significantly improves performance and unblocks new use cases.

There are still a few open TODOs where advice would be appreciated!

The PR also updates AccessHandles.md to reflect changes in WICG/file-system-access#367.


Preview | Diff

@fivedots
Copy link
Contributor Author

A first pass review was done by @domenic in WICG/file-system-access#344.

Copy link
Contributor

@tomayac tomayac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider changing the code samples as in https://github.com/WICG/file-system-access/blob/main/AccessHandle.md, so it's more clear by the variable names what is a FileSystemAccessHandle and what is a FileSystemFileHandle.

// In all contexts
const accessHandle = await fileHandle.createAccessHandle();
await accessHandle.writable.getWriter().write(buffer);
const reader = accessHandle.readable.getReader({ mode: "byob" });
// Assumes seekable streams, and SharedArrayBuffer support are available
await reader.read(buffer, { at: 1 });

// Only in a worker context
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });
const accessHandle1 = await fileHandle.createAccessHandle();
try {
  const accessHandle2 = await fileHandle.createAccessHandle();
} catch (e) {
  // This catch will always be executed, since there is an open access handle
}
await accessHandle1.close();
// Now a new access handle may be created

@fivedots
Copy link
Contributor Author

Updated examples on AccessHandles.md as proposed by @tomayac.

index.bs Show resolved Hide resolved
@annevk
Copy link
Member

annevk commented Apr 28, 2022

I don't think we should keep AccessHandle.md as a separate file. If the information in there is relevant to the standard, it should be folded into the main document. If it's not, it should probably be maintained elsewhere. At least we don't usually have such documents alongside WHATWG standards.

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a pass at this and mainly found style nits. Hope that helps. Thanks for pushing this forward!

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
@jesup
Copy link
Contributor

jesup commented Apr 28, 2022

I notice that while the webidl indicates that SyncAccessHandle is for workers only, the commentary for webdevelopers doesn't mention workers anywhere.

I also note that there's no definition of AccessHandles (such as you see in https://github.com/WICG/file-system-access/blob/main/AccessHandle.md#exposing-accesshandles-on-all-filesystems), which means the only way to read data is on worker via SyncAccessHandles (WritableFileStreams only support writing data here). I presume this is an intentional decision made sometime since AcessHandle.md was written? And this means there's no way to read data in an async fashion, it's sync only.

There's also no mention of OPFS until the very end of the document; it's referenced by description in the intro, but not by name.

And ditto to anne's points, especially as to why truncate/getSize are async (since they're only for SyncAccessHandle).

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
:: Writes the content of |buffer| into the file associated with |handle|, optionally at a given offset.
</div>

// TODO(fivedots): Figure out how to properly check the available storage quota (in this method and others) by passing the right storage shelf.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plans to resolve this todo in a manner that we can implement (without all of Storage API)? What does chrome's current implementation do, and what does Safari do?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's meant by a "storage shelf" but I can speak to what the current Chrome implementation looks like...

Quota is managed in the browser process, so in order to minimize IPCs we pre-allocate quota. Quota pre-allocations are based on a heuristic which tries to strike a balance between limiting IPCs (for performance) and limiting the amount of quota we're over-allocating (for fairness). Of course one can construct a write pattern such that every write() does an IPC to the browser, but in the average case write() calls shouldn't require an IPC.

Once the AccessHandle is closed, all over-allocated quota is released.

I'm happy to share more specifics, but I assumed quota management would be non-normative? Except for maybe specifying that quota should be accurate when the AccessHandle is closed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real missing part here is specifying what happens in quota cases for write() (as is done in WritableFileStream):

If the operations modifying stream.[[buffer]] in the previous steps failed due to exceeding the storage quota, reject p with a QuotaExceededError and abort, leaving stream.[[buffer]] unmodified.

Note: Storage quota only applies to files stored in the origin private file system. However this operation could still fail for other files, for example if the disk being written to runs out of disk space.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(A storage shelf is essentially the storage space for an "origin" (really storage key). https://storage.spec.whatwg.org/#model goes into this in more detail.)

Copy link
Contributor Author

@fivedots fivedots Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some handling for out-of-quota cases below:

  1. If |newSize| − |oldSize| exceeds the available [=storage quota=], throw a {{QuotaExceededError}}

Are there other cases I should handle?

The TODO was more intended as a request for help on to properly spec this behaviour, in particular while using the language that takes the storage bucket/shelf into account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that QuotaExceededError is thrown for write() and for truncate(). Is verbiage needed like "and abort." on these?
If writePosition is larger than oldSize, append writePosition − oldSize 0x00 (NUL) bytes to the end of fileContents. --- this could cause us to exceed quota (and modify the file and then throw, which probably is bad.) We need to check quota before modifying the file.

Creating a file or directory takes space on disk, even if the file is 0 length; if that's considered part of the quota, then those should be able to fail with QuotaExceededError as well. I'm not certain if quota is meant to just be contents, or an approximation of the space required on disk.

My best guess is that quota is just file contents, so this shouldn't be an issue. Double-checking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that QuotaExceededError is thrown for write() and for truncate(). Is verbiage needed like "and abort." on these?

Since these methods don't return a promise, aborting after throwing should not be needed.

If writePosition is larger than oldSize, append writePosition − oldSize 0x00 (NUL) bytes to the end of fileContents. --- this could cause us to exceed quota (and modify the file and then throw, which probably is bad.) We need to check quota before modifying the file.

In this case, the file should not be modified. |fileContents| is a copy of the file's data, the actual file is only modified on step 12 after doing a quota check.

Creating a file or directory takes space on disk, even if the file is 0 length; if that's considered part of the quota, then those should be able to fail with QuotaExceededError as well. I'm not certain if quota is meant to just be contents, or an approximation of the space required on disk.

Those checks (if they are necessary) should have already been done, since to obtain an access handle you need to open/create a file before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Copy link
Collaborator

@a-sully a-sully left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for not looking at this sooner! I've added some notes about Chrome's current implementation, which I hope can serve as a starting point but which of course we can readily change

index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
AccessHandle.md Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@jesup
Copy link
Contributor

jesup commented May 18, 2022

See this doc for the justification behind the split sync/async interface.

I think you mean this

@a-sully
Copy link
Collaborator

a-sully commented May 18, 2022

See this doc for the justification behind the split sync/async interface.

I think you mean this

Ah yes, sorry about that. I've updated the link in my comment.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request May 18, 2022
Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
aarongable pushed a commit to chromium/chromium that referenced this pull request May 19, 2022
Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request May 19, 2022
Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request May 19, 2022
Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}
@annevk annevk mentioned this pull request May 19, 2022
index.bs Show resolved Hide resolved
index.bs Show resolved Hide resolved
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request May 24, 2022
…ror if AccessHandle is locked, a=testonly

Automatic update from web-platform-tests
FSA: Reject with NoModificationAllowedError if AccessHandle is locked

Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}

--

wpt-commits: cc1e80e5a204d08eeba36a2bd29f206f3d7f8948
wpt-pr: 34115
jamienicol pushed a commit to jamienicol/gecko that referenced this pull request May 25, 2022
…ror if AccessHandle is locked, a=testonly

Automatic update from web-platform-tests
FSA: Reject with NoModificationAllowedError if AccessHandle is locked

Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}

--

wpt-commits: cc1e80e5a204d08eeba36a2bd29f206f3d7f8948
wpt-pr: 34115
@a-sully a-sully mentioned this pull request May 27, 2022
3 tasks
@a-sully a-sully mentioned this pull request Jun 7, 2022
Copy link
Collaborator

@a-sully a-sully left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I know @annevk will not be around for a while to address some of the open comment threads... @jesup @tomayac do you think we can just land the PR with TODOs for those questions? We're eager to land this PR so we can put up a follow-up to make the interface all-sync (as proposed in #7), which will make some (but not all) of those questions no longer relevant.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@a-sully
Copy link
Collaborator

a-sully commented Sep 6, 2022

Friendly ping @jesup @annevk

I'd like to get this PR approved and merged so we can move forward with making all the methods of the SyncAccessHandle sync :)

Thanks!

@a-sully a-sully mentioned this pull request Sep 7, 2022
3 tasks
@jesup
Copy link
Contributor

jesup commented Sep 14, 2022

@a-sully The WPT tests seem to assume you must pass 'at', and if you don't all reads start at 0. This is ... odd, especially for implementing the equivalent to read(), which maintains an internal position in the file, and reads from there. It means for almost all reads (or writes), you must pass at, which means you must track the expected position in the file - likely in a wrapper around syncaccesshandle.read()/write(). Emscriptem certainly can track the location in the file and insert at:nnnn always, but this seems like an odd choice for an API (and not great for performance, due to the extra parsing and validation and the basically forced extra kernel call to seek -- we could avoid the seek() call if we also track the position and elide if if it's a no-op, but that's not worth it either).
I imagine this is legacy decisions from the non-OPFS File System API

@jesup
Copy link
Contributor

jesup commented Sep 15, 2022

Though maybe not legacy -- SyncAccessHandles came later. Can (should?) we change this? If you're writing a bunch of data, it's a whole lot more (unnecessary) bookkeeping and overhead.

   position += handle.write(data1, {at: position});
   position += handle.write(data2, {at: position});

But you can't quite do that, since if there's an error and a partial write, you won't notice it. To be pedantic about catching errors, you need to do:

try {
  written = handle.write(data1, {at: position});
  if (written != data1.length()) { /* error */} else { postion += written;}
  written = handle.write(data2, {at: position});
  if (written != data2.length()) { /* error */} else { postion += written;}
} catch(e) { ... }

With classic posix-like read/write (with an assumed position update), it would be

  handle.write(data1);
  handle.write(data2);

or

try {
  if (handle.write(data1) != data1.length) { /* error */}
  if (handle.write(data2) != data2.length) { /* error */}
} catch(e) { ... }

@jesup
Copy link
Contributor

jesup commented Sep 15, 2022

We should probably merge this and then raise any points like the above as issues

@jesup
Copy link
Contributor

jesup commented Sep 15, 2022

@tomayac Thoughts on at: ?

@a-sully
Copy link
Collaborator

a-sully commented Sep 15, 2022

We should probably merge this and then raise any points like the above as issues

I agree. Would you mind opening an issue for this? I have some thoughts here and it would be nice to have all the discussion in one place

@jesup
Copy link
Contributor

jesup commented Sep 16, 2022

Editorial nit: in truncate(): "If the offset is smaller than offset, it remains unchanged."

@jesup
Copy link
Contributor

jesup commented Sep 16, 2022

#49

@fivedots
Copy link
Contributor Author

I also agree merging and moving the discussion to #49 sounds good.

I've updated the description of truncate to fix the confussion mentioned in #21 (comment).

@jesup would you mind approving the PR for completeness/visibility? I'll merge it in then!

dslee414 added a commit to dslee414/fs that referenced this pull request Sep 22, 2022
Patch of spec update PR: whatwg#21
dslee414 added a commit to dslee414/fs that referenced this pull request Sep 22, 2022
Patch of spec update PR: whatwg#21
@jesup
Copy link
Contributor

jesup commented Sep 28, 2022

I can't approve since I don't have commit access, but go ahead.

@a-sully a-sully merged commit 4785589 into whatwg:main Sep 29, 2022
mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this pull request Oct 14, 2022
Spec PR: whatwg/fs#21
Comment: https://github.com/whatwg/fs/pull/21/files#r876204287

Updates the implementation to match the spec and to match other cases
where we reject a promise because of a locked file.

Bug: N/A
Change-Id: I201fa40a866ecace62c6fe77ad70cffce57c2553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653903
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Auto-Submit: Austin Sullivan <[email protected]>
Commit-Queue: Marijn Kruisselbrink <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1005103}
NOKEYCHECK=True
GitOrigin-RevId: 320c5fa631934ca869fb0b6cfcf46aa997f9313a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants