dragonfly/pkg/gc/gc.go

182 lines
3.4 KiB
Go

/*
* 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")
}
}