Skip to content

Commit 72ea5d0

Browse files
committed
Merge pull request #401 from go-sql-driver/timeout
read / write timeouts
2 parents 809154b + f7f9f33 commit 72ea5d0

File tree

7 files changed

+92
-34
lines changed

7 files changed

+92
-34
lines changed

Diff for: README.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ Default: false
231231
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
232232

233233

234+
##### `readTimeout`
235+
236+
```
237+
Type: decimal number
238+
Default: 0
239+
```
240+
241+
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
242+
243+
234244
##### `strict`
235245

236246
```
@@ -251,7 +261,7 @@ Type: decimal number
251261
Default: OS default
252262
```
253263

254-
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
264+
*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
255265

256266

257267
##### `tls`
@@ -265,6 +275,16 @@ Default: false
265275
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
266276

267277

278+
##### `writeTimeout`
279+
280+
```
281+
Type: decimal number
282+
Default: 0
283+
```
284+
285+
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
286+
287+
268288
##### System Variables
269289

270290
All other parameters are interpreted as system variables:

Diff for: buffer.go

+19-8
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
package mysql
1010

11-
import "io"
11+
import (
12+
"io"
13+
"net"
14+
"time"
15+
)
1216

1317
const defaultBufSize = 4096
1418

@@ -18,17 +22,18 @@ const defaultBufSize = 4096
1822
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
1923
// Also highly optimized for this particular use case.
2024
type buffer struct {
21-
buf []byte
22-
rd io.Reader
23-
idx int
24-
length int
25+
buf []byte
26+
nc net.Conn
27+
idx int
28+
length int
29+
timeout time.Duration
2530
}
2631

27-
func newBuffer(rd io.Reader) buffer {
32+
func newBuffer(nc net.Conn) buffer {
2833
var b [defaultBufSize]byte
2934
return buffer{
3035
buf: b[:],
31-
rd: rd,
36+
nc: nc,
3237
}
3338
}
3439

@@ -54,7 +59,13 @@ func (b *buffer) fill(need int) error {
5459
b.idx = 0
5560

5661
for {
57-
nn, err := b.rd.Read(b.buf[n:])
62+
if b.timeout > 0 {
63+
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
64+
return err
65+
}
66+
}
67+
68+
nn, err := b.nc.Read(b.buf[n:])
5869
n += nn
5970

6071
switch err {

Diff for: connection.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type mysqlConn struct {
2424
cfg *Config
2525
maxPacketAllowed int
2626
maxWriteSize int
27+
writeTimeout time.Duration
2728
flags clientFlag
2829
status statusFlag
2930
sequence uint8
@@ -98,7 +99,7 @@ func (mc *mysqlConn) cleanup() {
9899
mc.netConn = nil
99100
}
100101
mc.cfg = nil
101-
mc.buf.rd = nil
102+
mc.buf.nc = nil
102103
}
103104

104105
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {

Diff for: driver.go

+4
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
8383

8484
mc.buf = newBuffer(mc.netConn)
8585

86+
// Set I/O timeouts
87+
mc.buf.timeout = mc.cfg.ReadTimeout
88+
mc.writeTimeout = mc.cfg.WriteTimeout
89+
8690
// Reading Handshake Initialization Packet
8791
cipher, err := mc.readInitPacket()
8892
if err != nil {

Diff for: dsn.go

+26-10
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ var (
2727

2828
// Config is a configuration parsed from a DSN string
2929
type Config struct {
30-
User string // Username
31-
Passwd string // Password
32-
Net string // Network type
33-
Addr string // Network address
34-
DBName string // Database name
35-
Params map[string]string // Connection parameters
36-
Loc *time.Location // Location for time.Time values
37-
TLS *tls.Config // TLS configuration
38-
Timeout time.Duration // Dial timeout
39-
Collation uint8 // Connection collation
30+
User string // Username
31+
Passwd string // Password
32+
Net string // Network type
33+
Addr string // Network address
34+
DBName string // Database name
35+
Params map[string]string // Connection parameters
36+
Loc *time.Location // Location for time.Time values
37+
TLS *tls.Config // TLS configuration
38+
Timeout time.Duration // Dial timeout
39+
ReadTimeout time.Duration // I/O read timeout
40+
WriteTimeout time.Duration // I/O write timeout
41+
Collation uint8 // Connection collation
4042

4143
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
4244
AllowCleartextPasswords bool // Allows the cleartext client side plugin
@@ -241,6 +243,13 @@ func parseDSNParams(cfg *Config, params string) (err error) {
241243
return errors.New("Invalid Bool value: " + value)
242244
}
243245

246+
// I/O read Timeout
247+
case "readTimeout":
248+
cfg.ReadTimeout, err = time.ParseDuration(value)
249+
if err != nil {
250+
return
251+
}
252+
244253
// Strict mode
245254
case "strict":
246255
var isBool bool
@@ -282,6 +291,13 @@ func parseDSNParams(cfg *Config, params string) (err error) {
282291
}
283292
}
284293

294+
// I/O write Timeout
295+
case "writeTimeout":
296+
cfg.WriteTimeout, err = time.ParseDuration(value)
297+
if err != nil {
298+
return
299+
}
300+
285301
default:
286302
// lazy init
287303
if cfg.Params == nil {

Diff for: dsn_test.go

+13-13
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@ var testDSNs = []struct {
1919
in string
2020
out string
2121
}{
22-
{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
23-
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false ParseTime:false Strict:false}"},
24-
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
25-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
26-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
27-
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Loc:UTC TLS:<nil> Timeout:30s Collation:224 AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
28-
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Loc:Local TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
29-
{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
30-
{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
31-
{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
32-
{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
33-
{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
34-
{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Loc:UTC TLS:<nil> Timeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
22+
{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
23+
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false ParseTime:false Strict:false}"},
24+
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
25+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
26+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
27+
{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Loc:UTC TLS:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s Collation:224 AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
28+
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Loc:Local TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
29+
{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
30+
{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
31+
{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
32+
{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
33+
{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
34+
{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Loc:UTC TLS:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 Collation:33 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false ParseTime:false Strict:false}"},
3535
}
3636

3737
func TestDSNParser(t *testing.T) {

Diff for: packets.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func (mc *mysqlConn) writePacket(data []byte) error {
100100
data[3] = mc.sequence
101101

102102
// Write packet
103+
if mc.writeTimeout > 0 {
104+
if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
105+
return err
106+
}
107+
}
108+
103109
n, err := mc.netConn.Write(data[:4+size])
104110
if err == nil && n == 4+size {
105111
mc.sequence++
@@ -278,7 +284,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
278284
return err
279285
}
280286
mc.netConn = tlsConn
281-
mc.buf.rd = tlsConn
287+
mc.buf.nc = tlsConn
282288
}
283289

284290
// Filler [23 bytes] (all 0x00)

0 commit comments

Comments
 (0)