init
This commit is contained in:
327
dtask/clock.go
Normal file
327
dtask/clock.go
Normal file
@@ -0,0 +1,327 @@
|
||||
// Package clock is a low consumption, low latency support for frequent updates of large capacity timing manager:
|
||||
// 1、能够添加一次性、重复性任务,并能在其执行前撤销或频繁更改。
|
||||
// 2、支持同一时间点,多个任务提醒。
|
||||
// 3、适用于中等密度,大跨度的单次、多次定时任务。
|
||||
// 4、支持10万次/秒的定时任务执行、提醒、撤销或添加操作,平均延迟10微秒内
|
||||
// 5、支持注册任务的函数调用,及事件通知。
|
||||
// 基本处理逻辑:
|
||||
// 1、重复性任务,流程是:
|
||||
// a、注册重复任务
|
||||
// b、时间抵达时,控制器调用注册函数,并发送通知
|
||||
// c、如果次数达到限制,则撤销;否则,控制器更新该任务的下次执行时间点
|
||||
// d、控制器等待下一个最近需要执行的任务
|
||||
// 2、一次性任务,可以是服务运行时,当前时间点之后的任意事件,流程是:
|
||||
// a、注册一次性任务
|
||||
// b、时间抵达时,控制器调用注册函数,并发送通知
|
||||
// c、控制器释放该任务
|
||||
// d、控制器等待下一个最近需要执行的任务
|
||||
// 使用方式,参见示例代码。
|
||||
package dtask
|
||||
|
||||
import (
|
||||
"gitlab.33.cn/chat/im/dtask/pkg/rbtree"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
This code is based on the following resources:
|
||||
source code: https://github.com/alex023/clock.git
|
||||
*/
|
||||
const _UNTOUCHED = time.Duration(math.MaxInt64)
|
||||
|
||||
var (
|
||||
defaultClock *Clock
|
||||
oncedo sync.Once
|
||||
)
|
||||
|
||||
//Default return singal default clock
|
||||
func Default() *Clock {
|
||||
oncedo.Do(initClock)
|
||||
return defaultClock
|
||||
}
|
||||
func initClock() {
|
||||
defaultClock = NewClock()
|
||||
}
|
||||
|
||||
// Clock is jobs schedule
|
||||
type Clock struct {
|
||||
seq uint64
|
||||
count uint64 //已执行次数,不得大于times
|
||||
waitJobsNum uint64 //num of jobs which wait for action
|
||||
jobQueue *rbtree.Rbtree //inner memory storage
|
||||
pauseChan chan struct{}
|
||||
resumeChan chan struct{}
|
||||
exitChan chan struct{}
|
||||
}
|
||||
|
||||
var singal = struct{}{}
|
||||
|
||||
//NewClock Create a task queue controller
|
||||
func NewClock() *Clock {
|
||||
c := &Clock{
|
||||
jobQueue: rbtree.New(),
|
||||
pauseChan: make(chan struct{}, 0),
|
||||
resumeChan: make(chan struct{}, 0),
|
||||
exitChan: make(chan struct{}, 0),
|
||||
}
|
||||
|
||||
c.start()
|
||||
|
||||
return c
|
||||
}
|
||||
func (jl *Clock) start() {
|
||||
now := time.Now()
|
||||
untouchedJob := jobItem{
|
||||
createTime: now,
|
||||
intervalTime: time.Duration(math.MaxInt64),
|
||||
fn: func() {
|
||||
//this jobItem is untouched.
|
||||
},
|
||||
}
|
||||
|
||||
_, inserted := jl.addJob(now, untouchedJob.intervalTime, 1, untouchedJob.fn)
|
||||
if !inserted {
|
||||
panic("[clock] internal error.Reason cannot insert job.")
|
||||
}
|
||||
//开启守护协程
|
||||
go jl.schedule()
|
||||
jl.resume()
|
||||
}
|
||||
|
||||
func (jl *Clock) pause() {
|
||||
jl.pauseChan <- singal
|
||||
}
|
||||
func (jl *Clock) resume() {
|
||||
jl.resumeChan <- singal
|
||||
}
|
||||
func (jl *Clock) exit() {
|
||||
jl.exitChan <- singal
|
||||
}
|
||||
|
||||
func (jl *Clock) immediate() {
|
||||
for {
|
||||
if item := jl.jobQueue.Min(); item != nil {
|
||||
atomic.AddUint64(&jl.count, 1)
|
||||
|
||||
job := item.(*jobItem)
|
||||
job.action(false)
|
||||
|
||||
jl.removeJob(job)
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (jl *Clock) schedule() {
|
||||
var (
|
||||
timeout time.Duration
|
||||
job *jobItem
|
||||
timer = newSafeTimer(_UNTOUCHED)
|
||||
)
|
||||
defer timer.Stop()
|
||||
Pause:
|
||||
<-jl.resumeChan
|
||||
for {
|
||||
job, _ = jl.jobQueue.Min().(*jobItem) //ignore ok-assert
|
||||
timeout = job.actionTime.Sub(time.Now())
|
||||
timer.SafeReset(timeout)
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.SCR()
|
||||
|
||||
atomic.AddUint64(&jl.count, 1)
|
||||
|
||||
job.action(true)
|
||||
|
||||
if job.actionMax == 0 || job.actionMax > job.actionCount {
|
||||
jl.jobQueue.Delete(job)
|
||||
job.actionTime = job.actionTime.Add(job.intervalTime)
|
||||
jl.jobQueue.Insert(job)
|
||||
} else {
|
||||
jl.removeJob(job)
|
||||
}
|
||||
case <-jl.pauseChan:
|
||||
goto Pause
|
||||
case <-jl.exitChan:
|
||||
goto Exit
|
||||
}
|
||||
}
|
||||
Exit:
|
||||
}
|
||||
|
||||
// UpdateJobTimeout update a timed task with time duration after now
|
||||
// @job: job identifier
|
||||
// @actionTime: new job schedule time,must be greater than 0
|
||||
func (jl *Clock) UpdateJobTimeout(job Job, actionTime time.Duration) (updated bool) {
|
||||
if job == nil || actionTime.Nanoseconds() <= 0 || !job.isAvailable() {
|
||||
return false
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
item, ok := job.(*jobItem)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
jl.pause()
|
||||
defer jl.resume()
|
||||
|
||||
// update jobitem in job queue
|
||||
jl.jobQueue.Delete(item)
|
||||
item.actionTime = now.Add(actionTime)
|
||||
jl.jobQueue.Insert(item)
|
||||
|
||||
updated = true
|
||||
return
|
||||
}
|
||||
|
||||
// AddJobWithInterval insert a timed task with time duration after now
|
||||
// @actionTime: Duration after now
|
||||
// @jobFunc: Callback function,not nil
|
||||
// return
|
||||
// @jobScheduled: A reference to a task that has been scheduled.
|
||||
func (jl *Clock) AddJobWithInterval(actionInterval time.Duration, jobFunc func()) (jobScheduled Job, inserted bool) {
|
||||
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
jl.pause()
|
||||
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
|
||||
jl.resume()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddJobWithDeadtime insert a timed task with time point after now
|
||||
// @actionTime: Execution start time. must after now
|
||||
// @jobFunc: Callback function,not nil
|
||||
// return
|
||||
// @jobScheduled : A reference to a task that has been scheduled.
|
||||
// @inserted : return false ,if actionTime before time.Now or jobFunc is nil
|
||||
func (jl *Clock) AddJobWithDeadtime(actionTime time.Time, jobFunc func()) (jobScheduled Job, inserted bool) {
|
||||
actionInterval := actionTime.Sub(time.Now())
|
||||
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
jl.pause()
|
||||
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
|
||||
jl.resume()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddJobRepeat add a repeat task with interval duration
|
||||
// @interval: The interval between two actions of the job
|
||||
// @actionMax: The number of job execution
|
||||
// @jobFunc: Callback function,not nil
|
||||
// return
|
||||
// @jobScheduled : A reference to a task that has been scheduled.
|
||||
// @inserted : return false ,if interval is not Positiveor jobFunc is nil
|
||||
//Note:
|
||||
// when jobTimes==0,the job will be executed without limitation。If you no longer use, be sure to call the DelJob method to release
|
||||
func (jl *Clock) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc func()) (jobScheduled Job, inserted bool) {
|
||||
if jobFunc == nil || interval.Nanoseconds() <= 0 {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
jl.pause()
|
||||
jobScheduled, inserted = jl.addJob(now, interval, actionMax, jobFunc)
|
||||
jl.resume()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (jl *Clock) addJob(createTime time.Time, actionInterval time.Duration, actionMax uint64, jobFunc func()) (job *jobItem, inserted bool) {
|
||||
jl.seq++
|
||||
jl.waitJobsNum++
|
||||
job = &jobItem{
|
||||
id: jl.seq,
|
||||
actionMax: actionMax,
|
||||
createTime: createTime,
|
||||
actionTime: createTime.Add(actionInterval),
|
||||
intervalTime: actionInterval,
|
||||
msgChan: make(chan Job, 10),
|
||||
fn: jobFunc,
|
||||
clock: jl,
|
||||
}
|
||||
jl.jobQueue.Insert(job)
|
||||
inserted = true
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (jl *Clock) removeJob(item *jobItem) {
|
||||
if jl.jobQueue.Delete(item) != nil {
|
||||
jl.waitJobsNum--
|
||||
|
||||
//job.Cancel --> rmJob -->removeJob; schedule -->removeJob
|
||||
//it is call repeatly when Job.Cancel
|
||||
if atomic.CompareAndSwapInt32(&item.cancelFlag, 0, 1) {
|
||||
item.innerCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (jl *Clock) rmJob(job *jobItem) {
|
||||
jl.pause()
|
||||
defer jl.resume()
|
||||
|
||||
jl.removeJob(job)
|
||||
return
|
||||
}
|
||||
|
||||
// Count 已经执行的任务数。对于重复任务,会计算多次
|
||||
func (jl *Clock) Count() uint64 {
|
||||
return atomic.LoadUint64(&jl.count)
|
||||
}
|
||||
|
||||
//重置Clock的内部状态
|
||||
func (jl *Clock) Reset() *Clock {
|
||||
jl.exit()
|
||||
jl.count = 0
|
||||
|
||||
jl.cleanJobs()
|
||||
jl.start()
|
||||
return jl
|
||||
}
|
||||
|
||||
func (jl *Clock) cleanJobs() {
|
||||
item := jl.jobQueue.Min()
|
||||
for item != nil {
|
||||
job, ok := item.(*jobItem)
|
||||
if ok {
|
||||
jl.removeJob(job)
|
||||
}
|
||||
item = jl.jobQueue.Min()
|
||||
}
|
||||
}
|
||||
|
||||
//WaitJobs get how much jobs waiting for call
|
||||
func (jl *Clock) WaitJobs() uint64 {
|
||||
jobs := atomic.LoadUint64(&jl.waitJobsNum) - 1
|
||||
return jobs
|
||||
}
|
||||
|
||||
//Stop stop clock , and cancel all waiting jobs
|
||||
func (jl *Clock) Stop() {
|
||||
jl.exit()
|
||||
|
||||
jl.cleanJobs()
|
||||
}
|
||||
|
||||
//StopGracefull stop clock ,and do once every waiting job including Once\Reapeat
|
||||
//Note:对于任务队列中,即使安排执行多次或者不限次数的,也仅仅执行一次。
|
||||
func (jl *Clock) StopGraceful() {
|
||||
jl.exit()
|
||||
|
||||
jl.immediate()
|
||||
}
|
||||
BIN
dtask/cmd/cli_task
Normal file
BIN
dtask/cmd/cli_task
Normal file
Binary file not shown.
BIN
dtask/cmd/client/dtaskCli
Normal file
BIN
dtask/cmd/client/dtaskCli
Normal file
Binary file not shown.
143
dtask/cmd/client/main.go
Normal file
143
dtask/cmd/client/main.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/oofpgDLD/dtask/proto"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "172.16.101.107:17070", "http service address")
|
||||
|
||||
var (
|
||||
closer = make(chan int, 1)
|
||||
times = 5
|
||||
users = 10000
|
||||
|
||||
u = url.URL{Scheme: "ws", Host: *addr, Path: "/test"}
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
|
||||
for i := 0; i < users; i++ {
|
||||
go NewUser(i, times)
|
||||
}
|
||||
|
||||
go getInputByScanner()
|
||||
// init signal
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
|
||||
for {
|
||||
s := <-c
|
||||
log.Printf("client get a signal %s", s.String())
|
||||
switch s {
|
||||
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
|
||||
log.Print("client exit")
|
||||
return
|
||||
case syscall.SIGHUP:
|
||||
// TODO reload
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewUser(id int, times int) {
|
||||
uid := fmt.Sprint(id)
|
||||
log.Printf("connecting to %s, id %v", u.String(), fmt.Sprint(id))
|
||||
c, resp, err := websocket.DefaultDialer.Dial(u.String(), http.Header{
|
||||
"FZM-UID": {fmt.Sprint(id)},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("dial:", err, resp, u.String())
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
return
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
}
|
||||
}()
|
||||
seq := 0
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
seq++
|
||||
msg, err := Msg(uid, int64(seq))
|
||||
if err != nil {
|
||||
log.Println("marshal proto:", err)
|
||||
return
|
||||
}
|
||||
err = c.WriteMessage(websocket.TextMessage, msg)
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
return
|
||||
}
|
||||
if seq >= times {
|
||||
ticker.Stop()
|
||||
}
|
||||
case index := <-closer:
|
||||
log.Println("interrupt")
|
||||
|
||||
if index == id {
|
||||
log.Println("exit")
|
||||
return
|
||||
}
|
||||
closer <- index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Msg(uid string, seq int64) ([]byte, error) {
|
||||
p := &proto.Proto{
|
||||
Uid: uid,
|
||||
Opt: proto.Start,
|
||||
Seq: seq,
|
||||
}
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
func getInputByScanner() string {
|
||||
var str string
|
||||
for {
|
||||
//使用os.Stdin开启输入流
|
||||
in := bufio.NewScanner(os.Stdin)
|
||||
if in.Scan() {
|
||||
str = in.Text()
|
||||
} else {
|
||||
str = "Find input error"
|
||||
}
|
||||
|
||||
index, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
closer <- int(index)
|
||||
}
|
||||
}
|
||||
BIN
dtask/cmd/dtaskSrv
Normal file
BIN
dtask/cmd/dtaskSrv
Normal file
Binary file not shown.
188
dtask/cmd/main.go
Normal file
188
dtask/cmd/main.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
gpprof "github.com/gin-contrib/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/oofpgDLD/dtask"
|
||||
"github.com/oofpgDLD/dtask/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
_trace = false
|
||||
_pprof = false
|
||||
_pprof_net = true
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", ":17070", "http service address")
|
||||
|
||||
func main() {
|
||||
if _trace {
|
||||
f, err := os.Create("trace.out")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = trace.Start(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer trace.Stop()
|
||||
}
|
||||
|
||||
if _pprof {
|
||||
f, err := os.Create("pprof.out")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = pprof.StartCPUProfile(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
// websocket
|
||||
r.GET("/test", ServeWs)
|
||||
r.GET("/test2", func(context *gin.Context) {
|
||||
context.String(http.StatusOK, "success")
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *addr,
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
if _pprof_net {
|
||||
log.Printf("serve %s", *addr)
|
||||
gpprof.Register(r)
|
||||
}
|
||||
go func() {
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Printf("http server stop %v", err)
|
||||
}
|
||||
}()
|
||||
// init signal
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
|
||||
for {
|
||||
s := <-c
|
||||
log.Printf("service get a signal %s", s.String())
|
||||
switch s {
|
||||
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
|
||||
log.Print("server exit")
|
||||
return
|
||||
case syscall.SIGHUP:
|
||||
// TODO reload
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
|
||||
// allow cross-origin
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
newline = []byte{'\n'}
|
||||
space = []byte{' '}
|
||||
)
|
||||
|
||||
func ServeWs(context *gin.Context) {
|
||||
var (
|
||||
ck *dtask.Clock
|
||||
jobCache map[string]dtask.Job
|
||||
wCh chan []byte
|
||||
)
|
||||
// userId = context.GetHeader("FZM-UID")
|
||||
c, err := upgrader.Upgrade(context.Writer, context.Request, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
wCh = make(chan []byte)
|
||||
defer c.Close()
|
||||
defer onClose(ck)
|
||||
done := make(chan struct{})
|
||||
ck = dtask.NewClock()
|
||||
jobCache = make(map[string]dtask.Job)
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
return
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
onRecvMsg(ck, jobCache, wCh, message)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case data := <-wCh:
|
||||
err = c.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onRecvMsg(ck *dtask.Clock, jc map[string]dtask.Job, wCh chan []byte, msg []byte) {
|
||||
msg = bytes.TrimSpace(bytes.Replace(msg, newline, space, -1))
|
||||
p := proto.Proto{}
|
||||
json.Unmarshal(msg, &p)
|
||||
switch p.Opt {
|
||||
case proto.Start:
|
||||
job, inserted := ck.AddJobRepeat(time.Second*5, 0, func() {
|
||||
wCh <- msg
|
||||
})
|
||||
if !inserted {
|
||||
break
|
||||
}
|
||||
jc[fmt.Sprint(p.Seq)] = job
|
||||
case proto.Stop:
|
||||
if job := jc[fmt.Sprint(p.Seq)]; job != nil {
|
||||
job.Cancel()
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func onClose(ck *dtask.Clock) {
|
||||
if ck == nil {
|
||||
log.Printf("onClose WsConnection args can not find")
|
||||
return
|
||||
}
|
||||
ck.Stop()
|
||||
}
|
||||
BIN
dtask/cmd/trace.out
Normal file
BIN
dtask/cmd/trace.out
Normal file
Binary file not shown.
6
dtask/debug.go
Normal file
6
dtask/debug.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package dtask
|
||||
|
||||
const (
|
||||
// Debug debug switch
|
||||
Debug = false
|
||||
)
|
||||
99
dtask/job.go
Normal file
99
dtask/job.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package dtask
|
||||
|
||||
import (
|
||||
"gitlab.33.cn/chat/im/dtask/pkg/rbtree"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
This code is based on the following resources:
|
||||
source code: https://github.com/alex023/clock.git
|
||||
*/
|
||||
// Job External access interface for timed tasks
|
||||
type Job interface {
|
||||
C() <-chan Job //C Get a Chan,which can get message if Job is executed
|
||||
Count() uint64 //计数器,表示已执行(或触发)的次数
|
||||
Max() uint64 //允许执行的最大次数
|
||||
Cancel() //撤销加载的任务,不再定时执行
|
||||
isAvailable() bool //return true,if job not action or not cancel
|
||||
}
|
||||
|
||||
// jobItem implementation of "Job" interface and "rbtree.Item" interface
|
||||
type jobItem struct {
|
||||
id uint64 //唯一键值,内部由管理器生成,以区分同一时刻的不同任务事件
|
||||
actionCount uint64 //计数器,表示已执行(或触发)的次数
|
||||
actionMax uint64 //允许执行的最大次数
|
||||
intervalTime time.Duration //间隔时间
|
||||
createTime time.Time //创建时间,略有误差
|
||||
actionTime time.Time //计算得出的最近一次执行时间点
|
||||
fn func() //事件函数
|
||||
msgChan chan Job //消息通道,执行时,控制器通过该通道向外部传递消息
|
||||
cancelFlag int32
|
||||
clock *Clock
|
||||
}
|
||||
|
||||
// Less Based rbtree ,implements Item interface for sort
|
||||
func (je jobItem) Less(another rbtree.Item) bool {
|
||||
item, ok := another.(*jobItem)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !je.actionTime.Equal(item.actionTime) {
|
||||
return je.actionTime.Before(item.actionTime)
|
||||
}
|
||||
return je.id < item.id
|
||||
}
|
||||
|
||||
func (je *jobItem) C() <-chan Job {
|
||||
return je.msgChan
|
||||
}
|
||||
|
||||
func (je *jobItem) action(async bool) {
|
||||
je.actionCount++
|
||||
if async {
|
||||
go safeCall(je.fn)
|
||||
} else {
|
||||
safeCall(je.fn)
|
||||
}
|
||||
select {
|
||||
case je.msgChan <- je:
|
||||
default:
|
||||
//some times,client should not receive msgChan,so must discard jobItem when blocking
|
||||
}
|
||||
}
|
||||
|
||||
func (je *jobItem) Cancel() {
|
||||
if atomic.CompareAndSwapInt32(&je.cancelFlag, 0, 1) {
|
||||
je.clock.rmJob(je)
|
||||
je.innerCancel()
|
||||
}
|
||||
}
|
||||
func (je *jobItem) isAvailable() bool {
|
||||
return je.cancelFlag == 0
|
||||
}
|
||||
func (je *jobItem) innerCancel() {
|
||||
je.clock = nil
|
||||
close(je.msgChan)
|
||||
}
|
||||
|
||||
// Count implement for Job
|
||||
func (je jobItem) Count() uint64 {
|
||||
return je.actionCount
|
||||
}
|
||||
|
||||
// Max implement for Job
|
||||
func (je jobItem) Max() uint64 {
|
||||
return je.actionMax
|
||||
}
|
||||
func safeCall(fn func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("[clock] recovering reason is %+v. More detail:", err)
|
||||
log.Println(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
27
dtask/pkg/rbtree/LICENSE
Normal file
27
dtask/pkg/rbtree/LICENSE
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2015, Hu Keping
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
(*) Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
(*) Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
(*) Neither the name of Hu Keping nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
143
dtask/pkg/rbtree/README.md
Normal file
143
dtask/pkg/rbtree/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Rbtree [](https://godoc.org/github.com/HuKeping/rbtree)
|
||||
|
||||
This is an implementation of Red-Black tree written by Golang and does **not** support `duplicate keys`.
|
||||
|
||||
## Installation
|
||||
|
||||
With a healthy Go language installed, simply run `go get github.com/HuKeping/rbtree`
|
||||
|
||||
## Example
|
||||
All you have to do is to implement a comparison function `Less() bool` for your Item
|
||||
which will be store in the Red-Black tree, here are some examples.
|
||||
#### A simple case for `int` items.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/HuKeping/rbtree"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
m := 0
|
||||
n := 10
|
||||
|
||||
for m < n {
|
||||
rbt.Insert(rbtree.Int(m))
|
||||
m++
|
||||
}
|
||||
|
||||
m = 0
|
||||
for m < n {
|
||||
if m%2 == 0 {
|
||||
rbt.Delete(rbtree.Int(m))
|
||||
}
|
||||
m++
|
||||
}
|
||||
|
||||
// 1, 3, 5, 7, 9 were expected.
|
||||
rbt.Ascend(rbt.Min(), Print)
|
||||
}
|
||||
|
||||
func Print(item rbtree.Item) bool {
|
||||
i, ok := item.(rbtree.Int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Println(i)
|
||||
return true
|
||||
}
|
||||
|
||||
#### A simple case for `string` items.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/HuKeping/rbtree"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
rbt.Insert(rbtree.String("Hello"))
|
||||
rbt.Insert(rbtree.String("World"))
|
||||
|
||||
rbt.Ascend(rbt.Min(), Print)
|
||||
}
|
||||
|
||||
func Print(item rbtree.Item) bool {
|
||||
i, ok := item.(rbtree.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Println(i)
|
||||
return true
|
||||
}
|
||||
|
||||
#### A quite interesting case for `struct` items.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/HuKeping/rbtree"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Var struct {
|
||||
Expiry time.Time `json:"expiry,omitempty"`
|
||||
ID string `json:"id",omitempty`
|
||||
}
|
||||
|
||||
// We will order the node by `Time`
|
||||
func (x Var) Less(than rbtree.Item) bool {
|
||||
return x.Expiry.Before(than.(Var).Expiry)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
var1 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 10),
|
||||
ID: "var1",
|
||||
}
|
||||
var2 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 20),
|
||||
ID: "var2",
|
||||
}
|
||||
var3 := Var{
|
||||
Expiry: var2.Expiry,
|
||||
ID: "var2-dup",
|
||||
}
|
||||
var4 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 40),
|
||||
ID: "var4",
|
||||
}
|
||||
var5 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 50),
|
||||
ID: "var5",
|
||||
}
|
||||
|
||||
rbt.Insert(var1)
|
||||
rbt.Insert(var2)
|
||||
rbt.Insert(var3)
|
||||
rbt.Insert(var4)
|
||||
rbt.Insert(var5)
|
||||
|
||||
tmp := Var{
|
||||
Expiry: var4.Expiry,
|
||||
ID: "This field is not the key factor",
|
||||
}
|
||||
|
||||
// var4 and var5 were expected
|
||||
rbt.Ascend(rbt.Get(tmp), Print)
|
||||
}
|
||||
|
||||
func Print(item rbtree.Item) bool {
|
||||
i, ok := item.(Var)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Printf("%+v\n", i)
|
||||
return true
|
||||
}
|
||||
42
dtask/pkg/rbtree/example/example_int/example_int.go
Normal file
42
dtask/pkg/rbtree/example/example_int/example_int.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/oofpgDLD/dtask/pkg/rbtree"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
m := 0
|
||||
n := 10
|
||||
|
||||
for m < n {
|
||||
rbt.Insert(rbtree.Int(m))
|
||||
m++
|
||||
}
|
||||
|
||||
m = 0
|
||||
for m < n {
|
||||
if m%2 == 0 {
|
||||
rbt.Delete(rbtree.Int(m))
|
||||
}
|
||||
m++
|
||||
}
|
||||
|
||||
rbt.Ascend(rbt.Min(), print)
|
||||
}
|
||||
|
||||
func print(item rbtree.Item) bool {
|
||||
i, ok := item.(rbtree.Int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Println(i)
|
||||
return true
|
||||
}
|
||||
29
dtask/pkg/rbtree/example/example_string/example_string.go
Normal file
29
dtask/pkg/rbtree/example/example_string/example_string.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/oofpgDLD/dtask/pkg/rbtree"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
rbt.Insert(rbtree.String("Hello"))
|
||||
rbt.Insert(rbtree.String("World"))
|
||||
|
||||
rbt.Ascend(rbt.Min(), print)
|
||||
}
|
||||
|
||||
func print(item rbtree.Item) bool {
|
||||
i, ok := item.(rbtree.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Println(i)
|
||||
return true
|
||||
}
|
||||
71
dtask/pkg/rbtree/example/example_struct/example_struct.go
Normal file
71
dtask/pkg/rbtree/example/example_struct/example_struct.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oofpgDLD/dtask/pkg/rbtree"
|
||||
)
|
||||
|
||||
// Var is the node of a struct
|
||||
type Var struct {
|
||||
Expiry time.Time `json:"expiry,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// Less will order the node by `Time`
|
||||
func (x Var) Less(than rbtree.Item) bool {
|
||||
return x.Expiry.Before(than.(Var).Expiry)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rbt := rbtree.New()
|
||||
|
||||
var1 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 10),
|
||||
ID: "var1",
|
||||
}
|
||||
var2 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 20),
|
||||
ID: "var2",
|
||||
}
|
||||
var3 := Var{
|
||||
Expiry: var2.Expiry,
|
||||
ID: "var2-dup",
|
||||
}
|
||||
var4 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 40),
|
||||
ID: "var4",
|
||||
}
|
||||
var5 := Var{
|
||||
Expiry: time.Now().Add(time.Second * 50),
|
||||
ID: "var5",
|
||||
}
|
||||
|
||||
rbt.Insert(var1)
|
||||
rbt.Insert(var2)
|
||||
rbt.Insert(var3)
|
||||
rbt.Insert(var4)
|
||||
rbt.Insert(var5)
|
||||
|
||||
tmp := Var{
|
||||
Expiry: var4.Expiry,
|
||||
ID: "This field is not the key factor",
|
||||
}
|
||||
|
||||
// var4 and var5 were expected
|
||||
rbt.Ascend(rbt.Get(tmp), print)
|
||||
}
|
||||
|
||||
func print(item rbtree.Item) bool {
|
||||
i, ok := item.(Var)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fmt.Printf("%+v\n", i)
|
||||
return true
|
||||
}
|
||||
93
dtask/pkg/rbtree/iterator.go
Normal file
93
dtask/pkg/rbtree/iterator.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rbtree
|
||||
|
||||
// Iterator is the function of iteration entity which would be
|
||||
// used by those functions like `Ascend`, `Dscend`, etc.
|
||||
//
|
||||
// A typical Iterator with Print :
|
||||
// func loop_with_print(item rbtree.Item) bool {
|
||||
// i, ok := item.(XXX)
|
||||
// if !ok {
|
||||
// return false
|
||||
// }
|
||||
// fmt.Printf("%+v\n", i)
|
||||
// return true
|
||||
// }
|
||||
type Iterator func(i Item) bool
|
||||
|
||||
// Ascend will call iterator once for each element greater or equal than pivot
|
||||
// in ascending order. It will stop whenever the iterator returns false.
|
||||
func (t *Rbtree) Ascend(pivot Item, iterator Iterator) {
|
||||
t.ascend(t.root, pivot, iterator)
|
||||
}
|
||||
|
||||
func (t *Rbtree) ascend(x *Node, pivot Item, iterator Iterator) bool {
|
||||
if x == t.NIL {
|
||||
return true
|
||||
}
|
||||
|
||||
if !less(x.Item, pivot) {
|
||||
if !t.ascend(x.Left, pivot, iterator) {
|
||||
return false
|
||||
}
|
||||
if !iterator(x.Item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return t.ascend(x.Right, pivot, iterator)
|
||||
}
|
||||
|
||||
// Descend will call iterator once for each element less or equal than pivot
|
||||
// in descending order. It will stop whenever the iterator returns false.
|
||||
func (t *Rbtree) Descend(pivot Item, iterator Iterator) {
|
||||
t.descend(t.root, pivot, iterator)
|
||||
}
|
||||
|
||||
func (t *Rbtree) descend(x *Node, pivot Item, iterator Iterator) bool {
|
||||
if x == t.NIL {
|
||||
return true
|
||||
}
|
||||
|
||||
if !less(pivot, x.Item) {
|
||||
if !t.descend(x.Right, pivot, iterator) {
|
||||
return false
|
||||
}
|
||||
if !iterator(x.Item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return t.descend(x.Left, pivot, iterator)
|
||||
}
|
||||
|
||||
// AscendRange will call iterator once for elements greater or equal than @ge
|
||||
// and less than @lt, which means the range would be [ge, lt).
|
||||
// It will stop whenever the iterator returns false.
|
||||
func (t *Rbtree) AscendRange(ge, lt Item, iterator Iterator) {
|
||||
t.ascendRange(t.root, ge, lt, iterator)
|
||||
}
|
||||
|
||||
func (t *Rbtree) ascendRange(x *Node, inf, sup Item, iterator Iterator) bool {
|
||||
if x == t.NIL {
|
||||
return true
|
||||
}
|
||||
|
||||
if !less(x.Item, sup) {
|
||||
return t.ascendRange(x.Left, inf, sup, iterator)
|
||||
}
|
||||
if less(x.Item, inf) {
|
||||
return t.ascendRange(x.Right, inf, sup, iterator)
|
||||
}
|
||||
|
||||
if !t.ascendRange(x.Left, inf, sup, iterator) {
|
||||
return false
|
||||
}
|
||||
if !iterator(x.Item) {
|
||||
return false
|
||||
}
|
||||
return t.ascendRange(x.Right, inf, sup, iterator)
|
||||
}
|
||||
426
dtask/pkg/rbtree/rbtree.go
Normal file
426
dtask/pkg/rbtree/rbtree.go
Normal file
@@ -0,0 +1,426 @@
|
||||
// Copyright 2015, Hu Keping . All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package rbtree implements operations on Red-Black tree.
|
||||
package rbtree
|
||||
|
||||
//
|
||||
// Red-Black tree properties: http://en.wikipedia.org/wiki/Rbtree
|
||||
//
|
||||
// 1) A node is either red or black
|
||||
// 2) The root is black
|
||||
// 3) All leaves (NULL) are black
|
||||
// 4) Both children of every red node are black
|
||||
// 5) Every simple path from root to leaves contains the same number
|
||||
// of black nodes.
|
||||
//
|
||||
|
||||
// Node of the rbtree has a pointer of the node of parent, left, right, also has own color and Item which client uses
|
||||
type Node struct {
|
||||
Left *Node
|
||||
Right *Node
|
||||
Parent *Node
|
||||
Color uint
|
||||
|
||||
// for use by client.
|
||||
Item
|
||||
}
|
||||
|
||||
const (
|
||||
// RED represents the color of the node is red
|
||||
RED = 0
|
||||
// BLACK represents the color of the node is black
|
||||
BLACK = 1
|
||||
)
|
||||
|
||||
// Item has a method to compare items which is less
|
||||
type Item interface {
|
||||
Less(than Item) bool
|
||||
}
|
||||
|
||||
// Rbtree represents a Red-Black tree.
|
||||
type Rbtree struct {
|
||||
NIL *Node
|
||||
root *Node
|
||||
count uint
|
||||
}
|
||||
|
||||
func less(x, y Item) bool {
|
||||
return x.Less(y)
|
||||
}
|
||||
|
||||
// New returns an initialized Red-Black tree
|
||||
func New() *Rbtree { return new(Rbtree).Init() }
|
||||
|
||||
// Init returns the initial of rbtree
|
||||
func (t *Rbtree) Init() *Rbtree {
|
||||
node := &Node{nil, nil, nil, BLACK, nil}
|
||||
return &Rbtree{
|
||||
NIL: node,
|
||||
root: node,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Rbtree) leftRotate(x *Node) {
|
||||
// Since we are doing the left rotation, the right child should *NOT* nil.
|
||||
if x.Right == t.NIL {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// The illation of left rotation
|
||||
//
|
||||
// | |
|
||||
// X Y
|
||||
// / \ left rotate / \
|
||||
// α Y -------------> X γ
|
||||
// / \ / \
|
||||
// β γ α β
|
||||
//
|
||||
// It should be note that during the rotating we do not change
|
||||
// the Nodes' color.
|
||||
//
|
||||
y := x.Right
|
||||
x.Right = y.Left
|
||||
if y.Left != t.NIL {
|
||||
y.Left.Parent = x
|
||||
}
|
||||
y.Parent = x.Parent
|
||||
|
||||
if x.Parent == t.NIL {
|
||||
t.root = y
|
||||
} else if x == x.Parent.Left {
|
||||
x.Parent.Left = y
|
||||
} else {
|
||||
x.Parent.Right = y
|
||||
}
|
||||
|
||||
y.Left = x
|
||||
x.Parent = y
|
||||
}
|
||||
|
||||
func (t *Rbtree) rightRotate(x *Node) {
|
||||
// Since we are doing the right rotation, the left child should *NOT* nil.
|
||||
if x.Left == t.NIL {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// The illation of right rotation
|
||||
//
|
||||
// | |
|
||||
// X Y
|
||||
// / \ right rotate / \
|
||||
// Y γ -------------> α X
|
||||
// / \ / \
|
||||
// α β β γ
|
||||
//
|
||||
// It should be note that during the rotating we do not change
|
||||
// the Nodes' color.
|
||||
//
|
||||
y := x.Left
|
||||
x.Left = y.Right
|
||||
if y.Right != t.NIL {
|
||||
y.Right.Parent = x
|
||||
}
|
||||
y.Parent = x.Parent
|
||||
|
||||
if x.Parent == t.NIL {
|
||||
t.root = y
|
||||
} else if x == x.Parent.Left {
|
||||
x.Parent.Left = y
|
||||
} else {
|
||||
x.Parent.Right = y
|
||||
}
|
||||
|
||||
y.Right = x
|
||||
x.Parent = y
|
||||
}
|
||||
|
||||
func (t *Rbtree) insert(z *Node) *Node {
|
||||
x := t.root
|
||||
y := t.NIL
|
||||
|
||||
for x != t.NIL {
|
||||
y = x
|
||||
if less(z.Item, x.Item) {
|
||||
x = x.Left
|
||||
} else if less(x.Item, z.Item) {
|
||||
x = x.Right
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
z.Parent = y
|
||||
if y == t.NIL {
|
||||
t.root = z
|
||||
} else if less(z.Item, y.Item) {
|
||||
y.Left = z
|
||||
} else {
|
||||
y.Right = z
|
||||
}
|
||||
|
||||
t.count++
|
||||
t.insertFixup(z)
|
||||
return z
|
||||
}
|
||||
|
||||
func (t *Rbtree) insertFixup(z *Node) {
|
||||
for z.Parent.Color == RED {
|
||||
//
|
||||
// Howerver, we do not need the assertion of non-nil grandparent
|
||||
// because
|
||||
//
|
||||
// 2) The root is black
|
||||
//
|
||||
// Since the color of the parent is RED, so the parent is not root
|
||||
// and the grandparent must be exist.
|
||||
//
|
||||
if z.Parent == z.Parent.Parent.Left {
|
||||
// Take y as the uncle, although it can be NIL, in that case
|
||||
// its color is BLACK
|
||||
y := z.Parent.Parent.Right
|
||||
if y.Color == RED {
|
||||
//
|
||||
// Case 1:
|
||||
// Parent and uncle are both RED, the grandparent must be BLACK
|
||||
// due to
|
||||
//
|
||||
// 4) Both children of every red node are black
|
||||
//
|
||||
// Since the current node and its parent are all RED, we still
|
||||
// in violation of 4), So repaint both the parent and the uncle
|
||||
// to BLACK and grandparent to RED(to maintain 5)
|
||||
//
|
||||
// 5) Every simple path from root to leaves contains the same
|
||||
// number of black nodes.
|
||||
//
|
||||
z.Parent.Color = BLACK
|
||||
y.Color = BLACK
|
||||
z.Parent.Parent.Color = RED
|
||||
z = z.Parent.Parent
|
||||
} else {
|
||||
if z == z.Parent.Right {
|
||||
//
|
||||
// Case 2:
|
||||
// Parent is RED and uncle is BLACK and the current node
|
||||
// is right child
|
||||
//
|
||||
// A left rotation on the parent of the current node will
|
||||
// switch the roles of each other. This still leaves us in
|
||||
// violation of 4).
|
||||
// The continuation into Case 3 will fix that.
|
||||
//
|
||||
z = z.Parent
|
||||
t.leftRotate(z)
|
||||
}
|
||||
//
|
||||
// Case 3:
|
||||
// Parent is RED and uncle is BLACK and the current node is
|
||||
// left child
|
||||
//
|
||||
// At the very beginning of Case 3, current node and parent are
|
||||
// both RED, thus we violate 4).
|
||||
// Repaint parent to BLACK will fix it, but 5) does not allow
|
||||
// this because all paths that go through the parent will get
|
||||
// 1 more black node. Then repaint grandparent to RED (as we
|
||||
// discussed before, the grandparent is BLACK) and do a right
|
||||
// rotation will fix that.
|
||||
//
|
||||
z.Parent.Color = BLACK
|
||||
z.Parent.Parent.Color = RED
|
||||
t.rightRotate(z.Parent.Parent)
|
||||
}
|
||||
} else { // same as then clause with "right" and "left" exchanged
|
||||
y := z.Parent.Parent.Left
|
||||
if y.Color == RED {
|
||||
z.Parent.Color = BLACK
|
||||
y.Color = BLACK
|
||||
z.Parent.Parent.Color = RED
|
||||
z = z.Parent.Parent
|
||||
} else {
|
||||
if z == z.Parent.Left {
|
||||
z = z.Parent
|
||||
t.rightRotate(z)
|
||||
}
|
||||
z.Parent.Color = BLACK
|
||||
z.Parent.Parent.Color = RED
|
||||
t.leftRotate(z.Parent.Parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.root.Color = BLACK
|
||||
}
|
||||
|
||||
// Just traverse the node from root to left recursively until left is NIL.
|
||||
// The node whose left is NIL is the node with minimum value.
|
||||
func (t *Rbtree) min(x *Node) *Node {
|
||||
if x == t.NIL {
|
||||
return t.NIL
|
||||
}
|
||||
|
||||
for x.Left != t.NIL {
|
||||
x = x.Left
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
// Just traverse the node from root to right recursively until right is NIL.
|
||||
// The node whose right is NIL is the node with maximum value.
|
||||
func (t *Rbtree) max(x *Node) *Node {
|
||||
if x == t.NIL {
|
||||
return t.NIL
|
||||
}
|
||||
|
||||
for x.Right != t.NIL {
|
||||
x = x.Right
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
func (t *Rbtree) search(x *Node) *Node {
|
||||
p := t.root
|
||||
|
||||
for p != t.NIL {
|
||||
if less(p.Item, x.Item) {
|
||||
p = p.Right
|
||||
} else if less(x.Item, p.Item) {
|
||||
p = p.Left
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
//TODO: Need Document
|
||||
func (t *Rbtree) successor(x *Node) *Node {
|
||||
if x == t.NIL {
|
||||
return t.NIL
|
||||
}
|
||||
|
||||
// Get the minimum from the right sub-tree if it existed.
|
||||
if x.Right != t.NIL {
|
||||
return t.min(x.Right)
|
||||
}
|
||||
|
||||
y := x.Parent
|
||||
for y != t.NIL && x == y.Right {
|
||||
x = y
|
||||
y = y.Parent
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
//TODO: Need Document
|
||||
func (t *Rbtree) delete(key *Node) *Node {
|
||||
z := t.search(key)
|
||||
|
||||
if z == t.NIL {
|
||||
return t.NIL
|
||||
}
|
||||
ret := &Node{t.NIL, t.NIL, t.NIL, z.Color, z.Item}
|
||||
|
||||
var y *Node
|
||||
var x *Node
|
||||
|
||||
if z.Left == t.NIL || z.Right == t.NIL {
|
||||
y = z
|
||||
} else {
|
||||
y = t.successor(z)
|
||||
}
|
||||
|
||||
if y.Left != t.NIL {
|
||||
x = y.Left
|
||||
} else {
|
||||
x = y.Right
|
||||
}
|
||||
|
||||
// Even if x is NIL, we do the assign. In that case all the NIL nodes will
|
||||
// change from {nil, nil, nil, BLACK, nil} to {nil, nil, ADDR, BLACK, nil},
|
||||
// but do not worry about that because it will not affect the compare
|
||||
// between Node-X with Node-NIL
|
||||
x.Parent = y.Parent
|
||||
|
||||
if y.Parent == t.NIL {
|
||||
t.root = x
|
||||
} else if y == y.Parent.Left {
|
||||
y.Parent.Left = x
|
||||
} else {
|
||||
y.Parent.Right = x
|
||||
}
|
||||
|
||||
if y != z {
|
||||
z.Item = y.Item
|
||||
}
|
||||
|
||||
if y.Color == BLACK {
|
||||
t.deleteFixup(x)
|
||||
}
|
||||
|
||||
t.count--
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *Rbtree) deleteFixup(x *Node) {
|
||||
for x != t.root && x.Color == BLACK {
|
||||
if x == x.Parent.Left {
|
||||
w := x.Parent.Right
|
||||
if w.Color == RED {
|
||||
w.Color = BLACK
|
||||
x.Parent.Color = RED
|
||||
t.leftRotate(x.Parent)
|
||||
w = x.Parent.Right
|
||||
}
|
||||
if w.Left.Color == BLACK && w.Right.Color == BLACK {
|
||||
w.Color = RED
|
||||
x = x.Parent
|
||||
} else {
|
||||
if w.Right.Color == BLACK {
|
||||
w.Left.Color = BLACK
|
||||
w.Color = RED
|
||||
t.rightRotate(w)
|
||||
w = x.Parent.Right
|
||||
}
|
||||
w.Color = x.Parent.Color
|
||||
x.Parent.Color = BLACK
|
||||
w.Right.Color = BLACK
|
||||
t.leftRotate(x.Parent)
|
||||
// this is to exit while loop
|
||||
x = t.root
|
||||
}
|
||||
} else { // the code below is has left and right switched from above
|
||||
w := x.Parent.Left
|
||||
if w.Color == RED {
|
||||
w.Color = BLACK
|
||||
x.Parent.Color = RED
|
||||
t.rightRotate(x.Parent)
|
||||
w = x.Parent.Left
|
||||
}
|
||||
if w.Left.Color == BLACK && w.Right.Color == BLACK {
|
||||
w.Color = RED
|
||||
x = x.Parent
|
||||
} else {
|
||||
if w.Left.Color == BLACK {
|
||||
w.Right.Color = BLACK
|
||||
w.Color = RED
|
||||
t.leftRotate(w)
|
||||
w = x.Parent.Left
|
||||
}
|
||||
w.Color = x.Parent.Color
|
||||
x.Parent.Color = BLACK
|
||||
w.Left.Color = BLACK
|
||||
t.rightRotate(x.Parent)
|
||||
x = t.root
|
||||
}
|
||||
}
|
||||
}
|
||||
x.Color = BLACK
|
||||
}
|
||||
200
dtask/pkg/rbtree/rbtree_test.go
Normal file
200
dtask/pkg/rbtree/rbtree_test.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rbtree
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInsertAndDelete(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
m := 0
|
||||
n := 1000
|
||||
for m < n {
|
||||
rbt.Insert(Int(m))
|
||||
m++
|
||||
}
|
||||
if rbt.Len() != uint(n) {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), n)
|
||||
}
|
||||
|
||||
for m > 0 {
|
||||
rbt.Delete(Int(m))
|
||||
m--
|
||||
}
|
||||
if rbt.Len() != 1 {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 1)
|
||||
}
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
id int
|
||||
text string
|
||||
}
|
||||
|
||||
func (ts *testStruct) Less(than Item) bool {
|
||||
return ts.id < than.(*testStruct).id
|
||||
}
|
||||
|
||||
func TestInsertOrGet(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
items := []*testStruct{
|
||||
{1, "this"},
|
||||
{2, "is"},
|
||||
{3, "a"},
|
||||
{4, "test"},
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
rbt.Insert(items[i])
|
||||
}
|
||||
|
||||
newItem := &testStruct{items[0].id, "not"}
|
||||
newItem = rbt.InsertOrGet(newItem).(*testStruct)
|
||||
|
||||
if newItem.text != items[0].text {
|
||||
t.Errorf("tree.InsertOrGet = {id: %d, text: %s}, expect {id %d, text %s}", newItem.id, newItem.text, items[0].id, items[0].text)
|
||||
}
|
||||
|
||||
newItem = &testStruct{5, "new"}
|
||||
newItem = rbt.InsertOrGet(newItem).(*testStruct)
|
||||
|
||||
if newItem.text != "new" {
|
||||
t.Errorf("tree.InsertOrGet = {id: %d, text: %s}, expect {id %d, text %s}", newItem.id, newItem.text, 5, "new")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertString(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(String("go"))
|
||||
rbt.Insert(String("lang"))
|
||||
|
||||
if rbt.Len() != 2 {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for duplicate
|
||||
func TestInsertDup(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(String("go"))
|
||||
rbt.Insert(String("go"))
|
||||
rbt.Insert(String("go"))
|
||||
|
||||
if rbt.Len() != 1 {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescend(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
m := 0
|
||||
n := 10
|
||||
for m < n {
|
||||
rbt.Insert(Int(m))
|
||||
m++
|
||||
}
|
||||
|
||||
var ret []Item
|
||||
|
||||
rbt.Descend(Int(1), func(i Item) bool {
|
||||
ret = append(ret, i)
|
||||
return true
|
||||
})
|
||||
expected := []Item{Int(1), Int(0)}
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Errorf("expected %v but got %v", expected, ret)
|
||||
}
|
||||
|
||||
ret = nil
|
||||
rbt.Descend(Int(10), func(i Item) bool {
|
||||
ret = append(ret, i)
|
||||
return true
|
||||
})
|
||||
expected = []Item{Int(9), Int(8), Int(7), Int(6), Int(5), Int(4), Int(3), Int(2), Int(1), Int(0)}
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Errorf("expected %v but got %v", expected, ret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(Int(1))
|
||||
rbt.Insert(Int(2))
|
||||
rbt.Insert(Int(3))
|
||||
|
||||
no := rbt.Get(Int(100))
|
||||
ok := rbt.Get(Int(1))
|
||||
|
||||
if no != nil {
|
||||
t.Errorf("100 is expect not exists")
|
||||
}
|
||||
|
||||
if ok == nil {
|
||||
t.Errorf("1 is expect exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAscend(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(String("a"))
|
||||
rbt.Insert(String("b"))
|
||||
rbt.Insert(String("c"))
|
||||
rbt.Insert(String("d"))
|
||||
|
||||
rbt.Delete(rbt.Min())
|
||||
|
||||
var ret []Item
|
||||
rbt.Ascend(rbt.Min(), func(i Item) bool {
|
||||
ret = append(ret, i)
|
||||
return true
|
||||
})
|
||||
|
||||
expected := []Item{String("b"), String("c"), String("d")}
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Errorf("expected %v but got %v", expected, ret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMax(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(String("z"))
|
||||
rbt.Insert(String("h"))
|
||||
rbt.Insert(String("a"))
|
||||
|
||||
expected := String("z")
|
||||
if rbt.Max() != expected {
|
||||
t.Errorf("expected Max of tree as %v but got %v", expected, rbt.Max())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAscendRange(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
strings := []String{"a", "b", "c", "aa", "ab", "ac", "abc", "acb", "bac"}
|
||||
for _, v := range strings {
|
||||
rbt.Insert(v)
|
||||
}
|
||||
|
||||
var ret []Item
|
||||
rbt.AscendRange(String("ab"), String("b"), func(i Item) bool {
|
||||
ret = append(ret, i)
|
||||
return true
|
||||
})
|
||||
expected := []Item{String("ab"), String("abc"), String("ac"), String("acb")}
|
||||
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Errorf("expected %v but got %v", expected, ret)
|
||||
}
|
||||
}
|
||||
88
dtask/pkg/rbtree/stats.go
Normal file
88
dtask/pkg/rbtree/stats.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2015, Hu Keping . All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rbtree
|
||||
|
||||
// This file contains most of the methods that can be used
|
||||
// by the user. Anyone who wants to look for some API about
|
||||
// the rbtree, this is the right place.
|
||||
|
||||
// Len returns number of nodes in the tree.
|
||||
func (t *Rbtree) Len() uint { return t.count }
|
||||
|
||||
// Insert func inserts a item as a new RED node
|
||||
func (t *Rbtree) Insert(item Item) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Always insert a RED node
|
||||
t.insert(&Node{t.NIL, t.NIL, t.NIL, RED, item})
|
||||
}
|
||||
|
||||
//InsertOrGet inserts or retrieves the item in the tree. If the
|
||||
//item is already in the tree then the return value will be that.
|
||||
//If the item is not in the tree the return value will be the item
|
||||
//you put in.
|
||||
func (t *Rbtree) InsertOrGet(item Item) Item {
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.insert(&Node{t.NIL, t.NIL, t.NIL, RED, item}).Item
|
||||
}
|
||||
|
||||
//Delete delete the item in the tree
|
||||
func (t *Rbtree) Delete(item Item) Item {
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The `color` field here is nobody
|
||||
return t.delete(&Node{t.NIL, t.NIL, t.NIL, RED, item}).Item
|
||||
}
|
||||
|
||||
//Get search for the specified items which is carried by a Node
|
||||
func (t *Rbtree) Get(item Item) Item {
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The `color` field here is nobody
|
||||
ret := t.search(&Node{t.NIL, t.NIL, t.NIL, RED, item})
|
||||
if ret == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ret.Item
|
||||
}
|
||||
|
||||
// Search does only search the node which includes it node
|
||||
//TODO: This is for debug, delete it in the future
|
||||
func (t *Rbtree) Search(item Item) *Node {
|
||||
|
||||
return t.search(&Node{t.NIL, t.NIL, t.NIL, RED, item})
|
||||
}
|
||||
|
||||
// Min return the item minimum one
|
||||
func (t *Rbtree) Min() Item {
|
||||
x := t.min(t.root)
|
||||
|
||||
if x == t.NIL {
|
||||
return nil
|
||||
}
|
||||
|
||||
return x.Item
|
||||
}
|
||||
|
||||
// Max return the item maxmum one
|
||||
func (t *Rbtree) Max() Item {
|
||||
x := t.max(t.root)
|
||||
|
||||
if x == t.NIL {
|
||||
return nil
|
||||
}
|
||||
|
||||
return x.Item
|
||||
}
|
||||
152
dtask/pkg/rbtree/stats_test.go
Normal file
152
dtask/pkg/rbtree/stats_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rbtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDeleteReturnValue(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
rbt.Insert(String("go"))
|
||||
rbt.Insert(String("lang"))
|
||||
|
||||
if rbt.Len() != 2 {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 2)
|
||||
}
|
||||
|
||||
// go should be in the rbtree
|
||||
deletedGo := rbt.Delete(String("go"))
|
||||
if deletedGo != String("go") {
|
||||
t.Errorf("expect %v, got %v", "go", deletedGo)
|
||||
}
|
||||
|
||||
// C should not be in the rbtree
|
||||
deletedC := rbt.Delete(String("C"))
|
||||
if deletedC != nil {
|
||||
t.Errorf("expect %v, got %v", nil, deletedC)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMin(t *testing.T) {
|
||||
rbt := New()
|
||||
|
||||
m := 0
|
||||
n := 1000
|
||||
for m < n {
|
||||
rbt.Insert(Int(m))
|
||||
m++
|
||||
}
|
||||
if rbt.Len() != uint(n) {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), n)
|
||||
}
|
||||
|
||||
for m >= 0 {
|
||||
rbt.Delete(rbt.Min())
|
||||
m--
|
||||
}
|
||||
|
||||
if rbt.Len() != 0 {
|
||||
t.Errorf("tree.Len() = %d, expect %d", rbt.Len(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// This test will first add 1000 numbers into a tree and then delete some
|
||||
// from it.
|
||||
//
|
||||
// All the adding and deleting are in goroutine so that the delete action
|
||||
// will not always succeed for example `delete(400)` but `add(400)` has not
|
||||
// been executed yet.
|
||||
//
|
||||
// Finally, we'll add back all the deleted nodes to see if there is
|
||||
// anything wrong.
|
||||
//
|
||||
func TestWithGoroutine(t *testing.T) {
|
||||
var Cache struct {
|
||||
rbt *Rbtree
|
||||
locker *sync.Mutex
|
||||
}
|
||||
|
||||
var DeletedArray struct {
|
||||
array []Item
|
||||
locker *sync.Mutex
|
||||
}
|
||||
|
||||
// Init the rbtree and the locker for later use
|
||||
Cache.rbt = New()
|
||||
Cache.locker = new(sync.Mutex)
|
||||
|
||||
// Init the locker for later use
|
||||
DeletedArray.locker = new(sync.Mutex)
|
||||
|
||||
i, m := 0, 0
|
||||
j, n := 1000, 1000
|
||||
|
||||
var expected []Item
|
||||
|
||||
// This loop will add intergers [m~n) to the rbtree.
|
||||
for m < n {
|
||||
expected = append(expected, Int(m))
|
||||
|
||||
go func(x Int) {
|
||||
Cache.locker.Lock()
|
||||
Cache.rbt.Insert(x)
|
||||
Cache.locker.Unlock()
|
||||
}(Int(m))
|
||||
m++
|
||||
}
|
||||
|
||||
// This loop will try to delete the even integers in [m~n),
|
||||
// Be noticed that the delete will not always succeeds since we are
|
||||
// in the goroutines.
|
||||
// We will record which ones have been removed.
|
||||
for i < j {
|
||||
if i%2 == 0 {
|
||||
go func(x Int) {
|
||||
Cache.locker.Lock()
|
||||
value := Cache.rbt.Delete(x)
|
||||
Cache.locker.Unlock()
|
||||
|
||||
DeletedArray.locker.Lock()
|
||||
DeletedArray.array = append(DeletedArray.array, value)
|
||||
DeletedArray.locker.Unlock()
|
||||
}(Int(i))
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// Let's give a little time to those goroutines to finish their job.
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// Add deleted Items back
|
||||
cnt := 0
|
||||
DeletedArray.locker.Lock()
|
||||
for _, v := range DeletedArray.array {
|
||||
if v != nil {
|
||||
Cache.locker.Lock()
|
||||
Cache.rbt.Insert(v)
|
||||
Cache.locker.Unlock()
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
DeletedArray.locker.Unlock()
|
||||
fmt.Printf("In TestWithGoroutine(), we have deleted [%v] nodes.\n", cnt)
|
||||
|
||||
var ret []Item
|
||||
Cache.rbt.Ascend(Cache.rbt.Min(), func(item Item) bool {
|
||||
ret = append(ret, item)
|
||||
return true
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Errorf("expected %v but got %v", expected, ret)
|
||||
}
|
||||
}
|
||||
21
dtask/pkg/rbtree/util.go
Normal file
21
dtask/pkg/rbtree/util.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2015, Hu Keping. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rbtree
|
||||
|
||||
// Int is type of int
|
||||
type Int int
|
||||
|
||||
// Less returns whether x(Int) is smaller than specified item
|
||||
func (x Int) Less(than Item) bool {
|
||||
return x < than.(Int)
|
||||
}
|
||||
|
||||
// String is type of string
|
||||
type String string
|
||||
|
||||
// Less returns whether x(String) is smaller than specified item
|
||||
func (x String) Less(than Item) bool {
|
||||
return x < than.(String)
|
||||
}
|
||||
12
dtask/proto/proto.go
Normal file
12
dtask/proto/proto.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package proto
|
||||
|
||||
const (
|
||||
Start = 1
|
||||
Stop = 2
|
||||
)
|
||||
|
||||
type Proto struct {
|
||||
Uid string
|
||||
Opt int
|
||||
Seq int64
|
||||
}
|
||||
33
dtask/safetimer.go
Normal file
33
dtask/safetimer.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package dtask
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
This code is based on the following resources:
|
||||
source code: https://play.golang.org/p/Ys9qqanqmU
|
||||
discuss: https://groups.google.com/forum/#!msg/golang-dev/c9UUfASVPoU/tlbK2BpFEwAJ
|
||||
*/
|
||||
type safetimer struct {
|
||||
*time.Timer
|
||||
scr bool
|
||||
}
|
||||
|
||||
//saw channel read, must be called after receiving value from safetimer chan
|
||||
func (t *safetimer) SCR() {
|
||||
t.scr = true
|
||||
}
|
||||
|
||||
func (t *safetimer) SafeReset(d time.Duration) bool {
|
||||
ret := t.Stop()
|
||||
if !ret && !t.scr {
|
||||
<-t.C
|
||||
}
|
||||
t.Timer.Reset(d)
|
||||
t.scr = false
|
||||
return ret
|
||||
}
|
||||
func newSafeTimer(d time.Duration) *safetimer {
|
||||
return &safetimer{
|
||||
Timer: time.NewTimer(d),
|
||||
}
|
||||
}
|
||||
71
dtask/script/pprof.sh
Normal file
71
dtask/script/pprof.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
default_url="http://172.16.101.107:16060"
|
||||
default_prefix="debug/pprof"
|
||||
default_type="profile"
|
||||
|
||||
read -p "input the request url (default url is '${default_url}'): " url
|
||||
if [ -z ${url} ]; then
|
||||
url=${default_url}
|
||||
fi
|
||||
read -p "input the pprof prefix (default prefix is '${default_prefix}'): " prefix
|
||||
if [ -z ${prefix} ]; then
|
||||
prefix=${default_prefix}
|
||||
fi
|
||||
|
||||
function show() {
|
||||
echo "chose the pprof type:"
|
||||
echo " 0: break the loop"
|
||||
echo " 1: profile"
|
||||
echo " 2: heap"
|
||||
echo " 3: allocs"
|
||||
echo " 4: goroutine"
|
||||
echo " 5: mutex"
|
||||
echo " 6: block"
|
||||
echo " 7: trace"
|
||||
echo " 8: threadcreate"
|
||||
echo " 9: cmdline"
|
||||
echo -n "input the number: "
|
||||
}
|
||||
|
||||
while true; do
|
||||
show
|
||||
read num
|
||||
case $num in
|
||||
0)
|
||||
break
|
||||
;;
|
||||
1)
|
||||
type="profile"
|
||||
;;
|
||||
2)
|
||||
type="heap"
|
||||
;;
|
||||
3)
|
||||
type="allocs"
|
||||
;;
|
||||
4)
|
||||
type="goroutine"
|
||||
;;
|
||||
5)
|
||||
type="mutex"
|
||||
;;
|
||||
6)
|
||||
type="block"
|
||||
;;
|
||||
7)
|
||||
type="trace"
|
||||
;;
|
||||
8)
|
||||
type="threadcreate"
|
||||
;;
|
||||
9)
|
||||
type="cmdline"
|
||||
;;
|
||||
*)
|
||||
type=${default_type}
|
||||
;;
|
||||
esac
|
||||
# echo 'file://wsl$/Ubuntu-18.04/tmp/' | clip.exe
|
||||
# echo "request: ${url}/${prefix}/${type}"
|
||||
go tool pprof "${url}/${prefix}/${type}"
|
||||
done
|
||||
1308
dtask/script/torch.svg
Normal file
1308
dtask/script/torch.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 54 KiB |
40
dtask/task.go
Normal file
40
dtask/task.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dtask
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
sync.RWMutex
|
||||
clock *Clock
|
||||
jobCache map[string]Job
|
||||
}
|
||||
|
||||
func NewTask() *Task {
|
||||
t := &Task{
|
||||
clock: NewClock(),
|
||||
jobCache: make(map[string]Job),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Task) Add(key string, job Job) {
|
||||
t.Lock()
|
||||
t.jobCache[key] = job
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *Task) Get(key string) Job {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.jobCache[key]
|
||||
}
|
||||
|
||||
func (t *Task) Stop() {
|
||||
t.clock.Stop()
|
||||
}
|
||||
|
||||
func (t *Task) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc func()) (jobScheduled Job, inserted bool) {
|
||||
return t.clock.AddJobRepeat(interval, actionMax, jobFunc)
|
||||
}
|
||||
Reference in New Issue
Block a user