dotcloud/docker

View on GitHub
internal/containerfs/rm.go

Summary

Maintainability
A
45 mins
Test Coverage
//go:build !darwin && !windows

package containerfs

import (
    "os"
    "syscall"
    "time"

    "github.com/moby/sys/mount"
    "github.com/pkg/errors"
)

// EnsureRemoveAll wraps [os.RemoveAll] to check for specific errors that can
// often be remedied.
// Only use [EnsureRemoveAll] if you really want to make every effort to remove
// a directory.
//
// Because of the way [os.Remove] (and by extension [os.RemoveAll]) works, there
// can be a race between reading directory entries and then actually attempting
// to remove everything in the directory.
// These types of errors do not need to be returned since it's ok for the dir to
// be gone we can just retry the remove operation.
//
// This should not return a [os.ErrNotExist] kind of error under any circumstances.
func EnsureRemoveAll(dir string) error {
    notExistErr := make(map[string]bool)

    // track retries
    exitOnErr := make(map[string]int)
    maxRetry := 50

    // Attempt to unmount anything beneath this dir first
    mount.RecursiveUnmount(dir)

    for {
        err := os.RemoveAll(dir)
        if err == nil {
            return nil
        }

        pe, ok := err.(*os.PathError)
        if !ok {
            return err
        }

        if os.IsNotExist(err) {
            if notExistErr[pe.Path] {
                return err
            }
            notExistErr[pe.Path] = true

            // There is a race where some subdir can be removed but after the parent
            //   dir entries have been read.
            // So the path could be from `os.Remove(subdir)`
            // If the reported non-existent path is not the passed in `dir` we
            // should just retry, but otherwise return with no error.
            if pe.Path == dir {
                return nil
            }
            continue
        }

        if pe.Err != syscall.EBUSY {
            return err
        }

        if e := mount.Unmount(pe.Path); e != nil {
            return errors.Wrapf(e, "error while removing %s", dir)
        }

        if exitOnErr[pe.Path] == maxRetry {
            return err
        }
        exitOnErr[pe.Path]++
        time.Sleep(100 * time.Millisecond)
    }
}