-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add support for context.Context #608
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
Changes from 1 commit
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 |
---|---|---|
|
@@ -10,13 +10,23 @@ package mysql | |
|
||
import ( | ||
"database/sql/driver" | ||
"errors" | ||
"io" | ||
"net" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
//a copy of context.Context from Go 1.7 and later. | ||
type mysqlContext interface { | ||
Deadline() (deadline time.Time, ok bool) | ||
Done() <-chan struct{} | ||
Err() error | ||
Value(key interface{}) interface{} | ||
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. If method Value is not used, you can drop it. It is not necessary to describe all methods of interface context.Context. |
||
} | ||
|
||
type mysqlConn struct { | ||
buf buffer | ||
netConn net.Conn | ||
|
@@ -31,6 +41,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 error // set non-nil 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. this field has type |
||
canceledErr error // set non-nil if conn is canceled | ||
} | ||
|
||
// Handles parameters set in DSN after the connection is established | ||
|
@@ -64,7 +81,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,11 +95,11 @@ 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) | ||
} | ||
|
||
mc.cleanup() | ||
mc.cleanup(errors.New("mysql: connection 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. Might a user see this error? If it is completely internal, please define it at the top of the file. |
||
|
||
return | ||
} | ||
|
@@ -91,20 +108,36 @@ func (mc *mysqlConn) Close() (err error) { | |
// function after successfully authentication, call Close instead. This function | ||
// is called before auth or on auth failure because MySQL will have already | ||
// closed the network connection. | ||
func (mc *mysqlConn) cleanup() { | ||
func (mc *mysqlConn) cleanup(err error) { | ||
if err == nil { | ||
panic("nil error") | ||
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. This should probably be a bit more descriptive |
||
} | ||
mc.mu.Lock() | ||
defer mc.mu.Unlock() | ||
|
||
if mc.closed != nil { | ||
return | ||
} | ||
|
||
// Makes cleanup idempotent | ||
if mc.netConn != nil { | ||
if err := mc.netConn.Close(); err != nil { | ||
errLog.Print(err) | ||
} | ||
mc.netConn = nil | ||
mc.closed = err | ||
close(mc.closech) | ||
if mc.netConn == nil { | ||
return | ||
} | ||
if err := mc.netConn.Close(); err != nil { | ||
errLog.Print(err) | ||
} | ||
mc.cfg = nil | ||
mc.buf.nc = nil | ||
} | ||
|
||
func (mc *mysqlConn) isBroken() bool { | ||
mc.mu.Lock() | ||
defer mc.mu.Unlock() | ||
return mc.closed != nil | ||
} | ||
|
||
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 +291,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 +348,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 +420,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(errors.New("mysql: query canceled")) | ||
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. same as for the other error |
||
} | ||
|
||
// 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.
s/from/for/
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.
and start the comment with a space please