Skip to content

Commit 5622634

Browse files
shogo82148julienschmidt
authored andcommitted
Add support for context.Context (go-sql-driver#608)
* Add supports to context.Context * add authors related context.Context support * fix comment of mysqlContext. - s/from/for/ - and start the comment with a space please * closed is now just bool flag. * drop read-only transactions support * remove unused methods from mysqlContext interface. * moved checking canceled logic into method of connection. * add a section about context.Context to the README. * use atomic variable for closed. * short circuit for context.Background() * fix illegal watching state * set rows.finish directly. * move namedValueToValue to utils_go18.go * move static interface implementation checks to the top of the file * add the new section about `context.Context` to the table of contents, and fix the section. * mark unsupported features with TODO comments * rename watcher to starter. * use mc.error() instead of duplicated logics.
1 parent e3f0fdc commit 5622634

14 files changed

+738
-28
lines changed

AUTHORS

+4
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@
1414
Aaron Hopkins <go-sql-driver at die.net>
1515
Arne Hormann <arnehormann at gmail.com>
1616
Asta Xie <xiemengjun at gmail.com>
17+
Bulat Gaifullin <gaifullinbf at gmail.com>
1718
Carlos Nieto <jose.carlos at menteslibres.net>
1819
Chris Moos <chris at tech9computers.com>
1920
Daniel Nichter <nil at codenode.com>
2021
Daniël van Eeden <git at myname.nl>
2122
Dave Protasowski <dprotaso at gmail.com>
2223
DisposaBoy <disposaboy at dby.me>
2324
Egor Smolyakov <egorsmkv at gmail.com>
25+
Evan Shaw <evan at vendhq.com>
2426
Frederick Mayle <frederickmayle at gmail.com>
2527
Gustavo Kristic <gkristic at gmail.com>
2628
Hanno Braun <mail at hannobraun.com>
2729
Henri Yandell <flamefew at gmail.com>
2830
Hirotaka Yamamoto <ymmt2005 at gmail.com>
31+
ICHINOSE Shogo <shogo82148 at gmail.com>
2932
INADA Naoki <songofacandy at gmail.com>
3033
Jacek Szwec <szwec.jacek at gmail.com>
3134
James Harr <james.harr at gmail.com>
@@ -45,6 +48,7 @@ Luke Scott <luke at webconnex.com>
4548
Michael Woolnough <michael.woolnough at gmail.com>
4649
Nicola Peduzzi <thenikso at gmail.com>
4750
Olivier Mengué <dolmen at cpan.org>
51+
oscarzhao <oscarzhaosl at gmail.com>
4852
Paul Bonser <misterpib at gmail.com>
4953
Peter Schultz <peter.schultz at classmarkets.com>
5054
Rebecca Chin <rchin at pivotal.io>

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
1919
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
2020
* [time.Time support](#timetime-support)
2121
* [Unicode support](#unicode-support)
22+
* [context.Context Support](#contextcontext-support)
2223
* [Testing / Development](#testing--development)
2324
* [License](#license)
2425

@@ -443,6 +444,9 @@ Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAM
443444

444445
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
445446

447+
## `context.Context` Support
448+
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
449+
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
446450

447451
## Testing / Development
448452
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.

benchmark_go18_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
// +build go1.8
10+
11+
package mysql
12+
13+
import (
14+
"context"
15+
"database/sql"
16+
"fmt"
17+
"runtime"
18+
"testing"
19+
)
20+
21+
func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
22+
ctx, cancel := context.WithCancel(context.Background())
23+
defer cancel()
24+
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
25+
26+
tb := (*TB)(b)
27+
stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?"))
28+
defer stmt.Close()
29+
30+
b.SetParallelism(p)
31+
b.ReportAllocs()
32+
b.ResetTimer()
33+
b.RunParallel(func(pb *testing.PB) {
34+
var got string
35+
for pb.Next() {
36+
tb.check(stmt.QueryRow(1).Scan(&got))
37+
if got != "one" {
38+
b.Fatalf("query = %q; want one", got)
39+
}
40+
}
41+
})
42+
}
43+
44+
func BenchmarkQueryContext(b *testing.B) {
45+
db := initDB(b,
46+
"DROP TABLE IF EXISTS foo",
47+
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
48+
`INSERT INTO foo VALUES (1, "one")`,
49+
`INSERT INTO foo VALUES (2, "two")`,
50+
)
51+
defer db.Close()
52+
for _, p := range []int{1, 2, 3, 4} {
53+
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
54+
benchmarkQueryContext(b, db, p)
55+
})
56+
}
57+
}
58+
59+
func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
60+
ctx, cancel := context.WithCancel(context.Background())
61+
defer cancel()
62+
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
63+
64+
tb := (*TB)(b)
65+
stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1"))
66+
defer stmt.Close()
67+
68+
b.SetParallelism(p)
69+
b.ReportAllocs()
70+
b.ResetTimer()
71+
b.RunParallel(func(pb *testing.PB) {
72+
for pb.Next() {
73+
if _, err := stmt.ExecContext(ctx); err != nil {
74+
b.Fatal(err)
75+
}
76+
}
77+
})
78+
}
79+
80+
func BenchmarkExecContext(b *testing.B) {
81+
db := initDB(b,
82+
"DROP TABLE IF EXISTS foo",
83+
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
84+
`INSERT INTO foo VALUES (1, "one")`,
85+
`INSERT INTO foo VALUES (2, "two")`,
86+
)
87+
defer db.Close()
88+
for _, p := range []int{1, 2, 3, 4} {
89+
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
90+
benchmarkQueryContext(b, db, p)
91+
})
92+
}
93+
}

connection.go

+84-11
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,21 @@ import (
1414
"net"
1515
"strconv"
1616
"strings"
17+
"sync"
18+
"sync/atomic"
1719
"time"
1820
)
1921

22+
// a copy of context.Context for Go 1.7 and later.
23+
type mysqlContext interface {
24+
Done() <-chan struct{}
25+
Err() error
26+
27+
// They are defined in context.Context, but go-mysql-driver does not use them.
28+
// Deadline() (deadline time.Time, ok bool)
29+
// Value(key interface{}) interface{}
30+
}
31+
2032
type mysqlConn struct {
2133
buf buffer
2234
netConn net.Conn
@@ -31,6 +43,19 @@ type mysqlConn struct {
3143
sequence uint8
3244
parseTime bool
3345
strict bool
46+
47+
// for context support (From Go 1.8)
48+
watching bool
49+
watcher chan<- mysqlContext
50+
closech chan struct{}
51+
finished chan<- struct{}
52+
53+
// set non-zero when conn is closed, before closech is closed.
54+
// accessed atomically.
55+
closed int32
56+
57+
mu sync.Mutex // guards following fields
58+
canceledErr error // set non-nil if conn is canceled
3459
}
3560

3661
// Handles parameters set in DSN after the connection is established
@@ -64,7 +89,7 @@ func (mc *mysqlConn) handleParams() (err error) {
6489
}
6590

6691
func (mc *mysqlConn) Begin() (driver.Tx, error) {
67-
if mc.netConn == nil {
92+
if mc.isBroken() {
6893
errLog.Print(ErrInvalidConn)
6994
return nil, driver.ErrBadConn
7095
}
@@ -78,7 +103,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
78103

79104
func (mc *mysqlConn) Close() (err error) {
80105
// Makes Close idempotent
81-
if mc.netConn != nil {
106+
if !mc.isBroken() {
82107
err = mc.writeCommandPacket(comQuit)
83108
}
84109

@@ -92,19 +117,36 @@ func (mc *mysqlConn) Close() (err error) {
92117
// is called before auth or on auth failure because MySQL will have already
93118
// closed the network connection.
94119
func (mc *mysqlConn) cleanup() {
120+
if atomic.SwapInt32(&mc.closed, 1) != 0 {
121+
return
122+
}
123+
95124
// Makes cleanup idempotent
96-
if mc.netConn != nil {
97-
if err := mc.netConn.Close(); err != nil {
98-
errLog.Print(err)
125+
close(mc.closech)
126+
if mc.netConn == nil {
127+
return
128+
}
129+
if err := mc.netConn.Close(); err != nil {
130+
errLog.Print(err)
131+
}
132+
}
133+
134+
func (mc *mysqlConn) isBroken() bool {
135+
return atomic.LoadInt32(&mc.closed) != 0
136+
}
137+
138+
func (mc *mysqlConn) error() error {
139+
if mc.isBroken() {
140+
if err := mc.canceled(); err != nil {
141+
return err
99142
}
100-
mc.netConn = nil
143+
return ErrInvalidConn
101144
}
102-
mc.cfg = nil
103-
mc.buf.nc = nil
145+
return nil
104146
}
105147

106148
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
107-
if mc.netConn == nil {
149+
if mc.isBroken() {
108150
errLog.Print(ErrInvalidConn)
109151
return nil, driver.ErrBadConn
110152
}
@@ -258,7 +300,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
258300
}
259301

260302
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
261-
if mc.netConn == nil {
303+
if mc.isBroken() {
262304
errLog.Print(ErrInvalidConn)
263305
return nil, driver.ErrBadConn
264306
}
@@ -315,7 +357,11 @@ func (mc *mysqlConn) exec(query string) error {
315357
}
316358

317359
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
318-
if mc.netConn == nil {
360+
return mc.query(query, args)
361+
}
362+
363+
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
364+
if mc.isBroken() {
319365
errLog.Print(ErrInvalidConn)
320366
return nil, driver.ErrBadConn
321367
}
@@ -387,3 +433,30 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
387433
}
388434
return nil, err
389435
}
436+
437+
// finish is called when the query has canceled.
438+
func (mc *mysqlConn) cancel(err error) {
439+
mc.mu.Lock()
440+
mc.canceledErr = err
441+
mc.mu.Unlock()
442+
mc.cleanup()
443+
}
444+
445+
// canceled returns non-nil if the connection was closed due to context cancelation.
446+
func (mc *mysqlConn) canceled() error {
447+
mc.mu.Lock()
448+
defer mc.mu.Unlock()
449+
return mc.canceledErr
450+
}
451+
452+
// finish is called when the query has succeeded.
453+
func (mc *mysqlConn) finish() {
454+
if !mc.watching || mc.finished == nil {
455+
return
456+
}
457+
select {
458+
case mc.finished <- struct{}{}:
459+
mc.watching = false
460+
case <-mc.closech:
461+
}
462+
}

0 commit comments

Comments
 (0)