Skip to content

Collection of fixes for macOS - #5654

Open
DaveGosselin-MariaDB wants to merge 11 commits into
11.4from
11.4-macos-testing-fixes
Open

Collection of fixes for macOS#5654
DaveGosselin-MariaDB wants to merge 11 commits into
11.4from
11.4-macos-testing-fixes

Conversation

@DaveGosselin-MariaDB

Copy link
Copy Markdown
Member

Each commit in this collection fixes a test failure specific to macOS.

Three commits, "MDEV-33616: Charge and credit the same size for the recovery buffer", "MDEV-33616: Only one of two routines named in a statement is found", and "MDEV-33616: Routines of a mixed case database are not listed" change the server code.

Each commit has its own explanation in its commit message.

DaveGosselin-MariaDB and others added 11 commits September 9, 2026 14:45
At lower_case_table_names=2 this returns nothing.

  CREATE DATABASE Db1;
  CREATE FUNCTION Db1.f1(a INT) RETURNS INT RETURN a;
  SELECT ROUTINE_NAME FROM information_schema.ROUTINES
   WHERE ROUTINE_SCHEMA='Db1';

mysql.proc records the function's database as db1, in lower case.
Creating a routine lower-cases its database name whenever
lower_case_table_names is anything but 0, at sql/sp_head.h:121.  The
datadir, SCHEMATA and DATABASE() all keep Db1.

CALL Db1.f1() still works, because calling a routine lower-cases the
database name too and then searches mysql.proc for db1.  The query
above never lower-cases it.  It searches for Db1, and mysql.proc.db
collates utf8mb3_bin, so the comparison runs byte for byte and no row
matches.

At setting 1 the server lower-cases the filter value as well, at
sql/sql_show.cc:4394, and lower-cases every name it stores, so the
query and the table always agree.  Setting 2 lower-cases the routine's
copy and nothing else.

The fix lower-cases the filter value before the search.

Sorting the same query brings the row back.

  SELECT ROUTINE_NAME FROM information_schema.ROUTINES
   WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME;

The sort keeps the filter from reaching that search.  The server reads
all of mysql.proc instead, then applies the WHERE to ROUTINE_SCHEMA,
which compares case insensitively.  That shape answered correctly all
along.

The same search fills PARAMETERS and backs SHOW FUNCTION STATUS, SHOW
PROCEDURE STATUS, SHOW PACKAGE STATUS and SHOW PACKAGE BODY STATUS.
Every one returned nothing for Db1.  mariadb-dump lists routines with
SHOW FUNCTION STATUS WHERE Db=..., at client/mysqldump.cc:2859, which
is the main.mysqldump failure.

Setting 0 keeps Db1 and db1 as two databases holding two routines.  A
case sensitive volume confirms both stay distinct before and after this
change.  beb9a54 (MDEV-20609) added the search in 10.11.1.
main.lowercase_routines runs both query shapes.
With lower_case_table_names 0 the server can have databases Db1 and db1,
each with a function f1.  A single statement naming both databases, like
SELECT Db1.f1(), db1.f1(), reported that db1.f1 does not exist.

The set of routines a statement uses compared its entries without regard
to case.  Only one routine was loaded but the reference to the other
found nothing.  The set now compares its entries exactly, as the routine
cache and the lock manager already do.
macOS declares select() in sys/select.h, which the HAVE_SELECT probe did
not include.  clang rejects a call to an undeclared function, so the
probe failed and HAVE_SELECT was left undefined.

my_sleep() then took its last fallback, a busy loop on time() that
rounds the requested interval up to a whole second.  Every sub-second
sleep in the server became a one second spin on a CPU, which is what
made rpl.rpl_perfschema_applier_status_by_worker,
rpl.rpl_shutdown_sighup and rpl.rpl_semi_sync_shutdown_await_ack fail.
macOS puts the data directory on a case insensitive file system, so
lower_case_table_names is 2 and both tests recorded an answer that only
holds for 0.

period.i_s_notembedded looked up I_S.PERIODS and I_S.KEY_PERIOD_USAGE by
the schema name TEST.  That comparison follows the table name
comparison, so it finds the table under 1 and 2 and finds nothing under
0.  Those four queries move to the new test period.i_s_case_sensitive,
which requires lower_case_table_names=0.  The win rdiff of
period.i_s_notembedded covered the same difference and is no longer
needed.

atomic.drop_db_long_names generated table and view names in upper case
and compared the DROP statements that DDL recovery writes to the binary
log.  Under 2 the names come back from the directory in lower case.
Generating them in lower case to begin with gives the same names on
every setting.  Lower case also changes where the view name sorts
against its table name for the letters after v, which moves one view
between two of the recorded DROP VIEW statements.
Introduces a new MTR include, not_mac.inc, which when included at the
top of a test, prevents that test from running on macOS.

sys_vars.sysvars_readonly_debug is the first user.  It expects the
server to fault when a read only sysvar is written behind the sysvar
interface.  That protection needs the ro_after_init section, which a
linker script places and ld64 has no option to take, so
HAVE_RO_AFTER_INIT stays undefined on macOS.  Without it no variable is
moved into the read only root either, so neither of the two assignments
is refused.
Its default value depends on the operating system, ON where the log can
be memory mapped and OFF elsewhere, so the recorded row only holds on
some platforms.  The other variables whose default depends on the
operating system are already excluded the same way.
The test replaces the number of buffer pool blocks with a fixed value so
that the message is stable.  The pattern only accepted 5.., and macOS
builds without a futex use SUX_LOCK_GENERIC, which enlarges buf_block_t
enough to bring the count down into 4...
The injected deadlock reaches the client as ER_GET_ERRNO carrying errno
11, and the text comes from my_strerror().  11 is EAGAIN on Linux and
EDEADLK on macOS, so the message reads "Resource temporarily
unavailable" on one and "Resource deadlock avoided" on the other.
Replace the quoted text so the test does not depend on it.
The client reports why it could not load client_ed25519, and macOS names
every path that dlopen() tried.  Two expressions are added, one for the
chunk that holds the start of that message and one for the chunks that
continue it.

Whether the message arrives in one chunk or several depends on the
vardir, because the path appears four times in the dlopen text.  With
--vardir /Volumes/<repo>/var the line is 417 bytes and fits the 512 byte
buffer that --exec output is read in.  With the default vardir it does
not.

Both expressions stop at a newline.  reg_replace compiles with
REG_DOTALL, so an unrestricted .* runs past the line terminator whenever
the whole message reaches the replacement in one chunk, and the error
line then joins the line after it.
main.large_pages fails on macOS with "Warning: Memory not freed: 16375"
at shutdown and no accompanying safemalloc report.  The residual stays
at 16375 whether innodb_buffer_pool_size is 8M or 128M, and dropping
--large-pages makes it go away.

recv_sys_t::find_checkpoint() asks for tmp_buf_size, which is
MTR_SIZE_MAX + 9, or 1048585 bytes.  my_large_malloc() rounds that up to
a multiple of the large page size and charges the rounded figure to
global_memory_used, while recv_sys_t::tmp_free() credits back the
1048585 that was requested.  The page size here is 16384, 1048585 rounds
up to 1064960, and the difference is the 16375 reported.  The caller
cannot see the rounded figure because ut_malloc_dontdump() takes the
size by value and, with a null ut_new_pfx_t, has nowhere to report what
it allocated.  ut_malloc_dontdump_size() writes the size back, and
recv_sys_t keeps it in tmp_buf_alloc_size for the free.  tmp_buf_size
remains the capacity that parse() asserts against.

Only macOS rounds up.  my_get_large_page_sizes() has no huge page
interface to consult there, so its fallback branch reports the ordinary
page size as the only large page size and the plain mmap() always
succeeds.  On Linux the candidate is 2 MiB, the MAP_HUGETLB mapping
fails with ENOMEM when no huge pages are reserved, and the retry loop
settles on large_page_size == 0, which records the request unrounded.
No memory was lost either way, since munmap() rounds its length up to a
whole page.  The counter was wrong, and the counter is what MTR checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
innodb.log_upgrade and innodb.log_upgrade_101_flags build 8GB redo log
files by seeking past the end of an empty file and writing a single
byte.  That needs a filesystem which leaves the skipped range
unallocated.  HFS on macOS allocates every block of it instead, so the
write fails with ENOSPC and the test reports a perl failure.

include/have_sparse_files.inc probes the vardir by writing one byte 64MB
into an empty file and comparing the allocated block count against that
offset.  The offset stays above 16MB since APFS allocates the whole
range for a file smaller than that rather than recording a hole.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

1 participant