forked from TrueCloudLab/frostfs-node
[#1208] innerring: Fix race condition between handling new epoch and block
Before resetting the timer, ensure the block tick is processed. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
4770cb8bf6
commit
e8bf18c0b4
5 changed files with 59 additions and 3 deletions
|
@ -23,7 +23,15 @@ func (np *Processor) processNewEpoch(ev netmapEvent.NewEpoch) {
|
|||
}
|
||||
|
||||
np.epochState.SetEpochCounter(epoch)
|
||||
if err := np.epochTimer.ResetEpochTimer(); err != nil {
|
||||
|
||||
h, err := np.netmapClient.Morph().TxHeight(ev.TxHash())
|
||||
if err != nil {
|
||||
np.log.Warn("can't get transaction height",
|
||||
zap.String("hash", ev.TxHash().StringLE()),
|
||||
zap.String("error", err.Error()))
|
||||
}
|
||||
|
||||
if err := np.epochTimer.ResetEpochTimer(h); err != nil {
|
||||
np.log.Warn("can't reset epoch timer",
|
||||
zap.String("error", err.Error()))
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
type (
|
||||
// EpochTimerReseter is a callback interface for tickers component.
|
||||
EpochTimerReseter interface {
|
||||
ResetEpochTimer() error
|
||||
ResetEpochTimer(uint32) error
|
||||
}
|
||||
|
||||
// EpochState is a callback interface for inner ring global state.
|
||||
|
|
|
@ -160,7 +160,8 @@ func (s *Server) WriteReport(r *audit.Report) error {
|
|||
// ResetEpochTimer resets block timer that produces events to update epoch
|
||||
// counter in netmap contract. Used to synchronize this even production
|
||||
// based on block with notification of last epoch.
|
||||
func (s *Server) ResetEpochTimer() error {
|
||||
func (s *Server) ResetEpochTimer(h uint32) error {
|
||||
s.epochTimer.Tick(h)
|
||||
return s.epochTimer.Reset()
|
||||
}
|
||||
|
||||
|
|
|
@ -341,6 +341,18 @@ func (c *Client) TxHalt(h util.Uint256) (res bool, err error) {
|
|||
return len(aer.Executions) > 0 && aer.Executions[0].VMState.HasFlag(vm.HaltState), nil
|
||||
}
|
||||
|
||||
// TxHeight returns true if transaction has been successfully executed and persisted.
|
||||
func (c *Client) TxHeight(h util.Uint256) (res uint32, err error) {
|
||||
if c.multiClient != nil {
|
||||
return res, c.multiClient.iterateClients(func(c *Client) error {
|
||||
res, err = c.TxHeight(h)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
return c.client.GetTransactionHeight(h)
|
||||
}
|
||||
|
||||
// NeoFSAlphabetList returns keys that stored in NeoFS Alphabet role. Main chain
|
||||
// stores alphabet node keys of inner ring there, however side chain stores both
|
||||
// alphabet and non alphabet node keys of inner ring.
|
||||
|
|
|
@ -13,6 +13,41 @@ func tickN(t *timer.BlockTimer, n uint32) {
|
|||
}
|
||||
}
|
||||
|
||||
// This test emulates inner-ring handling of a new epoch and a new block.
|
||||
// "resetting" consists of ticking the current height as well and invoking `Reset`.
|
||||
func TestIRBlockTimer_Reset(t *testing.T) {
|
||||
var baseCounter [2]int
|
||||
blockDur := uint32(3)
|
||||
|
||||
bt1 := timer.NewBlockTimer(
|
||||
func() (uint32, error) { return blockDur, nil },
|
||||
func() { baseCounter[0]++ })
|
||||
bt2 := timer.NewBlockTimer(
|
||||
func() (uint32, error) { return blockDur, nil },
|
||||
func() { baseCounter[1]++ })
|
||||
|
||||
require.NoError(t, bt1.Reset())
|
||||
require.NoError(t, bt2.Reset())
|
||||
|
||||
run := func(bt *timer.BlockTimer, direct bool) {
|
||||
if direct {
|
||||
bt.Tick(1)
|
||||
require.NoError(t, bt.Reset())
|
||||
bt.Tick(1)
|
||||
} else {
|
||||
bt.Tick(1)
|
||||
bt.Tick(1)
|
||||
require.NoError(t, bt.Reset())
|
||||
}
|
||||
bt.Tick(2)
|
||||
bt.Tick(3)
|
||||
}
|
||||
|
||||
run(bt1, true)
|
||||
run(bt2, false)
|
||||
require.Equal(t, baseCounter[0], baseCounter[1])
|
||||
}
|
||||
|
||||
func TestBlockTimer(t *testing.T) {
|
||||
blockDur := uint32(10)
|
||||
baseCallCounter := uint32(0)
|
||||
|
|
Loading…
Reference in a new issue