22#define WIN32_LEAN_AND_MEAN
25#pragma comment(lib, "ws2_32.lib")
29#include <netinet/in.h>
31#include <sys/socket.h>
38#define HEALTH_CHECK_TIMEOUT_MS 10000
39#define HEALTH_CHECK_INTERVAL_MS 100
40#define HEALTH_CHECK_CONNECT_TIMEOUT_MS 500
43static bool g_query_active =
false;
44static int g_query_port = -1;
47static HANDLE g_controller_handle = NULL;
48static DWORD g_controller_pid = 0;
50static pid_t g_controller_pid = -1;
59static bool try_http_connect(
int port,
int timeout_ms) {
61 SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
62 if (sock == INVALID_SOCKET) {
68 ioctlsocket(sock, FIONBIO, &mode);
70 struct sockaddr_in addr;
71 memset(&addr, 0,
sizeof(addr));
72 addr.sin_family = AF_INET;
73 addr.sin_port = htons((u_short)port);
74 addr.sin_addr.s_addr = inet_addr(
"127.0.0.1");
76 connect(sock, (
struct sockaddr *)&addr,
sizeof(addr));
81 FD_SET(sock, &writefds);
84 tv.tv_sec = timeout_ms / 1000;
85 tv.tv_usec = (timeout_ms % 1000) * 1000;
87 int result = select(0, NULL, &writefds, NULL, &tv);
92 int sock = socket(AF_INET, SOCK_STREAM, 0);
99 tv.tv_sec = timeout_ms / 1000;
100 tv.tv_usec = (timeout_ms % 1000) * 1000;
101 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
102 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv,
sizeof(tv));
104 struct sockaddr_in addr;
105 memset(&addr, 0,
sizeof(addr));
106 addr.sin_family = AF_INET;
107 addr.sin_port = htons((
uint16_t)port);
108 addr.sin_addr.s_addr = inet_addr(
"127.0.0.1");
110 int result = connect(sock, (
struct sockaddr *)&addr,
sizeof(addr));
123static bool wait_for_http_ready(
int port,
int timeout_ms) {
126 while (elapsed < timeout_ms) {
133 if (g_controller_handle != NULL) {
135 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
136 if (exit_code != STILL_ACTIVE) {
143 if (g_controller_pid > 0) {
145 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
146 if (result == g_controller_pid) {
148 g_controller_pid = -1;
172static bool find_query_server_path(
char *buffer,
size_t buffer_size) {
174 const char *search_paths[] = {
175 ".deps-cache/query-tool/ascii-query-server",
176 "../.deps-cache/query-tool/ascii-query-server",
177 "../../.deps-cache/query-tool/ascii-query-server",
179 "ascii-query-server",
183 for (
int i = 0; search_paths[i] != NULL; i++) {
186 snprintf(buffer, buffer_size,
"%s.exe", search_paths[i]);
187 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
191 snprintf(buffer, buffer_size,
"%s", search_paths[i]);
192 if (access(buffer, X_OK) == 0) {
199 const char *query_server_path = getenv(
"ASCIICHAT_QUERY_SERVER");
200 if (query_server_path != NULL) {
201 snprintf(buffer, buffer_size,
"%s", query_server_path);
203 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
207 if (access(buffer, X_OK) == 0) {
218 if (g_query_active) {
223 char server_path[1024];
224 if (!find_query_server_path(server_path,
sizeof(server_path))) {
225 fprintf(stderr,
"[query] Could not find ascii-query-server executable\n");
226 fprintf(stderr,
"[query] Set ASCIICHAT_QUERY_SERVER environment variable or ensure "
227 "it's in .deps-cache/query-tool/\n");
234 if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
235 fprintf(stderr,
"[query] WSAStartup failed\n");
240 snprintf(cmdline,
sizeof(cmdline),
"\"%s\" --attach %lu --port %d", server_path, (
unsigned long)GetCurrentProcessId(),
244 PROCESS_INFORMATION pi;
245 memset(&si, 0,
sizeof(si));
247 memset(&pi, 0,
sizeof(pi));
250 if (!CreateProcessA(NULL,
259 fprintf(stderr,
"[query] Failed to start query server: error %lu\n", GetLastError());
263 g_controller_handle = pi.hProcess;
264 g_controller_pid = pi.dwProcessId;
265 CloseHandle(pi.hThread);
267 fprintf(stderr,
"[query] Started query server (PID %lu) on port %d\n", (
unsigned long)g_controller_pid,
272 pid_t self_pid = getpid();
276 snprintf(port_str,
sizeof(port_str),
"%d", preferred_port);
277 snprintf(pid_str,
sizeof(pid_str),
"%d", self_pid);
279 pid_t child = fork();
281 fprintf(stderr,
"[query] fork() failed: %s\n", strerror(
errno));
290 execl(server_path,
"ascii-query-server",
"--attach", pid_str,
"--port", port_str, (
char *)NULL);
293 fprintf(stderr,
"[query] exec(%s) failed: %s\n", server_path, strerror(
errno));
298 g_controller_pid = child;
299 fprintf(stderr,
"[query] Started query server (PID %d) on port %d\n", child, preferred_port);
303 fprintf(stderr,
"[query] Waiting for HTTP server to be ready...\n");
305 fprintf(stderr,
"[query] Timeout waiting for query server to start\n");
310 g_query_active =
true;
311 g_query_port = preferred_port;
313 fprintf(stderr,
"[query] Query server ready at http://localhost:%d\n", preferred_port);
314 return preferred_port;
318 if (!g_query_active && g_controller_pid <= 0) {
322 fprintf(stderr,
"[query] Shutting down query server...\n");
325 if (g_controller_handle != NULL) {
327 TerminateProcess(g_controller_handle, 0);
330 WaitForSingleObject(g_controller_handle, 3000);
332 CloseHandle(g_controller_handle);
333 g_controller_handle = NULL;
334 g_controller_pid = 0;
340 if (g_controller_pid > 0) {
342 kill(g_controller_pid, SIGTERM);
347 while (wait_count < 30) {
348 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
349 if (result == g_controller_pid) {
360 if (wait_count >= 30) {
361 kill(g_controller_pid, SIGKILL);
362 waitpid(g_controller_pid, &status, 0);
365 g_controller_pid = -1;
369 g_query_active =
false;
372 fprintf(stderr,
"[query] Query server stopped\n");
376 if (!g_query_active) {
382 if (g_controller_handle != NULL) {
384 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
385 if (exit_code != STILL_ACTIVE) {
386 g_query_active =
false;
387 g_controller_handle = NULL;
388 g_controller_pid = 0;
394 if (g_controller_pid > 0) {
396 if (kill(g_controller_pid, 0) != 0) {
397 g_query_active =
false;
398 g_controller_pid = -1;
404 return g_query_active;
int query_init(int preferred_port)
Initialize the query tool by spawning the controller process.
bool query_is_active(void)
Check if the query tool controller is currently active.
int query_get_port(void)
Get the port number of the active query server.
void query_shutdown(void)
Shutdown the query tool and terminate the controller process.
#define HEALTH_CHECK_TIMEOUT_MS
#define HEALTH_CHECK_CONNECT_TIMEOUT_MS
#define HEALTH_CHECK_INTERVAL_MS
Runtime variable query tool API for debug builds.