diff --git a/daemon/daemon.go b/daemon/daemon.go index 33e880ecdd8222f4d81e732c05ae1e7eb1023f7d..af5f883a808dc024b50135f1a580b5b57a27e975 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -71,16 +71,15 @@ func NewDaemon(opts Options, store store.Store) *Daemon { // Run runs the daemon process func (d *Daemon) Run() error { - - if err := d.registerSubReaper(); err != nil { - return err - } - ctx, cancel := context.WithCancel(context.Background()) defer cancel() gc := gc.NewGC() gc.StartGC(ctx) + if err := d.registerSubReaper(gc); err != nil { + return err + } + logrus.Debugf("Daemon start with option %#v", d.opts) // Ensure we have only one daemon running at the same time @@ -186,30 +185,36 @@ func (d *Daemon) Cleanup() error { return err } -func (d *Daemon) registerSubReaper() error { +func (d *Daemon) registerSubReaper(g *gc.GarbageCollector) error { if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(1), 0, 0, 0); err != nil { //nolint, gomod return errors.Errorf("set subreaper failed: %v", err) } - go d.childProcessReap() - return nil -} -func (d *Daemon) childProcessReap() { - reapInterval := 10 * time.Second - var err error - for { - time.Sleep(reapInterval) - d.Lock() + childProcessReap := func(i interface{}) error { + var err error + + daemonTmp := i.(*Daemon) + daemonTmp.Lock() + defer daemonTmp.Unlock() + // if any of image build process is running, skip reap - if len(d.builders) != 0 { - d.Unlock() - continue + if len(daemonTmp.builders) != 0 { + return nil } if _, err = sys.Reap(false); err != nil { logrus.Errorf("Reap child process error: %v", err) } - d.Unlock() + return err } + + opt := &gc.RegisterOption{ + Name: "subReaper", + Interval: 10 * time.Second, + RecycleData: d, + RecycleFunc: childProcessReap, + } + + return g.RegisterGC(opt) } // setDaemonLock will check if there is another daemon running and return error if any diff --git a/pkg/gc/gc.go b/pkg/gc/gc.go index c8e4f1f073aaae551f99524b087ae88fe89a23b0..d71ae9f27be571207855d3db7d9187c5e426e5c1 100644 --- a/pkg/gc/gc.go +++ b/pkg/gc/gc.go @@ -1,18 +1,17 @@ -/****************************************************************************** - * Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. - * isula-build licensed under the Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR - * PURPOSE. - * See the Mulan PSL v2 for more details. - * Author: Feiyu Yang - * Create: 2020-06-20 - * Description: This file is used for recycling -******************************************************************************/ - +// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. +// isula-build licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Author: Feiyu Yang +// Create: 2020-06-20 +// Description: This file is used for recycling + +// Package gc provides garbage collectors package gc import ( @@ -26,21 +25,28 @@ import ( // RegisterOption is the register option for GC type RegisterOption struct { - recycleFunc func(interface{}) error - recycleData interface{} - name string - interval time.Duration - once bool + // RecycleFunc is the function that can recycle the resource + RecycleFunc func(interface{}) error + // RecycleData indicates the data to be recycled + RecycleData interface{} + // Name indicates the node name + Name string + // Interval indicates the recycle interval + Interval time.Duration + // Once is true when it is a once time recycle + Once bool } type node struct { - recycleFunc func(interface{}) error - recycleData interface{} - interval time.Duration - lastTrigger time.Time - running bool - once bool - success bool + garbageCollector *GarbageCollector + name string + recycleFunc func(interface{}) error + recycleData interface{} + interval time.Duration + lastTrigger time.Time + running bool + once bool + success bool sync.Mutex } @@ -51,13 +57,15 @@ type GarbageCollector struct { nodes map[string]*node } -func newNode(option *RegisterOption) *node { +func newNode(option *RegisterOption, g *GarbageCollector) *node { return &node{ - recycleFunc: option.recycleFunc, - recycleData: option.recycleData, - interval: option.interval, - lastTrigger: time.Now(), - once: option.once, + garbageCollector: g, + name: option.Name, + recycleFunc: option.RecycleFunc, + recycleData: option.RecycleData, + interval: option.Interval, + lastTrigger: time.Now(), + once: option.Once, } } @@ -70,7 +78,7 @@ func (n *node) isDiscarded() bool { } func (n *node) isReadyToRun(now time.Time) bool { - if n.isDiscarded() || n.running || now.Sub(n.lastTrigger) < n.interval { + if n.running || now.Sub(n.lastTrigger) < n.interval { return false } @@ -81,6 +89,11 @@ func (n *node) checkAndExec(now time.Time) { n.Lock() defer n.Unlock() + if n.isDiscarded() { + go n.garbageCollector.RemoveGCNode(n.name) + return + } + if !n.isReadyToRun(now) { return } @@ -100,8 +113,8 @@ func NewGC() *GarbageCollector { } // RegisterGC registers a recycling function -// once is false when the GC type is loop -// interval is the interval time in every loop +// Once is false when the GC type is loop +// Interval is the Interval time in every loop func (g *GarbageCollector) RegisterGC(option *RegisterOption) error { if option == nil { return errors.New("register option is nil") @@ -110,13 +123,13 @@ func (g *GarbageCollector) RegisterGC(option *RegisterOption) error { g.Lock() defer g.Unlock() - if _, ok := g.nodes[option.name]; ok { - return errors.Errorf("recycle function %s has been registered", option.name) + if _, ok := g.nodes[option.Name]; ok { + return errors.Errorf("recycle function %s has been registered", option.Name) } - g.nodes[option.name] = newNode(option) + g.nodes[option.Name] = newNode(option, g) - logrus.Debugf("Recycle function %s is registered successfully", option.name) + logrus.Infof("Recycle function %s is registered successfully", option.Name) return nil } @@ -137,17 +150,20 @@ func (g *GarbageCollector) StartGC(ctx context.Context) { defer tick.Stop() for { select { - case <-ctx.Done(): + case _, ok := <-ctx.Done(): + if !ok { + logrus.Warnf("Context channel has been closed") + } logrus.Debugf("GC exits now") return - case now := <-tick.C: + case now, ok := <-tick.C: + if !ok { + logrus.Warnf("Time tick channel has been closed") + return + } g.RLock() for name := range g.nodes { n := g.nodes[name] - if n.isDiscarded() { - go g.RemoveGCNode(name) - continue - } go n.checkAndExec(now) } g.RUnlock() diff --git a/pkg/gc/gc_test.go b/pkg/gc/gc_test.go index b48a22649fe54f9052e19a236797f4a680e1d4f0..445c3c83b68cc7ccf14c81a9c7f8df7595a8ca35 100644 --- a/pkg/gc/gc_test.go +++ b/pkg/gc/gc_test.go @@ -1,22 +1,21 @@ -/****************************************************************************** - * Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. - * isula-build licensed under the Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR - * PURPOSE. - * See the Mulan PSL v2 for more details. - * Author: Feiyu Yang - * Create: 2020-06-20 - * Description: This file is used for recycling test -******************************************************************************/ +// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. +// isula-build licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Author: Feiyu Yang +// Create: 2020-06-20 +// Description: This file is used for recycling test package gc import ( "context" + "sync" "testing" "time" @@ -27,6 +26,7 @@ import ( type mockDaemon struct { backend *mockBackend opts string + sync.RWMutex } type mockBackend struct { @@ -62,10 +62,10 @@ func TestGCRoutineExit(t *testing.T) { return nil } registerOption := &RegisterOption{ - name: "routineExit", - recycleFunc: f, - recycleData: d, - interval: 2 * time.Second, + Name: "routineExit", + RecycleFunc: f, + RecycleData: d, + Interval: 2 * time.Second, } err := gcExit.RegisterGC(registerOption) assert.NilError(t, err) @@ -74,35 +74,40 @@ func TestGCRoutineExit(t *testing.T) { cancel() time.Sleep(3 * time.Second) assert.Equal(t, d.backend, emptyBackend) - gcExit.RemoveGCNode(registerOption.name) + gcExit.RemoveGCNode(registerOption.Name) } func TestLoopGC(t *testing.T) { d := &mockDaemon{backend: backend} f := func(i interface{}) error { daemon := i.(*mockDaemon) + daemon.Lock() daemon.backend = emptyBackend + daemon.Unlock() return nil } registerOption := &RegisterOption{ - name: "recycleBackendResource", - recycleFunc: f, - recycleData: d, - interval: time.Second, + Name: "recycleBackendResource", + RecycleFunc: f, + RecycleData: d, + Interval: time.Second, } // register recycleBackendStore and the backend resource will be released err := gc.RegisterGC(registerOption) assert.NilError(t, err) time.Sleep(2 * time.Second) - assert.Equal(t, d.backend, emptyBackend) + d.RLock() + b := d.backend + d.RUnlock() + assert.Equal(t, b, emptyBackend) - // the same name has been registered + // the same Name has been registered err = gc.RegisterGC(registerOption) assert.ErrorContains(t, err, "has been registered") // remove success and the resource won't be recycled again - gc.RemoveGCNode(registerOption.name) + gc.RemoveGCNode(registerOption.Name) d.backend = backend time.Sleep(2 * time.Second) assert.Equal(t, d.backend, backend) @@ -110,16 +115,21 @@ func TestLoopGC(t *testing.T) { // new recycle func will be registered success f2 := func(i interface{}) error { daemon := i.(*mockDaemon) + daemon.Lock() daemon.opts = "ok" + daemon.Unlock() return nil } - registerOption.name = "recycleOpts" - registerOption.recycleFunc = f2 + registerOption.Name = "recycleOpts" + registerOption.RecycleFunc = f2 err = gc.RegisterGC(registerOption) assert.NilError(t, err) time.Sleep(2 * time.Second) - assert.Equal(t, d.opts, "ok") - gc.RemoveGCNode(registerOption.name) + d.RLock() + opts := d.opts + d.RUnlock() + assert.Equal(t, opts, "ok") + gc.RemoveGCNode(registerOption.Name) } func TestOnceGC(t *testing.T) { @@ -128,11 +138,11 @@ func TestOnceGC(t *testing.T) { return errors.New("recycle failed") } registerOption := &RegisterOption{ - name: "recycleTestErr", - recycleFunc: f, - recycleData: d, - interval: time.Second, - once: true, + Name: "recycleTestErr", + RecycleFunc: f, + RecycleData: d, + Interval: time.Second, + Once: true, } // register a gc which will always return error err := gc.RegisterGC(registerOption) @@ -142,52 +152,64 @@ func TestOnceGC(t *testing.T) { // register will failed err = gc.RegisterGC(registerOption) assert.ErrorContains(t, err, "has been registered") - gc.RemoveGCNode(registerOption.name) + gc.RemoveGCNode(registerOption.Name) - d.backend = &mockBackend{status: map[string]string{"once": "once"}} + d.backend = &mockBackend{status: map[string]string{"Once": "Once"}} // register a normal gc f2 := func(i interface{}) error { daemon := i.(*mockDaemon) + daemon.Lock() daemon.backend = emptyBackend + daemon.Unlock() return nil } - registerOption.name = "once" - registerOption.recycleFunc = f2 + registerOption.Name = "Once" + registerOption.RecycleFunc = f2 err = gc.RegisterGC(registerOption) assert.NilError(t, err) time.Sleep(4 * time.Second) - assert.Equal(t, d.backend, emptyBackend) + d.RLock() + b := d.backend + d.RUnlock() + assert.Equal(t, b, emptyBackend) // the last normal gc has been removed after executing, // new gc will be registered successfully err = gc.RegisterGC(registerOption) assert.NilError(t, err) - gc.RemoveGCNode(registerOption.name) + gc.RemoveGCNode(registerOption.Name) } func TestGCAlreadyInRunning(t *testing.T) { d := &mockDaemon{backend: backend} f := func(i interface{}) error { daemon := i.(*mockDaemon) + daemon.Lock() daemon.backend = emptyBackend + daemon.Unlock() time.Sleep(30 * time.Second) return nil } registerOption := &RegisterOption{ - name: "recycleSlow", - recycleFunc: f, - recycleData: d, - interval: time.Second, + Name: "recycleSlow", + RecycleFunc: f, + RecycleData: d, + Interval: time.Second, } err := gc.RegisterGC(registerOption) assert.NilError(t, err) time.Sleep(2 * time.Second) - assert.Equal(t, d.backend, emptyBackend) + d.RLock() + b := d.backend + d.RUnlock() + assert.Equal(t, b, emptyBackend) // a recycling work is doing and it won't be triggered now + d.RLock() d.backend = backend + d.RUnlock() time.Sleep(2 * time.Second) assert.Equal(t, d.backend, backend) - gc.RemoveGCNode(registerOption.name) + gc.RemoveGCNode(registerOption.Name) }