1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

PQ tidy up message queue processing, add in missing tests, fix...

in pwt_worker_base::init_worker_thd()
This commit is contained in:
Rex Johnston
2026-08-27 09:03:00 +12:00
parent b99913fa68
commit 0f0d7b24c1
8 changed files with 473 additions and 41 deletions

View File

@@ -75,6 +75,8 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/sql_parallel_workers.cc
../sql/sql_parallel_execution.cc
../sql/sql_parallel_costs.cc
../sql/sql_parallel_transport.cc
../sql/sql_parallel_thread.cc
../sql/opt_rewrite_date_cmp.cc
../sql/opt_rewrite_remove_casefold.cc
../sql/opt_sargable_left.cc

View File

@@ -0,0 +1,58 @@
SET @save_trace= @@optimizer_trace;
SET optimizer_trace='enabled=on';
SET SESSION parallel_worker_threads=4;
CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB;
INSERT INTO t1 SELECT seq, seq*10 FROM seq_1_to_2000;
#
# A plan that keeps its parallel scan reports both keys and abandons
# nothing (expect 1 1 0)
#
EXPLAIN SELECT a, b FROM t1 WHERE b > 19980;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL_parallel NULL NULL NULL NULL 2000 Using where
SELECT a, b FROM t1 WHERE b > 19980;
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
candidate chosen abandoned
1 1 0
# and it is a table that really is scanned in the workers (expect 1)
FLUSH STATUS;
SELECT a, b FROM t1 WHERE b > 19980;
ran_in_the_workers
1
#
# The gate examines the driving table, so a query it declines is not a
# candidate in the first place and names no table at all (expect 0 0 0).
# ORDER BY is declined by the query-shape half of the gate, and the
# driving table is still a plain full scan
#
EXPLAIN SELECT a, b FROM t1 WHERE b > 19980 ORDER BY b;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 2000 Using where; Using filesort
SELECT a, b FROM t1 WHERE b > 19980 ORDER BY b;
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
candidate chosen abandoned
0 0 0
# and a covering index read by the driving-table half: the rows arrive in
# key order from an index the engine is not asked to divide (expect 0 0 0)
#
CREATE TABLE t2 (a INT, b INT, KEY(a)) ENGINE=InnoDB;
INSERT INTO t2 SELECT seq, seq*10 FROM seq_1_to_2000;
EXPLAIN SELECT a FROM t2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 index NULL a 5 NULL 2000 Using index
SELECT a FROM t2;
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
candidate chosen abandoned
0 0 0
DROP TABLE t1, t2;
SET SESSION parallel_worker_threads=default;
SET optimizer_trace= @save_trace;

View File

@@ -0,0 +1,80 @@
#
# MDEV-39492 Parallel Query: what the optimizer trace says became of the
# parallel scan.
#
# The gate in make_join_readinfo() records the table it picks as
# "parallel_scan_candidate", and JOIN::optimize_stage2() reports what became of
# it once the plan has stopped changing: "chosen_for_parallel_scan" if the
# access path can still be handed out in chunks, "parallel_scan_abandoned" and
# "parallel_scan_abandoned_because" if the plan took it away. So
# chosen_for_parallel_scan names only tables that really are scanned in
# parallel, and a query that is never a candidate says neither.
#
--source include/have_innodb.inc
--source include/not_embedded.inc
--source include/have_sequence.inc
--disable_service_connection
SET @save_trace= @@optimizer_trace;
SET optimizer_trace='enabled=on';
SET SESSION parallel_worker_threads=4;
CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB;
INSERT INTO t1 SELECT seq, seq*10 FROM seq_1_to_2000;
--echo #
--echo # A plan that keeps its parallel scan reports both keys and abandons
--echo # nothing (expect 1 1 0)
--echo #
EXPLAIN SELECT a, b FROM t1 WHERE b > 19980;
--disable_result_log
SELECT a, b FROM t1 WHERE b > 19980;
--enable_result_log
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
--echo # and it is a table that really is scanned in the workers (expect 1)
FLUSH STATUS;
--disable_result_log
SELECT a, b FROM t1 WHERE b > 19980;
--enable_result_log
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers
--enable_query_log
--echo #
--echo # The gate examines the driving table, so a query it declines is not a
--echo # candidate in the first place and names no table at all (expect 0 0 0).
--echo # ORDER BY is declined by the query-shape half of the gate, and the
--echo # driving table is still a plain full scan
--echo #
EXPLAIN SELECT a, b FROM t1 WHERE b > 19980 ORDER BY b;
--disable_result_log
SELECT a, b FROM t1 WHERE b > 19980 ORDER BY b;
--enable_result_log
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
--echo # and a covering index read by the driving-table half: the rows arrive in
--echo # key order from an index the engine is not asked to divide (expect 0 0 0)
--echo #
CREATE TABLE t2 (a INT, b INT, KEY(a)) ENGINE=InnoDB;
INSERT INTO t2 SELECT seq, seq*10 FROM seq_1_to_2000;
EXPLAIN SELECT a FROM t2;
--disable_result_log
SELECT a FROM t2;
--enable_result_log
SELECT JSON_EXTRACT(trace,'$**.parallel_scan_candidate') IS NOT NULL AS candidate,
JSON_EXTRACT(trace,'$**.chosen_for_parallel_scan') IS NOT NULL AS chosen,
JSON_EXTRACT(trace,'$**.parallel_scan_abandoned') IS NOT NULL AS abandoned
FROM information_schema.optimizer_trace;
DROP TABLE t1, t2;
SET SESSION parallel_worker_threads=default;
SET optimizer_trace= @save_trace;

View File

@@ -0,0 +1,135 @@
set @save_dbug=@@global.debug_dbug;
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c VARCHAR(20)) ENGINE=InnoDB;
INSERT INTO t1 SELECT seq, seq MOD 7, CONCAT('row', seq MOD 5)
FROM seq_1_to_5000;
CREATE TABLE t2 (k INT PRIMARY KEY, v INT) ENGINE=InnoDB;
INSERT INTO t2 SELECT seq, seq*2 FROM seq_1_to_5000;
SET SESSION parallel_worker_threads=4;
#
# A worker writing its rows into a container is a temporary-table write,
# which the session can see once the workers are reaped. So the transport
# in use is visible: rows through tmp tables, none through batches.
#
# tmp-table transport (expect 1 1)
FLUSH STATUS;
SELECT a, b, c FROM t1 WHERE a > 4995;
a b c
4996 5 row1
4997 6 row2
4998 0 row3
4999 1 row4
5000 2 row0
ran_in_the_workers rows_went_through_tmp_tables
1 1
# batch transport, same rows (expect 1 1)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
SELECT a, b, c FROM t1 WHERE a > 4995;
a b c
4996 5 row1
4997 6 row2
4998 0 row3
4999 1 row4
5000 2 row0
ran_in_the_workers no_tmp_table_rows
1 1
set global debug_dbug=@save_dbug;
#
# A container is a temporary table, and a temporary table given a unique
# key would silently collapse repeated rows. t3 has no unique column and
# the query reads only its two low-cardinality ones, so all 2000 rows a
# worker stores are duplicates of each other bar six. Every one of them
# has to come back.
#
CREATE TABLE t3 (b INT, c VARCHAR(20)) ENGINE=InnoDB;
INSERT INTO t3 SELECT seq MOD 3, CONCAT('v', seq MOD 2) FROM seq_1_to_2000;
# tmp-table transport (expect 1 1)
FLUSH STATUS;
SELECT b, c FROM t3;
ran_in_the_workers every_duplicate_arrived
1 1
# batch transport (expect 1 1)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
SELECT b, c FROM t3;
ran_in_the_workers every_duplicate_arrived
1 1
set global debug_dbug=@save_dbug;
#
# And over a join, the two transports agree row for row: the two blocks
# below are the same query and must record identically.
#
# tmp-table transport
SELECT t1.a, t1.c, t2.v FROM t1, t2
WHERE t1.a = t2.k AND t1.b = 3 AND t1.a > 4900;
a c v
4903 row3 9806
4910 row0 9820
4917 row2 9834
4924 row4 9848
4931 row1 9862
4938 row3 9876
4945 row0 9890
4952 row2 9904
4959 row4 9918
4966 row1 9932
4973 row3 9946
4980 row0 9960
4987 row2 9974
4994 row4 9988
ran_in_the_workers
1
# batch transport
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
SELECT t1.a, t1.c, t2.v FROM t1, t2
WHERE t1.a = t2.k AND t1.b = 3 AND t1.a > 4900;
a c v
4903 row3 9806
4910 row0 9820
4917 row2 9834
4924 row4 9848
4931 row1 9862
4938 row3 9876
4945 row0 9890
4952 row2 9904
4959 row4 9918
4966 row1 9932
4973 row3 9946
4980 row0 9960
4987 row2 9974
4994 row4 9988
ran_in_the_workers
1
set global debug_dbug=@save_dbug;
#
# Known limitation, pinned here so it is not discovered by surprise: a
# worker's container is a heap table, nothing converts it to disk when it
# fills, and so a result set bigger than the session's temporary-table
# limit fails the statement. The serial plan for this query uses no
# temporary table at all, and neither does the batch transport, so this
# is the transport's own boundary and not the query's.
#
CREATE TABLE t4 (a INT PRIMARY KEY, b VARCHAR(200)) ENGINE=InnoDB;
INSERT INTO t4 SELECT seq, REPEAT('x',200) FROM seq_1_to_20000;
SET SESSION tmp_memory_table_size=65536;
SET SESSION max_heap_table_size=65536;
# tmp-table transport: 20000 rows of 200 bytes will not fit
SELECT a, b FROM t4;
ERROR HY000: Internal error: parallel query: the worker's result table is full (spilling a worker result set to disk is not implemented)
# batch transport keeps nothing, so it answers (expect 20000)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
SELECT a, b FROM t4;
rows_sent
20000
set global debug_dbug=@save_dbug;
# and the server is unharmed: the same query fits once the limit does
SET SESSION tmp_memory_table_size=default;
SET SESSION max_heap_table_size=default;
FLUSH STATUS;
SELECT a, b FROM t1;
ran_in_the_workers all_rows
1 1
DROP TABLE t1, t2, t3, t4;
SET SESSION parallel_worker_threads=default;

View File

@@ -0,0 +1,172 @@
#
# MDEV-39492 Parallel Query: the result-row transport.
#
# A worker's finished rows reach the manager through one of two transports
# (sql_parallel_transport.h): the temporary-table one, where a worker
# materialises its result set into its own container and the manager reads
# those back, or the batch one, where rows travel as record images through a
# buffer the worker blocks on. The temporary-table transport is what a query
# gets; the batch one is reachable under a debug flag while the two are
# compared, so that the code kept for that comparison stays exercised.
#
# Everything here has to be a plain SELECT that really runs in the workers:
# the gate declines aggregates and a derived table is materialised serially, so
# a COUNT(*) over the query under test would measure nothing at all. Row counts
# are read from Rows_sent for the same reason, and the statements they are read
# for run with the prepared-statement protocol disabled: it executes a statement
# twice and the counter would then read double.
#
--source include/have_innodb.inc
--source include/not_embedded.inc
--source include/have_sequence.inc
--source include/have_debug.inc
--disable_service_connection
set @save_dbug=@@global.debug_dbug;
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c VARCHAR(20)) ENGINE=InnoDB;
INSERT INTO t1 SELECT seq, seq MOD 7, CONCAT('row', seq MOD 5)
FROM seq_1_to_5000;
CREATE TABLE t2 (k INT PRIMARY KEY, v INT) ENGINE=InnoDB;
INSERT INTO t2 SELECT seq, seq*2 FROM seq_1_to_5000;
SET SESSION parallel_worker_threads=4;
--echo #
--echo # A worker writing its rows into a container is a temporary-table write,
--echo # which the session can see once the workers are reaped. So the transport
--echo # in use is visible: rows through tmp tables, none through batches.
--echo #
--echo # tmp-table transport (expect 1 1)
FLUSH STATUS;
--sorted_result
SELECT a, b, c FROM t1 WHERE a > 4995;
--let $tw= query_get_value(SHOW SESSION STATUS LIKE 'Handler_tmp_write', Value, 1)
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers, $tw > 0 AS rows_went_through_tmp_tables
--enable_query_log
--echo # batch transport, same rows (expect 1 1)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
--sorted_result
SELECT a, b, c FROM t1 WHERE a > 4995;
--let $tw= query_get_value(SHOW SESSION STATUS LIKE 'Handler_tmp_write', Value, 1)
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers, $tw = 0 AS no_tmp_table_rows
--enable_query_log
set global debug_dbug=@save_dbug;
--echo #
--echo # A container is a temporary table, and a temporary table given a unique
--echo # key would silently collapse repeated rows. t3 has no unique column and
--echo # the query reads only its two low-cardinality ones, so all 2000 rows a
--echo # worker stores are duplicates of each other bar six. Every one of them
--echo # has to come back.
--echo #
CREATE TABLE t3 (b INT, c VARCHAR(20)) ENGINE=InnoDB;
INSERT INTO t3 SELECT seq MOD 3, CONCAT('v', seq MOD 2) FROM seq_1_to_2000;
--echo # tmp-table transport (expect 1 1)
FLUSH STATUS;
--disable_ps_protocol
--disable_result_log
SELECT b, c FROM t3;
--enable_result_log
--enable_ps_protocol
--let $rs= query_get_value(SHOW SESSION STATUS LIKE 'Rows_sent', Value, 1)
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers, $rs = 2000 AS every_duplicate_arrived
--enable_query_log
--echo # batch transport (expect 1 1)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
--disable_ps_protocol
--disable_result_log
SELECT b, c FROM t3;
--enable_result_log
--enable_ps_protocol
--let $rs= query_get_value(SHOW SESSION STATUS LIKE 'Rows_sent', Value, 1)
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers, $rs = 2000 AS every_duplicate_arrived
--enable_query_log
set global debug_dbug=@save_dbug;
--echo #
--echo # And over a join, the two transports agree row for row: the two blocks
--echo # below are the same query and must record identically.
--echo #
--echo # tmp-table transport
--sorted_result
SELECT t1.a, t1.c, t2.v FROM t1, t2
WHERE t1.a = t2.k AND t1.b = 3 AND t1.a > 4900;
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers
--enable_query_log
--echo # batch transport
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
--sorted_result
SELECT t1.a, t1.c, t2.v FROM t1, t2
WHERE t1.a = t2.k AND t1.b = 3 AND t1.a > 4900;
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers
--enable_query_log
set global debug_dbug=@save_dbug;
--echo #
--echo # Known limitation, pinned here so it is not discovered by surprise: a
--echo # worker's container is a heap table, nothing converts it to disk when it
--echo # fills, and so a result set bigger than the session's temporary-table
--echo # limit fails the statement. The serial plan for this query uses no
--echo # temporary table at all, and neither does the batch transport, so this
--echo # is the transport's own boundary and not the query's.
--echo #
CREATE TABLE t4 (a INT PRIMARY KEY, b VARCHAR(200)) ENGINE=InnoDB;
INSERT INTO t4 SELECT seq, REPEAT('x',200) FROM seq_1_to_20000;
SET SESSION tmp_memory_table_size=65536;
SET SESSION max_heap_table_size=65536;
--echo # tmp-table transport: 20000 rows of 200 bytes will not fit
--error ER_INTERNAL_ERROR
SELECT a, b FROM t4;
--echo # batch transport keeps nothing, so it answers (expect 20000)
set global debug_dbug="+d,pwt_batch_transport";
FLUSH STATUS;
--disable_ps_protocol
--disable_result_log
SELECT a, b FROM t4;
--enable_result_log
--enable_ps_protocol
--let $rs= query_get_value(SHOW SESSION STATUS LIKE 'Rows_sent', Value, 1)
--disable_query_log
--eval SELECT $rs AS rows_sent
--enable_query_log
set global debug_dbug=@save_dbug;
--echo # and the server is unharmed: the same query fits once the limit does
SET SESSION tmp_memory_table_size=default;
SET SESSION max_heap_table_size=default;
FLUSH STATUS;
--disable_ps_protocol
--disable_result_log
SELECT a, b FROM t1;
--enable_result_log
--enable_ps_protocol
--let $rs= query_get_value(SHOW SESSION STATUS LIKE 'Rows_sent', Value, 1)
--let $pq= query_get_value(SHOW SESSION STATUS LIKE 'Parallel_queries_executed', Value, 1)
--disable_query_log
--eval SELECT $pq > 0 AS ran_in_the_workers, $rs = 5000 AS all_rows
--enable_query_log
DROP TABLE t1, t2, t3, t4;
SET SESSION parallel_worker_threads=default;

View File

@@ -243,7 +243,7 @@ void pwt_worker_base::init_and_run_thread_func()
mysql_mutex_lock(&thd->LOCK_thd_kill);
mysql_mutex_unlock(&thd->LOCK_thd_kill);
thd->pop_internal_handler(); // maybe not needed
//
/*
Null it while we still hold LOCK_worker, and tear the THD down from a local
copy afterwards. abort_worker() reads this member under the same lock and
@@ -268,10 +268,12 @@ void pwt_worker_base::init_and_run_thread_func()
destroy_background_thd(worker_thd);
}
/**
@brief
Empty warning/error queue from workers into our manager thread
void pwt_manager_base::process_pending_warnings()
{
/*
@description
Surface errors/warnings the workers queued via PWT_error_handler. A worker
error that mattered to the result has already aborted the join during
execution (fatal_error or a propagated kill), so thd is already in error by
@@ -279,7 +281,13 @@ void pwt_manager_base::process_pending_warnings()
status" assertion in the diagnostics area. So only raise a queued ERROR
when thd is not already in error -- otherwise keep it as a warning. Plain
warnings are always safe to add.
*/
@parameters
skip_interrupted whether or not to ignore a deluge of interrupted
errors
*/
void pwt_manager_base::process_pending_warnings(bool skip_interrupted)
{
bool surface_drop;
mysql_mutex_lock(&LOCK_pwt_manager);
surface_drop= messages_dropped;
@@ -289,12 +297,16 @@ void pwt_manager_base::process_pending_warnings()
{
if (pwt_error_message *err= event->error)
{
if (err->level == Sql_condition::enum_warning_level::WARN_LEVEL_ERROR &&
!thd->is_error())
my_message_sql(err->code, err->message, MYF(0));
else
push_warning(thd, Sql_condition::WARN_LEVEL_WARN, err->code,
// if called during initialization, skip processing
if (!skip_interrupted || err->code != ER_QUERY_INTERRUPTED)
{
if (err->level == Sql_condition::enum_warning_level::WARN_LEVEL_ERROR &&
!thd->is_error())
my_message_sql(err->code, err->message, MYF(0));
else
push_warning(thd, Sql_condition::WARN_LEVEL_WARN, err->code,
err->message);
}
my_free(err->message);
my_free(err);
}
@@ -336,6 +348,7 @@ bool pwt_worker_base::init_worker_thd(pwt_manager_base *manager_arg, THD *parent
thd= create_background_thd();
if (!thd)
{
my_free(const_cast<char*>(new_db.str));
my_error(ER_INTERNAL_ERROR, MYF(0),
"init_parallel_workers: failed to create worker thread THD");
return true; // Failed
@@ -435,29 +448,3 @@ pwt_manager_base::~pwt_manager_base()
{
mysql_mutex_destroy(&LOCK_pwt_manager);
}
/**
@brief
Free our message queue, discard the messages
*/
void pwt_manager_base::discard_pending_warnings()
{
// process queue
if (!parallel_messages.head())
return;
mysql_mutex_lock(&LOCK_pwt_manager);
pwt_queued_event *event;
while ((event= parallel_messages.get()))
{
if (pwt_error_message *err= event->error)
{
my_free(err->message);
my_free(err);
}
my_free(event);
}
mysql_mutex_unlock(&LOCK_pwt_manager);
}

View File

@@ -41,9 +41,7 @@ public:
THD *thd; /* Manager thread */
//TODO: why does this exist, shouldn't we process them all?
void discard_pending_warnings(); // called on initialization failure
void process_pending_warnings(); // called at end of normal executio
void process_pending_warnings(bool skip_interrupted);
// These are used by class PWT_error_handler:
void record_event(pwt_queued_event *event)

View File

@@ -331,7 +331,6 @@ cleanup_old_workers:
mysql_mutex_unlock(&LOCK_data);
for (uint j= 0; j < i; j++)
workers[j].abort_worker();
discard_pending_warnings();
/*
Release the transport's hold on the containers before freeing them: a sink
may still be naming a worker THD that is now gone. Null for a worker whose
@@ -346,6 +345,7 @@ cleanup_old_workers:
delete[] workers;
workers= nullptr;
nworkers= 0;
process_pending_warnings(true);
mysql_cond_destroy(&COND_data_avail);
mysql_mutex_destroy(&LOCK_data);
file->parallel_end_coordinator();
@@ -487,7 +487,7 @@ void pwt_manager::finalize_parallel_workers(THD *thd, JOIN *join)
quiesce_workers(); // stop + join (no-op if already reaped)
exec.scan_tab->table->file->parallel_end_coordinator();
process_pending_warnings();
process_pending_warnings(false);
for (uint i= 0; i < nworkers; i++) // workers are joined; both ends idle
if (workers[i].sink)
workers[i].sink->cleanup();