github.com/keysonzzz/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgScheduleTask/Task.go.bak (about)

     1  package kmgScheduleTask
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/bronze1man/kmg/encoding/kmgJson"
     6  	"github.com/bronze1man/kmg/kmgSql"
     7  	"github.com/bronze1man/kmg/kmgTime"
     8  	"reflect"
     9  	"runtime"
    10  	"strconv"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  type Status string
    16  
    17  const (
    18  	MySQLTableName               string        = "KmgScheduleTask"
    19  	DefaultMaxAllowExecuteTime   time.Duration = 10 * time.Minute
    20  	DefaultMaxAllowNumberOfRetry int           = 3
    21  )
    22  
    23  type Task struct {
    24  	Id                    int
    25  	ExecuteTime           time.Time                         // 计划执行的时间点
    26  	Func                  func(isDone chan bool, task Task) // Task 要执行的函数
    27  	Parameter             map[string]string                 // Task 的参数
    28  	IsDone                bool                              // Task 是否已经已经完成
    29  	MaxAllowExecuteSecond time.Duration
    30  	MaxAllowNumberOfRetry int // Task 允许的最大重试次数
    31  	NumberOfTry           int // Task 已重试的次数 TODO 暂时不在数据库保存该值
    32  }
    33  
    34  func (task *Task) doneAndPersist() {
    35  	row := map[string]string{}
    36  	if task.Id != 0 {
    37  		row["Id"] = strconv.Itoa(task.Id)
    38  	}
    39  	row["ExecuteTime"] = kmgTime.DefaultFormat(task.ExecuteTime)
    40  	row["Func"] = getFuncFullName(task.Func)
    41  	row["Parameter"] = kmgJson.MustMarshalToString(task.Parameter)
    42  	row["IsDone"] = "1"
    43  	row["MaxAllowExecuteSecond"] = task.MaxAllowExecuteSecond.String()
    44  	row["MaxAllowNumberOfRetry"] = strconv.Itoa(task.MaxAllowNumberOfRetry)
    45  	kmgSql.MustReplaceById(MySQLTableName, "Id", row)
    46  }
    47  
    48  var taskSlice []*Task
    49  
    50  var taskFuncMap map[string]func(isDone chan bool, task Task) = map[string]func(isDone chan bool, task Task){}
    51  
    52  func getFuncFromString(fullName string) func(isDone chan bool, task Task) {
    53  	function, ok := taskFuncMap[fullName]
    54  	if ok {
    55  		return function
    56  	}
    57  	return nil
    58  }
    59  
    60  func getFuncFullName(function interface{}) string {
    61  	v := reflect.ValueOf(function)
    62  	f := runtime.FuncForPC(v.Pointer())
    63  	return f.Name()
    64  }
    65  
    66  func recoverFromDb() {
    67  	rowList := kmgSql.MustQuery("SELECT * FROM `"+MySQLTableName+"` WHERE IsDone<>?", "1")
    68  	for _, row := range rowList {
    69  		task := &Task{
    70  			ExecuteTime: kmgTime.MustFromMysqlFormatDefaultTZ(row["ExecuteTime"]),
    71  			Func:        getFuncFromString(row["Func"]),
    72  			IsDone:      false,
    73  		}
    74  		task.Parameter = make(map[string]string)
    75  		kmgJson.MustUnmarshal([]byte(row["Parameter"]), task.Parameter)
    76  		id, err := strconv.Atoi(row["Id"])
    77  		if err != nil {
    78  			id = 0
    79  			log(task, err.Error())
    80  		}
    81  		task.Id = id
    82  		maxAllowExecuteSecond, err := strconv.Atoi(row["MaxAllowExecuteSecond"])
    83  		if err != nil {
    84  			maxAllowExecuteSecond = 0
    85  			log(task, err.Error())
    86  		}
    87  		task.MaxAllowExecuteSecond = time.Duration(maxAllowExecuteSecond)
    88  		maxAllowNumberOfRetry, err := strconv.Atoi(row["MaxAllowNumberOfRetry"])
    89  		if err != nil {
    90  			maxAllowNumberOfRetry = 0
    91  			log(task, err.Error())
    92  		}
    93  		task.MaxAllowNumberOfRetry = maxAllowNumberOfRetry
    94  		taskSlice = append(taskSlice, task)
    95  	}
    96  }
    97  
    98  func run() {
    99  	for _, task := range taskSlice {
   100  		if task.IsDone {
   101  			//TODO 将其从 taskSlice 中删除,提高一点点性能
   102  			continue
   103  		}
   104  		now := kmgTime.NowTime.Now()
   105  		d := now.Sub(task.ExecuteTime)
   106  		if int(d) < 0 {
   107  			continue
   108  		}
   109  		if task.Func == nil {
   110  			log(task, "task has no Func to execute")
   111  			task.IsDone = true // TODO 以后都不再理它,避免打一堆没用的Log
   112  			continue
   113  		}
   114  		exec(task)
   115  	}
   116  	time.Sleep(time.Second)
   117  	run()
   118  }
   119  
   120  func exec(task *Task) {
   121  	maxAllowNumberOfRetry := DefaultMaxAllowNumberOfRetry
   122  	if task.MaxAllowNumberOfRetry > 0 {
   123  		maxAllowNumberOfRetry = task.MaxAllowNumberOfRetry
   124  	}
   125  	if task.NumberOfTry >= maxAllowNumberOfRetry { //TODO 似乎这里不需要判断
   126  		return
   127  	}
   128  	task.NumberOfTry++
   129  	timeout := DefaultMaxAllowExecuteTime
   130  	if task.MaxAllowExecuteSecond > 0 {
   131  		timeout = task.MaxAllowExecuteSecond * time.Second
   132  	}
   133  	isDoneCh := make(chan bool)
   134  	go task.Func(isDoneCh, *task)
   135  	select {
   136  	case isDone := <-isDoneCh:
   137  		fmt.Println(isDone)
   138  		if isDone {
   139  			task.doneAndPersist()
   140  			return
   141  		}
   142  		if task.NumberOfTry >= maxAllowNumberOfRetry {
   143  			task.doneAndPersist()
   144  			log(task, "task failed "+strconv.Itoa(task.NumberOfTry)+" times")
   145  			return
   146  		}
   147  		exec(task)
   148  	case <-time.After(timeout):
   149  		task.doneAndPersist()
   150  		log(task, "time out")
   151  	}
   152  }
   153  
   154  func log(task *Task, info string) {
   155  	fmt.Println(info)
   156  }
   157  
   158  var initOnce sync.Once
   159  
   160  func Start() {
   161  	initOnce.Do(func() {
   162  		kmgSql.MustRegisterTable(kmgSql.Table{
   163  			Name: MySQLTableName,
   164  			FieldList: map[string]kmgSql.DbType{
   165  				"Id":                    kmgSql.DbTypeIntAutoIncrement,
   166  				"ExecuteTime":           kmgSql.DbTypeDatetime,
   167  				"Func":                  kmgSql.DbTypeString,
   168  				"Parameter":             kmgSql.DbTypeLongBlob,
   169  				"IsDone":                kmgSql.DbTypeBool,
   170  				"MaxAllowExecuteSecond": kmgSql.DbTypeString,
   171  				"MaxAllowNumberOfRetry": kmgSql.DbTypeInt,
   172  			},
   173  			PrimaryKey: "Id",
   174  		})
   175  		kmgSql.SyncDbCommand()
   176  		recoverFromDb()
   177  		run()
   178  	})
   179  }
   180  
   181  //可以在任何位置注册
   182  func RegisterTask(task Task) {
   183  	taskSlice = append(taskSlice, &task)
   184  }
   185  
   186  //必须在 Start 前注册好一些数据库中可能保存的 TaskFunc
   187  func RegisterTaskFunc(taskFunc interface{}) {
   188  	v := reflect.ValueOf(taskFunc)
   189  	f := runtime.FuncForPC(v.Pointer())
   190  	taskFuncMap[f.Name()] = v.Interface().(func(isDone chan bool, task Task))
   191  }