Skip to content

Commit 64c23fa

Browse files
authored
Merge pull request #2282 from knewstimek/pr/windows-worker-recovery-fix
fix(windows): preserve worker recovery errors
2 parents da3ea36 + 1d2bbc4 commit 64c23fa

4 files changed

Lines changed: 82 additions & 22 deletions

File tree

‎src/foundation/compat.c‎

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -269,36 +269,54 @@ int cbm_mkstemp(char *tmpl) {
269269
errno = ENAMETOOLONG;
270270
return CBM_NOT_FOUND;
271271
}
272-
/* Wide-API expansion and open: worker staging files land inside
273-
* CBM_CACHE_DIR, which users may place at non-ASCII paths; the ANSI CRT
274-
* (_mktemp/_open) mangles those bytes in the local codepage. */
272+
/* Keep the six-character mkstemp contract, but do not use _wmktemp:
273+
* that CRT helper has a tiny name space on Windows and retained worker
274+
* logs can exhaust it during recovery. The exclusive open closes races
275+
* with other processes; collisions simply draw another random name. */
275276
wchar_t *wide_template = cbm_utf8_to_wide(buf);
276-
if (!wide_template || !_wmktemp(wide_template)) {
277-
free(wide_template);
277+
if (!wide_template) {
278+
errno = EINVAL;
278279
return CBM_NOT_FOUND;
279280
}
280-
char *expanded_for_open = cbm_wide_to_utf8(wide_template);
281-
wchar_t *wide_open = expanded_for_open ? cbm_path_to_wide(expanded_for_open) : NULL;
282-
free(expanded_for_open);
283-
if (!wide_open) {
281+
size_t wide_len = wcslen(wide_template);
282+
if (wide_len < 6 || wcscmp(wide_template + wide_len - 6, L"XXXXXX") != 0) {
284283
free(wide_template);
284+
errno = EINVAL;
285285
return CBM_NOT_FOUND;
286286
}
287-
int fd = _wopen(wide_open, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
288-
free(wide_open);
289-
if (fd >= 0) {
287+
static const wchar_t hex[] = L"0123456789abcdef";
288+
for (int attempt = 0; attempt < 128; attempt++) {
289+
unsigned int random_bits = 0;
290+
if (!cbm_secure_random(&random_bits, sizeof(random_bits))) {
291+
errno = EIO;
292+
break;
293+
}
294+
for (int digit = 0; digit < 6; digit++) {
295+
wide_template[wide_len - 6 + digit] = hex[(random_bits >> (digit * 4)) & 0xf];
296+
}
290297
char *expanded = cbm_wide_to_utf8(wide_template);
291-
if (!expanded || strlen(expanded) >= sizeof(buf)) {
298+
wchar_t *wide_open = expanded ? cbm_path_to_wide(expanded) : NULL;
299+
if (!expanded || !wide_open || strlen(expanded) >= sizeof(buf)) {
300+
free(expanded);
301+
free(wide_open);
302+
errno = ENAMETOOLONG;
303+
break;
304+
}
305+
int fd = _wopen(wide_open, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
306+
free(wide_open);
307+
if (fd >= 0) {
308+
strcpy(tmpl, expanded);
292309
free(expanded);
293310
free(wide_template);
294-
(void)_close(fd);
295-
return CBM_NOT_FOUND;
311+
return fd;
296312
}
297-
strcpy(tmpl, expanded);
298313
free(expanded);
314+
if (errno != EEXIST) {
315+
break;
316+
}
299317
}
300318
free(wide_template);
301-
return fd;
319+
return CBM_NOT_FOUND;
302320
}
303321
#endif
304322

‎src/mcp/index_supervisor.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ui/http_server.h" /* cbm_http_server_resolve_binary_path */
1414

1515
#include <limits.h>
16+
#include <errno.h>
1617
#include <stdio.h>
1718
#include <stdint.h>
1819
#include <stdlib.h>
@@ -705,6 +706,11 @@ int cbm_index_worker_start_with_log(const char *args_json, size_t memory_budget_
705706
worker_result_init(&handle->result);
706707
if (!worker_unique_file(handle->response_path, sizeof(handle->response_path), "response") ||
707708
!worker_unique_file(handle->log_path, sizeof(handle->log_path), "log")) {
709+
int saved_errno = errno;
710+
char error_text[CBM_SZ_32];
711+
(void)snprintf(error_text, sizeof(error_text), "%d", saved_errno);
712+
cbm_log_error("index.supervisor.artifact_create_failed", "artifact",
713+
handle->response_path[0] ? "log" : "response", "errno", error_text);
708714
(void)cbm_unlink(handle->response_path);
709715
(void)cbm_unlink(handle->log_path);
710716
free(handle);

‎src/mcp/mcp.c‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10808,7 +10808,10 @@ static char *index_run_supervised(cbm_mcp_server_t *srv, const char *args) {
1080810808
cbm_mcp_supervised_result_disposition_t recovery_disposition =
1080910809
cbm_mcp_supervised_result_disposition(rc2, &wr2);
1081010810
if (recovery_disposition == CBM_MCP_SUPERVISED_RESULT_FALLBACK) {
10811-
last_outcome = wr2.outcome;
10811+
/* Keep the original worker failure if recovery setup fails. */
10812+
cbm_log_error("index.supervisor.recovery_start_failed", "original_outcome",
10813+
cbm_proc_outcome_str(last_outcome), "recovery_outcome",
10814+
cbm_proc_outcome_str(wr2.outcome));
1081210815
cbm_index_worker_result_free(&wr2);
1081310816
break; /* spawn failed mid-recovery — give up */
1081410817
}

‎tests/test_platform.c‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ TEST(platform_file_apis_survive_max_path_overflow) {
4646
ASSERT_NOT_NULL(cbm_mkdtemp(base));
4747

4848
enum { LONG_SEGMENTS = 5 };
49-
static const char segment[] =
50-
"segment-abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnop";
49+
static const char segment[] = "segment-abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnop";
5150
char deep[CBM_SZ_1K];
5251
written = snprintf(deep, sizeof(deep), "%s", base);
5352
ASSERT_TRUE(written > 0 && written < (int)sizeof(deep));
@@ -475,6 +474,28 @@ TEST(platform_mkstemp_and_mkdtemp_survive_non_ascii_directory) {
475474
PASS();
476475
}
477476

477+
#ifdef _WIN32
478+
TEST(platform_mkstemp_retained_files_exceed_crt_namespace) {
479+
char base[CBM_SZ_256] = "/tmp/cbm-many-temp-XXXXXX";
480+
ASSERT_NOT_NULL(cbm_mkdtemp(base));
481+
char paths[64][CBM_SZ_512] = {{0}};
482+
int created = 0;
483+
for (; created < 64; created++) {
484+
int written =
485+
snprintf(paths[created], sizeof(paths[created]), "%s/.worker-log-XXXXXX", base);
486+
ASSERT_TRUE(written > 0 && written < (int)sizeof(paths[created]));
487+
int descriptor = cbm_mkstemp(paths[created]);
488+
ASSERT_TRUE(descriptor >= 0);
489+
ASSERT_EQ(_close(descriptor), 0);
490+
}
491+
for (int index = 0; index < created; index++) {
492+
ASSERT_EQ(cbm_unlink(paths[index]), 0);
493+
}
494+
ASSERT_EQ(cbm_rmdir(base), 0);
495+
PASS();
496+
}
497+
#endif
498+
478499
typedef struct {
479500
atomic_int *ready;
480501
atomic_bool *go;
@@ -832,8 +853,17 @@ TEST(platform_env_long_refuses_what_it_cannot_read) {
832853

833854
/* Every one of these used to answer 0 through atol. */
834855
const char *unreadable[] = {
835-
"abc", "30s", " 30", "30 ", "", "1e3",
836-
"0x10", "+ 30", "--3", "3.5", "99999999999999999999999999",
856+
"abc",
857+
"30s",
858+
" 30",
859+
"30 ",
860+
"",
861+
"1e3",
862+
"0x10",
863+
"+ 30",
864+
"--3",
865+
"3.5",
866+
"99999999999999999999999999",
837867
};
838868
for (size_t i = 0; i < sizeof(unreadable) / sizeof(unreadable[0]); i++) {
839869
ASSERT_EQ(cbm_setenv(name, unreadable[i], 1), 0);
@@ -1135,6 +1165,9 @@ SUITE(platform) {
11351165
RUN_TEST(platform_mkdir_p_follow_owned_is_per_call_site);
11361166
RUN_TEST(platform_mkdir_p_resolves_link_text_from_the_link_directory);
11371167
RUN_TEST(platform_mkstemp_and_mkdtemp_survive_non_ascii_directory);
1168+
#ifdef _WIN32
1169+
RUN_TEST(platform_mkstemp_retained_files_exceed_crt_namespace);
1170+
#endif
11381171
RUN_TEST(platform_mkdtemp_is_thread_safe);
11391172
RUN_TEST(platform_counter_scaling_avoids_intermediate_overflow);
11401173
RUN_TEST(platform_counter_scaling_preserves_monotonic_deadlines);

0 commit comments

Comments
 (0)