Details
-
Bug
-
Status: Confirmed (View Workflow)
-
Minor
-
Resolution: Unresolved
-
10.11, 11.4, 11.8, 12.3, 13.1, 12.3.3
-
Linux/amd64
Description
In MariaDB 12.3.3, mariadb-binlog can issue two contradictory diagnostics during a successful remote raw-mode download with --stop-position=5:
The --stop-position option is ignored in raw mode
|
Did not reach stop position 5 before end of input
|
The second warning is misleading because the client has already stated that the stop position is ignored. This occurs when mariadb-binlog downloads a remote binary log in raw mode while an explicit positive --stop-position is supplied.
The following reproducer requires Linux, Python 3, and a complete installed MariaDB 12.3.3 distribution, including the server, clients, initialization script, and support files. Save it as run.py in an empty writable directory.
#!/usr/bin/env python3
|
"""Isolated raw-binlog warning test; requires an installed target MariaDB tree."""
|
import argparse |
import json |
import os |
from pathlib import Path |
import subprocess |
import tempfile |
import time |
|
|
p = argparse.ArgumentParser(description=__doc__) |
p.add_argument('--basedir', required=True, type=Path) |
a = p.parse_args() |
base = a.basedir.resolve() |
|
|
def binary(*names): |
for folder in ('bin', 'sbin', 'scripts'): |
for name in names: |
path = base / folder / name |
if path.is_file() and os.access(path, os.X_OK): |
return str(path) |
raise SystemExit('Required executable missing: ' + ', '.join(names)) |
|
|
server = binary('mariadbd', 'mysqld') |
install = binary('mariadb-install-db', 'mysql_install_db') |
client = binary('mariadb', 'mysql') |
binlog = binary('mariadb-binlog', 'mysqlbinlog') |
work = Path(tempfile.mkdtemp(prefix='raw-warning-', dir=Path.cwd())).resolve() |
print('Evidence directory:', work, flush=True) |
(work / 'raw').mkdir() |
|
|
def run(args, **kwargs): |
return subprocess.run(args, cwd=work, text=True, capture_output=True, |
timeout=120, **kwargs) |
|
|
init = run([install, '--no-defaults', '--basedir=' + str(base), |
'--datadir=' + str(work / 'data'), |
'--auth-root-authentication-method=normal', '--skip-test-db']) |
(work / 'initialize.stdout').write_text(init.stdout) |
(work / 'initialize.stderr').write_text(init.stderr) |
if init.returncode: |
raise SystemExit('Initialization failed; inspect initialize.stderr') |
args = [server, '--no-defaults', '--basedir=' + str(base), |
'--datadir=' + str(work / 'data'), '--socket=server.sock', |
'--pid-file=server.pid', '--skip-networking', '--server-id=1', |
'--log-bin=' + str(work / 'binlog')] |
if os.geteuid() == 0: |
args.append('--user=root') |
connection = [client, '--no-defaults', '--protocol=socket', |
'--socket=server.sock', '--user=root', '--batch', '--skip-column-names'] |
with (work / 'server.log').open('w') as log: |
proc = subprocess.Popen(args, cwd=work, stdout=log, stderr=log) |
try: |
for attempt in range(120): |
if proc.poll() is not None: |
raise RuntimeError('Server exited; inspect server.log') |
ready = run(connection + ['--execute=SELECT 1']) |
if ready.returncode == 0: |
break |
time.sleep(0.5) |
else: |
raise RuntimeError('Server did not become ready') |
version = run(connection + ['--execute=SELECT VERSION()'], check=True) |
(work / 'version.txt').write_text(version.stdout) |
master = run(connection + ['--execute=SHOW MASTER STATUS'], check=True) |
name = master.stdout.splitlines()[0].split('\t')[0] |
command = [binlog, '--no-defaults', '--raw', '--read-from-remote-server', |
'--protocol=socket', '--socket=server.sock', '--user=root', |
'--stop-position=5', '--result-file=raw/', name] |
(work / 'command.json').write_text(json.dumps(command, indent=2)) |
result = run(command) |
(work / 'client.stdout').write_text(result.stdout) |
(work / 'client.stderr').write_text(result.stderr) |
healthy = run(connection + ['--execute=SELECT 1']) |
ignored = result.stderr.count('The --stop-position option is ignored in raw mode') |
extra = result.stderr.count('Did not reach stop position') |
output = work / 'raw' / name |
valid = (result.returncode == 0 and healthy.returncode == 0 |
and output.is_file() and output.stat().st_size > 4 and ignored == 1) |
verdict = 'INVALID' if not valid else ('PASS' if extra == 0 else 'FAIL') |
evidence = dict(verdict=verdict, returncode=result.returncode, |
ignored_warnings=ignored, reachability_warnings=extra, |
healthy=healthy.returncode == 0) |
(work / 'result.json').write_text(json.dumps(evidence, indent=2)) |
print(json.dumps(evidence)) |
status = 2 if not valid else (0 if extra == 0 else 1) |
finally: |
if proc.poll() is None: |
proc.terminate()
|
try: |
proc.wait(timeout=60) |
except subprocess.TimeoutExpired: |
proc.kill()
|
proc.wait()
|
raise SystemExit(status) |
Run it with the installation prefix, not a source or build directory:
python3 run.py --basedir /absolute/path/to/installed/mariadb |
The script initializes a private data directory, starts a socket-only server with binary logging enabled, obtains the current binary-log name, and downloads that log in raw mode. A successful download that contains the contradictory reachability warning produces a result like:
{"verdict": "FAIL", "returncode": 0, "ignored_warnings": 1, "reachability_warnings": 1, "healthy": true}
|
A correct client should emit the ignored-option warning once and no Did not reach stop position warning. In that case the script reports PASS and exits with status 0. Setup or download failures are reported as INVALID and do not demonstrate the issue.
Source inspection explains the result: raw remote events are handled without updating last_processed_ev.position, which starts at zero. The common post-processing path nevertheless restores the supplied stop position and compares it with that untouched value, causing the reachability warning for the value 5. The supplied package was syntax-checked, but the reproducer was not runtime-executed in the available environment.