diff --git a/libnetwork/controller.go b/libnetwork/controller.go index d709d972b0..f5aaac5b37 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -59,6 +59,7 @@ import ( "github.com/docker/libnetwork/datastore" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/hostdiscovery" + "github.com/docker/libnetwork/sandbox" "github.com/docker/libnetwork/types" "github.com/docker/swarm/pkg/store" ) @@ -84,6 +85,9 @@ type NetworkController interface { // NetworkByID returns the Network which has the passed id. If not found, the error ErrNoSuchNetwork is returned. NetworkByID(id string) (Network, error) + + // GC triggers immediate garbage collection of resources which are garbage collected. + GC() } // NetworkWalker is a client provided function which will be used to walk the Networks. @@ -394,3 +398,7 @@ func (c *controller) loadDriver(networkType string) (driverapi.Driver, error) { } return d, nil } + +func (c *controller) GC() { + sandbox.GC() +} diff --git a/libnetwork/sandbox/namespace_linux.go b/libnetwork/sandbox/namespace_linux.go index c368d54f41..3d8a98ccf6 100644 --- a/libnetwork/sandbox/namespace_linux.go +++ b/libnetwork/sandbox/namespace_linux.go @@ -25,6 +25,7 @@ var ( gpmLock sync.Mutex gpmWg sync.WaitGroup gpmCleanupPeriod = 60 * time.Second + gpmChan = make(chan chan struct{}) ) // The networkNamespace type is the linux implementation of the Sandbox @@ -56,7 +57,18 @@ func removeUnusedPaths() { period := gpmCleanupPeriod gpmLock.Unlock() - for range time.Tick(period) { + ticker := time.NewTicker(period) + for { + var ( + gc chan struct{} + gcOk bool + ) + + select { + case <-ticker.C: + case gc, gcOk = <-gpmChan: + } + gpmLock.Lock() pathList := make([]string, 0, len(garbagePathMap)) for path := range garbagePathMap { @@ -71,6 +83,9 @@ func removeUnusedPaths() { } gpmWg.Done() + if gcOk { + close(gc) + } } } @@ -86,6 +101,18 @@ func removeFromGarbagePaths(path string) { gpmLock.Unlock() } +// GC triggers garbage collection of namespace path right away +// and waits for it. +func GC() { + waitGC := make(chan struct{}) + + // Trigger GC now + gpmChan <- waitGC + + // wait for gc to complete + <-waitGC +} + // GenerateKey generates a sandbox key based on the passed // container id. func GenerateKey(containerID string) string { diff --git a/libnetwork/sandbox/sandbox_linux_test.go b/libnetwork/sandbox/sandbox_linux_test.go index b00d14f350..67175b11e3 100644 --- a/libnetwork/sandbox/sandbox_linux_test.go +++ b/libnetwork/sandbox/sandbox_linux_test.go @@ -153,9 +153,16 @@ func verifySandbox(t *testing.T, s Sandbox) { } } -func verifyCleanup(t *testing.T, s Sandbox) { - time.Sleep(time.Duration(gpmCleanupPeriod * 2)) +func verifyCleanup(t *testing.T, s Sandbox, wait bool) { + if wait { + time.Sleep(time.Duration(gpmCleanupPeriod * 2)) + } + if _, err := os.Stat(s.Key()); err == nil { - t.Fatalf("The sandbox path %s is not getting cleanup event after twice the cleanup period", s.Key()) + if wait { + t.Fatalf("The sandbox path %s is not getting cleaned up even after twice the cleanup period", s.Key()) + } else { + t.Fatalf("The sandbox path %s is not cleaned up after running gc", s.Key()) + } } } diff --git a/libnetwork/sandbox/sandbox_test.go b/libnetwork/sandbox/sandbox_test.go index 639dc7917d..ca0d6ba1b3 100644 --- a/libnetwork/sandbox/sandbox_test.go +++ b/libnetwork/sandbox/sandbox_test.go @@ -54,7 +54,7 @@ func TestSandboxCreate(t *testing.T) { verifySandbox(t, s) s.Destroy() - verifyCleanup(t, s) + verifyCleanup(t, s, true) } func TestSandboxCreateTwice(t *testing.T) { @@ -77,6 +77,23 @@ func TestSandboxCreateTwice(t *testing.T) { s.Destroy() } +func TestSandboxGC(t *testing.T) { + key, err := newKey(t) + if err != nil { + t.Fatalf("Failed to obtain a key: %v", err) + } + + s, err := NewSandbox(key, true) + if err != nil { + t.Fatalf("Failed to create a new sandbox: %v", err) + } + + s.Destroy() + + GC() + verifyCleanup(t, s, false) +} + func TestAddRemoveInterface(t *testing.T) { key, err := newKey(t) if err != nil {