diff --git a/blockstore/autobatch.go b/blockstore/autobatch.go index d810212d2..d41d521ef 100644 --- a/blockstore/autobatch.go +++ b/blockstore/autobatch.go @@ -181,18 +181,22 @@ func (bs *AutobatchBlockstore) Get(ctx context.Context, c cid.Cid) (block.Block, } bs.stateLock.Lock() - defer bs.stateLock.Unlock() v, ok := bs.flushingBatch.blockMap[c] if ok { + bs.stateLock.Unlock() return v, nil } v, ok = bs.bufferedBatch.blockMap[c] if ok { + bs.stateLock.Unlock() return v, nil } + bs.stateLock.Unlock() - return nil, ipld.ErrNotFound{Cid: c} + // We have to check the backing store one more time because it may have been flushed by the + // time we were able to take the lock above. + return bs.backingBs.Get(ctx, c) } func (bs *AutobatchBlockstore) DeleteBlock(context.Context, cid.Cid) error { diff --git a/blockstore/autobatch_test.go b/blockstore/autobatch_test.go index 57a3b7d6c..495c6c4db 100644 --- a/blockstore/autobatch_test.go +++ b/blockstore/autobatch_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + ipld "github.com/ipfs/go-ipld-format" "github.com/stretchr/testify/require" ) @@ -29,6 +30,10 @@ func TestAutobatchBlockstore(t *testing.T) { require.NoError(t, err) require.Equal(t, b2.RawData(), v2.RawData()) + // Regression test for a deadlock. + _, err = ab.Get(ctx, b3.Cid()) + require.True(t, ipld.IsNotFound(err)) + require.NoError(t, ab.Flush(ctx)) require.NoError(t, ab.Shutdown(ctx)) } diff --git a/blockstore/union_test.go b/blockstore/union_test.go index a3ca117b2..579489947 100644 --- a/blockstore/union_test.go +++ b/blockstore/union_test.go @@ -13,6 +13,7 @@ var ( b0 = blocks.NewBlock([]byte("abc")) b1 = blocks.NewBlock([]byte("foo")) b2 = blocks.NewBlock([]byte("bar")) + b3 = blocks.NewBlock([]byte("baz")) ) func TestUnionBlockstore_Get(t *testing.T) { diff --git a/cmd/lotus-shed/actor.go b/cmd/lotus-shed/actor.go index 5f9d823bb..037b6d433 100644 --- a/cmd/lotus-shed/actor.go +++ b/cmd/lotus-shed/actor.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/hex" "fmt" "os" @@ -420,6 +421,11 @@ var actorControlSet = &cli.Command{ Name: "actor", Usage: "specify the address of miner actor", }, + &cli.BoolFlag{ + Name: "dump-bytes", + Usage: "Dumps the bytes of the message that would propose this change", + Value: false, + }, &cli.BoolFlag{ Name: "really-do-it", Usage: "Actually send transaction performing the action", @@ -427,11 +433,6 @@ var actorControlSet = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if !cctx.Bool("really-do-it") { - fmt.Println("Pass --really-do-it to actually execute this action") - return nil - } - var maddr address.Address if act := cctx.String("actor"); act != "" { var err error @@ -521,14 +522,36 @@ var actorControlSet = &cli.Command{ return xerrors.Errorf("serializing params: %w", err) } - smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{ + msg := &types.Message{ From: mi.Owner, To: maddr, Method: builtin.MethodsMiner.ChangeWorkerAddress, - Value: big.Zero(), Params: sp, - }, nil) + } + + if cctx.Bool("dump-bytes") { + + msg, err = nodeAPI.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) + if err != nil { + return err + } + + msgBytes, err := msg.Serialize() + if err != nil { + return err + } + + fmt.Fprintln(cctx.App.Writer, hex.EncodeToString(msgBytes)) + return nil + } + + if !cctx.Bool("really-do-it") { + fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action") + return nil + } + + smsg, err := nodeAPI.MpoolPushMessage(ctx, msg, nil) if err != nil { return xerrors.Errorf("mpool push: %w", err) }