Skip to content

feat(services): MySQL, and the port allocator every service now shares (T34c) - #7

Merged
haiquang9994 merged 4 commits into
masterfrom
t34c-mysql
Aug 21, 2026
Merged

feat(services): MySQL, and the port allocator every service now shares (T34c)#7
haiquang9994 merged 4 commits into
masterfrom
t34c-mysql

Conversation

@haiquang9994

Copy link
Copy Markdown
Collaborator

MySQL — the second database product rather than a second version of the first — and the port
allocation that came with it and now belongs to every service. Roadmap task T34c; phase 3 is
13 of 15.

One allocator decides every service's port

A recipe declares the port it would prefer; service.create allocates one under a lock held across
the INSERT, so two creates racing cannot be handed the same number. Free means free on the
machine
, not free in the table — the test is a bind, so a port an XAMPP is already holding is not
handed out and is reported with that program's name rather than renumbered in silence. The number is
written once and never recomputed.

services::pools::free_port went into it: a php-fpm pool asks for its 9000 by the same rule
mariadb@main asks for 3306. ServiceSummary carries the port now, and service.create answers a
ServiceCreation — the service, plus why it is not on the port it asked for — because a port a
person did not choose is one they have to be told about.

The MySQL recipe

Most of the machinery is T33's, reused rather than rebuilt: Recipe::ritual, ReadyCheck::Command
as an authenticated mysqladmin ping, StopBehaviour::Command as mysqladmin shutdown, and the
markers that let a half-finished data directory be cleaned without ever clearing a database
MixEngine did not create.

The bootstrap is a table of three routes and not a version test, and the route is an argument
rather than a cfg! — so all three are exercised wherever the tests run, where two of them would
otherwise be unreachable on any one machine: mysqld --initialize-insecure from 5.7 on;
mysql_install_db for 5.6 on Unix, run by the interpreter its own first line names; and 5.6 on
Windows copying the data/ directory upstream's zip ships built.

What the task did not expect to add is Step::secret_file. MySQL removed --bootstrap at
5.7.6, so the statement setting the root password cannot travel on standard input the way MariaDB's
does — --init-file takes a path. The three ways to get a generated password into that server are
a file, an argument list every process can read, or a temporary server on a port anybody can connect
to; only the first has an exposure MixEngine bounds. So a step may declare a file, the daemon writes
it inside owner-only run/ and removes it whether the step succeeded, failed or timed out, and its
content never reaches a Debug line.

Two measurements changed the template. --initialize-insecure creates only root@localhost
where MariaDB's installer creates four root rows, so skip-name-resolve could not travel from one
my.cnf to the other — and the suite's refusal assertion, which reads back 'root'@'localhost', is
what proves the lookup is still on. And a modern MySQL opens a second listener nobody asked for, the
X Protocol on 33060, which no allocation handed out and no services row records; loose-mysqlx = OFF closes it on 8.0 and newer and is a warning rather than a refusal on the two 5.x lines.

Judged against a real server

crates/mixengine-cli/tests/mysql.rs installs a real 8.4.10 and asserts what only a server can say:
the first start bootstraps an empty data directory, the service reaches running — an authenticated
ping, and therefore proof the generated password reached the keyring and came back — root without a
password is refused by name, the stop is a clean shutdown in the server's own log, a second start
does not bootstrap again, and a data directory MixEngine did not create is refused rather than
cleaned.

CI fetches MySQL on every runner that has one. There is no Windows-on-ARM cell in any MySQL line, so
that leg skips the step rather than failing a download that could not succeed; the Linux leg runs
inside the keyring namespace, for MariaDB's reason.

CI on this branch is green on all seven jobs, real-server suites included.

MariaDB and MySQL name the same default and so do two instances of
either, which is one problem and not two. A recipe now declares the port
it would like, `service.create` allocates one at the moment the row is
written, and the number is never recomputed after that.

Free means free on the machine: the test is a bind, so an XAMPP or
Windows' own MySQL80 holding 3306 moves the new service off it — and the
program that took it is named (T38) rather than the service renumbering
in silence. The table is consulted too, because a stopped MariaDB holds
its port as surely as a running one. Allocating and inserting are one
critical section, or two creates arriving together get one port.

`services::pools::free_port` is gone into it: a pool asks for its 9000
the same way `mariadb@main` asks for 3306, and by the same rule.

`service.create` answers a `ServiceCreation` — the service, plus why it
is not on the port it asked for — and `ServiceSummary` carries the port
so a listing can say which one a service was given.
…34c)

MySQL is not a MariaDB version: its own package, its own recipe, its own
rows. What the two share is the port they both want, which the allocator
already answers.

Bootstrapping is a table of three routes rather than a version test —
`mysqld --initialize-insecure` from 5.7 on, `mysql_install_db` for 5.6 on
Unix through the interpreter its own first line names, and 5.6 on Windows
copying the `data/` directory upstream's zip ships built. The route is an
argument rather than a `cfg!`, so all three are exercised wherever the
tests run.

Three things measured against 8.4.10 and 5.6.51 rather than assumed. There
is no `--bootstrap` after 5.7.6, so the statement that sets the root
password goes into a file the daemon writes and removes around one step —
`Step::secret_file`, never an argument list, never a log line. The server
that runs it binds nothing at all and stops itself with `SHUTDOWN`.
`--initialize-insecure` creates only `root@localhost`, so MariaDB's
`skip-name-resolve` cannot travel to this template. And a modern MySQL
opens a second listener on 33060 that no allocation handed out, which
`loose-mysqlx = OFF` closes in a spelling 5.6 also accepts.

The space-free view both installers need moves up into `recipes`, because
`mysql_install_db` is the ancestor of `mariadb-install-db` and leaves
`$basedir` unquoted in the same places.
The suite installs a real 8.4.10 through `package.install`, creates the
service, and asserts what only a server can say: the start bootstraps an
empty data directory, the service reaches `running` — which is an
authenticated `mysqladmin ping` and therefore proof the generated password
reached the keyring and came back — root without a password is refused by
name, the stop is a clean shutdown in the server's own log, a second start
does not bootstrap again, and a data directory MixEngine did not create is
refused rather than cleaned.

The refusal names `'root'@'localhost'`, which quietly proves the other half
of the measurement: the name lookup this template leaves on is what makes
that account reachable over TCP at all.

CI fetches MySQL on every runner that has one — there is no Windows-on-ARM
cell in any line, so that leg skips the step rather than failing a download
that could not succeed. The Linux leg runs it from inside the keyring
namespace, for MariaDB's reason.

T34c is ticked; phase 3 is 13 of 15.
Four links written in the T34c commits: two name their target twice, and
two point from a `pub mod` at a `pub(super)` item, which resolves only
because the project reads its docs with `--document-private-items`. CI
reads them with `-D warnings` as well, so both are errors there.

The private two lose the link rather than gain visibility — `Route` and
`hold` are internal because nothing outside their module may call them,
and widening an item to satisfy a doc link would be the lint deciding
the API.
@haiquang9994
haiquang9994 merged commit b84137b into master Aug 21, 2026
7 checks passed
@haiquang9994
haiquang9994 deleted the t34c-mysql branch August 21, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant