13#include <ascii-chat/tooling/query/query.h>
14#include <ascii-chat/util/time.h>
15#include <ascii-chat/common.h>
24#define WIN32_LEAN_AND_MEAN
27#pragma comment(lib, "ws2_32.lib")
31#include <netinet/in.h>
33#include <sys/socket.h>
40#define HEALTH_CHECK_TIMEOUT_NS (10000LL * NS_PER_MS_INT)
41#define HEALTH_CHECK_INTERVAL_NS (100LL * NS_PER_MS_INT)
42#define HEALTH_CHECK_CONNECT_TIMEOUT_NS (500LL * NS_PER_MS_INT)
45static bool g_query_active =
false;
46static int g_query_port = -1;
49static HANDLE g_controller_handle = NULL;
50static DWORD g_controller_pid = 0;
52static pid_t g_controller_pid = -1;
61static bool try_http_connect(
int port,
int timeout_ms) {
63 SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
64 if (sock == INVALID_SOCKET) {
70 ioctlsocket(sock, FIONBIO, &mode);
72 struct sockaddr_in addr;
73 memset(&addr, 0,
sizeof(addr));
74 addr.sin_family = AF_INET;
75 addr.sin_port = htons((u_short)port);
76 addr.sin_addr.s_addr = inet_addr(
"127.0.0.1");
78 connect(sock, (
struct sockaddr *)&addr,
sizeof(addr));
83 FD_SET(sock, &writefds);
86 tv.tv_sec = timeout_ms / 1000;
87 tv.tv_usec = (timeout_ms % 1000) * 1000;
89 int result = select(0, NULL, &writefds, NULL, &tv);
94 int sock = socket(AF_INET, SOCK_STREAM, 0);
101 tv.tv_sec = timeout_ms / 1000;
102 tv.tv_usec = (timeout_ms % 1000) * 1000;
103 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
104 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv,
sizeof(tv));
106 struct sockaddr_in addr;
107 memset(&addr, 0,
sizeof(addr));
108 addr.sin_family = AF_INET;
109 addr.sin_port = htons((uint16_t)port);
110 addr.sin_addr.s_addr = inet_addr(
"127.0.0.1");
112 int result = connect(sock, (
struct sockaddr *)&addr,
sizeof(addr));
125static bool wait_for_http_ready(
int port, uint64_t timeout_ns) {
126 uint64_t elapsed_ns = 0;
128 while (elapsed_ns < timeout_ns) {
135 if (g_controller_handle != NULL) {
137 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
138 if (exit_code != STILL_ACTIVE) {
145 if (g_controller_pid > 0) {
147 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
148 if (result == g_controller_pid) {
150 g_controller_pid = -1;
170static bool find_query_server_path(
char *buffer,
size_t buffer_size) {
172 const char *search_paths[] = {
173 ".deps-cache/query-tool/ascii-query-server",
174 "../.deps-cache/query-tool/ascii-query-server",
175 "../../.deps-cache/query-tool/ascii-query-server",
177 "ascii-query-server",
181 for (
int i = 0; search_paths[i] != NULL; i++) {
185 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
190 if (access(buffer, X_OK) == 0) {
197 const char *query_server_path = getenv(
"ASCIICHAT_QUERY_SERVER");
198 if (query_server_path != NULL) {
201 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
205 if (access(buffer, X_OK) == 0) {
216 if (g_query_active) {
222 if (!find_query_server_path(server_path,
sizeof(server_path))) {
223 fprintf(stderr,
"[query] Could not find ascii-query-server executable\n");
224 fprintf(stderr,
"[query] Set ASCIICHAT_QUERY_SERVER environment variable or ensure "
225 "it's in .deps-cache/query-tool/\n");
232 if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
233 fprintf(stderr,
"[query] WSAStartup failed\n");
238 safe_snprintf(cmdline,
sizeof(cmdline),
"\"%s\" --attach %lu --port %d", server_path,
239 (
unsigned long)GetCurrentProcessId(), preferred_port);
242 PROCESS_INFORMATION pi;
243 memset(&si, 0,
sizeof(si));
245 memset(&pi, 0,
sizeof(pi));
248 if (!CreateProcessA(NULL,
257 fprintf(stderr,
"[query] Failed to start query server: error %lu\n", GetLastError());
261 g_controller_handle = pi.hProcess;
262 g_controller_pid = pi.dwProcessId;
263 CloseHandle(pi.hThread);
265 fprintf(stderr,
"[query] Started query server (PID %lu) on port %d\n", (
unsigned long)g_controller_pid,
270 pid_t self_pid = getpid();
274 safe_snprintf(port_str,
sizeof(port_str),
"%d", preferred_port);
277 pid_t child = fork();
279 fprintf(stderr,
"[query] fork() failed: %s\n", strerror(errno));
288 execl(server_path,
"ascii-query-server",
"--attach", pid_str,
"--port", port_str, (
char *)NULL);
291 fprintf(stderr,
"[query] exec(%s) failed: %s\n", server_path, strerror(errno));
296 g_controller_pid = child;
297 fprintf(stderr,
"[query] Started query server (PID %d) on port %d\n", child, preferred_port);
301 fprintf(stderr,
"[query] Waiting for HTTP server to be ready...\n");
303 fprintf(stderr,
"[query] Timeout waiting for query server to start\n");
308 g_query_active =
true;
309 g_query_port = preferred_port;
311 fprintf(stderr,
"[query] Query server ready at http://localhost:%d\n", preferred_port);
312 return preferred_port;
316 if (!g_query_active && g_controller_pid <= 0) {
320 fprintf(stderr,
"[query] Shutting down query server...\n");
323 if (g_controller_handle != NULL) {
325 TerminateProcess(g_controller_handle, 0);
328 WaitForSingleObject(g_controller_handle, 3000);
330 CloseHandle(g_controller_handle);
331 g_controller_handle = NULL;
332 g_controller_pid = 0;
338 if (g_controller_pid > 0) {
340 kill(g_controller_pid, SIGTERM);
345 while (wait_count < 30) {
346 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
347 if (result == g_controller_pid) {
353 usleep(100 * US_PER_MS_INT);
358 if (wait_count >= 30) {
359 kill(g_controller_pid, SIGKILL);
360 waitpid(g_controller_pid, &status, 0);
363 g_controller_pid = -1;
367 g_query_active =
false;
370 fprintf(stderr,
"[query] Query server stopped\n");
374 if (!g_query_active) {
380 if (g_controller_handle != NULL) {
382 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
383 if (exit_code != STILL_ACTIVE) {
384 g_query_active =
false;
385 g_controller_handle = NULL;
386 g_controller_pid = 0;
392 if (g_controller_pid > 0) {
394 if (kill(g_controller_pid, 0) != 0) {
395 g_query_active =
false;
396 g_controller_pid = -1;
402 return g_query_active;
int buffer_size
Size of circular buffer.
int query_init(int preferred_port)
bool query_is_active(void)
#define HEALTH_CHECK_TIMEOUT_NS
#define HEALTH_CHECK_INTERVAL_NS
#define HEALTH_CHECK_CONNECT_TIMEOUT_NS
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
#define PLATFORM_MAX_PATH_LENGTH