unified task package

pull/1132/head
Darien Raymond 2018-05-27 13:02:29 +02:00
parent 7fa4bb434b
commit 13f3c356ca
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
21 changed files with 252 additions and 66 deletions

View File

@ -11,7 +11,7 @@ import (
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/dice" "v2ray.com/core/common/dice"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/signal" "v2ray.com/core/common/task"
"v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/internet/udp"
) )
@ -42,7 +42,7 @@ type UDPNameServer struct {
address net.Destination address net.Destination
requests map[uint16]*PendingRequest requests map[uint16]*PendingRequest
udpServer *udp.Dispatcher udpServer *udp.Dispatcher
cleanup *signal.PeriodicTask cleanup *task.Periodic
} }
func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer { func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
@ -51,7 +51,7 @@ func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPN
requests: make(map[uint16]*PendingRequest), requests: make(map[uint16]*PendingRequest),
udpServer: udp.NewDispatcher(dispatcher), udpServer: udp.NewDispatcher(dispatcher),
} }
s.cleanup = &signal.PeriodicTask{ s.cleanup = &task.Periodic{
Interval: time.Minute, Interval: time.Minute,
Execute: s.Cleanup, Execute: s.Cleanup,
} }

View File

@ -11,7 +11,7 @@ import (
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/signal" "v2ray.com/core/common/task"
) )
const ( const (
@ -33,7 +33,7 @@ type Server struct {
hosts map[string]net.IP hosts map[string]net.IP
records map[string]*DomainRecord records map[string]*DomainRecord
servers []NameServer servers []NameServer
task *signal.PeriodicTask task *task.Periodic
} }
func New(ctx context.Context, config *Config) (*Server, error) { func New(ctx context.Context, config *Config) (*Server, error) {
@ -42,7 +42,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
servers: make([]NameServer, len(config.NameServers)), servers: make([]NameServer, len(config.NameServers)),
hosts: config.GetInternalHosts(), hosts: config.GetInternalHosts(),
} }
server.task = &signal.PeriodicTask{ server.task = &task.Periodic{
Interval: time.Minute * 10, Interval: time.Minute * 10,
Execute: func() error { Execute: func() error {
server.cleanup() server.cleanup()

View File

@ -10,7 +10,7 @@ import (
"v2ray.com/core/app/proxyman/mux" "v2ray.com/core/app/proxyman/mux"
"v2ray.com/core/common/dice" "v2ray.com/core/common/dice"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/signal" "v2ray.com/core/common/task"
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
) )
@ -25,7 +25,7 @@ type DynamicInboundHandler struct {
worker []worker worker []worker
lastRefresh time.Time lastRefresh time.Time
mux *mux.Server mux *mux.Server
task *signal.PeriodicTask task *task.Periodic
} }
func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) { func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
@ -39,7 +39,7 @@ func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *p
v: v, v: v,
} }
h.task = &signal.PeriodicTask{ h.task = &task.Periodic{
Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()), Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()),
Execute: h.refresh, Execute: h.refresh,
} }

View File

@ -1,23 +0,0 @@
package functions
import "v2ray.com/core/common"
// Task is a function that may return an error.
type Task func() error
// OnSuccess returns a Task to run a follow task if pre-condition passes, otherwise the error in pre-condition is returned.
func OnSuccess(pre func() error, followup Task) Task {
return func() error {
if err := pre(); err != nil {
return err
}
return followup()
}
}
// Close returns a Task to close the object.
func Close(obj interface{}) Task {
return func() error {
return common.Close(obj)
}
}

9
common/task/common.go Normal file
View File

@ -0,0 +1,9 @@
package task
import "v2ray.com/core/common"
func Close(v interface{}) Task {
return func() error {
return common.Close(v)
}
}

View File

@ -1,12 +1,12 @@
package signal package task
import ( import (
"sync" "sync"
"time" "time"
) )
// PeriodicTask is a task that runs periodically. // Periodic is a task that runs periodically.
type PeriodicTask struct { type Periodic struct {
// Interval of the task being run // Interval of the task being run
Interval time.Duration Interval time.Duration
// Execute is the task function // Execute is the task function
@ -19,7 +19,7 @@ type PeriodicTask struct {
closed bool closed bool
} }
func (t *PeriodicTask) checkedExecute() error { func (t *Periodic) checkedExecute() error {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()
@ -41,7 +41,7 @@ func (t *PeriodicTask) checkedExecute() error {
} }
// Start implements common.Runnable. Start must not be called multiple times without Close being called. // Start implements common.Runnable. Start must not be called multiple times without Close being called.
func (t *PeriodicTask) Start() error { func (t *Periodic) Start() error {
t.access.Lock() t.access.Lock()
t.closed = false t.closed = false
t.access.Unlock() t.access.Unlock()
@ -55,7 +55,7 @@ func (t *PeriodicTask) Start() error {
} }
// Close implements common.Closable. // Close implements common.Closable.
func (t *PeriodicTask) Close() error { func (t *Periodic) Close() error {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()

View File

@ -1,19 +1,20 @@
package signal_test package task_test
import ( import (
"testing" "testing"
"time" "time"
"v2ray.com/core/common" . "v2ray.com/core/common/task"
. "v2ray.com/core/common/signal"
. "v2ray.com/ext/assert" . "v2ray.com/ext/assert"
"v2ray.com/core/common"
) )
func TestPeriodicTaskStop(t *testing.T) { func TestPeriodicTaskStop(t *testing.T) {
assert := With(t) assert := With(t)
value := 0 value := 0
task := &PeriodicTask{ task := &Periodic{
Interval: time.Second * 2, Interval: time.Second * 2,
Execute: func() error { Execute: func() error {
value++ value++

137
common/task/task.go Normal file
View File

@ -0,0 +1,137 @@
package task
import (
"context"
"v2ray.com/core/common/signal"
)
type Task func() error
type executionContext struct {
ctx context.Context
task Task
onSuccess Task
onFailure Task
}
func (c *executionContext) executeTask() error {
if c.ctx == nil && c.task == nil {
return nil
}
if c.ctx == nil {
return c.task()
}
if c.task == nil {
<-c.ctx.Done()
return c.ctx.Err()
}
return executeParallel(func() error {
<-c.ctx.Done()
return c.ctx.Err()
}, c.task)
}
func (c *executionContext) run() error {
err := c.executeTask()
if err == nil && c.onSuccess != nil {
return c.onSuccess()
}
if err != nil && c.onFailure != nil {
return c.onFailure()
}
return err
}
type ExecutionOption func(*executionContext)
func WithContext(ctx context.Context) ExecutionOption {
return func(c *executionContext) {
c.ctx = ctx
}
}
func Parallel(tasks ...Task) ExecutionOption {
return func(c *executionContext) {
c.task = func() error {
return executeParallel(tasks...)
}
}
}
func Sequential(tasks ...Task) ExecutionOption {
return func(c *executionContext) {
c.task = func() error {
return execute(tasks...)
}
}
}
func OnSuccess(task Task) ExecutionOption {
return func(c *executionContext) {
c.onSuccess = task
}
}
func OnFailure(task Task) ExecutionOption {
return func(c *executionContext) {
c.onFailure = task
}
}
func Single(task Task, opts ExecutionOption) Task {
return Run(append([]ExecutionOption{Sequential(task)}, opts)...)
}
func Run(opts ...ExecutionOption) Task {
var c executionContext
for _, opt := range opts {
opt(&c)
}
return func() error {
return c.run()
}
}
// execute runs a list of tasks sequentially, returns the first error encountered or nil if all tasks pass.
func execute(tasks ...Task) error {
for _, task := range tasks {
if err := task(); err != nil {
return err
}
}
return nil
}
// executeParallel executes a list of tasks asynchronously, returns the first error encountered or nil if all tasks pass.
func executeParallel(tasks ...Task) error {
n := len(tasks)
s := signal.NewSemaphore(n)
done := make(chan error, 1)
for _, task := range tasks {
<-s.Wait()
go func(f func() error) {
if err := f(); err != nil {
select {
case done <- err:
default:
}
}
s.Signal()
}(task)
}
for i := 0; i < n; i++ {
select {
case err := <-done:
return err
case <-s.Wait():
}
}
return nil
}

43
common/task/task_test.go Normal file
View File

@ -0,0 +1,43 @@
package task_test
import (
"context"
"errors"
"testing"
"time"
. "v2ray.com/core/common/task"
. "v2ray.com/ext/assert"
)
func TestExecuteParallel(t *testing.T) {
assert := With(t)
err := Run(Parallel(func() error {
time.Sleep(time.Millisecond * 200)
return errors.New("test")
}, func() error {
time.Sleep(time.Millisecond * 500)
return errors.New("test2")
}))()
assert(err.Error(), Equals, "test")
}
func TestExecuteParallelContextCancel(t *testing.T) {
assert := With(t)
ctx, cancel := context.WithCancel(context.Background())
err := Run(WithContext(ctx), Parallel(func() error {
time.Sleep(time.Millisecond * 2000)
return errors.New("test")
}, func() error {
time.Sleep(time.Millisecond * 5000)
return errors.New("test2")
}, func() error {
cancel()
return nil
}))()
assert(err.Error(), HasSubstring, "canceled")
}

View File

@ -9,9 +9,9 @@ import (
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/signal" "v2ray.com/core/common/signal"
"v2ray.com/core/common/task"
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
"v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet"
"v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/internet/udp"
@ -118,7 +118,10 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, functions.OnSuccess(requestDone, functions.Close(link.Writer)), responseDone); err != nil { if err := task.Run(task.WithContext(ctx),
task.Parallel(
task.Single(requestDone, task.OnSuccess(task.Close(link.Writer))),
responseDone))(); err != nil {
pipe.CloseError(link.Reader) pipe.CloseError(link.Reader)
pipe.CloseError(link.Writer) pipe.CloseError(link.Writer)
return newError("connection ends").Base(err) return newError("connection ends").Base(err)

View File

@ -10,10 +10,10 @@ import (
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/dice" "v2ray.com/core/common/dice"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/retry" "v2ray.com/core/common/retry"
"v2ray.com/core/common/signal" "v2ray.com/core/common/signal"
"v2ray.com/core/common/task"
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
"v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet"
) )
@ -136,7 +136,7 @@ func (h *Handler) Process(ctx context.Context, link *core.Link, dialer proxy.Dia
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, requestDone, functions.OnSuccess(responseDone, functions.Close(output))); err != nil { if err := task.Run(task.WithContext(ctx), task.Parallel(requestDone, task.Single(responseDone, task.OnSuccess(task.Close(output)))))(); err != nil {
return newError("connection ends").Base(err) return newError("connection ends").Base(err)
} }

View File

@ -1,4 +1,6 @@
package http package http
/*
type Client struct { type Client struct {
} }
*/

View File

@ -10,13 +10,14 @@ import (
"strings" "strings"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core/transport/pipe" "v2ray.com/core/transport/pipe"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/errors" "v2ray.com/core/common/errors"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/log" "v2ray.com/core/common/log"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
http_proto "v2ray.com/core/common/protocol/http" http_proto "v2ray.com/core/common/protocol/http"
@ -210,7 +211,8 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, functions.OnSuccess(requestDone, functions.Close(link.Writer)), responseDone); err != nil { var closeWriter = task.Single(requestDone, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(closeWriter, responseDone))(); err != nil {
pipe.CloseError(link.Reader) pipe.CloseError(link.Reader)
pipe.CloseError(link.Writer) pipe.CloseError(link.Writer)
return newError("connection ends").Base(err) return newError("connection ends").Base(err)

View File

@ -3,10 +3,11 @@ package shadowsocks
import ( import (
"context" "context"
"v2ray.com/core/common/task"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/retry" "v2ray.com/core/common/retry"
@ -158,7 +159,8 @@ func (c *Client) Process(ctx context.Context, link *core.Link, dialer proxy.Dial
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, requestDone, functions.OnSuccess(responseDone, functions.Close(link.Writer))); err != nil { var responseDoneAndCloseWriter = task.Single(responseDone, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestDone, responseDoneAndCloseWriter))(); err != nil {
return newError("connection ends").Base(err) return newError("connection ends").Base(err)
} }

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/log" "v2ray.com/core/common/log"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
@ -216,7 +217,8 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection,
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, functions.OnSuccess(requestDone, functions.Close(link.Writer)), responseDone); err != nil { var requestDoneAndCloseWriter = task.Single(requestDone, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestDoneAndCloseWriter, responseDone))(); err != nil {
pipe.CloseError(link.Reader) pipe.CloseError(link.Reader)
pipe.CloseError(link.Writer) pipe.CloseError(link.Writer)
return newError("connection ends").Base(err) return newError("connection ends").Base(err)

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/retry" "v2ray.com/core/common/retry"
@ -130,7 +131,8 @@ func (c *Client) Process(ctx context.Context, link *core.Link, dialer proxy.Dial
} }
} }
if err := signal.ExecuteParallel(ctx, requestFunc, functions.OnSuccess(responseFunc, functions.Close(link.Writer))); err != nil { var responseDonePost = task.Single(responseFunc, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestFunc, responseDonePost))(); err != nil {
return newError("connection ends").Base(err) return newError("connection ends").Base(err)
} }

View File

@ -5,10 +5,11 @@ import (
"io" "io"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/log" "v2ray.com/core/common/log"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
@ -160,7 +161,8 @@ func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
return nil return nil
} }
if err := signal.ExecuteParallel(ctx, functions.OnSuccess(requestDone, functions.Close(link.Writer)), responseDone); err != nil { var requestDonePost = task.Single(requestDone, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestDonePost, responseDone))(); err != nil {
pipe.CloseError(link.Reader) pipe.CloseError(link.Reader)
pipe.CloseError(link.Writer) pipe.CloseError(link.Writer)
return newError("connection ends").Base(err) return newError("connection ends").Base(err)

View File

@ -19,7 +19,7 @@ import (
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/serial" "v2ray.com/core/common/serial"
"v2ray.com/core/common/signal" "v2ray.com/core/common/task"
"v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess"
) )
@ -33,7 +33,7 @@ type sessionId struct {
type SessionHistory struct { type SessionHistory struct {
sync.RWMutex sync.RWMutex
cache map[sessionId]time.Time cache map[sessionId]time.Time
task *signal.PeriodicTask task *task.Periodic
} }
// NewSessionHistory creates a new SessionHistory object. // NewSessionHistory creates a new SessionHistory object.
@ -41,7 +41,7 @@ func NewSessionHistory() *SessionHistory {
h := &SessionHistory{ h := &SessionHistory{
cache: make(map[sessionId]time.Time, 128), cache: make(map[sessionId]time.Time, 128),
} }
h.task = &signal.PeriodicTask{ h.task = &task.Periodic{
Interval: time.Second * 30, Interval: time.Second * 30,
Execute: func() error { Execute: func() error {
h.removeExpiredEntries() h.removeExpiredEntries()

View File

@ -9,11 +9,12 @@ import (
"sync" "sync"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/errors" "v2ray.com/core/common/errors"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/log" "v2ray.com/core/common/log"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
@ -294,7 +295,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
return transferResponse(timer, session, request, response, link.Reader, writer) return transferResponse(timer, session, request, response, link.Reader, writer)
} }
if err := signal.ExecuteParallel(ctx, functions.OnSuccess(requestDone, functions.Close(link.Writer)), responseDone); err != nil { var requestDonePost = task.Single(requestDone, task.OnSuccess(task.Close(link.Writer)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestDonePost, responseDone))(); err != nil {
pipe.CloseError(link.Reader) pipe.CloseError(link.Reader)
pipe.CloseError(link.Writer) pipe.CloseError(link.Writer)
return newError("connection ends").Base(err) return newError("connection ends").Base(err)

View File

@ -6,12 +6,13 @@ import (
"context" "context"
"time" "time"
"v2ray.com/core/common/task"
"v2ray.com/core/transport/pipe" "v2ray.com/core/transport/pipe"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
"v2ray.com/core/common/functions"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/retry" "v2ray.com/core/common/retry"
@ -161,7 +162,8 @@ func (v *Handler) Process(ctx context.Context, link *core.Link, dialer proxy.Dia
return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) return buf.Copy(bodyReader, output, buf.UpdateActivity(timer))
} }
if err := signal.ExecuteParallel(ctx, requestDone, functions.OnSuccess(responseDone, functions.Close(output))); err != nil { var responseDonePost = task.Single(responseDone, task.OnSuccess(task.Close(output)))
if err := task.Run(task.WithContext(ctx), task.Parallel(requestDone, responseDonePost))(); err != nil {
return newError("connection ends").Base(err) return newError("connection ends").Base(err)
} }

View File

@ -14,7 +14,7 @@ import (
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/signal" "v2ray.com/core/common/task"
) )
const ( const (
@ -34,7 +34,7 @@ type TimedUserValidator struct {
userHash map[[16]byte]indexTimePair userHash map[[16]byte]indexTimePair
hasher protocol.IDHash hasher protocol.IDHash
baseTime protocol.Timestamp baseTime protocol.Timestamp
task *signal.PeriodicTask task *task.Periodic
} }
type indexTimePair struct { type indexTimePair struct {
@ -49,7 +49,7 @@ func NewTimedUserValidator(hasher protocol.IDHash) *TimedUserValidator {
hasher: hasher, hasher: hasher,
baseTime: protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2), baseTime: protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2),
} }
tuv.task = &signal.PeriodicTask{ tuv.task = &task.Periodic{
Interval: updateInterval, Interval: updateInterval,
Execute: func() error { Execute: func() error {
tuv.updateUserHash() tuv.updateUserHash()