-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for context.Context #608
Changes from 4 commits
7e64321
54ef181
66fa137
06b17e6
1705550
41940ff
fd8a559
a464739
1fdad70
f96feaa
4ce2087
208cb44
31a7266
09fbdfa
c4f9ae6
87ba95a
85f33dd
80f3f6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package | ||
// | ||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
// You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
// +build go1.8 | ||
|
||
package mysql | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"runtime" | ||
"testing" | ||
) | ||
|
||
func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0)) | ||
|
||
tb := (*TB)(b) | ||
stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?")) | ||
defer stmt.Close() | ||
|
||
b.SetParallelism(p) | ||
b.ReportAllocs() | ||
b.ResetTimer() | ||
b.RunParallel(func(pb *testing.PB) { | ||
var got string | ||
for pb.Next() { | ||
tb.check(stmt.QueryRow(1).Scan(&got)) | ||
if got != "one" { | ||
b.Fatalf("query = %q; want one", got) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
func BenchmarkQueryContext(b *testing.B) { | ||
db := initDB(b, | ||
"DROP TABLE IF EXISTS foo", | ||
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))", | ||
`INSERT INTO foo VALUES (1, "one")`, | ||
`INSERT INTO foo VALUES (2, "two")`, | ||
) | ||
defer db.Close() | ||
for _, p := range []int{1, 2, 3, 4} { | ||
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) { | ||
benchmarkQueryContext(b, db, p) | ||
}) | ||
} | ||
} | ||
|
||
func benchmarkExecContext(b *testing.B, db *sql.DB, p int) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0)) | ||
|
||
tb := (*TB)(b) | ||
stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1")) | ||
defer stmt.Close() | ||
|
||
b.SetParallelism(p) | ||
b.ReportAllocs() | ||
b.ResetTimer() | ||
b.RunParallel(func(pb *testing.PB) { | ||
for pb.Next() { | ||
if _, err := stmt.ExecContext(ctx); err != nil { | ||
b.Fatal(err) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
func BenchmarkExecContext(b *testing.B) { | ||
db := initDB(b, | ||
"DROP TABLE IF EXISTS foo", | ||
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))", | ||
`INSERT INTO foo VALUES (1, "one")`, | ||
`INSERT INTO foo VALUES (2, "two")`, | ||
) | ||
defer db.Close() | ||
for _, p := range []int{1, 2, 3, 4} { | ||
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) { | ||
benchmarkQueryContext(b, db, p) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,9 +14,18 @@ import ( | |
"net" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// a copy of context.Context for Go 1.7 and later. | ||
type mysqlContext interface { | ||
Deadline() (deadline time.Time, ok bool) | ||
Done() <-chan struct{} | ||
Err() error | ||
Value(key interface{}) interface{} | ||
} | ||
|
||
type mysqlConn struct { | ||
buf buffer | ||
netConn net.Conn | ||
|
@@ -31,6 +40,13 @@ type mysqlConn struct { | |
sequence uint8 | ||
parseTime bool | ||
strict bool | ||
watcher chan<- mysqlContext | ||
closech chan struct{} | ||
finished chan<- struct{} | ||
|
||
mu sync.Mutex // guards following fields | ||
closed bool // set true when conn is closed, before closech is closed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO: it will be better to use atomic variable for closed, because this field is used often than canceledError. I believe it will be faster than acquire lock for each time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very good point, but Go currently doesn't have any atomic bool type in the standard library. |
||
canceledErr error // set non-nil if conn is canceled | ||
} | ||
|
||
// Handles parameters set in DSN after the connection is established | ||
|
@@ -64,7 +80,7 @@ func (mc *mysqlConn) handleParams() (err error) { | |
} | ||
|
||
func (mc *mysqlConn) Begin() (driver.Tx, error) { | ||
if mc.netConn == nil { | ||
if mc.isBroken() { | ||
errLog.Print(ErrInvalidConn) | ||
return nil, driver.ErrBadConn | ||
} | ||
|
@@ -78,7 +94,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) { | |
|
||
func (mc *mysqlConn) Close() (err error) { | ||
// Makes Close idempotent | ||
if mc.netConn != nil { | ||
if !mc.isBroken() { | ||
err = mc.writeCommandPacket(comQuit) | ||
} | ||
|
||
|
@@ -92,19 +108,32 @@ func (mc *mysqlConn) Close() (err error) { | |
// is called before auth or on auth failure because MySQL will have already | ||
// closed the network connection. | ||
func (mc *mysqlConn) cleanup() { | ||
mc.mu.Lock() | ||
defer mc.mu.Unlock() | ||
|
||
if mc.closed { | ||
return | ||
} | ||
|
||
// Makes cleanup idempotent | ||
if mc.netConn != nil { | ||
if err := mc.netConn.Close(); err != nil { | ||
errLog.Print(err) | ||
} | ||
mc.netConn = nil | ||
mc.closed = true | ||
close(mc.closech) | ||
if mc.netConn == nil { | ||
return | ||
} | ||
mc.cfg = nil | ||
mc.buf.nc = nil | ||
if err := mc.netConn.Close(); err != nil { | ||
errLog.Print(err) | ||
} | ||
} | ||
|
||
func (mc *mysqlConn) isBroken() bool { | ||
mc.mu.Lock() | ||
defer mc.mu.Unlock() | ||
return mc.closed | ||
} | ||
|
||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { | ||
if mc.netConn == nil { | ||
if mc.isBroken() { | ||
errLog.Print(ErrInvalidConn) | ||
return nil, driver.ErrBadConn | ||
} | ||
|
@@ -258,7 +287,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin | |
} | ||
|
||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { | ||
if mc.netConn == nil { | ||
if mc.isBroken() { | ||
errLog.Print(ErrInvalidConn) | ||
return nil, driver.ErrBadConn | ||
} | ||
|
@@ -315,7 +344,7 @@ func (mc *mysqlConn) exec(query string) error { | |
} | ||
|
||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { | ||
if mc.netConn == nil { | ||
if mc.isBroken() { | ||
errLog.Print(ErrInvalidConn) | ||
return nil, driver.ErrBadConn | ||
} | ||
|
@@ -387,3 +416,29 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) { | |
} | ||
return nil, err | ||
} | ||
|
||
// finish is called when the query has canceled. | ||
func (mc *mysqlConn) cancel(err error) { | ||
mc.mu.Lock() | ||
mc.canceledErr = err | ||
mc.mu.Unlock() | ||
mc.cleanup() | ||
} | ||
|
||
// canceled returns non-nil if the connection was closed due to context cancelation. | ||
func (mc *mysqlConn) canceled() error { | ||
mc.mu.Lock() | ||
defer mc.mu.Unlock() | ||
return mc.canceledErr | ||
} | ||
|
||
// finish is called when the query has succeeded. | ||
func (mc *mysqlConn) finish() { | ||
if mc.finished == nil { | ||
return | ||
} | ||
select { | ||
case mc.finished <- struct{}{}: | ||
case <-mc.closech: | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If method Value is not used, you can drop it. It is not necessary to describe all methods of interface context.Context.