internal/lsp/fake: in WriteFileData, retry writes that fail with ERROR_LOCK_VIOLATION on Windows

The 'go' command reads the main module's 'go.mod' file with the file
locked. If 'gopls' starts a 'go' command in the background that reads
the 'go.mod' file, there is a narrow window in which that read can
race with the test's ioutil.WriteFile.

This change retries the write (as a user would probably do), fixing
the test failure. However, I suspect that there may be a broader UX
issue here that it will mask: in order to actually avoid the race
without a locking error, something in the LSP protocol needs to be
synchronizing gopls-initiated reads with editor-initiated writes. The
test failure this fixes indicates that that synchronization seems not
to be happening.

Fixes golang/go#50971

Change-Id: Iba140277bf957bae4937cc23f62d3aac817a7d36
Reviewed-on: https://go-review.googlesource.com/c/tools/+/382414
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Bryan C. Mills 2022-02-02 11:00:09 -05:00 committed by Bryan Mills
parent f6067dc943
commit 82366c6960
2 changed files with 37 additions and 3 deletions

View File

@ -14,6 +14,7 @@ import (
"path/filepath"
"strings"
"sync"
"time"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
@ -67,12 +68,25 @@ func WriteFileData(path string, content []byte, rel RelativeTo) error {
if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil {
return errors.Errorf("creating nested directory: %w", err)
}
if err := ioutil.WriteFile(fp, []byte(content), 0644); err != nil {
return errors.Errorf("writing %q: %w", path, err)
backoff := 1 * time.Millisecond
for {
err := ioutil.WriteFile(fp, []byte(content), 0644)
if err != nil {
if isWindowsErrLockViolation(err) {
time.Sleep(backoff)
backoff *= 2
continue
}
return errors.Errorf("writing %q: %w", path, err)
}
return nil
}
return nil
}
// isWindowsErrLockViolation reports whether err is ERROR_LOCK_VIOLATION
// on Windows.
var isWindowsErrLockViolation = func(err error) bool { return false }
// Workdir is a temporary working directory for tests. It exposes file
// operations in terms of relative paths, and fakes file watching by triggering
// events on file operations.

View File

@ -0,0 +1,20 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fake
import (
"syscall"
errors "golang.org/x/xerrors"
)
func init() {
// from https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const ERROR_LOCK_VIOLATION syscall.Errno = 33
isWindowsErrLockViolation = func(err error) bool {
return errors.Is(err, ERROR_LOCK_VIOLATION)
}
}