dotcloud/docker

View on GitHub
daemon/graphdriver/overlayutils/randomid.go

Summary

Maintainability
A
0 mins
Test Coverage
//go:build linux

package overlayutils // import "github.com/docker/docker/daemon/graphdriver/overlayutils"

import (
    "crypto/rand"
    "encoding/base32"
    "fmt"
    "io"
    "os"
    "syscall"
    "time"

    "github.com/containerd/log"
    "golang.org/x/sys/unix"
)

// GenerateID creates a new random string identifier with the given length
func GenerateID(l int, logger *log.Entry) string {
    const (
        // ensures we backoff for less than 450ms total. Use the following to
        // select new value, in units of 10ms:
        //     n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
        maxretries = 9
        backoff    = time.Millisecond * 10
    )

    var (
        totalBackoff time.Duration
        count        int
        retries      int
        size         = (l*5 + 7) / 8
        u            = make([]byte, size)
    )
    // TODO: Include time component, counter component, random component

    for {
        // This should never block but the read may fail. Because of this,
        // we just try to read the random number generator until we get
        // something. This is a very rare condition but may happen.
        b := time.Duration(retries) * backoff
        time.Sleep(b)
        totalBackoff += b

        n, err := io.ReadFull(rand.Reader, u[count:])
        if err != nil {
            if retryOnError(err) && retries < maxretries {
                count += n
                retries++
                logger.Errorf("error generating version 4 uuid, retrying: %v", err)
                continue
            }

            // Any other errors represent a system problem. What did someone
            // do to /dev/urandom?
            panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
        }

        break
    }

    s := base32.StdEncoding.EncodeToString(u)

    return s[:l]
}

// retryOnError tries to detect whether or not retrying would be fruitful.
func retryOnError(err error) bool {
    switch err := err.(type) {
    case *os.PathError:
        return retryOnError(err.Err) // unpack the target error
    case syscall.Errno:
        if err == unix.EPERM {
            // EPERM represents an entropy pool exhaustion, a condition under
            // which we backoff and retry.
            return true
        }
    }

    return false
}