提交 0d0ca246 编写于 作者: O openeuler-ci-bot 提交者: Gitee

!33 GC: Use GC to manage subreaper

Merge pull request !33 from holyfei/master
......@@ -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 = reaper.Reap(); 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
......
/******************************************************************************
* 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()
......
/******************************************************************************
* 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)
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册