From cc7bab32779082c2cc330bd520f448d3b3466421 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Fri, 4 Sep 2026 13:37:33 +0300 Subject: [PATCH 1/4] MDEV-41012 Galera appliers hang with foreign key of types UUID, INET4, INET6 Added a deterministic test for reproducing the issue --- mysql-test/suite/galera/r/MDEV-41012.result | 104 ++++++++++++++++++++ mysql-test/suite/galera/t/MDEV-41012.inc | 67 +++++++++++++ mysql-test/suite/galera/t/MDEV-41012.test | 40 ++++++++ 3 files changed, 211 insertions(+) create mode 100644 mysql-test/suite/galera/r/MDEV-41012.result create mode 100644 mysql-test/suite/galera/t/MDEV-41012.inc create mode 100644 mysql-test/suite/galera/t/MDEV-41012.test diff --git a/mysql-test/suite/galera/r/MDEV-41012.result b/mysql-test/suite/galera/r/MDEV-41012.result new file mode 100644 index 0000000000000..8f1b258f64a7f --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-41012.result @@ -0,0 +1,104 @@ +connection node_2; +connection node_1; +# +# UUID +# +connection node_1; +CREATE TABLE p (id UUID PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id UUID NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; +# +# INET6 +# +connection node_1; +CREATE TABLE p (id INET6 PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id INET6 NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('2001:db8::c0a8:16b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('2001:db8::c0a8:16b'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = '2001:db8::c0a8:16b'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; +# +# INET4 +# +connection node_1; +CREATE TABLE p (id INET4 PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id INET4 NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('192.168.1.107', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('192.168.1.107'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = '192.168.1.107'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; diff --git a/mysql-test/suite/galera/t/MDEV-41012.inc b/mysql-test/suite/galera/t/MDEV-41012.inc new file mode 100644 index 0000000000000..edb1430d74cb7 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012.inc @@ -0,0 +1,67 @@ +# +# Body of galera.MDEV-41012, parameterized by the data type of the foreign key. +# +# $fbt_type - data type of the parent PK / child FK column +# $fbt_value - a literal of that type whose binary representation contains +# bytes that latin1_swedish_ci folds (0xC0, 0x6A, 0x6B, 0xD2 ...) +# + +--connection node_1 +--eval CREATE TABLE p (id $fbt_type PRIMARY KEY, v INT) ENGINE=InnoDB +--eval CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id $fbt_type NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB +--eval INSERT INTO p VALUES ($fbt_value, 0) + +--connection node_2 +SET SESSION wsrep_sync_wait = 0; +--let $wait_condition = SELECT COUNT(*) = 1 FROM p +--source include/wait_condition.inc + +# Two appliers, so that the two writesets below *can* be applied in parallel +# if certification does not make them depend on each other. +SET GLOBAL wsrep_slave_threads = 2; + +# Park the first applier on entry to the apply monitor, i.e. before the child +# INSERT is applied and before its FK check locks the parent row. +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--eval INSERT INTO c (p_id) VALUES ($fbt_value) + +--connection node_2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_wait_sync_point.inc + +# clear the sync point. The second applier must run into the real dependency +--source include/galera_clear_sync_point.inc + +--let $apply_waits = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits'` + +--connection node_1 +--eval UPDATE p SET v = v + 1 WHERE id = $fbt_value + +--connection node_2 +# Wait until the second applier has blocked in the apply monitor because +--let $wait_condition = SELECT (SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits') > $apply_waits +--source include/wait_condition.inc + +# the second applier waited instead of applying it in parallel. +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; + +# Release the child INSERT +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT COUNT(*) = 1 FROM c +--source include/wait_condition.inc +--let $wait_condition = SELECT v = 1 FROM p +--source include/wait_condition.inc + +SELECT v FROM p; +SELECT COUNT(*) FROM c; + +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; + +--connection node_1 +DROP TABLE c, p; diff --git a/mysql-test/suite/galera/t/MDEV-41012.test b/mysql-test/suite/galera/t/MDEV-41012.test new file mode 100644 index 0000000000000..06a8c604dd6d1 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012.test @@ -0,0 +1,40 @@ +# +# MDEV-41012: BF-BF conflict when applying a child INSERT and a parent UPDATE +# in parallel, for a foreign key on a fixed binary data type. +# +# The test sends two write sets having access with the same primary key of fixed +# binary data type, the transactions are: +# * INSERT into the child table havinga reference key for the parent row +# * UPDATE of the parent row having the parent's primary key, +# +# The test orders appliers so that the child INSERT is parked before it +# is applied, the parent UPDATE is replicated after it, and we verify that the +# UPDATE has to wait for the INSERT instead of being applied in parallel. +# +# All three Field_fbt types (UUID, INET4 and INET6) are covered. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/galera_have_debug_sync.inc + +--echo # +--echo # UUID +--echo # +--let $fbt_type = UUID +--let $fbt_value = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b' +--source MDEV-41012.inc + +--echo # +--echo # INET6 +--echo # +--let $fbt_type = INET6 +--let $fbt_value = '2001:db8::c0a8:16b' +--source MDEV-41012.inc + +--echo # +--echo # INET4 +--echo # +--let $fbt_type = INET4 +--let $fbt_value = '192.168.1.107' +--source MDEV-41012.inc From 6afe5d8684e8446f49904d9200dd6b7567e18013 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Fri, 4 Sep 2026 15:03:00 +0300 Subject: [PATCH 2/4] MDEV-41012 Galera appliers hang with foreign key of types UUID, INET4, INET6 Added second test for testing key collisions from transactions modifying separate rows --- .../galera/r/MDEV-41012-collision.result | 115 ++++++++++++++++++ .../suite/galera/t/MDEV-41012-collision.inc | 67 ++++++++++ .../suite/galera/t/MDEV-41012-collision.test | 64 ++++++++++ 3 files changed, 246 insertions(+) create mode 100644 mysql-test/suite/galera/r/MDEV-41012-collision.result create mode 100644 mysql-test/suite/galera/t/MDEV-41012-collision.inc create mode 100644 mysql-test/suite/galera/t/MDEV-41012-collision.test diff --git a/mysql-test/suite/galera/r/MDEV-41012-collision.result b/mysql-test/suite/galera/r/MDEV-41012-collision.result new file mode 100644 index 0000000000000..dc3af178d72c9 --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-41012-collision.result @@ -0,0 +1,115 @@ +connection node_2; +connection node_1; +# +# Each pair below is two distinct values whose bytes fold to the same +# latin1_swedish_ci weight string (0xC0->0x41, 0x6A->0x4A, 0x6B->0x4B, +# 0xD2->0x4F, 0x7A->0x5A). A UUID is stored as a byte permutation of the +# value shown, which does not affect a byte-wise folding. +# +SELECT WEIGHT_STRING(_latin1 0xC0A8016B9E4D4F7AB3D26A1E8F9C0D1B) = +WEIGHT_STRING(_latin1 0x41A8014B9E4D4F5AB34F4A1E8F9C0D1B) AS uuid_pair_collides, +WEIGHT_STRING(_latin1 0x20010DB80000000000000000C0A8016B) = +WEIGHT_STRING(_latin1 0x20010DB8000000000000000041A8014B) AS inet6_pair_collides, +WEIGHT_STRING(_latin1 0xC0A8016B) = +WEIGHT_STRING(_latin1 0x41A8014B) AS inet4_pair_collides; +uuid_pair_collides inet6_pair_collides inet4_pair_collides +1 1 1 +# +# UUID +# +connection node_1; +CREATE TABLE t (id UUID PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b', 0), ('41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b 1 +41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; +# +# INET6 +# +connection node_1; +CREATE TABLE t (id INET6 PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('2001:db8::c0a8:16b', 0), ('2001:db8::41a8:14b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = '2001:db8::c0a8:16b'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '2001:db8::41a8:14b'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +2001:db8::c0a8:16b 1 +2001:db8::41a8:14b 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; +# +# INET4 +# +connection node_1; +CREATE TABLE t (id INET4 PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('192.168.1.107', 0), ('65.168.1.75', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = '192.168.1.107'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '65.168.1.75'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +192.168.1.107 1 +65.168.1.75 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; diff --git a/mysql-test/suite/galera/t/MDEV-41012-collision.inc b/mysql-test/suite/galera/t/MDEV-41012-collision.inc new file mode 100644 index 0000000000000..4922009de677a --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012-collision.inc @@ -0,0 +1,67 @@ +# +# Body of galera.MDEV-41012-collision, parameterized by the data type. +# +# $fbt_type - data type of the primary key column +# $fbt_value1 - a literal of that type +# $fbt_value2 - a *different* literal whose binary representation folds to +# the same latin1_swedish_ci weight string as $fbt_value1 +# + +--connection node_1 +--eval CREATE TABLE t (id $fbt_type PRIMARY KEY, v INT) ENGINE=InnoDB +--eval INSERT INTO t VALUES ($fbt_value1, 0), ($fbt_value2, 0) + +--connection node_2 +SET SESSION wsrep_sync_wait = 0; +--let $wait_condition = SELECT COUNT(*) = 2 FROM t +--source include/wait_condition.inc + +SET GLOBAL wsrep_slave_threads = 2; + +# Park the applier of the first UPDATE on entry to the apply monitor. +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--eval UPDATE t SET v = 1 WHERE id = $fbt_value1 + +--connection node_2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_wait_sync_point.inc + +# Disarm, so the second applier runs into the real dependency check. +--source include/galera_clear_sync_point.inc + +--let $apply_waits = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits'` + +--connection node_1 +--eval UPDATE t SET v = 2 WHERE id = $fbt_value2 + +--connection node_2 +# The two statements modify two *different* rows, so their +# certification keys must differ, there must be no dependency between the two +# writesets, and the second applier must be free to apply its row while the +# first one is still parked. +--let $wait_condition = SELECT (SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits') > $apply_waits OR EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0) +--source include/wait_condition.inc + +# While the key was collated with latin1_swedish_ci the two keys were equal, +# the second writeset was made to depend on the first, and both of these +# reported 0. +--disable_query_log +--eval SELECT VARIABLE_VALUE = $apply_waits AS no_false_dependency FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits' +--enable_query_log +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT SUM(v) = 3 FROM t +--source include/wait_condition.inc +SELECT id, v FROM t ORDER BY v; + +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; + +--connection node_1 +DROP TABLE t; diff --git a/mysql-test/suite/galera/t/MDEV-41012-collision.test b/mysql-test/suite/galera/t/MDEV-41012-collision.test new file mode 100644 index 0000000000000..f9bfba89e4530 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012-collision.test @@ -0,0 +1,64 @@ +# +# MDEV-41012: certification key collision between distinct values of a fixed +# binary data type (UUID, INET6, INET4). +# +# Testing key collisions with data types on Field_fbt: UUID, INET6 and INET4, +# where different keys could potentially generate same certification keys. +# i.e. a scenario where two transactions modifying two *different* +# rows certified as a conflict and the later one was aborted with a spurious +# ER_LOCK_DEADLOCK. +# +# A shared key also gives the two writesets a false dependency on each other, +# which serializes applying that should have run in parallel. The test detects +# the collision that way, because it can be driven deterministically. Whether +# the spurious ER_LOCK_DEADLOCK is observed instead depends on whether the +# first writeset has already been applied by the time the second transaction +# replicates, which is a race. +# +# This needs no foreign key: it is the row key path, +# wsrep_store_key_val_for_row(), alone. +# +# This is the second half of MDEV-41012. The other half, a key *mismatch* for +# one and the same row, is covered by galera.MDEV-41012. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/galera_have_debug_sync.inc + +--echo # +--echo # Each pair below is two distinct values whose bytes fold to the same +--echo # latin1_swedish_ci weight string (0xC0->0x41, 0x6A->0x4A, 0x6B->0x4B, +--echo # 0xD2->0x4F, 0x7A->0x5A). A UUID is stored as a byte permutation of the +--echo # value shown, which does not affect a byte-wise folding. +--echo # +SELECT WEIGHT_STRING(_latin1 0xC0A8016B9E4D4F7AB3D26A1E8F9C0D1B) = + WEIGHT_STRING(_latin1 0x41A8014B9E4D4F5AB34F4A1E8F9C0D1B) AS uuid_pair_collides, + WEIGHT_STRING(_latin1 0x20010DB80000000000000000C0A8016B) = + WEIGHT_STRING(_latin1 0x20010DB8000000000000000041A8014B) AS inet6_pair_collides, + WEIGHT_STRING(_latin1 0xC0A8016B) = + WEIGHT_STRING(_latin1 0x41A8014B) AS inet4_pair_collides; + +--echo # +--echo # UUID +--echo # +--let $fbt_type = UUID +--let $fbt_value1 = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b' +--let $fbt_value2 = '41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b' +--source MDEV-41012-collision.inc + +--echo # +--echo # INET6 +--echo # +--let $fbt_type = INET6 +--let $fbt_value1 = '2001:db8::c0a8:16b' +--let $fbt_value2 = '2001:db8::41a8:14b' +--source MDEV-41012-collision.inc + +--echo # +--echo # INET4 +--echo # +--let $fbt_type = INET4 +--let $fbt_value1 = '192.168.1.107' +--let $fbt_value2 = '65.168.1.75' +--source MDEV-41012-collision.inc From 33b4297e26fe14d654003e51d2df68059a8b9484 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Fri, 4 Sep 2026 15:31:21 +0300 Subject: [PATCH 3/4] MDEV-41012 Galera appliers hang with foreign key of types UUID, INET4, INET6 wsrep_store_key_val_for_row() built the certification key of a row by collating the column value whenever the field reports MYSQL_TYPE_STRING or MYSQL_TYPE_VAR_STRING, taking the collation from Field::charset(). The data types implemented on Field_fbt - UUID, INET6 and INET4 report MYSQL_TYPE_STRING, and their charset() is my_charset_numeric, which is latin1. Their values are however plain binary and accordingly get_innobase_type_from_mysql_type() maps them to DATA_FIXBINARY. Their keys were therefore run through latin1_swedish_ci, which folds them. That corrupts the key in two ways: 1. A key mismatch for one and the same row. The reference key that wsrep_rec_get_foreign_key() appends for the parent of a child INSERT is built from the InnoDB record and is not collated, so it no longer matched the primary key carried by the parent row's own writeset. Certification saw no dependency between a child INSERT and a concurrent parent UPDATE, and two appliers could apply them in parallel causing a hang or crash. 2. A key collision between distinct rows. The folding is many to one, so different values collapse onto one key, Certification compares keys byte for byte, so unrelated rows were treated as the same row. Concurrent transactions on them certified as a conflict and one was aborted with ER_LOCK_DEADLOCK. Fix is for wsrep_store_key_val_for_row() to skip the collation for fields that InnoDB stores as binary, using the same condition as get_innobase_type_from_mysql_type(). This is a no-op for the types that worked before. --- storage/innobase/handler/ha_innodb.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index e06ee34828398..e9158347102f1 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6867,10 +6867,21 @@ wsrep_store_key_val_for_row( /* Character set for the field is defined only to fields whose type is string and real field type is not enum or set. For these fields check - if character set is multi byte. */ - + if character set is multi byte. + + Fields that InnoDB stores as DATA_BINARY, + DATA_FIXBINARY or DATA_BLOB must not be collated + here: their bytes are opaque. Note that data types + implemented on top of Field_fbt (UUID, INET6) report + type() == MYSQL_TYPE_STRING and charset() == + my_charset_numeric (i.e. latin1), while their values + are plain binary and InnoDB maps them to + DATA_FIXBINARY. The condition below mirrors + get_innobase_type_from_mysql_type() */ if (real_type != MYSQL_TYPE_ENUM && real_type != MYSQL_TYPE_SET + && !field->binary() + && field->key_type() != HA_KEYTYPE_BINARY && ( mysql_type == MYSQL_TYPE_VAR_STRING || mysql_type == MYSQL_TYPE_STRING)) { From eaa0a90ff5850671a8d3499ad28201a3af946b63 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Tue, 8 Sep 2026 15:34:55 +0300 Subject: [PATCH 4/4] MDEV-41012 Galera appliers hang with foreign key of types UUID, INET4, INET6 Bumped application protocol version to level 5 --- sql/wsrep_mysqld.cc | 4 +++- sql/wsrep_server_service.cc | 9 ++++++++- storage/innobase/handler/ha_innodb.cc | 22 ++++++++++++++++------ storage/innobase/rem/rem0rec.cc | 25 ++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index bd25aef86967b..26a6dd38078b3 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -121,7 +121,7 @@ my_bool wsrep_incremental_data_collection= 0; // Incremental data collection bool wsrep_new_cluster= false; // Bootstrap the cluster? int wsrep_slave_count_change= 0; // No. of appliers to stop/start int wsrep_to_isolation= 0; // No. of active TO isolation threads -long wsrep_max_protocol_version= 4; // Maximum protocol version to use +long wsrep_max_protocol_version= 5; // Maximum protocol version to use long int wsrep_protocol_version= wsrep_max_protocol_version; ulong wsrep_trx_fragment_unit= WSREP_FRAG_BYTES; // unit for fragment size @@ -2001,6 +2001,7 @@ static bool wsrep_prepare_key_for_isolation(const char* db, case 2: case 3: case 4: + case 5: { *key_len= 0; if (db) @@ -2181,6 +2182,7 @@ bool wsrep_prepare_key(const uchar* cache_key, size_t cache_key_len, case 2: case 3: case 4: + case 5: { key[0].ptr= cache_key; key[0].len= strlen( (char*)cache_key ); diff --git a/sql/wsrep_server_service.cc b/sql/wsrep_server_service.cc index 0ede84e4cf98e..ca8c01e71d284 100644 --- a/sql/wsrep_server_service.cc +++ b/sql/wsrep_server_service.cc @@ -228,11 +228,18 @@ void Wsrep_server_service::log_view( static_cast(high_priority_service); /* Update global system variables */ mysql_mutex_lock(&LOCK_global_system_variables); + /* The negotiated protocol version gates the writeset format, so it must be + picked up regardless of wsrep_auto_increment_control. Leaving it at this + node's own maximum would make a node in a cluster that settled on an older + version keep emitting the newer format. */ + if (view.own_index() >= 0) + { + wsrep_protocol_version= view.protocol_version(); + } if (wsrep_auto_increment_control && view.own_index() >= 0) { global_system_variables.auto_increment_offset= view.own_index() + 1; global_system_variables.auto_increment_increment= view.members().size(); - wsrep_protocol_version= view.protocol_version(); } mysql_mutex_unlock(&LOCK_global_system_variables); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index e9158347102f1..eaad5ce90192c 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6872,16 +6872,26 @@ wsrep_store_key_val_for_row( Fields that InnoDB stores as DATA_BINARY, DATA_FIXBINARY or DATA_BLOB must not be collated here: their bytes are opaque. Note that data types - implemented on top of Field_fbt (UUID, INET6) report - type() == MYSQL_TYPE_STRING and charset() == + implemented on top of Field_fbt (UUID, INET6, INET4) + report type() == MYSQL_TYPE_STRING and charset() == my_charset_numeric (i.e. latin1), while their values are plain binary and InnoDB maps them to - DATA_FIXBINARY. The condition below mirrors - get_innobase_type_from_mysql_type() */ + DATA_FIXBINARY. The binary test below mirrors + get_innobase_type_from_mysql_type(). + + Skipping the collation changes the key bytes on the + wire, so it is gated on protocol version 5. Below + that version the value is still collated, and + wsrep_rec_get_foreign_key() collates the matching + reference key the same way, so the two agree in a + cluster that has not fully upgraded yet. + See MDEV-41012. */ if (real_type != MYSQL_TYPE_ENUM && real_type != MYSQL_TYPE_SET - && !field->binary() - && field->key_type() != HA_KEYTYPE_BINARY + && (wsrep_protocol_version < 5 + || (!field->binary() + && field->key_type() + != HA_KEYTYPE_BINARY)) && ( mysql_type == MYSQL_TYPE_VAR_STRING || mysql_type == MYSQL_TYPE_STRING)) { diff --git a/storage/innobase/rem/rem0rec.cc b/storage/innobase/rem/rem0rec.cc index 6480c0358ed35..f65414a4a3096 100644 --- a/storage/innobase/rem/rem0rec.cc +++ b/storage/innobase/rem/rem0rec.cc @@ -2670,6 +2670,7 @@ rec_offs_make_nth_extern( } #ifdef WITH_WSREP # include "ha_prototypes.h" +# include /* wsrep_protocol_version */ int wsrep_rec_get_foreign_key( @@ -2770,9 +2771,31 @@ wsrep_rec_get_foreign_key( dtype_get_charset_coll(col_f->prtype), data, buf, len, *buf_len); break; + case DATA_FIXBINARY: + if (wsrep_protocol_version < 5 + && !(col_f->prtype + & DATA_BINARY_TYPE)) { + /* A UUID, INET6 or INET4 column is + stored as fixed length binary, but + below protocol version 5 + wsrep_store_key_val_for_row() still + collates the row key of the parent + with the character set of the field. + Collate the reference key the same + way, so that the two match in a + cluster that has not fully upgraded + yet. See MDEV-41012. */ + len = wsrep_normalize_string( + (int)(col_f->prtype + & DATA_MYSQL_TYPE_MASK), + dtype_get_charset_coll( + col_f->prtype), + data, buf, len, *buf_len); + break; + } + /* fall through */ case DATA_BLOB: case DATA_BINARY: - case DATA_FIXBINARY: case DATA_GEOMETRY: memcpy(buf, data, len); break;