Skip to content

Feat/core 2.6.1 and q fixes - #7

Merged
vbanas merged 2 commits into
masterfrom
feat/core-2.6.1-and-q-fixes
Sep 8, 2026
Merged

vbanas merged 2 commits into
masterfrom
feat/core-2.6.1-and-q-fixes

Conversation

@vbanas

@vbanas vbanas commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Update to the 2.6.1 rayforce, latest commits in rayforce-q + new IPC error codes support

Move the vendored pins from core v2.6.0 (b3e9aa1) to v2.6.1 (a99a019),
with CORE_VERSION / CORE_COMMIT in rayforce-sys/build.rs moved alongside,
and rayforce-q from 2.1.1 (c35ed3c) to 1eabaf4.

Nothing in the binding layer moves. include/rayforce.h is byte-identical
between the two core tags, and every symbol in INTERNAL_FNS keeps its
signature; src/lang/internal.h only adds four ray_mc_* declarations, which
are not allowlisted. The new src/core/mcast.c reaches the archive through
the Makefile's own wildcard and needs no link library beyond the -lm
-lpthread already emitted. The Makefile's only change is a TEST_FILTER
passthrough for `make test`, so stage_core and build_core_lib are
untouched. Cargo.toml's include globs already carry the new sources into
the packaged crate.

The core changes four answers a caller can see. A grouped
count (distinct ...) now counts a null as a value: the per-group kernels
used to skip nulls while the ungrouped form kept one, and the three
kernels disagreed among themselves, so the result moved with the row,
group and core counts. .csv.read also accepts Rayfall's dotted temporal
spellings alongside the ISO forms the writer emits. `if` with a null
branch no longer writes an ordinary huge number where a null belongs, and
an F64 past the int64 range narrows to the integer null rather than an
undefined cast. A periodic timer that overruns re-arms at its next
deadline instead of replaying every fire it missed. The engine binary now
exits 1 when -p cannot bind, which tests/ipc.rs relies on when it spawns
one. `update where:` and `upsert` write in place only on a named flat
table; the builders in query.rs pass a table value rather than a quoted
name, so they keep taking the copy path.

rayforce-q moves off a tag deliberately: no tag carries these fixes yet,
and one of them is that writing to a closed peer raised SIGPIPE, whose
default disposition kills the process — a library has no business doing
that to its host. q_send_all now passes MSG_NOSIGNAL, or SO_NOSIGPIPE on
the BSDs. Alongside it, q_exchange accepts a frame as the reply only when
its message type says it is one; a q identity reply, which is what an
assignment answers, decodes to the null object instead of failing the
exchange as an unsupported wire type; a native RAY_DICT result encodes,
where the serializer had no branch for it; and a decode that left trailing
bytes frees its error object through ray_error_free rather than
ray_release. Only q.c is compiled here, so the restriction of
.q.connect / .q.send / .q.close under -U IPC rides along without effect,
and q.h is unchanged.

The suite passes against both pins with a real spawned server, as does
clippy at -D warnings.
Every negative return from ray_ipc_connect collapsed into one string,
"connect to {host}:{port} failed", which reads the same whether nothing
was listening, the password was wrong, or the peer speaks a wire version
this build would misparse every atom of. The core distinguishes six
causes and v2.6.1 added two of them, so the message now ends in the
reason: connection refused, authentication failed, wire version
mismatch, timed out, or the OS error text.

This is the shape QConnection::connect_with already used for its own
three Q_ERR_* codes, down to the "connect to {host}:{port}" phrasing, so
the two clients now fail alike rather than one of them being the good
example. rayforce-sys gains RAY_IPC_ERR_* constants mirroring Q_ERR_*:
include/rayforce.h declares ray_ipc_connect with no contract at all, so
they are maintained by hand against connect_fail_code() in the core's
src/core/ipc.c, and an unrecognised code falls through to the refused
arm rather than inventing a name for it.

Two of the reasons read less plainly than they look, and the doc comment
says so. "timed out" also covers a server that is alive and listening
but busy inside a long evaluation, because the core folds EAGAIN and
EWOULDBLOCK in with ETIMEDOUT — from the client they are the same
silence. The OS error comes from errno, read immediately after the call
returns, which the core does not reliably bridge from WSAGetLastError()
on Windows; a host that fails to resolve surfaces through it as "No
route to host", the errno the core stamps on that failure.

One cause stays out of reach. The core answers -2, "server requires
authentication", only when handed a null password, and an empty &str
arrives as a valid pointer to an empty string, which the server rejects
as a bad credential instead. The arm is written anyway: it is one
call-site change away from live, and a dead arm is cheaper than a wrong
message.

connect_failure_is_an_error asserted only is_err(), so it passed just as
well before this change as after. It now checks that the refused path
names itself and keeps the port, and a new test drives the wire-version
path — a listener that completes the accept, reads the two-byte
handshake and answers with version 0xFF against the core's 3. That path
had no coverage at all, and it is the one a caller is most likely to hit
by pointing a new client at an old server.
@vbanas
vbanas merged commit c26f786 into master Sep 8, 2026
2 checks passed
@vbanas
vbanas deleted the feat/core-2.6.1-and-q-fixes branch September 8, 2026 16:29
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