mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Fix race condition when waiting for a concurrent layer pull
Before, this only waited for the download to complete. There was no guarantee that the layer had been registered in the graph and was ready use. This is especially problematic with v2 pulls, which wait for all downloads before extracting layers. Change Broadcaster to allow an error value to be propagated from Close to the waiters. Make the wait stop when the extraction is finished, rather than just the download. This also fixes v2 layer downloads to prefix the pool key with "layer:" instead of "img:". "img:" is the wrong prefix, because this is what v1 uses for entire images. A v1 pull waiting for one of these operations to finish would only wait for that particular layer, not all its dependencies. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
f9e5a693ed
commit
23e68679f0
6 changed files with 224 additions and 55 deletions
|
@ -2,6 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
@ -37,3 +39,134 @@ func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentPullWholeRepo pulls the same repo concurrently.
|
||||
func (s *DockerRegistrySuite) TestConcurrentPullWholeRepo(c *check.C) {
|
||||
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
|
||||
|
||||
repos := []string{}
|
||||
for _, tag := range []string{"recent", "fresh", "todays"} {
|
||||
repo := fmt.Sprintf("%v:%v", repoName, tag)
|
||||
_, err := buildImage(repo, fmt.Sprintf(`
|
||||
FROM busybox
|
||||
ENTRYPOINT ["/bin/echo"]
|
||||
ENV FOO foo
|
||||
ENV BAR bar
|
||||
CMD echo %s
|
||||
`, repo), true)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
dockerCmd(c, "push", repo)
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
|
||||
// Clear local images store.
|
||||
args := append([]string{"rmi"}, repos...)
|
||||
dockerCmd(c, args...)
|
||||
|
||||
// Run multiple re-pulls concurrently
|
||||
results := make(chan error)
|
||||
numPulls := 3
|
||||
|
||||
for i := 0; i != numPulls; i++ {
|
||||
go func() {
|
||||
_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", "-a", repoName))
|
||||
results <- err
|
||||
}()
|
||||
}
|
||||
|
||||
// These checks are separate from the loop above because the check
|
||||
// package is not goroutine-safe.
|
||||
for i := 0; i != numPulls; i++ {
|
||||
err := <-results
|
||||
c.Assert(err, check.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
|
||||
}
|
||||
|
||||
// Ensure all tags were pulled successfully
|
||||
for _, repo := range repos {
|
||||
dockerCmd(c, "inspect", repo)
|
||||
out, _ := dockerCmd(c, "run", "--rm", repo)
|
||||
if strings.TrimSpace(out) != "/bin/sh -c echo "+repo {
|
||||
c.Fatalf("CMD did not contain /bin/sh -c echo %s: %s", repo, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentFailingPull tries a concurrent pull that doesn't succeed.
|
||||
func (s *DockerRegistrySuite) TestConcurrentFailingPull(c *check.C) {
|
||||
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
|
||||
|
||||
// Run multiple pulls concurrently
|
||||
results := make(chan error)
|
||||
numPulls := 3
|
||||
|
||||
for i := 0; i != numPulls; i++ {
|
||||
go func() {
|
||||
_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repoName+":asdfasdf"))
|
||||
results <- err
|
||||
}()
|
||||
}
|
||||
|
||||
// These checks are separate from the loop above because the check
|
||||
// package is not goroutine-safe.
|
||||
for i := 0; i != numPulls; i++ {
|
||||
err := <-results
|
||||
if err == nil {
|
||||
c.Fatal("expected pull to fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentPullMultipleTags pulls multiple tags from the same repo
|
||||
// concurrently.
|
||||
func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
|
||||
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
|
||||
|
||||
repos := []string{}
|
||||
for _, tag := range []string{"recent", "fresh", "todays"} {
|
||||
repo := fmt.Sprintf("%v:%v", repoName, tag)
|
||||
_, err := buildImage(repo, fmt.Sprintf(`
|
||||
FROM busybox
|
||||
ENTRYPOINT ["/bin/echo"]
|
||||
ENV FOO foo
|
||||
ENV BAR bar
|
||||
CMD echo %s
|
||||
`, repo), true)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
dockerCmd(c, "push", repo)
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
|
||||
// Clear local images store.
|
||||
args := append([]string{"rmi"}, repos...)
|
||||
dockerCmd(c, args...)
|
||||
|
||||
// Re-pull individual tags, in parallel
|
||||
results := make(chan error)
|
||||
|
||||
for _, repo := range repos {
|
||||
go func(repo string) {
|
||||
_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repo))
|
||||
results <- err
|
||||
}(repo)
|
||||
}
|
||||
|
||||
// These checks are separate from the loop above because the check
|
||||
// package is not goroutine-safe.
|
||||
for range repos {
|
||||
err := <-results
|
||||
c.Assert(err, check.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
|
||||
}
|
||||
|
||||
// Ensure all tags were pulled successfully
|
||||
for _, repo := range repos {
|
||||
dockerCmd(c, "inspect", repo)
|
||||
out, _ := dockerCmd(c, "run", "--rm", repo)
|
||||
if strings.TrimSpace(out) != "/bin/sh -c echo "+repo {
|
||||
c.Fatalf("CMD did not contain /bin/sh -c echo %s: %s", repo, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue