diff --git a/container_test.go b/container_test.go index 6eedc6c658..161b32d76b 100644 --- a/container_test.go +++ b/container_test.go @@ -1,7 +1,9 @@ package docker import ( + "fmt" "testing" + "time" ) func TestStart(t *testing.T) { @@ -240,3 +242,87 @@ func TestMultipleContainers(t *testing.T) { t.Fatal(err) } } + +func BenchmarkRunSequencial(b *testing.B) { + docker, err := newTestDocker() + if err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { + container, err := docker.Create( + fmt.Sprintf("bench_%v", i), + "echo", + []string{"-n", "foo"}, + []string{"/var/lib/docker/images/ubuntu"}, + &Config{}, + ) + if err != nil { + b.Fatal(err) + } + defer docker.Destroy(container) + output, err := container.Output() + if err != nil { + b.Fatal(err) + } + if string(output) != "foo" { + b.Fatalf("Unexecpted output: %v", string(output)) + } + if err := docker.Destroy(container); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkRunParallel(b *testing.B) { + docker, err := newTestDocker() + if err != nil { + b.Fatal(err) + } + + var tasks []chan error + + for i := 0; i < b.N; i++ { + complete := make(chan error) + tasks = append(tasks, complete) + go func(i int, complete chan error) { + container, err := docker.Create( + fmt.Sprintf("bench_%v", i), + "echo", + []string{"-n", "foo"}, + []string{"/var/lib/docker/images/ubuntu"}, + &Config{}, + ) + if err != nil { + complete <- err + return + } + defer docker.Destroy(container) + if err := container.Start(); err != nil { + complete <- err + return + } + if err := container.WaitTimeout(15 * time.Second); err != nil { + complete <- err + return + } + // if string(output) != "foo" { + // complete <- fmt.Errorf("Unexecpted output: %v", string(output)) + // } + if err := docker.Destroy(container); err != nil { + complete <- err + return + } + complete <- nil + }(i, complete) + } + var errors []error + for _, task := range tasks { + err := <-task + if err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + b.Fatal(errors) + } +} diff --git a/docker.go b/docker.go index 8a79e989a9..7f9442306a 100644 --- a/docker.go +++ b/docker.go @@ -67,10 +67,14 @@ func (docker *Docker) Destroy(container *Container) error { if err := container.Stop(); err != nil { return err } - if err := os.RemoveAll(container.Root); err != nil { - return err + if container.Filesystem.IsMounted() { + if err := container.Filesystem.Umount(); err != nil { + log.Printf("Unable to umount container %v: %v", container.Id, err) + } + } + if err := os.RemoveAll(container.Root); err != nil { + log.Printf("Unable to remove filesystem for %v: %v", container.Id, err) } - docker.containers.Remove(element) return nil } diff --git a/filesystem.go b/filesystem.go index 543770dc6b..030625a150 100644 --- a/filesystem.go +++ b/filesystem.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "syscall" + "time" "io" "io/ioutil" ) @@ -40,33 +41,59 @@ func (fs *Filesystem) Mount() error { roBranches += fmt.Sprintf("%v=ro:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - return syscall.Mount("none", fs.RootFS, "aufs", 0, branches) + if err := syscall.Mount("none", fs.RootFS, "aufs", 0, branches); err != nil { + return err + } + if !fs.IsMounted() { + return errors.New("Mount failed") + } + return nil } func (fs *Filesystem) Umount() error { if !fs.IsMounted() { return errors.New("Umount: Filesystem not mounted") } - return syscall.Unmount(fs.RootFS, 0) + if err := syscall.Unmount(fs.RootFS, 0); err != nil { + return err + } + if fs.IsMounted() { + return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", fs.RootFS) + } + // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint + // for some time. We'll just keep retrying until it succeeds. + for retries := 0; retries < 1000; retries++ { + err := os.Remove(fs.RootFS) + if err == nil { + // rm mntpoint succeeded + return nil + } + if os.IsNotExist(err) { + // mntpoint doesn't exist anymore. Success. + return nil + } + // fmt.Printf("(%v) Remove %v returned: %v\n", retries, fs.RootFS, err) + time.Sleep(10 * time.Millisecond) + } + return fmt.Errorf("Umount: Failed to umount %v", fs.RootFS) } func (fs *Filesystem) IsMounted() bool { - f, err := os.Open(fs.RootFS) + mntpoint, err := os.Stat(fs.RootFS) if err != nil { if os.IsNotExist(err) { return false } panic(err) } - list, err := f.Readdirnames(1) - f.Close() + parent, err := os.Stat(filepath.Join(fs.RootFS, "..")) if err != nil { - return false + panic(err) } - if len(list) > 0 { - return true - } - return false + + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) + return mntpointSt.Dev != parentSt.Dev } // Tar returns the contents of the filesystem as an uncompressed tar stream diff --git a/filesystem_test.go b/filesystem_test.go index b4deb02b36..fb57344ede 100644 --- a/filesystem_test.go +++ b/filesystem_test.go @@ -27,21 +27,41 @@ func TestFilesystem(t *testing.T) { t.Errorf("Umount succeeded even though the filesystem was not mounted") } + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } + if err := filesystem.Mount(); err != nil { t.Fatal(err) } + if !filesystem.IsMounted() { + t.Fatal("Filesystem should be mounted") + } + if err := filesystem.Mount(); err == nil { t.Errorf("Double mount succeeded") } + if !filesystem.IsMounted() { + t.Fatal("Filesystem should be mounted") + } + if err := filesystem.Umount(); err != nil { t.Fatal(err) } + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } + if err := filesystem.Umount(); err == nil { t.Errorf("Umount succeeded even though the filesystem was already umounted") } + + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } } func TestFilesystemMultiLayer(t *testing.T) {