-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Fix "Access is Denied" error on windows (with lock file) #4725
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #4725 +/- ##
==========================================
- Coverage 59.66% 59.55% -0.12%
==========================================
Files 287 287
Lines 24760 24831 +71
==========================================
+ Hits 14774 14788 +14
- Misses 9100 9138 +38
- Partials 886 905 +19 |
``` make dev make vendor ``` Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Previously the code attempts to use ioutils.AtomicWriteFile to update meta.json but this fails on Windows if another process has the meta.json open for reading (unlike on Unix OSes). Example error: ``` rename C:\Users\docker\.docker\contexts\meta\<id>\.tmp-meta.json2945721760 C:\Users\docker\.docker\contexts\meta\<id>\meta.json: Access is denied. ``` So if one `docker context inspect` is running, then another `docker context` command can fail transiently in `CreateOrUpdate`. Avoid this problem by serialising accesses to the context filesystem db using a lock file via github.com/gofrs/flock which uses LockFileEx on Windows and syscall.Flock on Unix. The lock is associated with a file descriptor / handle and released by calling Close() or when the process exits. Note this means that functions which could previously fail for one reason can now fail for two (the original + a lock/unlock failure). Use go-multierror to return all the errors. When we upgrade to go 1.20 we can use errors.Join. Signed-off-by: David Scott <dave@recoil.org>
Didn't look at the code yet, but do you think this is something we should have as part of that package? (i.e., would other uses of the package have the same issue?) |
cli/context/store/store.go
Outdated
"path" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/docker/docker/errdefs" | ||
"github.com/gofrs/flock" | ||
"github.com/hashicorp/go-multierror" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Go's native multi-errors for this? (errors.Join()
(https://pkg.go.dev/errors#Join)), or would we loose too much functionality?
I think the general trend is to (try) moving away from hashicorp's packages as licensing for some of them becomes more risky (plus trying to use stdlib where possible in general)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can switch to errors.Join
if you don't mind requiring Go 1.20. The vendor.mod requires Go 1.19 at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed a few patches to switch to the Go 1.20 errors package to see how it looks.
Signed-off-by: David Scott <dave@recoil.org>
https://github.com/pkg/errors has been archived since Dec 2021 Replace - errors.Errorf and errors.Wrap with fmt.Errorf("%w") - multierror.Append with errors.Join Signed-off-by: David Scott <dave@recoil.org>
This reverts commit 13af591.
I think other users of the package on Windows will definitely have the same issue. A colleague (@fredericdalleau ) separately suggested a possibility which is to use a different file sharing mode when opening the file on Windows, to permit the rename on top. This is the approach we use in Docker Desktop's "grpcfuse" filesharing to make Windows file I/O more like Unix file I/O. I'll take a look at that possibility too. |
For others; this touched on the Which ... is what we ended up doing in the docker daemon 😞 😞 😞 😞 ; https://github.com/moby/moby/blob/199793350807908c04b56741b11e6b943158ac51/daemon/logger/loggerutils/file_windows.go#L25-L72 |
Thanks for the Have a look at this unit test in that other branch: djs55@81b67b2 I think the only way to have clients see the Unix behaviour on Windows is to either serialise calls with a lock(file) or handle the transient errors with a sleep/retry loop. In the current code clients will see transient "Access is Denied" if there is a concurrent read; if we use |
IIUC, the general issue is (potentially silly comments ahead);
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placeholder -1; we should be able to keep using the atomic writer, which we should change in Moby to make use of our FILE_SHARE_DELETE I/O helper.
Let me close this one as stale. Feel free to reopen if we should still reconsider this one. |
- What I did
Fix a bug where, if one process is reading a
meta.json
on Windows, anotherCreateOrUpdate
will fail withAccess is Denied
.Example error:
- How I did it
Previously the code attempts to use moby's ioutils.AtomicWriteFile (called from here) to update
meta.json
but this fails on Windows calling os.Rename if another process has themeta.json
open for reading (unlike on Unix OSes).Avoid this problem by serialising accesses to the context filesystem db using a lock file via github.com/gofrs/flock which uses LockFileEx on Windows and syscall.Flock on Unix.
The lock is associated with a file descriptor / handle and released by calling
Close()
or when the process exits. The Microsoft docs have:and man 2 flock has:
Note the Unlock() function returns an error which means that functions which could previously fail for one reason can now fail for two (the original + a lock/unlock failure). Use github.com/hashicorp/go-multierror to return all the errors. When we upgrade to go 1.20 we can use errors.Join.
- How to verify it
I saw this in CI where one process was running a
docker
command which read ameta.json
while another was trying to update themeta.json
. I've just seen it in a Docker Desktop bug report this morning.- Description for the changelog
Access is Denied
.- A picture of a cute animal (not mandatory but encouraged)