Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ typedef struct st_heap_info
my_bool implicit_emptied;
my_bool has_zerocopy_blobs; /* Last hp_read_blobs produced zero-copy ptrs */
my_bool has_pending_blob_free; /* pending_blob_chains awaits freeing */
my_bool lock_granted; /* thr_lock() gave `lock' to this handle */
THR_LOCK_DATA lock;
LIST open_list;
} HP_INFO;
Expand Down
1 change: 1 addition & 0 deletions mysql-test/suite/heap/blob_delayed_insert.opt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--skip-log-bin
51 changes: 51 additions & 0 deletions mysql-test/suite/heap/blob_delayed_insert.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
CREATE TABLE t1 (a INT, b BLOB) ENGINE=MEMORY;
INSERT DELAYED INTO t1 VALUES (1, REPEAT('x', 300)), (2, REPEAT('y', 300));
SELECT VARIABLE_VALUE > 0 AS delayed_thread_ran
FROM information_schema.global_status
WHERE VARIABLE_NAME = 'DELAYED_WRITES';
delayed_thread_ran
1
INSERT DELAYED INTO t1 VALUES (3, REPEAT('z', 300)), (4, REPEAT('w', 300));
connect reader,localhost,root,,test;
connection default;
INSERT DELAYED INTO t1 VALUES (5, REPEAT('v', 300));
connection reader;
SELECT COUNT(*) >= 0 AS reader_ran FROM t1;
reader_ran
1
connection default;
INSERT DELAYED INTO t1 VALUES (6, REPEAT('u', 300));
disconnect reader;
SELECT a, LENGTH(b) FROM t1;
a LENGTH(b)
1 300
2 300
3 300
4 300
5 300
6 300
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
INSERT INTO t1 VALUES (7, REPEAT('t', 300));
UPDATE t1 SET b = REPEAT('s', 400) WHERE a = 7;
DELETE FROM t1 WHERE a = 7;
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
FLUSH TABLES;
INSERT INTO t1 VALUES (8, REPEAT('r', 300));
UPDATE t1 SET b = REPEAT('q', 400) WHERE a = 8;
DELETE FROM t1 WHERE a = 8;
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
SELECT a, LENGTH(b) FROM t1;
a LENGTH(b)
1 300
2 300
3 300
4 300
5 300
6 300
DROP TABLE t1;
76 changes: 76 additions & 0 deletions mysql-test/suite/heap/blob_delayed_insert.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#
# The record of a THR_LOCK grant survives an INSERT DELAYED lock cycle.
#
# thr_lock() does not call the grant callback once per external_lock(): a
# delayed insert is granted TL_WRITE_DELAYED and then calls it again on the
# same request when thr_upgrade_write_delay_lock() turns that into a real
# write lock, with no external_lock() in between. A handle that counted
# grants would keep the surplus for its whole life and then report a lock it
# does not hold on every later statement.
#
# The binary log must be off: with statement binlogging INSERT DELAYED is
# downgraded to an ordinary write lock and the delayed thread never runs.
#

CREATE TABLE t1 (a INT, b BLOB) ENGINE=MEMORY;

INSERT DELAYED INTO t1 VALUES (1, REPEAT('x', 300)), (2, REPEAT('y', 300));

--let $wait_condition= SELECT COUNT(*) = 2 FROM t1
--source include/wait_condition.inc

# The delayed thread really did the writing, rather than the statement being
# silently downgraded to a plain INSERT.
SELECT VARIABLE_VALUE > 0 AS delayed_thread_ran
FROM information_schema.global_status
WHERE VARIABLE_NAME = 'DELAYED_WRITES';

# Second cycle on the still-live delayed thread: it locks the same handle
# again, which is where a leaked grant is noticed.
INSERT DELAYED INTO t1 VALUES (3, REPEAT('z', 300)), (4, REPEAT('w', 300));

--let $wait_condition= SELECT COUNT(*) = 4 FROM t1
--source include/wait_condition.inc

# A third cycle with a concurrent reader, so the delayed thread has to give
# the lock up and take it again mid-batch.
connect (reader,localhost,root,,test);

connection default;
INSERT DELAYED INTO t1 VALUES (5, REPEAT('v', 300));

connection reader;
SELECT COUNT(*) >= 0 AS reader_ran FROM t1;

connection default;
INSERT DELAYED INTO t1 VALUES (6, REPEAT('u', 300));

--let $wait_condition= SELECT COUNT(*) = 6 FROM t1
--source include/wait_condition.inc

disconnect reader;

--sorted_result
SELECT a, LENGTH(b) FROM t1;
CHECK TABLE t1;

# Ordinary lock cycles on the same table afterwards. These write blobs, so
# they park and redeem chains, which a handle wrongly believing it holds the
# lock would do outside it.
INSERT INTO t1 VALUES (7, REPEAT('t', 300));
UPDATE t1 SET b = REPEAT('s', 400) WHERE a = 7;
DELETE FROM t1 WHERE a = 7;
CHECK TABLE t1;

# And once more after the table is reopened, so the same share is reached
# through a fresh handle.
FLUSH TABLES;
INSERT INTO t1 VALUES (8, REPEAT('r', 300));
UPDATE t1 SET b = REPEAT('q', 400) WHERE a = 8;
DELETE FROM t1 WHERE a = 8;
CHECK TABLE t1;

--sorted_result
SELECT a, LENGTH(b) FROM t1;

DROP TABLE t1;
59 changes: 59 additions & 0 deletions mysql-test/suite/heap/blob_lock_twice.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
CREATE TABLE ht (a INT, b BLOB) ENGINE=MEMORY;
CREATE TABLE mi (a INT, b BLOB) ENGINE=MyISAM;
INSERT INTO ht VALUES (1,'foo'),(2,'bar');
INSERT INTO mi VALUES (1,'foo'),(2,'bar');
# ===== single aliased entry, referenced as "table AS alias" =====
# -- MyISAM
LOCK TABLE mi AS m1 WRITE;
SELECT COUNT(*) FROM mi AS m1;
COUNT(*)
2
UPDATE mi AS m1 SET b='x' WHERE a=1;
# the unaliased name is not locked
SELECT COUNT(*) FROM mi;
ERROR HY000: Table 'mi' was not locked with LOCK TABLES
UNLOCK TABLES;
# -- MEMORY
LOCK TABLE ht AS h1 WRITE;
SELECT COUNT(*) FROM ht AS h1;
COUNT(*)
2
UPDATE ht AS h1 SET b='x' WHERE a=1;
# the unaliased name is not locked
SELECT COUNT(*) FROM ht;
ERROR HY000: Table 'ht' was not locked with LOCK TABLES
UNLOCK TABLES;
# ===== two aliased entries, one WRITE and one READ =====
# -- MyISAM
LOCK TABLE mi AS m1 WRITE, mi AS m2 READ;
SELECT COUNT(*) FROM mi AS m2;
COUNT(*)
2
UPDATE mi AS m1 SET b='y' WHERE a=1;
# INSERT takes no alias, so it cannot reach an aliased lock at all
INSERT INTO mi SELECT a+10, b FROM mi AS m2;
ERROR HY000: Table 'mi' was not locked with LOCK TABLES
UNLOCK TABLES;
# -- MEMORY
LOCK TABLE ht AS h1 WRITE, ht AS h2 READ;
SELECT COUNT(*) FROM ht AS h2;
COUNT(*)
2
UPDATE ht AS h1 SET b='y' WHERE a=1;
# INSERT takes no alias, so it cannot reach an aliased lock at all
INSERT INTO ht SELECT a+10, b FROM ht AS h2;
ERROR HY000: Table 'ht' was not locked with LOCK TABLES
UNLOCK TABLES;
# ===== blob update and delete under the double lock (MEMORY) =====
LOCK TABLE ht AS h1 WRITE, ht AS h2 READ;
UPDATE ht AS h1, ht AS h2 SET h1.b=REPEAT('z', 900)
WHERE h1.a=h2.a AND h1.a=1;
DELETE FROM ht AS h1 WHERE a=2;
UNLOCK TABLES;
CHECK TABLE ht;
Table Op Msg_type Msg_text
test.ht check status OK
SELECT a, LENGTH(b) FROM ht;
a LENGTH(b)
1 900
DROP TABLE ht, mi;
69 changes: 69 additions & 0 deletions mysql-test/suite/heap/blob_lock_twice.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#
# A MEMORY table with a blob locked twice in the same lock set.
#
# Two aliased entries give one share two handles, and thr_lock() grants each
# of them separately, so the grant a handle records is its own. Every row
# operation below runs with both grants live, and the blob update parks a
# chain that has to be redeemed when the handles are unlocked. MyISAM runs
# the same shapes as a control.
#
# An aliased entry can only be referenced by its alias while LOCK TABLES is
# in effect; the unaliased name is not in the lock set at all. That is
# asserted rather than avoided, because it is what keeps the two handles
# distinct.
#
CREATE TABLE ht (a INT, b BLOB) ENGINE=MEMORY;
CREATE TABLE mi (a INT, b BLOB) ENGINE=MyISAM;
INSERT INTO ht VALUES (1,'foo'),(2,'bar');
INSERT INTO mi VALUES (1,'foo'),(2,'bar');

--echo # ===== single aliased entry, referenced as "table AS alias" =====
--echo # -- MyISAM
LOCK TABLE mi AS m1 WRITE;
SELECT COUNT(*) FROM mi AS m1;
UPDATE mi AS m1 SET b='x' WHERE a=1;
--echo # the unaliased name is not locked
--error ER_TABLE_NOT_LOCKED
SELECT COUNT(*) FROM mi;
UNLOCK TABLES;

--echo # -- MEMORY
LOCK TABLE ht AS h1 WRITE;
SELECT COUNT(*) FROM ht AS h1;
UPDATE ht AS h1 SET b='x' WHERE a=1;
--echo # the unaliased name is not locked
--error ER_TABLE_NOT_LOCKED
SELECT COUNT(*) FROM ht;
UNLOCK TABLES;

--echo # ===== two aliased entries, one WRITE and one READ =====
--echo # -- MyISAM
LOCK TABLE mi AS m1 WRITE, mi AS m2 READ;
SELECT COUNT(*) FROM mi AS m2;
UPDATE mi AS m1 SET b='y' WHERE a=1;
--echo # INSERT takes no alias, so it cannot reach an aliased lock at all
--error ER_TABLE_NOT_LOCKED
INSERT INTO mi SELECT a+10, b FROM mi AS m2;
UNLOCK TABLES;

--echo # -- MEMORY
LOCK TABLE ht AS h1 WRITE, ht AS h2 READ;
SELECT COUNT(*) FROM ht AS h2;
UPDATE ht AS h1 SET b='y' WHERE a=1;
--echo # INSERT takes no alias, so it cannot reach an aliased lock at all
--error ER_TABLE_NOT_LOCKED
INSERT INTO ht SELECT a+10, b FROM ht AS h2;
UNLOCK TABLES;

--echo # ===== blob update and delete under the double lock (MEMORY) =====
LOCK TABLE ht AS h1 WRITE, ht AS h2 READ;
UPDATE ht AS h1, ht AS h2 SET h1.b=REPEAT('z', 900)
WHERE h1.a=h2.a AND h1.a=1;
DELETE FROM ht AS h1 WHERE a=2;
UNLOCK TABLES;

CHECK TABLE ht;
--sorted_result
SELECT a, LENGTH(b) FROM ht;

DROP TABLE ht, mi;
26 changes: 26 additions & 0 deletions mysql-test/suite/heap/blob_online_alter.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
CREATE TABLE t1 (a INT PRIMARY KEY, b BLOB) ENGINE=MEMORY;
INSERT INTO t1 VALUES (1, REPEAT('x', 300)), (2, REPEAT('y', 300)),
(3, REPEAT('w', 300)), (4, REPEAT('v', 300));
connect alterer,localhost,root,,test;
SET DEBUG_SYNC= 'alter_table_online_downgraded SIGNAL downgraded EXECUTE 1';
SET DEBUG_SYNC= 'alter_table_online_progress WAIT_FOR dml_done EXECUTE 1';
ALTER TABLE t1 ADD COLUMN c INT, ALGORITHM=COPY, LOCK=NONE;
connection default;
SET SESSION lock_wait_timeout= 20;
SET DEBUG_SYNC= 'now WAIT_FOR downgraded';
DELETE FROM t1 WHERE a = 1;
UPDATE t1 SET b = REPEAT('z', 900) WHERE a = 2;
DELETE FROM t1 WHERE a = 3;
SET DEBUG_SYNC= 'now SIGNAL dml_done';
connection alterer;
connection default;
disconnect alterer;
SET DEBUG_SYNC= 'RESET';
SELECT a, LENGTH(b), c FROM t1;
a LENGTH(b) c
2 900 NULL
4 300 NULL
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
DROP TABLE t1;
47 changes: 47 additions & 0 deletions mysql-test/suite/heap/blob_online_alter.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# Blob chains parked on an online-ALTER copy target.
#
# The copy target is locked with a direct handler::ha_external_lock() rather
# than through the SQL layer's lock set, so thr_lock() never grants it
# anything and the handle holds a write lock that no grant records. Rows
# deleted or updated on the source while the copy runs are replayed onto that
# target, and a blob delete/update there parks a chain that has to be redeemed
# when the copy is unlocked.
#
# The ALTER is not paused while it holds the source lock -- doing that blocks
# the very statements this test needs to run. It is released at the
# post-downgrade point and only made to wait once it is replaying.
#
--source include/have_debug_sync.inc

CREATE TABLE t1 (a INT PRIMARY KEY, b BLOB) ENGINE=MEMORY;
INSERT INTO t1 VALUES (1, REPEAT('x', 300)), (2, REPEAT('y', 300)),
(3, REPEAT('w', 300)), (4, REPEAT('v', 300));

connect (alterer,localhost,root,,test);
SET DEBUG_SYNC= 'alter_table_online_downgraded SIGNAL downgraded EXECUTE 1';
SET DEBUG_SYNC= 'alter_table_online_progress WAIT_FOR dml_done EXECUTE 1';
--send ALTER TABLE t1 ADD COLUMN c INT, ALGORITHM=COPY, LOCK=NONE

connection default;
SET SESSION lock_wait_timeout= 20;
SET DEBUG_SYNC= 'now WAIT_FOR downgraded';

# Both shapes that park a chain: a delete, and an update that grows the blob
DELETE FROM t1 WHERE a = 1;
UPDATE t1 SET b = REPEAT('z', 900) WHERE a = 2;
DELETE FROM t1 WHERE a = 3;

SET DEBUG_SYNC= 'now SIGNAL dml_done';

connection alterer;
--reap

connection default;
disconnect alterer;
SET DEBUG_SYNC= 'RESET';

--sorted_result
SELECT a, LENGTH(b), c FROM t1;
CHECK TABLE t1;
DROP TABLE t1;
Loading
Loading