Repository: dragonflyoss/Dragonfly2
Pull request #267 (2021-05-26 10:24:49)

Added GC public library
Signed-off-by: Gaius <gaius.qi@gmail.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
- Added GC public library.
<!--- Describe your changes in detail -->

## Related Issue
#266 
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context
Standardize the GC usage of each module.
<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate):
N/A
## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#267](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (bdf45ba) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/724deb697e22af5bc7247a508aa21d3867cf6ed8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (724deb6) will **decrease** coverage by `0.14%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main     #267      +/-   ##
==========================================
- Coverage   44.83%   44.69%   -0.15%     
==========================================
  Files          54       54              
  Lines        4262     4262              
==========================================
- Hits         1911     1905       -6     
- Misses       2113     2120       +7     
+ Partials      238      237       -1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `44.69% <ø> (-0.15%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/daemon/peer/peertask\_stream.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/267/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3N0cmVhbS5nbw==) | `55.84% <0.00%> (-2.27%)` | :arrow_down: |


Patch:
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index 7134ebf5..7f3718e0 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -60,7 +60,6 @@ type PeerHostOption struct {
 	Proxy        *ProxyOption    `mapstructure:"proxy" yaml:"proxy"`
 	Upload       UploadOption    `mapstructure:"upload" yaml:"upload"`
 	Storage      StorageOption   `mapstructure:"storage" yaml:"storage"`
-	Telemetry    TelemetryOption `mapstructure:"telemetry" yaml:"telemetry"`
 	ConfigServer string          `mapstructure:"configServer" yaml:"configServer"`
 }
 
diff --git a/cmd/cdn/cmd/root.go b/cmd/cdn/cmd/root.go
index a0ee8232..8f3ee4aa 100644
--- a/cmd/cdn/cmd/root.go
+++ b/cmd/cdn/cmd/root.go
@@ -73,7 +73,7 @@ func runCdnSystem() error {
 	s, _ := yaml.Marshal(cfg)
 	logger.Infof("cdn system configuration:\n%s", string(s))
 
-	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Jaeger)
+	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry.Jaeger)
 	defer ff()
 
 	svr, err := server.New(cfg)
diff --git a/cmd/dependency/base/option.go b/cmd/dependency/base/option.go
index f3465f6c..d4f90f2c 100644
--- a/cmd/dependency/base/option.go
+++ b/cmd/dependency/base/option.go
@@ -17,8 +17,13 @@
 package base
 
 type Options struct {
-	Console   bool   `yaml:"console" mapstructure:"console"`
-	Verbose   bool   `yaml:"verbose" mapstructure:"verbose"`
-	PProfPort int    `yaml:"pprof-port" mapstructure:"pprof-port"`
-	Jaeger    string `yaml:"jaeger" mapstructure:"jaeger"`
+	Console   bool            `yaml:"console" mapstructure:"console"`
+	Verbose   bool            `yaml:"verbose" mapstructure:"verbose"`
+	PProfPort int             `yaml:"pprof-port" mapstructure:"pprof-port"`
+	Telemetry TelemetryOption `yaml:",inline" mapstructure:",squash"`
+}
+
+// TelemetryOption is the option for telemetry
+type TelemetryOption struct {
+	Jaeger string `yaml:"jaeger" mapstructure:"jaeger"`
 }
diff --git a/cmd/dependency/dependency.go b/cmd/dependency/dependency.go
index f894f1f3..0fdf4e75 100644
--- a/cmd/dependency/dependency.go
+++ b/cmd/dependency/dependency.go
@@ -58,26 +58,26 @@ func InitCobra(cmd *cobra.Command, useConfigFile bool, config interface{}) {
 	rootName := cmd.Root().Name()
 	cobra.OnInitialize(func() { initConfig(useConfigFile, rootName, config) })
 
-	// Add common flags
-	flags := cmd.Flags()
-	flags.Bool("console", false, "whether logger output records to the stdout")
-	flags.Bool("verbose", false, "whether logger use debug level")
-	flags.Int("pprof-port", -1, "listen port for pprof, 0 represents random port")
-	flags.String("jaeger", "", "jaeger endpoint url, like: http://localhost:14250/api/traces")
-	flags.String("config", "", fmt.Sprintf("the path of configuration file with yaml extension name, default is %s, it can also be set by env var:%s", filepath.Join(dfpath.DefaultConfigDir, rootName+".yaml"), strings.ToUpper(rootName+"_config")))
-
-	// Bind common flags
-	if err := viper.BindPFlags(flags); err != nil {
-		panic(errors.Wrap(err, "bind common flags to viper"))
-	}
+	if !cmd.HasParent() {
+		// Add common flags
+		flags := cmd.PersistentFlags()
+		flags.Bool("console", false, "whether logger output records to the stdout")
+		flags.Bool("verbose", false, "whether logger use debug level")
+		flags.Int("pprof-port", -1, "listen port for pprof, 0 represents random port")
+		flags.String("jaeger", "", "jaeger endpoint url, like: http://localhost:14250/api/traces")
+		flags.String("config", "", fmt.Sprintf("the path of configuration file with yaml extension name, default is %s, it can also be set by env var:%s", filepath.Join(dfpath.DefaultConfigDir, rootName+".yaml"), strings.ToUpper(rootName+"_config")))
+
+		// Bind common flags
+		if err := viper.BindPFlags(flags); err != nil {
+			panic(errors.Wrap(err, "bind common flags to viper"))
+		}
 
-	// Config for binding env
-	viper.SetEnvPrefix(rootName)
-	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-	_ = viper.BindEnv("config")
+		// Config for binding env
+		viper.SetEnvPrefix(rootName)
+		viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+		_ = viper.BindEnv("config")
 
-	// Add common cmds only on root cmd
-	if !cmd.HasParent() {
+		// Add common cmds only on root cmd
 		cmd.AddCommand(VersionCmd)
 		cmd.AddCommand(newDocCommand(cmd.Name()))
 	}
diff --git a/cmd/dfget/cmd/daemon.go b/cmd/dfget/cmd/daemon.go
index 3f062f14..f5b4e95d 100644
--- a/cmd/dfget/cmd/daemon.go
+++ b/cmd/dfget/cmd/daemon.go
@@ -133,7 +133,7 @@ func runDaemon() error {
 	s, _ := yaml.Marshal(cfg)
 	logger.Infof("client daemon configuration:\n%s", string(s))
 
-	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Jaeger)
+	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry.Jaeger)
 	defer ff()
 
 	svr, err := server.New(cfg)
diff --git a/cmd/dfget/cmd/root.go b/cmd/dfget/cmd/root.go
index 1d398c17..c44665e7 100644
--- a/cmd/dfget/cmd/root.go
+++ b/cmd/dfget/cmd/root.go
@@ -153,7 +153,7 @@ func runDfget() error {
 	s, _ := yaml.Marshal(dfgetConfig)
 	logger.Infof("client dfget configuration:\n%s", string(s))
 
-	ff := dependency.InitMonitor(dfgetConfig.Verbose, dfgetConfig.PProfPort, dfgetConfig.Jaeger)
+	ff := dependency.InitMonitor(dfgetConfig.Verbose, dfgetConfig.PProfPort, dfgetConfig.Telemetry.Jaeger)
 	defer ff()
 
 	logger.Info("start to check and spawn daemon")
diff --git a/cmd/manager/cmd/root.go b/cmd/manager/cmd/root.go
index 0c5ecd13..de6fd96e 100644
--- a/cmd/manager/cmd/root.go
+++ b/cmd/manager/cmd/root.go
@@ -72,7 +72,7 @@ func runManager() error {
 	s, _ := yaml.Marshal(cfg)
 	logger.Infof("manager configuration:\n%s", string(s))
 
-	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Jaeger)
+	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry.Jaeger)
 	defer ff()
 
 	svr, err := server.New(cfg)
diff --git a/cmd/scheduler/cmd/root.go b/cmd/scheduler/cmd/root.go
index 82c00ae2..ad78022b 100644
--- a/cmd/scheduler/cmd/root.go
+++ b/cmd/scheduler/cmd/root.go
@@ -78,7 +78,7 @@ func runScheduler() error {
 	s, _ := yaml.Marshal(cfg)
 	logger.Infof("scheduler configuration:\n%s", string(s))
 
-	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Jaeger)
+	ff := dependency.InitMonitor(cfg.Verbose, cfg.PProfPort, cfg.Telemetry.Jaeger)
 	defer ff()
 
 	svr, err := server.New(cfg)
diff --git a/internal/dynconfig/dynconfig.go b/internal/dynconfig/dynconfig.go
index 4aeadf55..efce43f9 100644
--- a/internal/dynconfig/dynconfig.go
+++ b/internal/dynconfig/dynconfig.go
@@ -108,7 +108,7 @@ func New(sourceType SourceType, options ...Option) (*Dynconfig, error) {
 		return nil, err
 	}
 
-	if err := d.Validate(); err != nil {
+	if err := d.validate(); err != nil {
 		return nil, err
 	}
 
@@ -145,8 +145,8 @@ func NewDynconfigWithOptions(sourceType SourceType, options ...Option) (*Dynconf
 	return d, nil
 }
 
-// Validate parameters
-func (d *Dynconfig) Validate() error {
+// validate parameters
+func (d *Dynconfig) validate() error {
 	if d.sourceType == ManagerSourceType {
 		if d.managerClient == nil {
 			return errors.New("missing parameter ManagerClient, use method WithManagerClient to assign")
diff --git a/pkg/gc/gc.go b/pkg/gc/gc.go
new file mode 100644
index 00000000..7c1b75f0
--- /dev/null
+++ b/pkg/gc/gc.go
@@ -0,0 +1,181 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package gc
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"time"
+
+	"github.com/sirupsen/logrus"
+)
+
+// GC is the interface used for release resource
+type GC interface {
+	// Add adds GC task
+	Add(string, Task)
+
+	// Run GC task
+	Run(string) error
+
+	// Run all registered GC tasks
+	RunAll()
+
+	// Serve running the GC task
+	Serve()
+
+	// Stop running the GC task
+	Stop()
+}
+
+// GC provides task release function
+type gc struct {
+	tasks    *sync.Map
+	interval time.Duration
+	timeout  time.Duration
+	logger   Logger
+	done     chan bool
+}
+
+// Option is a functional option for configuring the GC
+type Option func(g *gc)
+
+// WithInterval set the interval for GC collection
+func WithInterval(interval time.Duration) Option {
+	return func(g *gc) {
+		g.interval = interval
+	}
+}
+
+// WithTimeout set the timeout for GC collection
+func WithTimeout(timeout time.Duration) Option {
+	return func(g *gc) {
+		g.timeout = timeout
+	}
+}
+
+// WithLogger set the logger for GC
+func WithLogger(logger Logger) Option {
+	return func(g *gc) {
+		g.logger = logger
+	}
+}
+
+// New returns a new GC instence
+func New(options ...Option) (GC, error) {
+	g := &gc{
+		tasks:  &sync.Map{},
+		done:   make(chan bool),
+		logger: logrus.New(),
+	}
+
+	for _, opt := range options {
+		opt(g)
+	}
+
+	if err := g.validate(); err != nil {
+		return nil, err
+	}
+
+	return g, nil
+}
+
+func (g gc) Add(k string, t Task) {
+	g.tasks.Store(k, t)
+}
+
+func (g gc) Run(k string) error {
+	v, ok := g.tasks.Load(k)
+	if !ok {
+		return errors.New("can not find the task")
+	}
+
+	go g.run(context.Background(), k, v.(Task))
+	return nil
+}
+
+func (g gc) RunAll() {
+	g.runAll(context.Background())
+}
+
+func (g gc) Serve() {
+	go func() {
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		tick := time.NewTicker(g.interval)
+		for {
+			select {
+			case <-tick.C:
+				g.runAll(ctx)
+			case <-g.done:
+				g.logger.Infof("GC stop")
+				return
+			}
+		}
+	}()
+}
+
+func (g gc) Stop() {
+	close(g.done)
+}
+
+func (g gc) validate() error {
+	if g.interval <= 0 {
+		return errors.New("interval value is greater than 0")
+	}
+
+	if g.timeout <= 0 {
+		return errors.New("timeout value is greater than 0")
+	}
+
+	if g.timeout >= g.interval {
+		return errors.New("timeout value needs to be less than the interval value")
+	}
+
+	return nil
+}
+
+func (g gc) runAll(ctx context.Context) {
+	g.tasks.Range(func(k, v interface{}) bool {
+		go g.run(ctx, k.(string), v.(Task))
+		return true
+	})
+}
+
+func (g gc) run(ctx context.Context, k string, t Task) {
+	done := make(chan struct{})
+
+	go func() {
+		g.logger.Infof("%s GC %s", k, "start")
+		defer close(done)
+		if err := t.RunGC(); err != nil {
+			g.logger.Errorf("%s GC error: %v", k, err)
+			return
+		}
+	}()
+
+	select {
+	case <-time.After(g.timeout):
+		g.logger.Infof("%s GC %s", k, "timeout")
+	case <-done:
+		g.logger.Infof("%s GC %s", k, "done")
+	case <-ctx.Done():
+		g.logger.Infof("%s GC %s", k, "stop")
+	}
+}
diff --git a/pkg/gc/logger.go b/pkg/gc/logger.go
new file mode 100644
index 00000000..41f07905
--- /dev/null
+++ b/pkg/gc/logger.go
@@ -0,0 +1,25 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package gc
+
+// Logger is the interface used in GC for logging
+type Logger interface {
+	// Infof logs routine messages for GC
+	Infof(template string, args ...interface{})
+	// Error logs error messages for GC
+	Errorf(template string, args ...interface{})
+}
diff --git a/pkg/gc/mocks/logger_mock.go b/pkg/gc/mocks/logger_mock.go
new file mode 100644
index 00000000..ef6e47a8
--- /dev/null
+++ b/pkg/gc/mocks/logger_mock.go
@@ -0,0 +1,68 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: ./logger.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockLogger is a mock of Logger interface.
+type MockLogger struct {
+	ctrl     *gomock.Controller
+	recorder *MockLoggerMockRecorder
+}
+
+// MockLoggerMockRecorder is the mock recorder for MockLogger.
+type MockLoggerMockRecorder struct {
+	mock *MockLogger
+}
+
+// NewMockLogger creates a new mock instance.
+func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
+	mock := &MockLogger{ctrl: ctrl}
+	mock.recorder = &MockLoggerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
+	return m.recorder
+}
+
+// Errorf mocks base method.
+func (m *MockLogger) Errorf(template string, args ...interface{}) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{template}
+	for _, a := range args {
+		varargs = append(varargs, a)
+	}
+	m.ctrl.Call(m, "Errorf", varargs...)
+}
+
+// Errorf indicates an expected call of Errorf.
+func (mr *MockLoggerMockRecorder) Errorf(template interface{}, args ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{template}, args...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...)
+}
+
+// Infof mocks base method.
+func (m *MockLogger) Infof(template string, args ...interface{}) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{template}
+	for _, a := range args {
+		varargs = append(varargs, a)
+	}
+	m.ctrl.Call(m, "Infof", varargs...)
+}
+
+// Infof indicates an expected call of Infof.
+func (mr *MockLoggerMockRecorder) Infof(template interface{}, args ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{template}, args...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...)
+}
diff --git a/pkg/gc/mocks/task_mock.go b/pkg/gc/mocks/task_mock.go
new file mode 100644
index 00000000..6e797827
--- /dev/null
+++ b/pkg/gc/mocks/task_mock.go
@@ -0,0 +1,48 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: ./task.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockTask is a mock of Task interface.
+type MockTask struct {
+	ctrl     *gomock.Controller
+	recorder *MockTaskMockRecorder
+}
+
+// MockTaskMockRecorder is the mock recorder for MockTask.
+type MockTaskMockRecorder struct {
+	mock *MockTask
+}
+
+// NewMockTask creates a new mock instance.
+func NewMockTask(ctrl *gomock.Controller) *MockTask {
+	mock := &MockTask{ctrl: ctrl}
+	mock.recorder = &MockTaskMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockTask) EXPECT() *MockTaskMockRecorder {
+	return m.recorder
+}
+
+// RunGC mocks base method.
+func (m *MockTask) RunGC() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "RunGC")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// RunGC indicates an expected call of RunGC.
+func (mr *MockTaskMockRecorder) RunGC() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunGC", reflect.TypeOf((*MockTask)(nil).RunGC))
+}
diff --git a/pkg/gc/task.go b/pkg/gc/task.go
new file mode 100644
index 00000000..f17e536e
--- /dev/null
+++ b/pkg/gc/task.go
@@ -0,0 +1,23 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package gc
+
+// Task is an interface used to run GC instance
+type Task interface {
+	// The actual function to run GC task
+	RunGC() error
+}
diff --git a/pkg/structure/queue/lru_queue.go b/pkg/structure/queue/lru_queue.go
deleted file mode 100644
index a26db49b..00000000
--- a/pkg/structure/queue/lru_queue.go
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package queue
-
-import (
-	"container/list"
-	"sync"
-
-	"d7y.io/dragonfly/v2/pkg/dferrors"
-)
-
-// cQElementData is the value of list.Element.Value.
-// It records the key and data of item.
-type cQElementData struct {
-	key  string
-	data interface{}
-}
-
-// LRUQueue is implementation of LRU.
-type LRUQueue struct {
-	lock     sync.Mutex
-	capacity int
-
-	itemMap map[string]*list.Element
-	l       *list.List
-}
-
-func NewLRUQueue(capacity int) *LRUQueue {
-	return &LRUQueue{
-		capacity: capacity,
-		itemMap:  make(map[string]*list.Element, capacity),
-		l:        list.New(),
-	}
-}
-
-// Put puts item to front, return the obsolete item
-func (q *LRUQueue) Put(key string, data interface{}) (obsoleteKey string, obsoleteData interface{}) {
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	if i, ok := q.itemMap[key]; ok {
-		i.Value.(*cQElementData).data = data
-		q.putAtFront(i)
-		return
-	}
-
-	if len(q.itemMap) >= q.capacity {
-		// remove the earliest item
-		i := q.removeFromTail()
-		if i != nil {
-			delete(q.itemMap, i.Value.(*cQElementData).key)
-			obsoleteKey = i.Value.(*cQElementData).key
-			obsoleteData = i.Value.(*cQElementData).data
-		}
-	}
-
-	i := q.putValue(&cQElementData{key: key, data: data})
-	q.itemMap[key] = i
-	return
-}
-
-// Get will return the item by key. And it will put the item to front.
-func (q *LRUQueue) Get(key string) (interface{}, error) {
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	data, exist := q.itemMap[key]
-	if !exist {
-		return nil, dferrors.ErrDataNotFound
-	}
-
-	q.putAtFront(data)
-
-	return data.Value.(*cQElementData).data, nil
-}
-
-// GetFront will get several items from front and not poll out them.
-func (q *LRUQueue) GetFront(count int) []interface{} {
-	if count <= 0 {
-		return nil
-	}
-
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	result := make([]interface{}, count)
-	item := q.l.Front()
-	index := 0
-	for {
-		if item == nil {
-			break
-		}
-
-		result[index] = item.Value.(*cQElementData).data
-		index++
-		if index >= count {
-			break
-		}
-
-		item = item.Next()
-	}
-
-	return result[:index]
-}
-
-// GetItemByKey will return the item by key. But it will not put the item to front.
-func (q *LRUQueue) GetItemByKey(key string) (interface{}, error) {
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	if data, exist := q.itemMap[key]; exist {
-		return data.Value.(*cQElementData).data, nil
-	}
-
-	return nil, dferrors.ErrDataNotFound
-}
-
-// Delete deletes the item by key, return the deleted item if item exists.
-func (q *LRUQueue) Delete(key string) interface{} {
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	data, exist := q.itemMap[key]
-	if !exist {
-		return nil
-	}
-
-	retData := data.Value.(*cQElementData).data
-	delete(q.itemMap, key)
-	q.removeElement(data)
-
-	return retData
-}
-
-func (q *LRUQueue) putAtFront(i *list.Element) {
-	q.l.MoveToFront(i)
-}
-
-func (q *LRUQueue) putValue(data interface{}) *list.Element {
-	e := q.l.PushFront(data)
-	return e
-}
-
-func (q *LRUQueue) removeFromTail() *list.Element {
-	e := q.l.Back()
-	q.l.Remove(e)
-
-	return e
-}
-
-func (q *LRUQueue) removeElement(i *list.Element) {
-	q.l.Remove(i)
-}
diff --git a/pkg/structure/queue/queue.go b/pkg/structure/queue/queue.go
deleted file mode 100644
index 349726f5..00000000
--- a/pkg/structure/queue/queue.go
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package queue
-
-import (
-	"container/list"
-
-	"d7y.io/dragonfly/v2/pkg/util/ifaceutils"
-
-	"sync"
-	"sync/atomic"
-	"time"
-	"unsafe"
-)
-
-// Queue blocking queue. The items putted into queue mustn't be nil.
-type Queue interface {
-	// Put puts item into the queue and keeps blocking if the queue is full.
-	// It will return immediately and do nothing if the item is nil.
-	Put(item interface{})
-
-	// PutTimeout puts item into the queue and waits for timeout if the queue is full.
-	// If timeout <= 0, it will return false immediately when queue is full.
-	// It will return immediately and do nothing if the item is nil.
-	PutTimeout(item interface{}, timeout time.Duration) bool
-
-	// Poll gets an item from the queue and keeps blocking if the queue is empty.
-	Poll() interface{}
-
-	// PollTimeout gets an item from the queue and waits for timeout if the queue is empty.
-	// If timeout <= 0, it will return (nil, bool) immediately when queue is empty.
-	PollTimeout(timeout time.Duration) (interface{}, bool)
-
-	// Len returns the current size of the queue.
-	Len() int
-}
-
-// NewQueue creates a blocking queue.
-// If capacity <= 0, the queue capacity is infinite.
-func NewQueue(capacity int) Queue {
-	if capacity <= 0 {
-		c := make(chan struct{})
-		return &infiniteQueue{
-			store: list.New(),
-			empty: unsafe.Pointer(&c),
-		}
-	}
-	return &finiteQueue{
-		store: make(chan interface{}, capacity),
-	}
-}
-
-// infiniteQueue implements infinite blocking queue.
-type infiniteQueue struct {
-	sync.Mutex
-	store *list.List
-	empty unsafe.Pointer
-}
-
-var _ Queue = &infiniteQueue{}
-
-func (q *infiniteQueue) Put(item interface{}) {
-	if ifaceutils.IsNil(item) {
-		return
-	}
-	q.Lock()
-	defer q.Unlock()
-	q.store.PushBack(item)
-	if q.store.Len() < 2 {
-		// empty -> has one element
-		q.broadcast()
-	}
-}
-
-func (q *infiniteQueue) PutTimeout(item interface{}, timeout time.Duration) bool {
-	q.Put(item)
-	return !ifaceutils.IsNil(item)
-}
-
-func (q *infiniteQueue) Poll() interface{} {
-	q.Lock()
-	defer q.Unlock()
-	for q.store.Len() == 0 {
-		q.wait()
-	}
-	item := q.store.Front()
-	q.store.Remove(item)
-	return item.Value
-}
-
-func (q *infiniteQueue) PollTimeout(timeout time.Duration) (interface{}, bool) {
-	deadline := time.Now().Add(timeout)
-	q.Lock()
-	defer q.Unlock()
-	for q.store.Len() == 0 {
-		timeout = -time.Since(deadline)
-		if timeout <= 0 || !q.waitTimeout(timeout) {
-			return nil, false
-		}
-	}
-	item := q.store.Front()
-	q.store.Remove(item)
-	return item.Value, true
-}
-
-func (q *infiniteQueue) Len() int {
-	q.Lock()
-	defer q.Unlock()
-	return q.store.Len()
-}
-
-func (q *infiniteQueue) wait() {
-	c := q.notifyChan()
-	q.Unlock()
-	defer q.Lock()
-	<-c
-}
-
-func (q *infiniteQueue) waitTimeout(timeout time.Duration) bool {
-	c := q.notifyChan()
-
-	q.Unlock()
-	defer q.Lock()
-	select {
-	case <-c:
-		return true
-	case <-time.After(timeout):
-		return false
-	}
-}
-
-func (q *infiniteQueue) notifyChan() <-chan struct{} {
-	ptr := atomic.LoadPointer(&q.empty)
-	return *((*chan struct{})(ptr))
-}
-
-// broadcast notifies all the Poll goroutines to re-check whether the queue is empty.
-func (q *infiniteQueue) broadcast() {
-	c := make(chan struct{})
-	old := atomic.SwapPointer(&q.empty, unsafe.Pointer(&c))
-	close(*(*chan struct{})(old))
-}
-
-// finiteQueue implements finite blocking queue by buffered channel.
-type finiteQueue struct {
-	store chan interface{}
-}
-
-func (q *finiteQueue) Put(item interface{}) {
-	if ifaceutils.IsNil(item) {
-		return
-	}
-	q.store <- item
-}
-
-func (q *finiteQueue) PutTimeout(item interface{}, timeout time.Duration) bool {
-	if ifaceutils.IsNil(item) {
-		return false
-	}
-	if timeout <= 0 {
-		select {
-		case q.store <- item:
-			return true
-		default:
-			return false
-		}
-	}
-	select {
-	case q.store <- item:
-		return true
-	case <-time.After(timeout):
-		return false
-	}
-}
-
-func (q *finiteQueue) Poll() interface{} {
-	item := <-q.store
-	return item
-}
-
-func (q *finiteQueue) PollTimeout(timeout time.Duration) (interface{}, bool) {
-	if timeout <= 0 {
-		select {
-		case item := <-q.store:
-			return item, true
-		default:
-			return nil, false
-		}
-	}
-	select {
-	case item := <-q.store:
-		return item, true
-	case <-time.After(timeout):
-		return nil, false
-	}
-}
-
-func (q *finiteQueue) Len() int {
-	return len(q.store)
-}


Test patch:
diff --git a/pkg/gc/gc_test.go b/pkg/gc/gc_test.go
new file mode 100644
index 00000000..84e30078
--- /dev/null
+++ b/pkg/gc/gc_test.go
@@ -0,0 +1,270 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package gc
+
+import (
+	"errors"
+	"sync"
+	"testing"
+	"time"
+
+	"d7y.io/dragonfly/v2/pkg/gc/mocks"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGCNew(t *testing.T) {
+	tests := []struct {
+		name     string
+		interval time.Duration
+		timeout  time.Duration
+		expect   func(t *testing.T, err error)
+	}{
+		{
+			name:     "new GC instance succeeded",
+			interval: 2 * time.Second,
+			timeout:  1 * time.Second,
+			expect: func(t *testing.T, err error) {
+				assert := assert.New(t)
+				assert.NoError(err)
+			},
+		},
+		{
+			name:     "new GC without interval",
+			interval: 0,
+			timeout:  1 * time.Second,
+			expect: func(t *testing.T, err error) {
+				assert := assert.New(t)
+				assert.EqualError(err, "interval value is greater than 0")
+			},
+		},
+		{
+			name:     "new GC without timeout",
+			interval: 2 * time.Second,
+			timeout:  0,
+			expect: func(t *testing.T, err error) {
+				assert := assert.New(t)
+				assert.EqualError(err, "timeout value is greater than 0")
+			},
+		},
+		{
+			name:     "timeout is greater than interval",
+			interval: 1 * time.Second,
+			timeout:  2 * time.Second,
+			expect: func(t *testing.T, err error) {
+				assert := assert.New(t)
+				assert.EqualError(err, "timeout value needs to be less than the interval value")
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			ctl := gomock.NewController(t)
+			mockLogger := mocks.NewMockLogger(ctl)
+
+			_, err := New([]Option{
+				WithInterval(tc.interval),
+				WithTimeout(tc.timeout),
+				WithLogger(mockLogger),
+			}...)
+
+			tc.expect(t, err)
+		})
+	}
+}
+
+func TestGCRun(t *testing.T) {
+	tests := []struct {
+		name     string
+		taskName string
+		interval time.Duration
+		timeout  time.Duration
+		run      func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask, t *testing.T)
+	}{
+		{
+			name:     "run task succeeded",
+			taskName: "foo",
+			interval: 2 * time.Hour,
+			timeout:  1 * time.Hour,
+			run: func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask, t *testing.T) {
+				var wg sync.WaitGroup
+				wg.Add(3)
+				defer wg.Wait()
+
+				gomock.InOrder(
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("start")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					mt.EXPECT().RunGC().Do(func() { wg.Done() }).Return(nil).Times(1),
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("done")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+				)
+
+				gc.Add(taskName, mt)
+				gc.Run(taskName)
+			},
+		},
+		{
+			name:     "task run GC failed",
+			taskName: "foo",
+			interval: 2 * time.Hour,
+			timeout:  1 * time.Hour,
+			run: func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask, t *testing.T) {
+				var wg sync.WaitGroup
+				wg.Add(4)
+				defer wg.Wait()
+
+				err := errors.New("bar")
+				gomock.InOrder(
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("start")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					mt.EXPECT().RunGC().Do(func() { wg.Done() }).Return(err).Times(1),
+					ml.EXPECT().Errorf(gomock.Any(), gomock.Eq("foo"), gomock.Eq(err)).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("done")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+				)
+
+				gc.Add(taskName, mt)
+				gc.Run(taskName)
+			},
+		},
+		{
+			name:     "task load wrong key",
+			taskName: "foo",
+			interval: 2 * time.Hour,
+			timeout:  1 * time.Hour,
+			run: func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask, t *testing.T) {
+				assert := assert.New(t)
+				gc.Add(taskName, mt)
+				err := gc.Run("bar")
+				assert.EqualError(err, "can not find the task")
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			ctl := gomock.NewController(t)
+			mockLogger := mocks.NewMockLogger(ctl)
+			mockTask := mocks.NewMockTask(ctl)
+
+			gc, err := New([]Option{
+				WithInterval(tc.interval),
+				WithTimeout(tc.timeout),
+				WithLogger(mockLogger),
+			}...)
+			if err != nil {
+				t.Fatal(err)
+			}
+			tc.run(gc, tc.taskName, mockLogger, mockTask, t)
+		})
+	}
+}
+
+func TestGCRunAll(t *testing.T) {
+	tests := []struct {
+		name     string
+		taskName string
+		interval time.Duration
+		timeout  time.Duration
+		run      func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask)
+	}{
+		{
+			name:     "run task succeeded",
+			taskName: "foo",
+			interval: 2 * time.Hour,
+			timeout:  1 * time.Hour,
+			run: func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask) {
+				var wg sync.WaitGroup
+				wg.Add(3)
+				defer wg.Wait()
+
+				gomock.InOrder(
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("start")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					mt.EXPECT().RunGC().Do(func() { wg.Done() }).Return(nil).Times(1),
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("done")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+				)
+
+				gc.Add(taskName, mt)
+				gc.RunAll()
+			},
+		},
+		{
+			name:     "task run GC failed",
+			taskName: "foo",
+			interval: 2 * time.Hour,
+			timeout:  1 * time.Hour,
+			run: func(gc GC, taskName string, ml *mocks.MockLogger, mt *mocks.MockTask) {
+				var wg sync.WaitGroup
+				wg.Add(4)
+				defer wg.Wait()
+
+				err := errors.New("bar")
+				gomock.InOrder(
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("start")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					mt.EXPECT().RunGC().Do(func() { wg.Done() }).Return(err).Times(1),
+					ml.EXPECT().Errorf(gomock.Any(), gomock.Eq("foo"), gomock.Eq(err)).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+					ml.EXPECT().Infof(gomock.Any(), gomock.Eq("foo"), gomock.Eq("done")).Do(func(template interface{}, args ...interface{}) { wg.Done() }).Times(1),
+				)
+
+				gc.Add(taskName, mt)
+				gc.RunAll()
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			ctl := gomock.NewController(t)
+			mockLogger := mocks.NewMockLogger(ctl)
+			mockTask := mocks.NewMockTask(ctl)
+			gc, err := New([]Option{
+				WithInterval(tc.interval),
+				WithTimeout(tc.timeout),
+				WithLogger(mockLogger),
+			}...)
+			if err != nil {
+				t.Fatal(err)
+			}
+			tc.run(gc, tc.taskName, mockLogger, mockTask)
+		})
+	}
+}
+
+func TestGCServe(t *testing.T) {
+	ctl := gomock.NewController(t)
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	defer wg.Wait()
+
+	mockLogger := mocks.NewMockLogger(ctl)
+	mockTask := mocks.NewMockTask(ctl)
+
+	gc, err := New([]Option{
+		WithInterval(2 * time.Hour),
+		WithTimeout(1 * time.Hour),
+		WithLogger(mockLogger),
+	}...)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mockLogger.EXPECT().Infof(gomock.Eq("GC stop")).Do(func(args ...interface{}) {
+		wg.Done()
+	}).Times(1)
+
+	gc.Add("foo", mockTask)
+	gc.Serve()
+	gc.Stop()
+}
diff --git a/pkg/structure/queue/lru_queue_test.go b/pkg/structure/queue/lru_queue_test.go
deleted file mode 100644
index 130c6f7c..00000000
--- a/pkg/structure/queue/lru_queue_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package queue
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/suite"
-)
-
-func TestLruQueue(t *testing.T) {
-	suite.Run(t, new(LruQueueTestSuite))
-}
-
-type LruQueueTestSuite struct {
-	suite.Suite
-}
-
-func (suite *LruQueueTestSuite) TestLRUQueue() {
-	q := NewLRUQueue(5)
-
-	q.Put("key1", 1)
-
-	v1, err := q.GetItemByKey("key1")
-	suite.Nil(err)
-	suite.Equal(v1.(int), 1)
-
-	items := q.GetFront(1)
-	suite.Equal(len(items), 1)
-	suite.Equal(items[0], 1)
-
-	q.Put("key2", 2)
-	q.Put("key1", 3)
-
-	v1, err = q.GetItemByKey("key1")
-	suite.Nil(err)
-	suite.Equal(v1.(int), 3)
-
-	items = q.GetFront(10)
-	suite.Equal(len(items), 2)
-	suite.Equal(items[0], 3)
-	suite.Equal(items[1], 2)
-
-	items = q.GetFront(1)
-	suite.Equal(len(items), 1)
-	suite.Equal(items[0], 3)
-
-	_, err = q.GetItemByKey("key3")
-	suite.NotNil(err)
-
-	obsoleteKey, _ := q.Put("key3", "data3")
-	suite.Equal(obsoleteKey, "")
-	obsoleteKey, _ = q.Put("key4", "data4")
-	suite.Equal(obsoleteKey, "")
-	obsoleteKey, _ = q.Put("key5", "data5")
-	suite.Equal(obsoleteKey, "")
-
-	items = q.GetFront(10)
-	suite.Equal(len(items), 5)
-	suite.Equal(items[0], "data5")
-	suite.Equal(items[1], "data4")
-	suite.Equal(items[2], "data3")
-	suite.Equal(items[3], 3)
-	suite.Equal(items[4], 2)
-
-	obsoleteKey, obsoleteData := q.Put("key6", "data6")
-	suite.Equal(obsoleteKey, "key2")
-	suite.Equal(obsoleteData.(int), 2)
-	_, err = q.GetItemByKey("key2")
-	suite.NotNil(err)
-
-	items = q.GetFront(5)
-	suite.Equal(len(items), 5)
-	suite.Equal(items[0], "data6")
-	suite.Equal(items[1], "data5")
-	suite.Equal(items[2], "data4")
-	suite.Equal(items[3], "data3")
-	suite.Equal(items[4], 3)
-
-	v1, err = q.Get("key5")
-	suite.Nil(err)
-	suite.Equal(v1.(string), "data5")
-
-	items = q.GetFront(5)
-	suite.Equal(len(items), 5)
-	suite.Equal(items[0], "data5")
-	suite.Equal(items[1], "data6")
-	suite.Equal(items[2], "data4")
-	suite.Equal(items[3], "data3")
-	suite.Equal(items[4], 3)
-
-	v1 = q.Delete("key3")
-	suite.Equal(v1, "data3")
-
-	items = q.GetFront(5)
-	suite.Equal(len(items), 4)
-	suite.Equal(items[0], "data5")
-	suite.Equal(items[1], "data6")
-	suite.Equal(items[2], "data4")
-	suite.Equal(items[3], 3)
-
-	v1 = q.Delete("key3")
-	suite.Nil(v1)
-}
diff --git a/pkg/structure/queue/queue_test.go b/pkg/structure/queue/queue_test.go
deleted file mode 100644
index 5fa14f78..00000000
--- a/pkg/structure/queue/queue_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package queue
-
-import (
-	"math/rand"
-	"sync"
-	"sync/atomic"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-)
-
-func TestQueue(t *testing.T) {
-	suite.Run(t, new(QueueTestSuite))
-}
-
-type QueueTestSuite struct {
-	suite.Suite
-}
-
-func (suite *QueueTestSuite) SetupTest() {
-	rand.Seed(time.Now().UnixNano())
-}
-
-func (suite *QueueTestSuite) TestQueue_infiniteQueue() {
-	timeout := 50 * time.Millisecond
-	q := NewQueue(0)
-
-	q.Put(nil)
-	suite.Equal(q.Len(), 0)
-
-	q.PutTimeout(nil, 0)
-	suite.Equal(q.Len(), 0)
-
-	q.Put(0)
-	suite.Equal(q.Len(), 1)
-	suite.Equal(q.Poll(), 0)
-	suite.Equal(q.Len(), 0)
-
-	{ // test Poll
-		time.AfterFunc(timeout, func() { q.Put(1) })
-		start := time.Now()
-		suite.Equal(q.Poll(), 1)
-		suite.Equal(time.Since(start) > timeout, true)
-	}
-
-	{ // test PollTimeout
-		v, ok := q.PollTimeout(0)
-		suite.Nil(v)
-		suite.Equal(ok, false)
-
-		start := time.Now()
-		v, ok = q.PollTimeout(timeout)
-		suite.Nil(v)
-		suite.Equal(ok, false)
-		suite.Equal(time.Since(start) > timeout, true)
-
-		time.AfterFunc(timeout/2, func() { q.Put(1) })
-		start = time.Now()
-		v, ok = q.PollTimeout(timeout)
-		suite.Equal(ok, true)
-		suite.Equal(v, 1)
-		suite.Equal(time.Since(start) >= timeout/2, true)
-		suite.Equal(time.Since(start) < timeout, true)
-	}
-}
-func (suite *QueueTestSuite) TestQueue_infiniteQueue_PollTimeout() {
-	timeout := 50 * time.Millisecond
-	q := NewQueue(0)
-
-	wg := sync.WaitGroup{}
-	var cnt int32
-	f := func(i int) {
-		if _, ok := q.PollTimeout(timeout); ok {
-			atomic.AddInt32(&cnt, 1)
-		}
-		wg.Done()
-	}
-	start := time.Now()
-	n := 6
-	wg.Add(n)
-	for i := 0; i < n; i++ {
-		go f(i)
-	}
-	time.AfterFunc(timeout/2, func() {
-		for i := 0; i < n-1; i++ {
-			q.Put(i)
-		}
-	})
-	wg.Wait()
-
-	suite.Equal(time.Since(start) > timeout, true)
-	suite.Equal(cnt, int32(n-1))
-}
-
-func (suite *QueueTestSuite) TestQueue_finiteQueue() {
-	timeout := 50 * time.Millisecond
-	q := NewQueue(2)
-
-	q.Put(nil)
-	suite.Equal(q.Len(), 0)
-
-	q.PutTimeout(nil, 0)
-	suite.Equal(q.Len(), 0)
-
-	q.Put(1)
-	suite.Equal(q.Len(), 1)
-
-	start := time.Now()
-	q.PutTimeout(2, timeout)
-	q.PutTimeout(3, timeout)
-	q.PutTimeout(4, 0)
-	suite.Equal(q.Len(), 2)
-	suite.Equal(time.Since(start) >= timeout, true)
-	suite.Equal(time.Since(start) < 4*timeout, true)
-
-	suite.Equal(q.Poll(), 1)
-	suite.Equal(q.Len(), 1)
-	suite.Equal(q.Poll(), 2)
-
-	{
-		q.PutTimeout(1, 0)
-		item, ok := q.PollTimeout(timeout)
-		suite.Equal(ok, true)
-		suite.Equal(item, 1)
-
-		start = time.Now()
-		item, ok = q.PollTimeout(timeout)
-		suite.Equal(ok, false)
-		suite.Nil(item)
-		suite.Equal(time.Since(start) >= timeout, true)
-
-		start = time.Now()
-		q.PutTimeout(1, 0)
-		item, ok = q.PollTimeout(0)
-		suite.Equal(ok, true)
-		suite.Equal(item, 1)
-		item, ok = q.PollTimeout(0)
-		suite.Equal(ok, false)
-		suite.Nil(item)
-		suite.Equal(time.Since(start) < timeout, true)
-	}
-}