Skip to content

Commit a05cc94

Browse files
committed
don't use Timeout.timeout
1 parent b367feb commit a05cc94

File tree

7 files changed

+77
-37
lines changed

7 files changed

+77
-37
lines changed

lib/mysql.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def options(opt, value=nil)
181181
# when Mysql::OPT_OPTIONAL_RESULTSET_METADATA
182182
# when Mysql::OPT_PROTOCOL
183183
when Mysql::OPT_READ_TIMEOUT
184-
@opts[:read_timeout] = value.to_i
184+
@opts[:read_timeout] = value
185185
# when Mysql::OPT_RECONNECT
186186
# when Mysql::OPT_RETRY_COUNT
187187
# when Mysql::SET_CLIENT_IP
@@ -199,7 +199,7 @@ def options(opt, value=nil)
199199
# when Mysql::OPT_TLS_VERSION
200200
# when Mysql::OPT_USE_RESULT
201201
when Mysql::OPT_WRITE_TIMEOUT
202-
@opts[:write_timeout] = value.to_i
202+
@opts[:write_timeout] = value
203203
# when Mysql::OPT_ZSTD_COMPRESSION_LEVEL
204204
# when Mysql::PLUGIN_DIR
205205
# when Mysql::READ_DEFAULT_FILE

lib/mysql/authenticator.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def authenticate(user, passwd, db, scramble, plugin_name)
5555
end
5656
end
5757
else
58-
raise ClientError, "invalid packet: #{pkt.to_s}"
58+
raise ClientError, "invalid packet: #{pkt.rest}"
5959
end
6060
end
6161
end

lib/mysql/authenticator/caching_sha2_password.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def name
2020
def authenticate(passwd, scramble)
2121
yield hash_password(passwd, scramble)
2222
pkt = @protocol.read
23-
data = pkt.to_s
23+
data = pkt.rest
2424
if data.size == 2 && data[0] == "\x01"
2525
case data[1]
2626
when "\x03" # fast_auth_success
@@ -34,7 +34,7 @@ def authenticate(passwd, scramble)
3434
@protocol.write "\2" # request public key
3535
pkt = @protocol.read
3636
pkt.utiny # skip
37-
pubkey = pkt.to_s
37+
pubkey = pkt.rest
3838
hash = (passwd+"\0").unpack("C*").zip(scramble.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
3939
enc = OpenSSL::PKey::RSA.new(pubkey).public_encrypt(hash, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
4040
@protocol.write enc

lib/mysql/authenticator/sha256_password.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ def authenticate(passwd, scramble)
2424
end
2525
yield "\x01" # request public key
2626
pkt = @protocol.read
27-
data = pkt.to_s
27+
data = pkt.rest
2828
if data[0] == "\x01"
2929
pkt.utiny # skip
30-
pubkey = pkt.to_s
30+
pubkey = pkt.rest
3131
hash = (passwd+"\0").unpack("C*").zip(scramble.unpack("C*")).map{|a, b| a ^ b}.pack("C*")
3232
enc = OpenSSL::PKey::RSA.new(pubkey).public_encrypt(hash, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
3333
@protocol.write enc

lib/mysql/packet.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def eof?
7070
@data[0] == ?\xfe && @data.length == 5
7171
end
7272

73-
def to_s
73+
def rest
7474
@data
7575
end
7676

lib/mysql/protocol.rb

+63-27
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
44

55
require "socket"
6-
require "timeout"
76
require "stringio"
87
require "openssl"
98
require_relative 'authenticator.rb'
@@ -511,20 +510,18 @@ def read
511510
data = ''
512511
len = nil
513512
begin
514-
Timeout.timeout @opts[:read_timeout] do
515-
header = @socket.read(4)
516-
raise EOFError unless header && header.length == 4
517-
len1, len2, seq = header.unpack("CvC")
518-
len = (len2 << 8) + len1
519-
raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
520-
@seq = (@seq + 1) % 256
521-
ret = @socket.read(len)
522-
raise EOFError unless ret && ret.length == len
523-
data.concat ret
524-
end
513+
header = read_timeout(4, @opts[:read_timeout])
514+
raise EOFError unless header && header.length == 4
515+
len1, len2, seq = header.unpack("CvC")
516+
len = (len2 << 8) + len1
517+
raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
518+
@seq = (@seq + 1) % 256
519+
ret = read_timeout(len, @opts[:read_timeout])
520+
raise EOFError unless ret && ret.length == len
521+
data.concat ret
525522
rescue EOFError
526523
raise ClientError::ServerGoneError, 'MySQL server has gone away'
527-
rescue Timeout::Error
524+
rescue Errno::ETIMEDOUT
528525
raise ClientError, "read timeout"
529526
end while len == MAX_PACKET_LENGTH
530527

@@ -546,34 +543,73 @@ def read
546543
Packet.new(data)
547544
end
548545

546+
def read_timeout(len, timeout)
547+
return @socket.read(len) if timeout.nil? || timeout == 0
548+
result = ''
549+
e = ::Time.now + timeout
550+
while result.size < len
551+
now = ::Time.now
552+
raise Errno::ETIMEDOUT if now > e
553+
r = @socket.read_nonblock(len - result.size, exception: false)
554+
case r
555+
when :wait_readable
556+
IO.select([@socket], nil, nil, e - now)
557+
next
558+
when :wait_writable
559+
IO.select(nil, [@socket], nil, e - now)
560+
next
561+
else
562+
result << r
563+
end
564+
end
565+
return result
566+
end
567+
549568
# Write one packet data
550569
# === Argument
551570
# data :: [String / IO] packet data. If data is nil, write empty packet.
552571
def write(data)
553572
begin
554-
Timeout.timeout @opts[:write_timeout] do
555-
@socket.sync = false
556-
if data.nil?
557-
@socket.write [0, 0, @seq].pack("CvC")
573+
@socket.sync = false
574+
if data.nil?
575+
write_timeout([0, 0, @seq].pack("CvC"), @opts[:write_timeout])
576+
@seq = (@seq + 1) % 256
577+
else
578+
data = StringIO.new data if data.is_a? String
579+
while d = data.read(MAX_PACKET_LENGTH)
580+
write_timeout([d.length%256, d.length/256, @seq].pack("CvC")+d, @opts[:write_timeout])
558581
@seq = (@seq + 1) % 256
559-
else
560-
data = StringIO.new data if data.is_a? String
561-
while d = data.read(MAX_PACKET_LENGTH)
562-
@socket.write [d.length%256, d.length/256, @seq].pack("CvC")
563-
@socket.write d
564-
@seq = (@seq + 1) % 256
565-
end
566582
end
567-
@socket.sync = true
568-
@socket.flush
569583
end
584+
@socket.sync = true
585+
@socket.flush
570586
rescue Errno::EPIPE
571587
raise ClientError::ServerGoneError, 'MySQL server has gone away'
572-
rescue Timeout::Error
588+
rescue Errno::ETIMEDOUT
573589
raise ClientError, "write timeout"
574590
end
575591
end
576592

593+
def write_timeout(data, timeout)
594+
return @socket.write(data) if timeout.nil? || timeout == 0
595+
len = 0
596+
e = ::Time.now + timeout
597+
while len < data.size
598+
now = ::Time.now
599+
raise Errno::ETIMEDOUT if now > e
600+
l = @socket.write_nonblock(data[len..-1], exception: false)
601+
case l
602+
when :wait_readable
603+
IO.select([@socket], nil, nil, e - now)
604+
when :wait_writable
605+
IO.select(nil, [@socket], nil, e - now)
606+
else
607+
len += l
608+
end
609+
end
610+
return len
611+
end
612+
577613
# Read EOF packet
578614
# === Exception
579615
# [ProtocolError] packet is not EOF

test/test_mysql.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,14 @@ class TestMysql < Test::Unit::TestCase
201201
end
202202
end
203203
test 'OPT_READ_TIMEOUT: set timeout for reading packet' do
204-
assert{ @m.options(Mysql::OPT_READ_TIMEOUT, 10) == @m }
204+
assert{ @m.options(Mysql::OPT_READ_TIMEOUT, 1) == @m }
205+
@m.connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, MYSQL_SOCKET)
206+
@m.query("select 123").entries
205207
end
206208
test 'OPT_WRITE_TIMEOUT: set timeout for writing packet' do
207-
assert{ @m.options(Mysql::OPT_WRITE_TIMEOUT, 10) == @m }
209+
assert{ @m.options(Mysql::OPT_WRITE_TIMEOUT, 1) == @m }
210+
@m.connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, MYSQL_SOCKET)
211+
@m.query("select 123").entries
208212
end
209213
test 'SET_CHARSET_NAME: set charset for connection' do
210214
assert{ @m.options(Mysql::SET_CHARSET_NAME, 'utf8mb3') == @m }

0 commit comments

Comments
 (0)