Skip to content

Commit ed127b3

Browse files
authored
Merge pull request #1 from smarty/normalize-context-cancellation
Normalize context cancellation errors
2 parents ac9ba37 + 01b6e4a commit ed127b3

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func NewBindingPool(handle *sql.DB, options ...option) BindingConnectionPool {
2626
}
2727
func newPool(handle *sql.DB, config configuration) ConnectionPool {
2828
var pool ConnectionPool = NewLibraryConnectionPoolAdapter(handle, config.txOptions)
29+
pool = NewNormalizeContextCancellationConnectionPool(pool)
2930

3031
if config.splitStatement {
3132
pool = NewSplitStatementConnectionPool(pool, config.parameterPrefix)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sqldb
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
type NormalizeContextCancellationConnectionPool struct {
10+
inner ConnectionPool
11+
}
12+
13+
func NewNormalizeContextCancellationConnectionPool(inner ConnectionPool) *NormalizeContextCancellationConnectionPool {
14+
return &NormalizeContextCancellationConnectionPool{inner: inner}
15+
}
16+
17+
func (this *NormalizeContextCancellationConnectionPool) Ping(ctx context.Context) error {
18+
return this.normalizeContextCancellationError(this.inner.Ping(ctx))
19+
}
20+
21+
func (this *NormalizeContextCancellationConnectionPool) BeginTransaction(ctx context.Context) (Transaction, error) {
22+
if tx, err := this.inner.BeginTransaction(ctx); err == nil {
23+
return NewStackTraceTransaction(tx), nil
24+
} else {
25+
return nil, this.normalizeContextCancellationError(err)
26+
}
27+
}
28+
29+
func (this *NormalizeContextCancellationConnectionPool) Close() error {
30+
return this.normalizeContextCancellationError(this.inner.Close())
31+
}
32+
33+
func (this *NormalizeContextCancellationConnectionPool) Execute(ctx context.Context, statement string, parameters ...interface{}) (uint64, error) {
34+
affected, err := this.inner.Execute(ctx, statement, parameters...)
35+
return affected, this.normalizeContextCancellationError(err)
36+
}
37+
38+
func (this *NormalizeContextCancellationConnectionPool) Select(ctx context.Context, query string, parameters ...interface{}) (SelectResult, error) {
39+
result, err := this.inner.Select(ctx, query, parameters...)
40+
return result, this.normalizeContextCancellationError(err)
41+
}
42+
43+
// TODO remove manual check of "use of closed network connection" with release of https://github.com/go-sql-driver/mysql/pull/1615
44+
func (this *NormalizeContextCancellationConnectionPool) normalizeContextCancellationError(err error) error {
45+
if err == nil {
46+
return nil
47+
}
48+
if strings.Contains(err.Error(), "operation was canceled") {
49+
return fmt.Errorf("%w: %w", context.Canceled, err)
50+
}
51+
if strings.Contains(err.Error(), "use of closed network connection") {
52+
return fmt.Errorf("%w: %w", context.Canceled, err)
53+
}
54+
return err
55+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package sqldb
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"github.com/smarty/assertions/should"
9+
"github.com/smarty/gunit"
10+
)
11+
12+
func TestNormalizeContextCancellationConnectionPoolFixture(t *testing.T) {
13+
gunit.Run(new(NormalizeContextCancellationConnectionPoolFixture), t)
14+
}
15+
16+
type NormalizeContextCancellationConnectionPoolFixture struct {
17+
*gunit.Fixture
18+
19+
inner *FakeConnectionPool
20+
adapter *NormalizeContextCancellationConnectionPool
21+
}
22+
23+
func (this *NormalizeContextCancellationConnectionPoolFixture) Setup() {
24+
this.inner = &FakeConnectionPool{}
25+
this.adapter = NewNormalizeContextCancellationConnectionPool(this.inner)
26+
}
27+
28+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestPing_Successful() {
29+
err := this.adapter.Ping(context.Background())
30+
31+
this.So(err, should.BeNil)
32+
this.So(this.inner.pingCalls, should.Equal, 1)
33+
}
34+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestPing_Failed() {
35+
pingErr := errors.New("PING ERROR")
36+
this.inner.pingError = pingErr
37+
38+
err := this.adapter.Ping(context.Background())
39+
40+
this.So(this.inner.pingCalls, should.Equal, 1)
41+
this.So(err, should.Equal, pingErr)
42+
}
43+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestPing_AdaptContextCancelled() {
44+
this.inner.pingError = operationCanceledErr
45+
46+
err := this.adapter.Ping(context.Background())
47+
48+
this.So(this.inner.pingCalls, should.Equal, 1)
49+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
50+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
51+
}
52+
53+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestBeginTransaction_Successful() {
54+
transaction := new(FakeTransaction)
55+
this.inner.transaction = transaction
56+
57+
tx, err := this.adapter.BeginTransaction(context.Background())
58+
59+
this.So(err, should.BeNil)
60+
this.So(this.inner.transactionCalls, should.Equal, 1)
61+
this.So(tx, should.NotBeNil)
62+
}
63+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestBeginTransaction_Failed() {
64+
transactionErr := errors.New("BEGIN TRANSACTION ERROR")
65+
this.inner.transactionError = transactionErr
66+
67+
tx, err := this.adapter.BeginTransaction(context.Background())
68+
69+
this.So(tx, should.BeNil)
70+
this.So(err, should.Equal, transactionErr)
71+
}
72+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestBeginTransaction_AdaptContextCancelled() {
73+
this.inner.transactionError = operationCanceledErr
74+
75+
tx, err := this.adapter.BeginTransaction(context.Background())
76+
77+
this.So(tx, should.BeNil)
78+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
79+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
80+
}
81+
82+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestClose_Successful() {
83+
err := this.adapter.Close()
84+
85+
this.So(err, should.BeNil)
86+
this.So(this.inner.closeCalls, should.Equal, 1)
87+
}
88+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestClose_Failed() {
89+
closeErr := errors.New("CLOSE ERROR")
90+
this.inner.closeError = closeErr
91+
92+
err := this.adapter.Close()
93+
94+
this.So(this.inner.closeCalls, should.Equal, 1)
95+
this.So(err, should.Equal, closeErr)
96+
}
97+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestClose_AdaptContextCancelled() {
98+
this.inner.closeError = operationCanceledErr
99+
100+
err := this.adapter.Close()
101+
102+
this.So(this.inner.closeCalls, should.Equal, 1)
103+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
104+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
105+
}
106+
107+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestExecute_Successful() {
108+
this.inner.executeResult = 42
109+
110+
result, err := this.adapter.Execute(context.Background(), "statement")
111+
112+
this.So(result, should.Equal, 42)
113+
this.So(err, should.BeNil)
114+
this.So(this.inner.executeCalls, should.Equal, 1)
115+
this.So(this.inner.executeStatement, should.Equal, "statement")
116+
}
117+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestExecute_Failed() {
118+
this.inner.executeResult = 42
119+
executeErr := errors.New("EXECUTE ERROR")
120+
this.inner.executeError = executeErr
121+
122+
result, err := this.adapter.Execute(context.Background(), "statement")
123+
124+
this.So(result, should.Equal, 42)
125+
this.So(err, should.Equal, executeErr)
126+
this.So(this.inner.executeCalls, should.Equal, 1)
127+
this.So(this.inner.executeStatement, should.Equal, "statement")
128+
}
129+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestExecute_AdaptContextCancelled() {
130+
this.inner.executeResult = 42
131+
this.inner.executeError = operationCanceledErr
132+
133+
result, err := this.adapter.Execute(context.Background(), "statement")
134+
135+
this.So(result, should.Equal, 42)
136+
this.So(this.inner.executeCalls, should.Equal, 1)
137+
this.So(this.inner.executeStatement, should.Equal, "statement")
138+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
139+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
140+
}
141+
142+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestSelect_Successful() {
143+
expectedResult := new(FakeSelectResult)
144+
this.inner.selectResult = expectedResult
145+
146+
result, err := this.adapter.Select(context.Background(), "query", 1, 2, 3)
147+
148+
this.So(result, should.Equal, expectedResult)
149+
this.So(err, should.BeNil)
150+
this.So(this.inner.selectCalls, should.Equal, 1)
151+
this.So(this.inner.selectStatement, should.Equal, "query")
152+
this.So(this.inner.selectParameters, should.Equal, []any{1, 2, 3})
153+
}
154+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestSelect_Failed() {
155+
expectedResult := new(FakeSelectResult)
156+
this.inner.selectResult = expectedResult
157+
selectErr := errors.New("SELECT ERROR")
158+
this.inner.selectError = selectErr
159+
160+
result, err := this.adapter.Select(context.Background(), "query", 1, 2, 3)
161+
162+
this.So(result, should.Equal, expectedResult)
163+
this.So(err, should.Equal, selectErr)
164+
this.So(this.inner.selectCalls, should.Equal, 1)
165+
this.So(this.inner.selectStatement, should.Equal, "query")
166+
this.So(this.inner.selectParameters, should.Equal, []any{1, 2, 3})
167+
}
168+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestSelect_AdaptContextCancelled() {
169+
expectedResult := new(FakeSelectResult)
170+
this.inner.selectResult = expectedResult
171+
this.inner.selectError = operationCanceledErr
172+
173+
result, err := this.adapter.Select(context.Background(), "query", 1, 2, 3)
174+
175+
this.So(result, should.Equal, expectedResult)
176+
this.So(this.inner.selectCalls, should.Equal, 1)
177+
this.So(this.inner.selectStatement, should.Equal, "query")
178+
this.So(this.inner.selectParameters, should.Equal, []any{1, 2, 3})
179+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
180+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
181+
}
182+
183+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestContextCancellationErrorAdapter_NilError() {
184+
err := this.adapter.normalizeContextCancellationError(nil)
185+
this.So(err, should.BeNil)
186+
}
187+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestContextCancellationErrorAdapter_GenericError() {
188+
genericErr := errors.New("generic error")
189+
err := this.adapter.normalizeContextCancellationError(genericErr)
190+
this.So(err, should.Equal, genericErr)
191+
}
192+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestContextCancellationErrorAdapter_OperationCanceledError() {
193+
err := this.adapter.normalizeContextCancellationError(operationCanceledErr)
194+
this.So(errors.Is(err, operationCanceledErr), should.BeTrue)
195+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
196+
}
197+
func (this *NormalizeContextCancellationConnectionPoolFixture) TestContextCancellationErrorAdapter_ClosedConnectionError() {
198+
err := this.adapter.normalizeContextCancellationError(closedNetworkConnectionErr)
199+
this.So(errors.Is(err, closedNetworkConnectionErr), should.BeTrue)
200+
this.So(errors.Is(err, context.Canceled), should.BeTrue)
201+
}
202+
203+
var (
204+
operationCanceledErr = errors.New("operation was canceled")
205+
closedNetworkConnectionErr = errors.New("use of closed network connection")
206+
)

0 commit comments

Comments
 (0)