diff --git a/include/net.h b/include/net.h index f2d1579..7d2465d 100644 --- a/include/net.h +++ b/include/net.h @@ -31,6 +31,17 @@ int nt_publish(const char *event_json, int relay_count, int timeout_ms); +/* + * Same as nt_publish_detailed(), but skips db relay-post logging. + * Intended for background worker usage when DB handle is thread-affine. + */ +int nt_publish_detailed_nodb(const char *event_json, + const char **relay_urls, + int relay_count, + int timeout_ms, + nt_publish_result_t **results_out, + int *result_count_out); + /* Free result array returned by nt_publish_detailed(). */ void nt_publish_results_free(nt_publish_result_t *results, int result_count); diff --git a/include/publish.h b/include/publish.h index 55f3c93..363da3f 100644 --- a/include/publish.h +++ b/include/publish.h @@ -13,4 +13,14 @@ int publish_signed_event_with_report(const char *label, cJSON *tags, int timeout_ms); +/* + * Fire-and-forget publish wrapper. + * Returns 0 when background job is started, -1 on immediate setup failure. + */ +int publish_signed_event_with_report_async(const char *label, + int kind, + const char *content, + cJSON *tags, + int timeout_ms); + #endif /* PUBLISH_H */ diff --git a/include/state.h b/include/state.h index 4f3e56a..ce03db9 100644 --- a/include/state.h +++ b/include/state.h @@ -34,6 +34,7 @@ extern nt_state_t g_state; void state_init(void); void state_cleanup(void); int state_load_user_info(void); +int state_load_user_info_async(void); void state_parse_relay_list(void); const char **state_get_read_relays(int *count); const char **state_get_write_relays(int *count); diff --git a/src/main.c b/src/main.c index f81b987..ba37640 100644 --- a/src/main.c +++ b/src/main.c @@ -11,8 +11,8 @@ */ #define NT_VERSION_MAJOR 0 #define NT_VERSION_MINOR 0 -#define NT_VERSION_PATCH 14 -#define NT_VERSION "v0.0.14" +#define NT_VERSION_PATCH 15 +#define NT_VERSION "v0.0.15" #include #include diff --git a/src/menu_login.c b/src/menu_login.c index 109cfb6..3175b13 100644 --- a/src/menu_login.c +++ b/src/menu_login.c @@ -364,12 +364,10 @@ void menu_login(void) { free(local_items); free(labels); - (void)state_load_user_info(); - - if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) { - nt_log("No profile found on relays. Setting up new user..."); - menu_add_new_user(); - (void)state_load_user_info(); + if (state_load_user_info_async() != 0) { + nt_log("Failed to start background user-info load."); + } else { + nt_log("Loading profile/relay data in background..."); } return; @@ -418,7 +416,7 @@ void menu_login(void) { snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : ""); (void)menu_prompt("> Login > URL Signer", "Signer URL (http://.fips:8080)", - endpoint_url, + (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "", endpoint_url, sizeof(endpoint_url)); @@ -486,12 +484,10 @@ void menu_login(void) { nt_log("Signed in via n_signer URL (%s) npub: %s", endpoint_url, g_state.npub_bech32); - (void)state_load_user_info(); - - if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) { - nt_log("No profile found on relays. Setting up new user..."); - menu_add_new_user(); - (void)state_load_user_info(); + if (state_load_user_info_async() != 0) { + nt_log("Failed to start background user-info load."); + } else { + nt_log("Loading profile/relay data in background..."); } return; @@ -511,7 +507,7 @@ void menu_login(void) { (void)menu_prompt("> Login > Qrexec Signer", "Target signer qube (nsigner-vault)", - target_qube, + (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault", target_qube, sizeof(target_qube)); if (target_qube[0] == '\0') { @@ -520,7 +516,7 @@ void menu_login(void) { (void)menu_prompt("> Login > Qrexec Signer", "Qrexec service (qubes.NsignerRpc)", - service_name, + "qubes.NsignerRpc", service_name, sizeof(service_name)); if (service_name[0] == '\0') { @@ -567,12 +563,10 @@ void menu_login(void) { nt_log("Signed in via qrexec n_signer (%s:%s) npub: %s", target_qube, service_name, g_state.npub_bech32); - (void)state_load_user_info(); - - if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) { - nt_log("No profile found on relays. Setting up new user..."); - menu_add_new_user(); - (void)state_load_user_info(); + if (state_load_user_info_async() != 0) { + nt_log("Failed to start background user-info load."); + } else { + nt_log("Loading profile/relay data in background..."); } return; diff --git a/src/menu_tweet.c b/src/menu_tweet.c index db1709a..ced2a7e 100644 --- a/src/menu_tweet.c +++ b/src/menu_tweet.c @@ -11,7 +11,7 @@ void menu_tweet(void) { TuiFrame frame = nt_frame("> Main Menu > Tweet"); TuiStatus status = nt_status(); char text[4096]; - int posted; + int queued; tuin_render_header(&frame); nt_print_reset(); @@ -28,11 +28,12 @@ void menu_tweet(void) { return; } - posted = publish_signed_event_with_report("Tweet", 1, text, NULL, 8000); + queued = publish_signed_event_with_report_async("Tweet", 1, text, NULL, 8000); - if (posted < 0) { - tuin_notice("Tweet publish failed."); - } else { - nt_log("Tweet published."); + if (queued != 0) { + tuin_notice("Tweet publish failed to start."); + return; } + + nt_log("Tweet queued; publishing in background."); } diff --git a/src/net.c b/src/net.c index 0e2b97e..f95e848 100644 --- a/src/net.c +++ b/src/net.c @@ -3,6 +3,7 @@ #include "db.h" #include "state.h" #include "signer.h" +#include "tui.h" #include "../resources/nostr_core_lib/cjson/cJSON.h" #include "../resources/nostr_core_lib/nostr_core/nip042.h" @@ -396,12 +397,13 @@ static int nt_wait_publish_ok(nostr_ws_client_t *client, } /* Publish one event to each relay: connect -> EVENT -> OK -> close. */ -int nt_publish_detailed(const char *event_json, - const char **relay_urls, - int relay_count, - int timeout_ms, - nt_publish_result_t **results_out, - int *result_count_out) { +static int nt_publish_detailed_impl(const char *event_json, + const char **relay_urls, + int relay_count, + int timeout_ms, + int with_db_log, + nt_publish_result_t **results_out, + int *result_count_out) { cJSON *event = NULL; char *event_id = NULL; nt_publish_result_t *results = NULL; @@ -456,7 +458,9 @@ int nt_publish_detailed(const char *event_json, client = nostr_ws_connect(relay); if (!client) { response = nt_strdup_local("connect_failed"); - db_log_relay_post(relay, event_id, 0, response ? response : "connect_failed"); + if (with_db_log) { + db_log_relay_post(relay, event_id, 0, response ? response : "connect_failed"); + } slot->posted = 0; slot->response = response; continue; @@ -466,7 +470,9 @@ int nt_publish_detailed(const char *event_json, if (nostr_relay_send_event(client, event) != NOSTR_WS_SUCCESS) { response = nt_strdup_local("send_failed"); - db_log_relay_post(relay, event_id, 0, response ? response : "send_failed"); + if (with_db_log) { + db_log_relay_post(relay, event_id, 0, response ? response : "send_failed"); + } slot->posted = 0; slot->response = response; nostr_ws_close(client); @@ -482,7 +488,9 @@ int nt_publish_detailed(const char *event_json, accepted_count++; } - db_log_relay_post(relay, event_id, posted, response ? response : "no_response"); + if (with_db_log) { + db_log_relay_post(relay, event_id, posted, response ? response : "no_response"); + } slot->posted = posted; slot->response = response; @@ -498,6 +506,36 @@ int nt_publish_detailed(const char *event_json, return accepted_count; } +int nt_publish_detailed(const char *event_json, + const char **relay_urls, + int relay_count, + int timeout_ms, + nt_publish_result_t **results_out, + int *result_count_out) { + return nt_publish_detailed_impl(event_json, + relay_urls, + relay_count, + timeout_ms, + 1, + results_out, + result_count_out); +} + +int nt_publish_detailed_nodb(const char *event_json, + const char **relay_urls, + int relay_count, + int timeout_ms, + nt_publish_result_t **results_out, + int *result_count_out) { + return nt_publish_detailed_impl(event_json, + relay_urls, + relay_count, + timeout_ms, + 0, + results_out, + result_count_out); +} + int nt_publish(const char *event_json, const char **relay_urls, int relay_count, @@ -569,11 +607,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url, cJSON_AddItemToArray(req_msg, cJSON_Duplicate(filter, 1)); { char *req_json = cJSON_PrintUnformatted(req_msg); - fprintf(stderr, - "%s: request to %s: %s\n", - phase_label ? phase_label : "query", - relay_url ? relay_url : "(unknown relay)", - req_json ? req_json : "(serialize_failed)"); + nt_log("%s: request to %s: %s", + phase_label ? phase_label : "query", + relay_url ? relay_url : "(unknown relay)", + req_json ? req_json : "(serialize_failed)"); free(req_json); } cJSON_Delete(req_msg); @@ -601,11 +638,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url, } if (verbose) { - fprintf(stderr, - "%s: response from %s: %s\n", - phase_label ? phase_label : "query", - relay_url ? relay_url : "(unknown relay)", - buffer); + nt_log("%s: response from %s: %s", + phase_label ? phase_label : "query", + relay_url ? relay_url : "(unknown relay)", + buffer); } if (nostr_parse_relay_message(buffer, &message_type, &parsed) != NOSTR_WS_SUCCESS) { @@ -619,11 +655,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url, if (cJSON_IsObject(event_obj)) { if (verbose) { char *received_event = cJSON_PrintUnformatted(event_obj); - fprintf(stderr, - "%s: event from %s: %s\n", - phase_label ? phase_label : "query", - relay_url ? relay_url : "(unknown relay)", - received_event ? received_event : "(serialize_failed)"); + nt_log("%s: event from %s: %s", + phase_label ? phase_label : "query", + relay_url ? relay_url : "(unknown relay)", + received_event ? received_event : "(serialize_failed)"); free(received_event); } @@ -716,10 +751,9 @@ int nt_query_sync_verbose(const char *filter_json, } if (verbose && phase_label) { - fprintf(stderr, - "%s: querying %d relays via nostr_core relay pool\n", - phase_label, - relay_count); + nt_log("%s: querying %d relays via nostr_core relay pool", + phase_label, + relay_count); } pool_events = nostr_relay_pool_query_sync(pool, @@ -757,10 +791,9 @@ int nt_query_sync_verbose(const char *filter_json, free(pool_events); if (verbose && phase_label) { - fprintf(stderr, - "%s: relay pool collected %d unique events\n", - phase_label, - events_n); + nt_log("%s: relay pool collected %d unique events", + phase_label, + events_n); } nostr_relay_pool_destroy(pool); diff --git a/src/nt_tui_adapter.c b/src/nt_tui_adapter.c index c478ec9..0e5478c 100644 --- a/src/nt_tui_adapter.c +++ b/src/nt_tui_adapter.c @@ -1,6 +1,7 @@ #include "tui.h" #include +#include #include #include #include @@ -16,9 +17,11 @@ static int g_body_row = 0; static char g_app_name[128] = "NOSTR TERMINAL"; static char g_app_version[64] = ""; static char g_user[256] = ""; +static char g_status_line[512] = ""; static NtLogEntry g_log_entries[NT_LOG_MAX]; static int g_log_head = 0; static int g_log_count = 0; +static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER; static WINDOW *nt_body_window(void) { return (WINDOW *)tuin_body_window(); @@ -86,17 +89,6 @@ int nt_run_external_editor(int (*spawn)(void *user), void *user) { return result; } -static const char *nt_log_latest(void) { - int idx; - - if (g_log_count <= 0) { - return ""; - } - - idx = (g_log_head - 1 + NT_LOG_MAX) % NT_LOG_MAX; - return g_log_entries[idx].text; -} - TuiFrame nt_frame(const char *breadcrumb) { static char composed_title[512]; TuiFrame frame; @@ -115,7 +107,15 @@ TuiFrame nt_frame(const char *breadcrumb) { TuiStatus nt_status(void) { TuiStatus status; - status.text = nt_log_latest(); + + pthread_mutex_lock(&g_log_mutex); + if (g_status_line[0] == '\0' && g_log_count > 0) { + int idx = (g_log_head - 1 + NT_LOG_MAX) % NT_LOG_MAX; + snprintf(g_status_line, sizeof(g_status_line), "%s", g_log_entries[idx].text); + } + status.text = g_status_line; + pthread_mutex_unlock(&g_log_mutex); + return status; } @@ -160,13 +160,18 @@ void nt_log(const char *fmt, ...) { snprintf(ts, sizeof(ts), "[--:--:--]"); } + pthread_mutex_lock(&g_log_mutex); + idx = g_log_head; snprintf(g_log_entries[idx].text, sizeof(g_log_entries[idx].text), "%s %s", ts, msg); + snprintf(g_status_line, sizeof(g_status_line), "%s", g_log_entries[idx].text); g_log_head = (g_log_head + 1) % NT_LOG_MAX; if (g_log_count < NT_LOG_MAX) { g_log_count++; } + + pthread_mutex_unlock(&g_log_mutex); } void nt_log_show(void) { @@ -177,6 +182,7 @@ void nt_log_show(void) { int i; log_text[0] = '\0'; + pthread_mutex_lock(&g_log_mutex); for (i = 0; i < g_log_count; ++i) { int idx = (g_log_head - g_log_count + i + NT_LOG_MAX) % NT_LOG_MAX; int n = snprintf(log_text + pos, sizeof(log_text) - (size_t)pos, "%s%s", @@ -191,13 +197,17 @@ void nt_log_show(void) { } pos += n; } + pthread_mutex_unlock(&g_log_mutex); - tuin_pager_run(&frame, log_text, &status); + tuin_pager_run_ex(&frame, log_text, &status, 1); } void nt_log_clear(void) { + pthread_mutex_lock(&g_log_mutex); g_log_head = 0; g_log_count = 0; + g_status_line[0] = '\0'; + pthread_mutex_unlock(&g_log_mutex); } void nt_pager_show_text(const char *breadcrumb, const char *text) { diff --git a/src/publish.c b/src/publish.c index 201f9e8..ddf9aa5 100644 --- a/src/publish.c +++ b/src/publish.c @@ -5,15 +5,109 @@ #include "tui.h" #include "signer.h" +#include #include #include +#include #include -int publish_signed_event_with_report(const char *label, - int kind, - const char *content, - cJSON *tags, - int timeout_ms) { +typedef struct { + char *label; + char *event_json; + char **relay_urls; + int relay_count; + int timeout_ms; +} NtPublishJob; + +static int publish_signed_event_with_report_impl(const char *label, + int kind, + const char *content, + cJSON *tags, + int timeout_ms, + int with_db_log); + +static char *publish_strdup_local(const char *s) { + size_t n; + char *out; + + if (!s) { + return NULL; + } + n = strlen(s); + out = (char *)malloc(n + 1U); + if (!out) { + return NULL; + } + memcpy(out, s, n + 1U); + return out; +} + +static void publish_job_free(NtPublishJob *job) { + if (!job) { + return; + } + free(job->label); + free(job->event_json); + if (job->relay_urls) { + int i; + for (i = 0; i < job->relay_count; i++) { + free(job->relay_urls[i]); + } + free(job->relay_urls); + } + free(job); +} + +static void *publish_worker_main(void *arg) { + NtPublishJob *job = (NtPublishJob *)arg; + nt_publish_result_t *results = NULL; + int result_count = 0; + int accepted_count = 0; + int i; + + if (!job) { + return NULL; + } + + nt_log("%s: posting to %d relay(s)...", job->label, job->relay_count); + + accepted_count = nt_publish_detailed_nodb(job->event_json, + (const char **)job->relay_urls, + job->relay_count, + job->timeout_ms, + &results, + &result_count); + + if (result_count <= 0) { + nt_log("%s: no relay attempts were made.", job->label); + publish_job_free(job); + return NULL; + } + + for (i = 0; i < result_count; i++) { + const char *relay = results[i].relay_url ? results[i].relay_url : "(unknown relay)"; + const char *response = results[i].response ? results[i].response : "(no response)"; + + if (results[i].posted) { + nt_log("[OK] %s", relay); + } else { + nt_log("[FAIL] %s", relay); + nt_log(" %s", response); + } + } + + nt_log("%s: accepted by %d/%d relay(s).", job->label, accepted_count, result_count); + nt_publish_results_free(results, result_count); + publish_job_free(job); + return NULL; +} + +static int publish_signed_event_with_report_impl(const char *label, + int kind, + const char *content, + cJSON *tags, + int timeout_ms, + int with_db_log) { char *event_json = NULL; const char **write_relays; int relay_count = 0; @@ -38,12 +132,19 @@ int publish_signed_event_with_report(const char *label, nt_log("%s", event_json); nt_log("Posting to %d relay(s)...", relay_count); - accepted_count = nt_publish_detailed(event_json, - write_relays, - relay_count, - timeout_ms, - &results, - &result_count); + accepted_count = with_db_log + ? nt_publish_detailed(event_json, + write_relays, + relay_count, + timeout_ms, + &results, + &result_count) + : nt_publish_detailed_nodb(event_json, + write_relays, + relay_count, + timeout_ms, + &results, + &result_count); free(event_json); @@ -68,3 +169,103 @@ int publish_signed_event_with_report(const char *label, nt_publish_results_free(results, result_count); return accepted_count; } + +int publish_signed_event_with_report(const char *label, + int kind, + const char *content, + cJSON *tags, + int timeout_ms) { + return publish_signed_event_with_report_impl(label, + kind, + content, + tags, + timeout_ms, + 1); +} + +int publish_signed_event_with_report_async(const char *label, + int kind, + const char *content, + cJSON *tags, + int timeout_ms) { + NtPublishJob *job; + pthread_t tid; + char *event_json = NULL; + const char **write_relays = NULL; + int relay_count = 0; + int i; + + if (!label || label[0] == '\0') { + label = "Event"; + } + + /* Sign synchronously on UI thread; only network publish happens in background. */ + if (signer_create_and_sign(kind, content ? content : "", tags, time(NULL), &event_json) != 0 || !event_json) { + nt_log("%s: failed to serialize event.", label); + return -1; + } + + write_relays = state_get_write_relays(&relay_count); + + job = (NtPublishJob *)calloc(1, sizeof(*job)); + if (!job) { + free(event_json); + return -1; + } + + job->label = publish_strdup_local(label); + job->event_json = event_json; + job->relay_count = relay_count; + job->timeout_ms = timeout_ms; + + if (relay_count > 0) { + job->relay_urls = (char **)calloc((size_t)relay_count, sizeof(char *)); + if (!job->relay_urls) { + publish_job_free(job); + return -1; + } + + for (i = 0; i < relay_count; i++) { + const char *src = (write_relays && write_relays[i]) ? write_relays[i] : ""; + job->relay_urls[i] = publish_strdup_local(src); + if (!job->relay_urls[i]) { + publish_job_free(job); + return -1; + } + } + } + + if (!job->label || !job->event_json) { + publish_job_free(job); + return -1; + } + + { + pthread_attr_t attr; + int has_attr = 0; + + if (pthread_attr_init(&attr) == 0) { + has_attr = 1; + (void)pthread_attr_setstacksize(&attr, 1024U * 1024U); + } + + if (pthread_create(&tid, has_attr ? &attr : NULL, publish_worker_main, job) != 0) { + if (has_attr) { + (void)pthread_attr_destroy(&attr); + } + publish_job_free(job); + return -1; + } + + if (has_attr) { + (void)pthread_attr_destroy(&attr); + } + } + + if (pthread_detach(tid) != 0) { + publish_job_free(job); + return -1; + } + + return 0; +} diff --git a/src/state.c b/src/state.c index d83cced..3cda592 100644 --- a/src/state.c +++ b/src/state.c @@ -10,13 +10,19 @@ #include "../resources/nostr_core_lib/nostr_core/nip044.h" #include "../resources/nostr_core_lib/nostr_core/utils.h" +#include #include #include #include #include +#include "tui.h" + nt_state_t g_state; +static pthread_mutex_t g_user_info_load_mutex = PTHREAD_MUTEX_INITIALIZER; +static int g_user_info_loading = 0; + static char *state_strdup_local(const char *s) { size_t n; char *out; @@ -337,7 +343,7 @@ int state_load_user_info(void) { free(g_state.user_settings_json); g_state.user_settings_json = NULL; - fprintf(stderr, "Attempting to fetch kind 10002 relays from bootstrap relays\n"); + nt_log("Attempting to fetch kind 10002 relays from bootstrap relays"); snprintf(filter, sizeof(filter), "{\"authors\":[\"%s\"],\"kinds\":[10002],\"limit\":1}", @@ -357,7 +363,7 @@ int state_load_user_info(void) { state_parse_relay_list(); relays = state_get_read_relays(&relay_count); - fprintf(stderr, "Attempting to fetch kinds [0,3,10096,17375] from user relays\n"); + nt_log("Attempting to fetch kinds [0,3,10096,17375] from user relays"); snprintf(filter, sizeof(filter), "{\"authors\":[\"%s\"],\"kinds\":[0,3,10096,17375],\"limit\":200}", @@ -424,7 +430,7 @@ int state_load_user_info(void) { free(kind10096_event); free(kind17375_event); - fprintf(stderr, "Attempting to fetch user-settings (kind 30078, d=user-settings)\n"); + nt_log("Attempting to fetch user-settings (kind 30078, d=user-settings)"); snprintf(filter, sizeof(filter), "{\"authors\":[\"%s\"],\"kinds\":[30078],\"#d\":[\"user-settings\"],\"limit\":1}", @@ -453,14 +459,13 @@ int state_load_user_info(void) { free(settings_event_json); } - fprintf(stderr, - "Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s user_settings=%s\n", - g_state.kind10002_json ? "ok" : "missing", - (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") != 0) ? "ok" : "missing", - (g_state.kind3_json && strstr(g_state.kind3_json, "\"tags\"") != NULL) ? "ok" : "missing", - (g_state.kind10096_json && strcmp(g_state.kind10096_json, "{}") != 0) ? "ok" : "missing", - (g_state.kind17375_json && strcmp(g_state.kind17375_json, "{}") != 0) ? "ok" : "missing", - g_state.user_settings_json ? "ok" : "missing"); + nt_log("Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s user_settings=%s", + g_state.kind10002_json ? "ok" : "missing", + (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") != 0) ? "ok" : "missing", + (g_state.kind3_json && strstr(g_state.kind3_json, "\"tags\"") != NULL) ? "ok" : "missing", + (g_state.kind10096_json && strcmp(g_state.kind10096_json, "{}") != 0) ? "ok" : "missing", + (g_state.kind17375_json && strcmp(g_state.kind17375_json, "{}") != 0) ? "ok" : "missing", + g_state.user_settings_json ? "ok" : "missing"); snprintf(g_state.user_name, sizeof(g_state.user_name), "%s", "not logged in"); if (g_state.kind0_json) { @@ -481,6 +486,52 @@ int state_load_user_info(void) { return 0; } +static void *state_user_info_load_worker(void *arg) { + (void)arg; + (void)state_load_user_info(); + + pthread_mutex_lock(&g_user_info_load_mutex); + g_user_info_loading = 0; + pthread_mutex_unlock(&g_user_info_load_mutex); + return NULL; +} + +int state_load_user_info_async(void) { + pthread_t tid; + pthread_attr_t attr; + int has_attr = 0; + + pthread_mutex_lock(&g_user_info_load_mutex); + if (g_user_info_loading) { + pthread_mutex_unlock(&g_user_info_load_mutex); + return 0; + } + g_user_info_loading = 1; + pthread_mutex_unlock(&g_user_info_load_mutex); + + if (pthread_attr_init(&attr) == 0) { + has_attr = 1; + (void)pthread_attr_setstacksize(&attr, 1024U * 1024U); + } + + if (pthread_create(&tid, has_attr ? &attr : NULL, state_user_info_load_worker, NULL) != 0) { + if (has_attr) { + (void)pthread_attr_destroy(&attr); + } + pthread_mutex_lock(&g_user_info_load_mutex); + g_user_info_loading = 0; + pthread_mutex_unlock(&g_user_info_load_mutex); + return -1; + } + + if (has_attr) { + (void)pthread_attr_destroy(&attr); + } + + (void)pthread_detach(tid); + return 0; +} + const char **state_get_read_relays(int *count) { if (g_state.read_relay_count > 0 && g_state.read_relays) { if (count) {