Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-42116

mariadb-binlog raw download reports that an ignored stop position was not reached

    XMLWordPrintable

Details

    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.

      Attachments

        Activity

          People

            bnestere Brandon Nesterenko
            faner fan
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.