This commit is contained in:
2022-03-17 15:55:27 +08:00
commit bd5a9fad97
92 changed files with 13861 additions and 0 deletions

327
dtask/clock.go Normal file
View 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

Binary file not shown.

BIN
dtask/cmd/client/dtaskCli Normal file

Binary file not shown.

143
dtask/cmd/client/main.go Normal file
View 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

Binary file not shown.

188
dtask/cmd/main.go Normal file
View 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

Binary file not shown.

6
dtask/debug.go Normal file
View File

@@ -0,0 +1,6 @@
package dtask
const (
// Debug debug switch
Debug = false
)

99
dtask/job.go Normal file
View 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 Chanwhich can get message if Job is executed
Count() uint64 //计数器,表示已执行(或触发)的次数
Max() uint64 //允许执行的最大次数
Cancel() //撤销加载的任务,不再定时执行
isAvailable() bool //return trueif 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
View 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
View File

@@ -0,0 +1,143 @@
# Rbtree [![GoDoc](https://godoc.org/github.com/HuKeping/rbtree?status.svg)](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
}

View 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
}

View 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
}

View 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
}

View 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
View 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
}

View 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
View 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
}

View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 54 KiB

40
dtask/task.go Normal file
View 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)
}