mirror of
https://github.com/containerd/containerd.git
synced 2026-08-09 01:21:15 +00:00
Merge pull request #13329 from ayush-panta/fix-flaky-windows-test-content-client
content: handle sharing violations on Windows
This commit is contained in:
@@ -632,7 +632,7 @@ func (s *store) writer(ctx context.Context, ref string, total int64, expected di
|
||||
// be cancelled. Any resources associated with the ingest will be cleaned.
|
||||
func (s *store) Abort(ctx context.Context, ref string) error {
|
||||
root := s.ingestRoot(ref)
|
||||
if err := os.RemoveAll(root); err != nil {
|
||||
if err := removePath(root); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("ingest ref %q: %w", ref, errdefs.ErrNotFound)
|
||||
}
|
||||
@@ -684,7 +684,7 @@ func readFileString(path string) (string, error) {
|
||||
|
||||
// readFileTimestamp reads a file with just a timestamp present.
|
||||
func readFileTimestamp(p string) (time.Time, error) {
|
||||
b, err := os.ReadFile(p)
|
||||
b, err := readFileWithRetry(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = fmt.Errorf("%s: %w", err.Error(), errdefs.ErrNotFound)
|
||||
|
||||
29
plugins/content/local/store_other.go
Normal file
29
plugins/content/local/store_other.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package local
|
||||
|
||||
import "os"
|
||||
|
||||
func removePath(path string) error {
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func readFileWithRetry(path string) ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
@@ -17,10 +17,81 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func getATime(fi os.FileInfo) time.Time {
|
||||
return fi.ModTime()
|
||||
}
|
||||
|
||||
const (
|
||||
// maxFileRetries is the number of attempts for file operations that
|
||||
// may fail due to transient sharing violations on Windows.
|
||||
maxFileRetries = 5
|
||||
|
||||
// fileRetryDelay is the base delay between retries. Each attempt
|
||||
// waits (attempt number) * fileRetryDelay.
|
||||
fileRetryDelay = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
// isRetryableError returns true for Windows errors that are known to be
|
||||
// transient when a file handle has not been fully released by the OS.
|
||||
func isRetryableError(err error) bool {
|
||||
return errors.Is(err, windows.ERROR_SHARING_VIOLATION) ||
|
||||
errors.Is(err, windows.ERROR_LOCK_VIOLATION) ||
|
||||
errors.Is(err, windows.ERROR_DIR_NOT_EMPTY)
|
||||
}
|
||||
|
||||
// removePath wraps os.RemoveAll with a retry loop for Windows.
|
||||
//
|
||||
// On Windows, a file that has been closed by the application may
|
||||
// still be held briefly by the OS. Unlike Unix, Windows does not
|
||||
// allow deleting a file while any handle is open, so os.RemoveAll
|
||||
// can fail with a sharing violation if it races with a recent Close.
|
||||
// Retrying after a short delay allows the OS to finish releasing the handle.
|
||||
func removePath(path string) error {
|
||||
var err error
|
||||
for i := range maxFileRetries {
|
||||
err = os.RemoveAll(path)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRetryableError(err) {
|
||||
return err
|
||||
}
|
||||
if i < maxFileRetries-1 {
|
||||
time.Sleep(time.Duration(i+1) * fileRetryDelay)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// readFileWithRetry wraps os.ReadFile with a retry loop for Windows.
|
||||
//
|
||||
// On Windows, a file that has been closed by the application may
|
||||
// still be held briefly by the OS. Unlike Unix, Windows does not
|
||||
// allow reading a file while an exclusive handle is open, so
|
||||
// os.ReadFile can fail with a sharing violation if it races with
|
||||
// a recent Close. Retrying after a short delay allows the OS to
|
||||
// finish releasing the handle.
|
||||
func readFileWithRetry(path string) ([]byte, error) {
|
||||
var err error
|
||||
for i := range maxFileRetries {
|
||||
var b []byte
|
||||
b, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
if os.IsNotExist(err) || !isRetryableError(err) {
|
||||
return nil, err
|
||||
}
|
||||
if i < maxFileRetries-1 {
|
||||
time.Sleep(time.Duration(i+1) * fileRetryDelay)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user