Skip to content

Commit 6320cf8

Browse files
committed
BUG20653441: Fix connection hanging when a query is killed
Connection was hanging when a query is killed from a different connection. We fix this by correctly reading packets from MySQL server which contain error information. A test case has been added for BUG#20653441
1 parent 85212e3 commit 6320cf8

File tree

3 files changed

+83
-3
lines changed

3 files changed

+83
-3
lines changed

lib/mysql/connector/errors.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,11 @@ def get_exception(packet):
143143
"""
144144
errno = errmsg = None
145145

146-
if packet[4] != 255:
147-
raise ValueError("Packet is not an error packet")
146+
try:
147+
if packet[4] != 255:
148+
raise ValueError("Packet is not an error packet")
149+
except IndexError as err:
150+
return InterfaceError("Failed getting Error information (%r)" % err)
148151

149152
sqlstate = None
150153
try:

lib/mysql/connector/protocol.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from . import errors, utils
3434
from .authentication import get_auth_plugin
3535
from .catch23 import PY2, struct_unpack
36+
from .errors import get_exception
3637

3738

3839
class MySQLProtocol(object):
@@ -335,8 +336,10 @@ def read_text_result(self, sock, count=1):
335336
rowdata = utils.read_lc_string_list(packet[4:])
336337
if eof is None and rowdata is not None:
337338
rows.append(rowdata)
339+
elif eof is None and rowdata is None:
340+
raise get_exception(packet)
338341
i += 1
339-
return (rows, eof)
342+
return rows, eof
340343

341344
def _parse_binary_integer(self, packet, field):
342345
"""Parse an integer from a binary packet"""
@@ -466,6 +469,8 @@ def read_binary_result(self, sock, columns, count=1):
466469
values = self._parse_binary_values(columns, packet[5:])
467470
if eof is None and values is not None:
468471
rows.append(values)
472+
elif eof is None and values is None:
473+
raise get_exception(packet)
469474
i += 1
470475
return (rows, eof)
471476

tests/test_bugs.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3578,3 +3578,75 @@ def test_set(self):
35783578
exp.append((row.id, row.name, row.dept))
35793579
self.assertEqual(exp, data[1:])
35803580
cur.close()
3581+
3582+
3583+
class BugOra20653441(tests.MySQLConnectorTests):
3584+
3585+
"""BUG#20653441: PYTHON CONNECTOR HANGS IF A QUERY IS KILLED (ERROR 1317)"""
3586+
3587+
def setUp(self):
3588+
self.table_name = 'Bug20653441'
3589+
3590+
def _setup(self):
3591+
self.cnx.cmd_query("DROP TABLE IF EXISTS {0}".format(self.table_name))
3592+
table = (
3593+
"CREATE TABLE {table} ("
3594+
" id INT UNSIGNED NOT NULL AUTO_INCREMENT,"
3595+
" c1 VARCHAR(255) DEFAULT '{default}',"
3596+
" PRIMARY KEY (id)"
3597+
")"
3598+
).format(table=self.table_name, default='a' * 255)
3599+
self.cnx.cmd_query(table)
3600+
3601+
stmt = "INSERT INTO {table} (id) VALUES {values}".format(
3602+
table=self.table_name,
3603+
values=','.join(['(NULL)'] * 1024)
3604+
)
3605+
self.cnx.cmd_query(stmt)
3606+
3607+
def tearDown(self):
3608+
try:
3609+
cnx = connection.MySQLConnection(**tests.get_mysql_config())
3610+
cnx.cmd_query(
3611+
"DROP TABLE IF EXISTS {0}".format(self.table_name))
3612+
cnx.close()
3613+
except:
3614+
pass
3615+
3616+
@foreach_cnx()
3617+
def test_kill_query(self):
3618+
self._setup()
3619+
3620+
def kill(connection_id):
3621+
"""Kill query using separate connection"""
3622+
killer = connection.MySQLConnection(**tests.get_mysql_config())
3623+
time.sleep(1)
3624+
killer.cmd_query("KILL QUERY {0}".format(connection_id))
3625+
killer.close()
3626+
3627+
def sleepy_select(cnx):
3628+
"""Execute a SELECT statement which takes a while to complete"""
3629+
cur = cnx.cursor()
3630+
# Ugly query ahead!
3631+
stmt = "SELECT x1.*, x2.* from {table} as x1, {table} as x2".format(
3632+
table=self.table_name)
3633+
cur.execute(stmt)
3634+
# Save the error so we can check in the calling thread
3635+
cnx.test_error = None
3636+
3637+
try:
3638+
cur.fetchall()
3639+
except errors.Error as err:
3640+
cnx.test_error = err
3641+
3642+
worker = Thread(target=sleepy_select, args=[self.cnx])
3643+
killer = Thread(target=kill, args=[self.cnx.connection_id])
3644+
worker.start()
3645+
killer.start()
3646+
worker.join()
3647+
killer.join()
3648+
3649+
self.assertTrue(isinstance(self.cnx.test_error, errors.DatabaseError))
3650+
self.assertEqual(str(self.cnx.test_error),
3651+
"1317 (70100): Query execution was interrupted")
3652+
self.cnx.close()

0 commit comments

Comments
 (0)