Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ rayforce: pull_core
@mkdir -p $(CORE)/src/q
@cp q.c q.h q_server.c q_server.h embed/rayforce_q.c $(CORE)/src/q/
@sed -e 's@ray_runtime_t\* rt = ray_runtime_create(argc, argv);@& extern void q_env_register(void); if (rt) q_env_register();@' \
-e 's@ else if (strcmp(argv\[i\], "--") == 0)@ else if ((strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) \&\& i + 1 < argc) { i++; } &@' \
-e 's@if (poll) ray_runtime_set_poll(poll);@if (poll) { ray_runtime_set_poll(poll); extern int64_t q_serve_from_args(ray_poll_t*, int, char**); q_serve_from_args(poll, argc, argv); }@' \
-e 's@ else if (strcmp(argv\[i\], "--") == 0)@ else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) { if (i + 1 < argc) i++; } &@' \
-e 's@if (poll) ray_runtime_set_poll(poll);@if (poll) { ray_runtime_set_poll(poll); extern int64_t q_serve_from_args(ray_poll_t*, int, char**); if (q_serve_from_args(poll, argc, argv) < 0) { rc = 2; goto done; } }@' \
$(CORE)/src/app/main.c > $(CORE)/src/app/main.c.q && \
mv $(CORE)/src/app/main.c.q $(CORE)/src/app/main.c
@$(MAKE) -C $(CORE) release
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ The server is exposed via the `-q PORT` flag — it shares the REPL's event loop
./rayforce -q 25565 # serve Rayfall over the Q wire on port 25565
```

Both `-q` and `--q-serve` require a decimal TCP port in `1..65535`. A
missing or invalid port prints a diagnostic and exits with status `2`, so a
service supervisor does not mistake a failed Q listener for a healthy
process. Arguments after `--` are passed to the Rayfall application and are
not interpreted as Q-server options.

# API

See **[INTEGRATING.md](./docs/INTEGRATING.md)** for more details.
Expand Down
16 changes: 11 additions & 5 deletions embed/rayforce_q.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,18 @@ void q_env_register(void) {
ray_release(f);
}

/* Folded entry point, called from the rayforce binary's main()
* Starts the Q server when `-q PORT` / `--q-serve PORT` is present */
/* Folded entry point, called from the rayforce binary's main().
* Returns the listener id on success, 0 when no Q-server flag is present,
* or -1 when a Q-server flag is malformed or cannot be started. */
int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv) {
for (int i = 1; i < argc; i++) {
if ((strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) &&
i + 1 < argc) {
if (strcmp(argv[i], "--") == 0)
break;
if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "q: missing port after %s (expected 1..65535)\n", argv[i]);
return -1;
}
int port = 0;
if (!q_parse_port(argv[++i], &port)) {
fprintf(stderr, "q: invalid port %s (expected 1..65535)\n", argv[i]);
Expand All @@ -226,5 +232,5 @@ int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv) {
return q_serve(poll, port);
}
}
return -1;
return 0;
}
40 changes: 40 additions & 0 deletions test/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

/* Registers `.q.connect` / `.q.send` / `.q.close` */
void q_env_register(void);
int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv);

typedef struct {
uint8_t endianness;
Expand Down Expand Up @@ -72,6 +73,42 @@ static int run_server(int port) {
return 0;
}

static int run_q_cli_selftest(void) {
ray_runtime_t *rt = ray_runtime_create(0, NULL);
if (rt == NULL) {
fprintf(stderr, "q CLI selftest: failed to create runtime\n");
return 1;
}
ray_poll_t *poll = ray_poll_create();
if (poll == NULL) {
fprintf(stderr, "q CLI selftest: failed to create poll\n");
ray_runtime_destroy(rt);
return 1;
}

char *no_flag[] = {"rayforce", "--file", "app.rfl"};
char *bad_port[] = {"rayforce", "-q", "70000"};
char *junk_port[] = {"rayforce", "--q-serve", "1abc"};
char *missing_port[] = {"rayforce", "-q"};
char *app_args[] = {"rayforce", "--", "-q", "70000"};
int failures = 0;
if (q_serve_from_args(poll, 3, no_flag) != 0)
failures++;
if (q_serve_from_args(poll, 3, bad_port) >= 0)
failures++;
if (q_serve_from_args(poll, 3, junk_port) >= 0)
failures++;
if (q_serve_from_args(poll, 2, missing_port) >= 0)
failures++;
if (q_serve_from_args(poll, 4, app_args) != 0)
failures++;

ray_poll_destroy(poll);
ray_runtime_destroy(rt);
printf("q CLI selftest: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}

static int fmt_eq(ray_t *a, ray_t *b) {
if (a == NULL && b == NULL)
return 1;
Expand Down Expand Up @@ -426,6 +463,9 @@ int main(int argc, char **argv) {
if (argc >= 2 && strcmp(argv[1], "--exchange-selftest") == 0)
return run_exchange_selftest();

if (argc >= 2 && strcmp(argv[1], "--q-cli-selftest") == 0)
return run_q_cli_selftest();

/* Server role: `driver --serve PORT`. */
if (argc >= 3 && strcmp(argv[1], "--serve") == 0)
return run_server(atoi(argv[2]));
Expand Down
3 changes: 3 additions & 0 deletions test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ echo "running codec selftest..."
echo "running exchange selftest..."
"$DRIVER" --exchange-selftest

echo "running Q CLI argument selftest..."
"$DRIVER" --q-cli-selftest

# ---- Leg 1: Rayforce server
SERVERPORT="${SERVERPORT:-$(free_port)}"
"$DRIVER" --serve "$SERVERPORT" &
Expand Down
Loading