Skip to content

Commit edea018

Browse files
sasklacznjvrzm
andauthored
Add panic recovery (#173)
* panic recovery added * Update datasource.go Refactor error source in the response Co-authored-by: Nathan Vērzemnieks <[email protected]> * - remove query from the error response --------- Co-authored-by: Nathan Vērzemnieks <[email protected]>
1 parent d731a74 commit edea018

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

datasource.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net/http"
1010
"os"
11+
"runtime/debug"
1112
"strconv"
1213
"sync"
1314
"time"
@@ -119,6 +120,23 @@ func (ds *SQLDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
119120
// Execute each query and store the results by query RefID
120121
for _, q := range req.Queries {
121122
go func(query backend.DataQuery) {
123+
defer wg.Done()
124+
125+
// Panic recovery
126+
defer func() {
127+
if r := recover(); r != nil {
128+
stack := string(debug.Stack())
129+
errorMsg := fmt.Sprintf("SQL datasource query execution panic: %v", r)
130+
131+
backend.Logger.Error(errorMsg,
132+
"panic", r,
133+
"refID", query.RefID,
134+
"stack", stack)
135+
136+
response.Set(query.RefID, backend.ErrorResponseWithErrorSource(backend.PluginError(errors.New(errorMsg))))
137+
}
138+
}()
139+
122140
frames, err := ds.handleQuery(ctx, query, headers)
123141
if err == nil {
124142
if responseMutator, ok := ds.driver().(ResponseMutator); ok {
@@ -134,8 +152,6 @@ func (ds *SQLDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
134152
Error: err,
135153
ErrorSource: ErrorSource(err),
136154
})
137-
138-
wg.Done()
139155
}(q)
140156
}
141157

datasource_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,38 @@ func Test_default_macro_errors(t *testing.T) {
191191
}
192192
}
193193

194+
func Test_query_panic_recovery(t *testing.T) {
195+
cfg := `{ "timeout": 0, "retries": 0, "retryOn": [] }`
196+
opts := test.DriverOpts{}
197+
198+
// Create a macro that triggers a panic
199+
panicMacro := func(query *sqlds.Query, args []string) (string, error) {
200+
panic("Random panic for testing purposes")
201+
}
202+
macros := sqlds.Macros{
203+
"panicTest": panicMacro,
204+
}
205+
206+
req, _, ds := queryRequest(t, "panic-test", opts, cfg, macros)
207+
208+
// Set up a query that uses the panic-triggering macro
209+
req.Queries[0].JSON = []byte(`{ "rawSql": "SELECT $__panicTest() FROM test_table;" }`)
210+
211+
// Execute the query
212+
data, err := ds.QueryData(context.Background(), req)
213+
214+
// Verify that the panic was caught and converted to an error
215+
assert.Nil(t, err)
216+
assert.NotNil(t, data.Responses)
217+
218+
res := data.Responses["foo"]
219+
assert.NotNil(t, res.Error)
220+
assert.Equal(t, backend.ErrorSourcePlugin, res.ErrorSource)
221+
assert.Contains(t, res.Error.Error(), "SQL datasource query execution panic")
222+
assert.Contains(t, res.Error.Error(), "Random panic for testing purposes")
223+
assert.Nil(t, res.Frames)
224+
}
225+
194226
func queryRequest(t *testing.T, name string, opts test.DriverOpts, cfg string, marcos sqlds.Macros) (*backend.QueryDataRequest, *test.SqlHandler, *sqlds.SQLDatasource) {
195227
driver, handler := test.NewDriver(name, test.Data{}, nil, opts, marcos)
196228
ds := sqlds.NewDatasource(driver)

0 commit comments

Comments
 (0)