func (t *TimeoutWheel) Schedule(
	d time.Duration,
	expireCb func(interface{}),
	arg interface{},
) (Timeout, error) {
	dTicks := (d + t.tickInterval - 1) / t.tickInterval
	deadline := atomic.LoadUint64(&t.ticks) + uint64(dTicks)
	timeout := t.getTimeoutLocked(deadline)
	if t.state != running {
		t.putTimeoutLocked(timeout)
		timeout.mtx.Unlock()
		return Timeout{}, ErrSystemStopped
	}
	bucket := &t.buckets[deadline&t.bucketMask]
	timeout.expireCb = expireCb
	timeout.expireArg = arg
	timeout.deadline = deadline
	timeout.state = timeoutActive
	out := Timeout{timeout: timeout, generation: timeout.generation}
	if bucket.lastTick >= deadline {
		t.putTimeoutLocked(timeout)
		timeout.mtx.Unlock()
		expireCb(arg)
		return out, nil
	}
	timeout.prependLocked(bucket)
	timeout.mtx.Unlock()
	return out, nil
}
Schedule adds a new function to be called after some duration of time has
elapsed. The returned Timeout can be used to cancel calling the function. If the duration falls
between two ticks, the latter tick is used.
package goetty
import (
	"errors"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)
const (
	defaultTickInterval = time.Millisecond
	defaultNumBuckets   = 2048
	cacheline    = 64
	bitsInUint64 = 64
)
const (
	stopped = iota
	stopping
	running
)
const (
	timeoutInactive = iota
	timeoutExpired
	timeoutActive
)
var (
	ErrSystemStopped = errors.New("Timeout System is stopped")
	defaultPoolSize = uint64(1 << uint(findMSB(uint64(runtime.NumCPU()))+2))
)
type Timeout struct {
	generation uint64
	timeout    *timeout
}
func (t *Timeout) Stop() bool {
	if t.timeout == nil {
		return false
	}
	t.timeout.mtx.Lock()
	if t.timeout.generation != t.generation || t.timeout.state != timeoutActive {
		t.timeout.mtx.Unlock()
		return false
	}
	t.timeout.removeLocked()
	t.timeout.wheel.putTimeoutLocked(t.timeout)
	t.timeout.mtx.Unlock()
	return true
}
type timeout struct {
	mtx       *paddedMutex
	expireCb  func(interface{})
	expireArg interface{}
	deadline  uint64
	next  *timeout
	prev  **timeout
	state int32
	wheel      *TimeoutWheel
	generation uint64
}
type timeoutList struct {
	lastTick uint64
	head     *timeout
}
func (t *timeout) prependLocked(list *timeoutList) {
	if list.head != nil {
		t.prev = list.head.prev
		list.head.prev = &t.next
	} else {
		t.prev = &list.head
	}
	t.next = list.head
	list.head = t
}
func (t *timeout) removeLocked() {
	if t.next != nil {
		t.next.prev = t.prev
	}
	*t.prev = t.next
	t.next = nil
	t.prev = nil
}
type TimeoutWheel struct {
	ticks uint64
	mtxPool      []paddedMutex
	bucketMask   uint64
	poolMask     uint64
	tickInterval time.Duration
	buckets      []timeoutList
	freelists    []timeoutList
	state     int
	calloutCh chan timeoutList
	done      chan struct{}
}
type Option func(*opts)
type opts struct {
	tickInterval time.Duration
	size         uint64
	poolsize     uint64
}
type paddedMutex struct {
	sync.Mutex
	_ [cacheline - unsafe.Sizeof(sync.Mutex{})]byte
}
func WithTickInterval(interval time.Duration) Option {
	return func(opts *opts) { opts.tickInterval = interval }
}
func WithBucketsExponent(bucketExp uint) Option {
	return func(opts *opts) {
		opts.size = uint64(1 << bucketExp)
	}
}
func WithLocksExponent(lockExp uint) Option {
	return func(opts *opts) {
		opts.poolsize = uint64(1 << lockExp)
	}
}
func NewTimeoutWheel(options ...Option) *TimeoutWheel {
	opts := &opts{
		tickInterval: defaultTickInterval,
		size:         defaultNumBuckets,
		poolsize:     defaultPoolSize,
	}
	for _, option := range options {
		option(opts)
	}
	poolsize := opts.poolsize
	if opts.size < opts.poolsize {
		poolsize = opts.size
	}
	t := &TimeoutWheel{
		mtxPool:      make([]paddedMutex, poolsize),
		freelists:    make([]timeoutList, poolsize),
		state:        stopped,
		poolMask:     poolsize - 1,
		buckets:      make([]timeoutList, opts.size),
		tickInterval: opts.tickInterval,
		bucketMask:   opts.size - 1,
		ticks:        0,
	}
	t.Start()
	return t
}
func (t *TimeoutWheel) Start() {
	t.lockAllBuckets()
	defer t.unlockAllBuckets()
	for t.state != stopped {
		switch t.state {
		case stopping:
			t.unlockAllBuckets()
			<-t.done
			t.lockAllBuckets()
		case running:
			panic("Tried to start a running TimeoutWheel")
		}
	}
	t.state = running
	t.done = make(chan struct{})
	t.calloutCh = make(chan timeoutList)
	go t.doTick()
	go t.doExpired()
}
func (t *TimeoutWheel) Stop() {
	t.lockAllBuckets()
	if t.state == running {
		t.state = stopping
		close(t.calloutCh)
		for i := range t.buckets {
			t.freeBucketLocked(t.buckets[i])
		}
	}
	t.unlockAllBuckets()
	<-t.done
}