Merge pull request #2364 from erzel/fixing_panic_for_error_split_query

Fixed the panic-handling code in vttablet.
This commit is contained in:
Erez Louidor 2016-12-13 10:50:12 -08:00 коммит произвёл GitHub
Родитель 749b276aa0 60910ade7d
Коммит daacee8f04
3 изменённых файлов: 132 добавлений и 26 удалений

Просмотреть файл

@ -64,7 +64,8 @@ func Errorf(msg string, args ...interface{}) error {
return stackError{fmt.Errorf(msg, args...), stack}
}
// Taken from runtime/debug.go
// Stack is taken from runtime/debug.go
// calldepth is the number of (bottommost) frames to skip.
func Stack(calldepth int) []byte {
return stack(calldepth)
}

Просмотреть файл

@ -971,12 +971,12 @@ func (tsv *TabletServer) ExecuteBatch(ctx context.Context, target *querypb.Targe
return nil, err
}
defer tsv.endRequest(false)
defer tsv.handleError("batch", nil, &err, nil)
defer tsv.handlePanicAndSendLogStats("batch", nil, &err, nil)
if asTransaction {
transactionID, err = tsv.Begin(ctx, target)
if err != nil {
return nil, tsv.handleErrorNoPanic("batch", nil, err, nil)
return nil, tsv.handleError("batch", nil, err, nil)
}
// If transaction was not committed by the end, it means
// that there was an error, roll it back.
@ -990,14 +990,14 @@ func (tsv *TabletServer) ExecuteBatch(ctx context.Context, target *querypb.Targe
for _, bound := range queries {
localReply, err := tsv.Execute(ctx, target, bound.Sql, bound.BindVariables, transactionID, options)
if err != nil {
return nil, tsv.handleErrorNoPanic("batch", nil, err, nil)
return nil, tsv.handleError("batch", nil, err, nil)
}
results = append(results, *localReply)
}
if asTransaction {
if err = tsv.Commit(ctx, target, transactionID); err != nil {
transactionID = 0
return nil, tsv.handleErrorNoPanic("batch", nil, err, nil)
return nil, tsv.handleError("batch", nil, err, nil)
}
transactionID = 0
}
@ -1081,7 +1081,7 @@ func (tsv *TabletServer) SplitQuery(
defer sqlExecuter.done()
algorithmObject, err := createSplitQueryAlgorithmObject(algorithm, splitParams, sqlExecuter)
if err != nil {
return err
return splitQueryToTabletError(err)
}
splits, err = splitquery.NewSplitter(splitParams, algorithmObject).Split()
if err != nil {
@ -1105,7 +1105,7 @@ func (tsv *TabletServer) execRequest(
logStats.Target = target
logStats.OriginalSQL = sql
logStats.BindVariables = bindVariables
defer tsv.handleError(sql, bindVariables, &err, logStats)
defer tsv.handlePanicAndSendLogStats(sql, bindVariables, &err, logStats)
if err = tsv.startRequest(target, isTx, allowOnShutdown); err != nil {
return err
}
@ -1117,23 +1117,51 @@ func (tsv *TabletServer) execRequest(
err = exec(ctx, logStats)
if err != nil {
return tsv.handleErrorNoPanic(sql, bindVariables, err, logStats)
return tsv.handleError(sql, bindVariables, err, logStats)
}
return nil
}
// handleError handles panics during query execution and sets
// the supplied error return value.
func (tsv *TabletServer) handleError(sql string, bindVariables map[string]interface{}, err *error, logStats *LogStats) {
func (tsv *TabletServer) handlePanicAndSendLogStats(
sql string,
bindVariables map[string]interface{},
err *error,
logStats *LogStats,
) {
if x := recover(); x != nil {
*err = tsv.handleErrorNoPanic(sql, bindVariables, x, logStats)
terr, ok := x.(*TabletError)
if ok {
// If the thrown error is a *TabletError then it's a (deprecated)
// use of panic to report a "regular" (i.e. non-logic) error;
// thus we call handleError.
// TODO(erez): Remove this once we report all regular
// errors without panic()'s.
*err = tsv.handleError(sql, bindVariables, terr, logStats)
} else {
errorMessage := fmt.Sprintf(
"Uncaught panic for %v:\n%v\n%s",
querytypes.QueryAsString(sql, bindVariables),
x,
tb.Stack(4) /* Skip the last 4 boiler-plate frames. */)
log.Errorf(errorMessage)
*err = NewTabletError(vtrpcpb.ErrorCode_UNKNOWN_ERROR, errorMessage)
tsv.qe.queryServiceStats.InternalErrors.Add("Panic", 1)
if logStats != nil {
logStats.Error = nil
}
}
}
if logStats != nil {
logStats.Send()
}
}
func (tsv *TabletServer) handleErrorNoPanic(sql string, bindVariables map[string]interface{}, err interface{}, logStats *LogStats) error {
func (tsv *TabletServer) handleError(
sql string,
bindVariables map[string]interface{},
err error,
logStats *LogStats,
) error {
var terr *TabletError
defer func() {
if logStats != nil {
@ -1142,9 +1170,7 @@ func (tsv *TabletServer) handleErrorNoPanic(sql string, bindVariables map[string
}()
terr, ok := err.(*TabletError)
if !ok {
log.Errorf("Uncaught panic for %v:\n%v\n%s", querytypes.QueryAsString(sql, bindVariables), err, tb.Stack(4))
tsv.qe.queryServiceStats.InternalErrors.Add("Panic", 1)
return NewTabletError(vtrpcpb.ErrorCode_UNKNOWN_ERROR, "%v: uncaught panic for %v", err, querytypes.QueryAsString(sql, bindVariables))
panic(fmt.Sprintf("Got an error that is not a TabletError: %v", err))
}
var myError error
if tsv.config.TerseErrors && terr.SQLError != 0 && len(bindVariables) != 0 {
@ -1356,7 +1382,7 @@ func createSplitQueryAlgorithmObject(
}
// splitQueryToTabletError converts the given error assumed to be returned from the
// splitquery-package into a TabletError suitablet to be returned to the caller.
// splitquery-package into a TabletError suitable to be returned to the caller.
// It returns nil if 'err' is nil.
func splitQueryToTabletError(err error) error {
if err == nil {

Просмотреть файл

@ -1489,6 +1489,40 @@ func TestTabletServerSplitQueryInvalidParams(t *testing.T) {
}
}
// Tests that using Equal Splits on a string column returns an error.
func TestTabletServerSplitQueryEqualSplitsOnStringColumn(t *testing.T) {
db := setUpTabletServerTest()
testUtils := newTestUtils()
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
dbconfigs := testUtils.newDBConfigs(db)
target := querypb.Target{TabletType: topodatapb.TabletType_RDONLY}
err := tsv.StartService(target, dbconfigs, testUtils.newMysqld(&dbconfigs))
if err != nil {
t.Fatalf("StartService failed: %v", err)
}
defer tsv.StopService()
ctx := context.Background()
sql := "select * from test_table"
_, err = tsv.SplitQuery(
ctx,
&querypb.Target{TabletType: topodatapb.TabletType_RDONLY},
sql,
nil, /* bindVariables */
// EQUAL_SPLITS should not work on a string column.
[]string{"name_string"}, /* splitColumns */
10, /* splitCount */
0, /* numRowsPerQueryPart */
querypb.SplitQueryRequest_EQUAL_SPLITS)
want :=
"error: splitquery: using the EQUAL_SPLITS algorithm in SplitQuery" +
" requires having a numeric (integral or float) split-column." +
" Got type: {Name: 'name_string', Type: VARCHAR}"
if err.Error() != want {
t.Fatalf("got: %v, want: %v", err, want)
}
}
func TestHandleExecUnknownError(t *testing.T) {
ctx := context.Background()
logStats := NewLogStats(ctx, "TestHandleExecError")
@ -1496,7 +1530,7 @@ func TestHandleExecUnknownError(t *testing.T) {
testUtils := newTestUtils()
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
defer tsv.handleError("select * from test_table", nil, &err, logStats)
defer tsv.handlePanicAndSendLogStats("select * from test_table", nil, &err, logStats)
panic("unknown exec error")
}
@ -1513,7 +1547,7 @@ func TestHandleExecTabletError(t *testing.T) {
testUtils := newTestUtils()
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
defer tsv.handleError("select * from test_table", nil, &err, logStats)
defer tsv.handlePanicAndSendLogStats("select * from test_table", nil, &err, logStats)
panic(NewTabletError(vtrpcpb.ErrorCode_INTERNAL_ERROR, "tablet error"))
}
@ -1531,7 +1565,7 @@ func TestTerseErrorsNonSQLError(t *testing.T) {
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
tsv.config.TerseErrors = true
defer tsv.handleError("select * from test_table", nil, &err, logStats)
defer tsv.handlePanicAndSendLogStats("select * from test_table", nil, &err, logStats)
panic(NewTabletError(vtrpcpb.ErrorCode_INTERNAL_ERROR, "tablet error"))
}
@ -1549,7 +1583,11 @@ func TestTerseErrorsBindVars(t *testing.T) {
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
tsv.config.TerseErrors = true
defer tsv.handleError("select * from test_table", map[string]interface{}{"a": 1}, &err, logStats)
defer tsv.handlePanicAndSendLogStats(
"select * from test_table",
map[string]interface{}{"a": 1},
&err,
logStats)
panic(&TabletError{
ErrorCode: vtrpcpb.ErrorCode_DEADLINE_EXCEEDED,
Message: "msg",
@ -1572,7 +1610,7 @@ func TestTerseErrorsNoBindVars(t *testing.T) {
config := testUtils.newQueryServiceConfig()
tsv := NewTabletServer(config)
tsv.config.TerseErrors = true
defer tsv.handleError("select * from test_table", nil, &err, logStats)
defer tsv.handlePanicAndSendLogStats("select * from test_table", nil, &err, logStats)
panic(&TabletError{
ErrorCode: vtrpcpb.ErrorCode_DEADLINE_EXCEEDED,
Message: "msg",
@ -1713,10 +1751,34 @@ func getSupportedQueries() map[string]*sqltypes.Result {
},
},
"select * from test_table where 1 != 1": {
Fields: getTestTableFields(),
Fields: []*querypb.Field{{
Name: "pk",
Type: sqltypes.Int32,
}, {
Name: "name",
Type: sqltypes.Int32,
}, {
Name: "addr",
Type: sqltypes.Int32,
}, {
Name: "name_string",
Type: sqltypes.VarChar,
}},
},
"select * from `test_table` where 1 != 1": {
Fields: getTestTableFields(),
Fields: []*querypb.Field{{
Name: "pk",
Type: sqltypes.Int32,
}, {
Name: "name",
Type: sqltypes.Int32,
}, {
Name: "addr",
Type: sqltypes.Int32,
}, {
Name: "name_string",
Type: sqltypes.VarChar,
}},
},
baseShowTables: {
RowsAffected: 1,
@ -1735,7 +1797,7 @@ func getSupportedQueries() map[string]*sqltypes.Result {
},
},
"describe `test_table`": {
RowsAffected: 3,
RowsAffected: 4,
Rows: [][]sqltypes.Value{
{
sqltypes.MakeString([]byte("pk")),
@ -1761,11 +1823,19 @@ func getSupportedQueries() map[string]*sqltypes.Result {
sqltypes.MakeString([]byte("1")),
sqltypes.MakeString([]byte{}),
},
{
sqltypes.MakeString([]byte("name_string")), /* Field */
sqltypes.MakeString([]byte("varchar(10)")), /* Type */
sqltypes.MakeString([]byte{}), /* NULL */
sqltypes.MakeString([]byte{}), /* Key */
sqltypes.MakeString([]byte("foo")), /* Default Value */
sqltypes.MakeString([]byte{}), /* Extra */
},
},
},
// for SplitQuery because it needs a primary key column
"show index from `test_table`": {
RowsAffected: 2,
RowsAffected: 3,
Rows: [][]sqltypes.Value{
{
sqltypes.MakeString([]byte{}),
@ -1785,6 +1855,15 @@ func getSupportedQueries() map[string]*sqltypes.Result {
sqltypes.MakeString([]byte{}),
sqltypes.MakeString([]byte("300")),
},
{
sqltypes.MakeString([]byte{}),
sqltypes.MakeString([]byte{}),
sqltypes.MakeString([]byte("name_string_INDEX")),
sqltypes.MakeString([]byte{}),
sqltypes.MakeString([]byte("name_string")),
sqltypes.MakeString([]byte{}),
sqltypes.MakeString([]byte("100")),
},
},
},
"begin": {},