From 874c044dbb8b3f416906124acc6ddb55e2eb2d33 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Wed, 16 Aug 2023 18:14:08 +0800 Subject: [PATCH] dumpling: fix dumpling ignore file writer close error (#45374) (#45403) close pingcap/tidb#45353 --- dumpling/export/ir_impl.go | 1 + dumpling/export/metadata.go | 9 +++++--- dumpling/export/writer.go | 13 ++++++++--- dumpling/export/writer_test.go | 14 ++++++++++++ dumpling/export/writer_util.go | 20 ++++++++++++----- dumpling/tests/basic/run.sh | 41 ++++++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 12 deletions(-) diff --git a/dumpling/export/ir_impl.go b/dumpling/export/ir_impl.go index bca821f612623..3f3f7f9e416c4 100644 --- a/dumpling/export/ir_impl.go +++ b/dumpling/export/ir_impl.go @@ -353,6 +353,7 @@ func newMultiQueriesChunk(queries []string, colLength int) *multiQueriesChunk { func (td *multiQueriesChunk) Start(tctx *tcontext.Context, conn *sql.Conn) error { td.tctx = tctx td.conn = conn + td.SQLRowIter = nil return nil } diff --git a/dumpling/export/metadata.go b/dumpling/export/metadata.go index 7d2cf53128688..4adbe2f91626b 100644 --- a/dumpling/export/metadata.go +++ b/dumpling/export/metadata.go @@ -219,9 +219,12 @@ func (m *globalMetadata) writeGlobalMetaData() error { if err != nil { return err } - defer tearDown(m.tctx) - - return write(m.tctx, fileWriter, m.String()) + err = write(m.tctx, fileWriter, m.String()) + tearDownErr := tearDown(m.tctx) + if err == nil { + return tearDownErr + } + return err } func getValidStr(str []string, idx int) string { diff --git a/dumpling/export/writer.go b/dumpling/export/writer.go index e64c067c807b7..291b5fcc4ac1d 100644 --- a/dumpling/export/writer.go +++ b/dumpling/export/writer.go @@ -237,10 +237,13 @@ func (w *Writer) tryToWriteTableData(tctx *tcontext.Context, meta TableMeta, ir for { fileWriter, tearDown := buildInterceptFileWriter(tctx, w.extStorage, fileName, conf.CompressType) n, err := format.WriteInsert(tctx, conf, meta, ir, fileWriter, w.metrics) - tearDown(tctx) + tearDownErr := tearDown(tctx) if err != nil { return err } + if tearDownErr != nil { + return tearDownErr + } if w, ok := fileWriter.(*InterceptFileWriter); ok && !w.SomethingIsWritten { break @@ -275,15 +278,19 @@ func writeMetaToFile(tctx *tcontext.Context, target, metaSQL string, s storage.E if err != nil { return errors.Trace(err) } - defer tearDown(tctx) - return WriteMeta(tctx, &metaData{ + err = WriteMeta(tctx, &metaData{ target: target, metaSQL: metaSQL, specCmts: []string{ "/*!40101 SET NAMES binary*/;", }, }, fileWriter) + tearDownErr := tearDown(tctx) + if err == nil { + return tearDownErr + } + return err } type outputFileNamer struct { diff --git a/dumpling/export/writer_test.go b/dumpling/export/writer_test.go index b5f54f3debcb9..458a6540353dd 100644 --- a/dumpling/export/writer_test.go +++ b/dumpling/export/writer_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/pingcap/failpoint" tcontext "github.com/pingcap/tidb/dumpling/context" "github.com/pingcap/tidb/util/promutil" "github.com/stretchr/testify/require" @@ -71,6 +72,12 @@ func TestWriteTableMeta(t *testing.T) { bytes, err := ioutil.ReadFile(p) require.NoError(t, err) require.Equal(t, "/*!40101 SET NAMES binary*/;\nCREATE TABLE t (a INT);\n", string(bytes)) + + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/dumpling/export/FailToCloseMetaFile", "return(true)")) + defer failpoint.Disable("github.com/pingcap/tidb/dumpling/export/FailToCloseMetaFile") + + err = writer.WriteTableMeta("test", "t", "CREATE TABLE t (a INT)") + require.ErrorContains(t, err, "injected error: fail to close meta file") } func TestWriteViewMeta(t *testing.T) { @@ -137,6 +144,13 @@ func TestWriteTableData(t *testing.T) { "(3,'male','john@mail.com','020-1256','healthy'),\n" + "(4,'female','sarah@mail.com','020-1235','healthy');\n" require.Equal(t, expected, string(bytes)) + + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/dumpling/export/FailToCloseDataFile", "return(true)")) + defer failpoint.Disable("github.com/pingcap/tidb/dumpling/export/FailToCloseDataFile") + + tableIR = newMockTableIR("test", "employee", data, specCmts, colTypes) + err = writer.WriteTableData(tableIR, tableIR, 0) + require.ErrorContains(t, err, "injected error: fail to close data file") } func TestWriteTableDataWithFileSize(t *testing.T) { diff --git a/dumpling/export/writer_util.go b/dumpling/export/writer_util.go index 809f82e59c9b5..fe7672f4f0015 100644 --- a/dumpling/export/writer_util.go +++ b/dumpling/export/writer_util.go @@ -451,7 +451,7 @@ func writeBytes(tctx *tcontext.Context, writer storage.ExternalFileWriter, p []b return errors.Trace(err) } -func buildFileWriter(tctx *tcontext.Context, s storage.ExternalStorage, fileName string, compressType storage.CompressType) (storage.ExternalFileWriter, func(ctx context.Context), error) { +func buildFileWriter(tctx *tcontext.Context, s storage.ExternalStorage, fileName string, compressType storage.CompressType) (storage.ExternalFileWriter, func(ctx context.Context) error, error) { fileName += compressFileSuffix(compressType) fullPath := s.URI() + "/" + fileName writer, err := storage.WithCompression(s, compressType).Create(tctx, fileName) @@ -462,20 +462,24 @@ func buildFileWriter(tctx *tcontext.Context, s storage.ExternalStorage, fileName return nil, nil, errors.Trace(err) } tctx.L().Debug("opened file", zap.String("path", fullPath)) - tearDownRoutine := func(ctx context.Context) { + tearDownRoutine := func(ctx context.Context) error { err := writer.Close(ctx) + failpoint.Inject("FailToCloseMetaFile", func(_ failpoint.Value) { + err = errors.New("injected error: fail to close meta file") + }) if err == nil { - return + return nil } err = errors.Trace(err) tctx.L().Warn("fail to close file", zap.String("path", fullPath), zap.Error(err)) + return err } return writer, tearDownRoutine, nil } -func buildInterceptFileWriter(pCtx *tcontext.Context, s storage.ExternalStorage, fileName string, compressType storage.CompressType) (storage.ExternalFileWriter, func(context.Context)) { +func buildInterceptFileWriter(pCtx *tcontext.Context, s storage.ExternalStorage, fileName string, compressType storage.CompressType) (storage.ExternalFileWriter, func(context.Context) error) { fileName += compressFileSuffix(compressType) var writer storage.ExternalFileWriter fullPath := s.URI() + "/" + fileName @@ -497,17 +501,21 @@ func buildInterceptFileWriter(pCtx *tcontext.Context, s storage.ExternalStorage, } fileWriter.initRoutine = initRoutine - tearDownRoutine := func(ctx context.Context) { + tearDownRoutine := func(ctx context.Context) error { if writer == nil { - return + return nil } pCtx.L().Debug("tear down lazy file writer...", zap.String("path", fullPath)) err := writer.Close(ctx) + failpoint.Inject("FailToCloseDataFile", func(_ failpoint.Value) { + err = errors.New("injected error: fail to close data file") + }) if err != nil { pCtx.L().Warn("fail to close file", zap.String("path", fullPath), zap.Error(err)) } + return err } return fileWriter, tearDownRoutine } diff --git a/dumpling/tests/basic/run.sh b/dumpling/tests/basic/run.sh index 11a738805275b..2134c4f3c3594 100644 --- a/dumpling/tests/basic/run.sh +++ b/dumpling/tests/basic/run.sh @@ -128,3 +128,44 @@ run_dumpling --consistency lock -B "$DB_NAME" -L ${DUMPLING_OUTPUT_DIR}/dumpling cnt=$(grep -w "$DB_NAME" ${DUMPLING_OUTPUT_DIR}/${DB_NAME}-schema-create.sql|wc -l) echo "records count is ${cnt}" [ "$cnt" = 1 ] + +echo "Test for failing to close meta/data file" +export GO_FAILPOINTS="github.com/pingcap/tidb/dumpling/export/FailToCloseMetaFile=1*return" +rm ${DUMPLING_OUTPUT_DIR}/dumpling.log +set +e +run_dumpling -B "test_db" -L ${DUMPLING_OUTPUT_DIR}/dumpling.log +set -e +cnt=$(grep -w "dump failed error stack info" ${DUMPLING_OUTPUT_DIR}/dumpling.log|wc -l) +[ "$cnt" -ge 1 ] + +# dumpling retry will make it succeed +export GO_FAILPOINTS="github.com/pingcap/tidb/dumpling/export/FailToCloseDataFile=1*return" +export DUMPLING_TEST_PORT=4000 +run_sql "drop database if exists test_db;" +run_sql "create database test_db;" +run_sql "create table test_db.test_table (a int primary key);" +run_sql "insert into test_db.test_table values (1),(2),(3),(4),(5),(6),(7),(8);" +rm ${DUMPLING_OUTPUT_DIR}/dumpling.log +set +e +run_dumpling -B "test_db" -L ${DUMPLING_OUTPUT_DIR}/dumpling.log +set -e +cnt=$(grep -w "dump data successfully" ${DUMPLING_OUTPUT_DIR}/dumpling.log|wc -l) +[ "$cnt" -ge 1 ] +cnt=$(grep -w "(.*)" ${DUMPLING_OUTPUT_DIR}/test_db.test_table.000000000.sql|wc -l) +echo "records count is ${cnt}" +[ "$cnt" -eq 8 ] + +export GO_FAILPOINTS="github.com/pingcap/tidb/dumpling/export/FailToCloseDataFile=5*return" +rm ${DUMPLING_OUTPUT_DIR}/dumpling.log +set +e +run_dumpling -B "test_db" -L ${DUMPLING_OUTPUT_DIR}/dumpling.log +set -e +cnt=$(grep -w "dump failed error stack info" ${DUMPLING_OUTPUT_DIR}/dumpling.log|wc -l) +[ "$cnt" -ge 1 ] + +echo "Test for empty query result, should success." +run_sql "drop database if exists test_db;" +run_sql "create database test_db;" +run_sql "create table test_db.test_table (a int primary key);" +export GO_FAILPOINTS="" +run_dumpling --sql "select * from test_db.test_table" --filetype csv > ${DUMPLING_OUTPUT_DIR}/dumpling.log