chain events: Timeouts, cancellation
This commit is contained in:
parent
56b5574a72
commit
e4c73e9fd2
@ -67,8 +67,9 @@ func NewEvents(cs eventChainStore) *Events {
|
|||||||
|
|
||||||
confQueue: map[uint64]map[uint64][]*queuedEvent{},
|
confQueue: map[uint64]map[uint64][]*queuedEvent{},
|
||||||
revertQueue: map[uint64][]uint64{},
|
revertQueue: map[uint64][]uint64{},
|
||||||
triggers: map[uint64]callHandler{},
|
triggers: map[uint64]*callHandler{},
|
||||||
callTuples: map[callTuple][]uint64{},
|
callTuples: map[callTuple][]uint64{},
|
||||||
|
timeouts: map[uint64]map[uint64]int{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
// `ts` is the tipset, in which the `msg` is included.
|
// `ts` is the tipset, in which the `msg` is included.
|
||||||
// `curH`-`ts.Height` = `confidence`
|
// `curH`-`ts.Height` = `confidence`
|
||||||
type CalledHandler func(msg *types.Message, ts *types.TipSet, curH uint64) error
|
type CalledHandler func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error)
|
||||||
|
|
||||||
// CheckFunc is used before one-shoot callbacks for atomicity
|
// CheckFunc is used before one-shoot callbacks for atomicity
|
||||||
// guarantees. If the condition the callbacks wait for has already happened in
|
// guarantees. If the condition the callbacks wait for has already happened in
|
||||||
@ -26,6 +26,8 @@ type callHandler struct {
|
|||||||
confidence int
|
confidence int
|
||||||
timeout uint64
|
timeout uint64
|
||||||
|
|
||||||
|
disabled bool // TODO: GC after gcConfidence reached
|
||||||
|
|
||||||
handle CalledHandler
|
handle CalledHandler
|
||||||
revert RevertHandler
|
revert RevertHandler
|
||||||
}
|
}
|
||||||
@ -47,6 +49,9 @@ type calledEvents struct {
|
|||||||
|
|
||||||
ctr uint64
|
ctr uint64
|
||||||
|
|
||||||
|
triggers map[uint64]*callHandler
|
||||||
|
callTuples map[callTuple][]uint64
|
||||||
|
|
||||||
// maps block heights to events
|
// maps block heights to events
|
||||||
// [triggerH][msgH][event]
|
// [triggerH][msgH][event]
|
||||||
confQueue map[uint64]map[uint64][]*queuedEvent
|
confQueue map[uint64]map[uint64][]*queuedEvent
|
||||||
@ -54,8 +59,8 @@ type calledEvents struct {
|
|||||||
// [msgH][triggerH]
|
// [msgH][triggerH]
|
||||||
revertQueue map[uint64][]uint64
|
revertQueue map[uint64][]uint64
|
||||||
|
|
||||||
triggers map[uint64]callHandler
|
// [timeoutH+confidence][triggerId]{calls}
|
||||||
callTuples map[callTuple][]uint64
|
timeouts map[uint64]map[uint64]int
|
||||||
}
|
}
|
||||||
|
|
||||||
type callTuple struct {
|
type callTuple struct {
|
||||||
@ -77,6 +82,7 @@ func (e *calledEvents) headChangeCalled(rev, app []*types.TipSet) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
e.applyWithConfidence(ts)
|
e.applyWithConfidence(ts)
|
||||||
|
e.applyTimeouts(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -160,17 +166,58 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet) {
|
|||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
trigger := e.triggers[event.trigger]
|
trigger := e.triggers[event.trigger]
|
||||||
|
if trigger.disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := trigger.handle(event.msg, triggerTs, ts.Height()); err != nil {
|
more, err := trigger.handle(event.msg, triggerTs, ts.Height())
|
||||||
|
if err != nil {
|
||||||
log.Errorf("chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, origH, ts.Height(), err)
|
log.Errorf("chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, origH, ts.Height(), err)
|
||||||
continue // don't revert failed calls
|
continue // don't revert failed calls
|
||||||
}
|
}
|
||||||
|
|
||||||
event.called = true
|
event.called = true
|
||||||
|
|
||||||
|
touts, ok := e.timeouts[trigger.timeout]
|
||||||
|
if ok {
|
||||||
|
touts[event.trigger]++
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger.disabled = !more
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *calledEvents) applyTimeouts(ts *types.TipSet) {
|
||||||
|
triggers, ok := e.timeouts[ts.Height()]
|
||||||
|
if !ok {
|
||||||
|
return // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
for triggerId, calls := range triggers {
|
||||||
|
if calls > 0 {
|
||||||
|
continue // don't timeout if the method was called
|
||||||
|
}
|
||||||
|
trigger := e.triggers[triggerId]
|
||||||
|
if trigger.disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutTs, err := e.tsc.get(ts.Height() - uint64(trigger.confidence))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-uint64(trigger.confidence), ts.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
more, err := trigger.handle(nil, timeoutTs, ts.Height())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err)
|
||||||
|
continue // don't revert failed calls
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger.disabled = !more // allows messages after timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) error {
|
func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) error {
|
||||||
seen := map[cid.Cid]struct{}{}
|
seen := map[cid.Cid]struct{}{}
|
||||||
|
|
||||||
@ -204,7 +251,7 @@ func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Messa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHandler, confidence int, actor address.Address, method uint64) error {
|
func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHandler, confidence int, timeout uint64, actor address.Address, method uint64) error {
|
||||||
e.lk.Lock()
|
e.lk.Lock()
|
||||||
defer e.lk.Unlock()
|
defer e.lk.Unlock()
|
||||||
|
|
||||||
@ -221,9 +268,9 @@ func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHand
|
|||||||
id := e.ctr
|
id := e.ctr
|
||||||
e.ctr++
|
e.ctr++
|
||||||
|
|
||||||
e.triggers[id] = callHandler{
|
e.triggers[id] = &callHandler{
|
||||||
confidence: confidence,
|
confidence: confidence,
|
||||||
timeout: math.MaxUint64, // TODO
|
timeout: timeout + uint64(confidence),
|
||||||
|
|
||||||
handle: hnd,
|
handle: hnd,
|
||||||
revert: rev,
|
revert: rev,
|
||||||
@ -235,6 +282,13 @@ func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHand
|
|||||||
}
|
}
|
||||||
|
|
||||||
e.callTuples[ct] = append(e.callTuples[ct], id)
|
e.callTuples[ct] = append(e.callTuples[ct], id)
|
||||||
|
if timeout != math.MaxUint64 {
|
||||||
|
if e.timeouts[timeout+uint64(confidence)] == nil {
|
||||||
|
e.timeouts[timeout+uint64(confidence)] = map[uint64]int{}
|
||||||
|
}
|
||||||
|
e.timeouts[timeout+uint64(confidence)][id] = 0
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,6 +191,7 @@ func TestCalled(t *testing.T) {
|
|||||||
t0123, err := address.NewFromString("t0123")
|
t0123, err := address.NewFromString("t0123")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
more := true
|
||||||
var applied, reverted bool
|
var applied, reverted bool
|
||||||
var appliedMsg *types.Message
|
var appliedMsg *types.Message
|
||||||
var appliedTs *types.TipSet
|
var appliedTs *types.TipSet
|
||||||
@ -198,16 +199,16 @@ func TestCalled(t *testing.T) {
|
|||||||
|
|
||||||
err = events.Called(func(ts *types.TipSet) (b bool, e error) {
|
err = events.Called(func(ts *types.TipSet) (b bool, e error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}, func(msg *types.Message, ts *types.TipSet, curH uint64) error {
|
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
|
||||||
applied = true
|
applied = true
|
||||||
appliedMsg = msg
|
appliedMsg = msg
|
||||||
appliedTs = ts
|
appliedTs = ts
|
||||||
appliedH = curH
|
appliedH = curH
|
||||||
return nil
|
return more, nil
|
||||||
}, func(ts *types.TipSet) error {
|
}, func(ts *types.TipSet) error {
|
||||||
reverted = true
|
reverted = true
|
||||||
return nil
|
return nil
|
||||||
}, 3, t0123, 5)
|
}, 3, 20, t0123, 5)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// create few blocks to make sure nothing get's randomly called
|
// create few blocks to make sure nothing get's randomly called
|
||||||
@ -326,4 +327,86 @@ func TestCalled(t *testing.T) {
|
|||||||
require.Equal(t, false, applied)
|
require.Equal(t, false, applied)
|
||||||
require.Equal(t, false, reverted)
|
require.Equal(t, false, reverted)
|
||||||
|
|
||||||
|
// test timeout (it's set to 20 in the call to `events.Called` above)
|
||||||
|
|
||||||
|
fcs.advance(0, 6, nil) // H=25
|
||||||
|
|
||||||
|
require.Equal(t, false, applied) // not calling timeout as we received messages
|
||||||
|
require.Equal(t, false, reverted)
|
||||||
|
|
||||||
|
// test unregistering with more
|
||||||
|
|
||||||
|
more = false
|
||||||
|
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=26; H=29
|
||||||
|
0: fcs.fakeMsgs(fakeMsg{
|
||||||
|
bmsgs: []*types.Message{
|
||||||
|
{To: t0123, Method: 5, Nonce: 4}, // this signals we don't want more
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(t, true, applied)
|
||||||
|
require.Equal(t, false, reverted)
|
||||||
|
applied = false
|
||||||
|
|
||||||
|
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=26; H=29
|
||||||
|
0: fcs.fakeMsgs(fakeMsg{
|
||||||
|
bmsgs: []*types.Message{
|
||||||
|
{To: t0123, Method: 5, Nonce: 5},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(t, false, applied) // should not get any further notifications
|
||||||
|
require.Equal(t, false, reverted)
|
||||||
|
|
||||||
|
// revert after disabled
|
||||||
|
|
||||||
|
fcs.advance(5, 1, nil) // try reverting msg sent after disabling
|
||||||
|
|
||||||
|
require.Equal(t, false, applied)
|
||||||
|
require.Equal(t, false, reverted)
|
||||||
|
|
||||||
|
fcs.advance(5, 1, nil) // try reverting msg sent before disabling
|
||||||
|
|
||||||
|
require.Equal(t, false, applied)
|
||||||
|
require.Equal(t, true, reverted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalledTimeout(t *testing.T) {
|
||||||
|
fcs := &fakeCS{
|
||||||
|
t: t,
|
||||||
|
h: 1,
|
||||||
|
|
||||||
|
msgs: map[cid.Cid]fakeMsg{},
|
||||||
|
tsc: newTSCache(2 * build.ForkLengthThreshold),
|
||||||
|
}
|
||||||
|
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||||
|
|
||||||
|
events := NewEvents(fcs)
|
||||||
|
|
||||||
|
t0123, err := address.NewFromString("t0123")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
|
||||||
|
err = events.Called(func(ts *types.TipSet) (b bool, e error) {
|
||||||
|
return false, nil
|
||||||
|
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
|
||||||
|
called = true
|
||||||
|
require.Nil(t, msg)
|
||||||
|
require.Equal(t, uint64(20), ts.Height())
|
||||||
|
require.Equal(t, uint64(23), curH)
|
||||||
|
return false, nil
|
||||||
|
}, func(ts *types.TipSet) error {
|
||||||
|
t.Fatal("revert on timeout")
|
||||||
|
return nil
|
||||||
|
}, 3, 20, t0123, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fcs.advance(0, 21, nil)
|
||||||
|
require.False(t, called)
|
||||||
|
|
||||||
|
fcs.advance(0, 5, nil)
|
||||||
|
require.True(t, called)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user