Skip to content
/ server Public

MDEV-38494 .mariadb_history rename race condition#4699

Open
MooSayed1 wants to merge 1 commit intoMariaDB:10.11from
MooSayed1:MDEV-38494-history-race
Open

MDEV-38494 .mariadb_history rename race condition#4699
MooSayed1 wants to merge 1 commit intoMariaDB:10.11from
MooSayed1:MDEV-38494-history-race

Conversation

@MooSayed1
Copy link

MDEV-38494: .mariadb_history rename race condition

The Jira issue number for this PR is: MDEV-38494

Description

When multiple mariadb CLI sessions exit at the same time (e.g. multiple
terminal tabs), all sessions write their history to the same temp file path
~/.mariadb_history.TMP and then rename it. The second rename fails because
the first session already moved the file:

bin/mariadb: Error on rename of '~/.mariadb_history.TMP' to
             '~/.mariadb_history' (Errcode: 2 "No such file or directory")

The root cause is in client/mysql.cc. The temp filename is constructed as:

sprintf(histfile_tmp, "%s.TMP", histfile);

All concurrent sessions use the same .TMP path. When session A renames
it to the final history file, session B's subsequent rename fails with ENOENT
because the file is already gone.

The fix includes the process PID in the temp filename so each session has its
own unique rename target:

// Include PID in temp file name to prevent concurrent-session rename races.
sprintf(histfile_tmp, "%s.TMP%lu", histfile, (unsigned long) getpid());

Release Notes

mariadb CLI no longer shows Error on rename of '.mariadb_history.TMP'
when multiple sessions exit concurrently.

How can this PR be tested?

./mtr main.mariadb_history_race

The test pre-creates $HISTFILE.TMP as a sentinel, then runs an interactive
mariadb session via a Python PTY wrapper (so isatty() returns true and
history is actually written). With the fix, the client uses $HISTFILE.TMP<PID>
and the sentinel survives. Without the fix, the client overwrites and renames
the sentinel → test fails.

Basing the PR against the correct MariaDB version

This is a bug fix. The earliest affected branch is 10.11, so this PR targets
upstream/10.11.

PR quality check

  • I checked the CODING_STANDARDS.md file and my PR conforms to this where appropriate.
  • For any trivial modifications to the PR, I am ok with the reviewer making the changes themselves.

@MooSayed1
Copy link
Author

I used a Python helper script (mariadb_history_race.py) alongside the .test file to create a PTY session.
so using a .py helper file acceptable in the main directory?

@MooSayed1 MooSayed1 force-pushed the MDEV-38494-history-race branch from a1f4504 to f4d2ba2 Compare February 25, 2026 19:55
@gkodinov gkodinov added the External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. label Feb 26, 2026
@gkodinov
Copy link
Member

I used a Python helper script (mariadb_history_race.py) alongside the .test file to create a PTY session. so using a .py helper file acceptable in the main directory?

we usually use perl. mtr can run perl scripts directly

@svoj
Copy link
Contributor

svoj commented Feb 26, 2026

There's mysql-test/main/mysql-interactive.test that tests CLI in interactive mode.

The fix does silence error message, but doesn't solve loss of history records from concurrent sessions. OTOH bug report didn't request it.

Copy link
Member

@gkodinov gkodinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution. This is a preliminary review.

client/mysql.cc Outdated
read_history(histfile);
if (!(histfile_tmp= (char*) my_malloc(PSI_NOT_INSTRUMENTED,
strlen(histfile) + 5, MYF(MY_WME))))
strlen(histfile) + 20, MYF(MY_WME))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment stating why it's 20. Ideally I'd use some sizeof()

client/mysql.cc Outdated
}
sprintf(histfile_tmp, "%s.TMP", histfile);
// Include PID in temp file name to prevent concurrent-session rename races.
sprintf(histfile_tmp, "%s.TMP%lu", histfile, (unsigned long) getpid());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please change to snprintf().

Also, I'd rather prefer %s.%lu.TMP myself. Extensions are how the OS recognizes file types. Also, pid_t is a signed type: https://man.archlinux.org/man/pid_t.3type.en. I wonder what does this do to your naming scheme. I'd take the absolute value.

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use perl. This is what mtr uses and understands. I'd personally avoid the whole thing and make sure mysql's --skip-batch works as one would expect and sets batch to 0.

When multiple mariadb sessions exit at the same time, they all write
to the same $HISTFILE.TMP then rename it to $HISTFILE.  The second
rename fails with Errcode 2 because the first already moved the file.

Fix: include the process PID in the temp file name so each session
uses its own unique path and no rename collision can occur.
@MooSayed1 MooSayed1 force-pushed the MDEV-38494-history-race branch from f4d2ba2 to 2d206ed Compare February 26, 2026 20:22
@MooSayed1 MooSayed1 requested a review from gkodinov February 26, 2026 20:52
@MooSayed1
Copy link
Author

Thanks for helping and reviewing @svoj @gkodinov
now I'm working on removing socat from the test entirely. Instead of simulating a PTY, implementing --skip-batch support — changing the --batch option from GET_NO_ARG to GET_BOOL so that MariaDB's built-in --skip-xxx negation works.
so just confirm that approtch looks okay to u if so i'll continue with it.

@gkodinov
Copy link
Member

Thanks for helping and reviewing @svoj @gkodinov now I'm working on removing socat from the test entirely. Instead of simulating a PTY, implementing --skip-batch support — changing the --batch option from GET_NO_ARG to GET_BOOL so that MariaDB's built-in --skip-xxx negation works. so just confirm that approtch looks okay to u if so i'll continue with it.

It's probably best done in a separate PR I guess. Since, as a change in behavior, it needs to go to main.
But it does sound right to me.

Copy link
Member

@gkodinov gkodinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thank you! Stay tuned for the final review please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements.

Development

Successfully merging this pull request may close these issues.

4 participants