Skip to content

Commit 00c7ffc

Browse files
authored
lightning: fix panic when nextKey twice (#40959) (#41017)
close #40934
1 parent 064638f commit 00c7ffc

File tree

9 files changed

+37
-11
lines changed

9 files changed

+37
-11
lines changed

br/pkg/lightning/backend/local/local.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,9 @@ func (local *local) ImportEngine(ctx context.Context, engineUUID uuid.UUID, regi
13851385
// the table when table is created.
13861386
needSplit := len(unfinishedRanges) > 1 || lfTotalSize > regionSplitSize || lfLength > regionSplitKeys
13871387
// split region by given ranges
1388+
failpoint.Inject("failToSplit", func(_ failpoint.Value) {
1389+
needSplit = true
1390+
})
13881391
for i := 0; i < maxRetryTimes; i++ {
13891392
err = local.SplitAndScatterRegionInBatches(ctx, unfinishedRanges, lf.tableInfo, needSplit, regionSplitSize, maxBatchSplitRanges)
13901393
if err == nil || common.IsContextCanceledError(err) {
@@ -1856,7 +1859,8 @@ func nextKey(key []byte) []byte {
18561859

18571860
// in tikv <= 4.x, tikv will truncate the row key, so we should fetch the next valid row key
18581861
// See: https://github.com/tikv/tikv/blob/f7f22f70e1585d7ca38a59ea30e774949160c3e8/components/raftstore/src/coprocessor/split_observer.rs#L36-L41
1859-
if tablecodec.IsRecordKey(key) {
1862+
// we only do this for IntHandle, which is checked by length
1863+
if tablecodec.IsRecordKey(key) && len(key) == tablecodec.RecordRowKeyLen {
18601864
tableID, handle, _ := tablecodec.DecodeRecordKey(key)
18611865
nextHandle := handle.Next()
18621866
// int handle overflow, use the next table prefix as nextKey
@@ -1866,7 +1870,7 @@ func nextKey(key []byte) []byte {
18661870
return tablecodec.EncodeRowKeyWithHandle(tableID, nextHandle)
18671871
}
18681872

1869-
// if key is an index, directly append a 0x00 to the key.
1873+
// for index key and CommonHandle, directly append a 0x00 to the key.
18701874
res := make([]byte, 0, len(key)+1)
18711875
res = append(res, key...)
18721876
res = append(res, 0)

br/pkg/lightning/backend/local/local_test.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,21 @@ func TestNextKey(t *testing.T) {
116116
require.NoError(t, err)
117117
nextHdl, err := tidbkv.NewCommonHandle(nextKeyBytes)
118118
require.NoError(t, err)
119-
expectNextKey := []byte(tablecodec.EncodeRowKeyWithHandle(1, nextHdl))
120-
require.Equal(t, expectNextKey, nextKey(key))
119+
nextValidKey := []byte(tablecodec.EncodeRowKeyWithHandle(1, nextHdl))
120+
// nextKey may return a key that can't be decoded, but it must not be larger than the valid next key.
121+
require.True(t, bytes.Compare(nextKey(key), nextValidKey) <= 0, "datums: %v", datums)
121122
}
122123

124+
// a special case that when len(string datum) % 8 == 7, nextKey twice should not panic.
125+
keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewStringDatum("1234567"))
126+
require.NoError(t, err)
127+
h, err := tidbkv.NewCommonHandle(keyBytes)
128+
require.NoError(t, err)
129+
key = tablecodec.EncodeRowKeyWithHandle(1, h)
130+
nextOnce := nextKey(key)
131+
// should not panic
132+
_ = nextKey(nextOnce)
133+
123134
// dIAAAAAAAAD/PV9pgAAAAAD/AAABA4AAAAD/AAAAAQOAAAD/AAAAAAEAAAD8
124135
// a index key with: table: 61, index: 1, int64: 1, int64: 1
125136
a := []byte{116, 128, 0, 0, 0, 0, 0, 0, 255, 61, 95, 105, 128, 0, 0, 0, 0, 255, 0, 0, 1, 3, 128, 0, 0, 0, 255, 0, 0, 0, 1, 3, 128, 0, 0, 255, 0, 0, 0, 0, 1, 0, 0, 0, 252}

br/pkg/lightning/backend/local/localhelper.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"github.com/docker/go-units"
2929
"github.com/pingcap/errors"
30+
"github.com/pingcap/failpoint"
3031
sst "github.com/pingcap/kvproto/pkg/import_sstpb"
3132
"github.com/pingcap/kvproto/pkg/metapb"
3233
"github.com/pingcap/kvproto/pkg/pdpb"
@@ -381,7 +382,14 @@ func fetchTableRegionSizeStats(ctx context.Context, db *sql.DB, tableID int64) (
381382
return stats, errors.Trace(err)
382383
}
383384

384-
func (local *local) BatchSplitRegions(ctx context.Context, region *split.RegionInfo, keys [][]byte) (*split.RegionInfo, []*split.RegionInfo, error) {
385+
func (local *local) BatchSplitRegions(
386+
ctx context.Context,
387+
region *split.RegionInfo,
388+
keys [][]byte,
389+
) (*split.RegionInfo, []*split.RegionInfo, error) {
390+
failpoint.Inject("failToSplit", func(_ failpoint.Value) {
391+
failpoint.Return(nil, nil, errors.New("retryable error"))
392+
})
385393
region, newRegions, err := local.splitCli.BatchSplitRegionsWithOrigin(ctx, region, keys)
386394
if err != nil {
387395
return nil, nil, errors.Annotatef(err, "batch split regions failed")
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
create table a (c int);
1+
create table a (c VARCHAR(20) PRIMARY KEY);
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
insert into a values (1);
1+
insert into a values ('0000001');
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
insert into a values (2);
1+
insert into a values ('0000002');
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
insert into a values (3),(4);
1+
insert into a values ('0000003'),('0000004');

br/tests/lightning_local_backend/run.sh

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ grep -Fq 'table(s) [`cpeng`.`a`, `cpeng`.`b`] are not empty' $TEST_DIR/lightning
3434

3535

3636
# First, verify that inject with not leader error is fine.
37-
export GO_FAILPOINTS='github.com/pingcap/tidb/br/pkg/lightning/backend/local/FailIngestMeta=1*return("notleader")'
37+
export GO_FAILPOINTS='github.com/pingcap/tidb/br/pkg/lightning/backend/local/FailIngestMeta=1*return("notleader");github.com/pingcap/tidb/br/pkg/lightning/backend/local/failToSplit=2*return("")'
3838
rm -f "$TEST_DIR/lightning-local.log"
3939
run_sql 'DROP DATABASE IF EXISTS cpeng;'
40-
run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml"
40+
run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml" -L debug
41+
grep -Eq "split regions.*retryable error" "$TEST_DIR/lightning-local.log"
4142

4243
# Check that everything is correctly imported
4344
run_sql 'SELECT count(*), sum(c) FROM cpeng.a'

kv/key.go

+2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ type Handle interface {
140140
// IntValue returns the int64 value if IsInt is true, it panics if IsInt returns false.
141141
IntValue() int64
142142
// Next returns the minimum handle that is greater than this handle.
143+
// The returned handle is not guaranteed to be able to decode.
143144
Next() Handle
144145
// Equal returns if the handle equals to another handle, it panics if the types are different.
145146
Equal(h Handle) bool
@@ -279,6 +280,7 @@ func (ch *CommonHandle) IntValue() int64 {
279280
}
280281

281282
// Next implements the Handle interface.
283+
// Note that the returned encoded field is not guaranteed to be able to decode.
282284
func (ch *CommonHandle) Next() Handle {
283285
return &CommonHandle{
284286
encoded: Key(ch.encoded).PrefixNext(),

0 commit comments

Comments
 (0)