Skip to content

Commit 3efb33a

Browse files
authored
Merge pull request #148 from kmatzen/fix/issue-119-nonblocking-check-update
fix(#119): Make /api/check-update non-blocking
2 parents ef79a60 + 3099578 commit 3efb33a

File tree

3 files changed

+79
-24
lines changed

3 files changed

+79
-24
lines changed

host/updater.c

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include <stdlib.h>
77
#include <string.h>
88
#include <ctype.h>
9+
#include <pthread.h>
910

1011
static char latest_version[64] = {0};
11-
static int checked = 0;
12+
static int check_state = 0; /* 0=idle, 1=checking, 2=checked */
13+
static pthread_mutex_t check_mutex = PTHREAD_MUTEX_INITIALIZER;
1214

1315
static int parse_version(const char *s, int *major, int *minor, int *patch) {
1416
if (!s) return -1;
@@ -29,16 +31,13 @@ int updater_compare_versions(const char *a, const char *b) {
2931
return a_pat - b_pat;
3032
}
3133

32-
int updater_check(void) {
34+
/* Blocking implementation (runs in background thread) */
35+
static int do_check(void) {
3336
FILE *fp;
3437
char buf[4096];
3538
const char *tag;
3639
size_t len;
3740

38-
/*
39-
* Query GitHub Releases API. curl is available on Raspberry Pi OS
40-
* by default; the -s flag suppresses progress, -m limits timeout.
41-
*/
4241
fp = popen("curl -s -m 10 "
4342
"https://api.github.com/repos/kmatzen/millennium/releases/latest"
4443
" 2>/dev/null", "r");
@@ -55,20 +54,15 @@ int updater_check(void) {
5554
}
5655
buf[len] = '\0';
5756

58-
/*
59-
* Minimal JSON extraction: find "tag_name" : "vX.Y.Z"
60-
* Full JSON parsing is overkill for a single field.
61-
*/
6257
tag = strstr(buf, "\"tag_name\"");
6358
if (!tag) {
6459
logger_debug_with_category("Updater", "No tag_name in GitHub response (may have no releases)");
6560
return -1;
6661
}
6762

68-
/* Advance past the key and colon to the value */
6963
tag = strchr(tag + 10, '"');
7064
if (!tag) return -1;
71-
tag++; /* skip opening quote */
65+
tag++;
7266

7367
{
7468
const char *end = strchr(tag, '"');
@@ -77,7 +71,6 @@ int updater_check(void) {
7771
latest_version[end - tag] = '\0';
7872
}
7973

80-
checked = 1;
8174
{
8275
char log_msg[128];
8376
snprintf(log_msg, sizeof(log_msg),
@@ -88,13 +81,70 @@ int updater_check(void) {
8881
return 0;
8982
}
9083

84+
static void *check_thread_func(void *arg) {
85+
(void)arg;
86+
int rc = do_check();
87+
pthread_mutex_lock(&check_mutex);
88+
check_state = 2; /* checked */
89+
if (rc != 0) latest_version[0] = '\0';
90+
pthread_mutex_unlock(&check_mutex);
91+
return NULL;
92+
}
93+
94+
/* #119: Non-blocking. Starts background check if idle; returns immediately. */
95+
void updater_check_async(void) {
96+
pthread_t th;
97+
pthread_mutex_lock(&check_mutex);
98+
if (check_state == 0) {
99+
check_state = 1;
100+
pthread_mutex_unlock(&check_mutex);
101+
if (pthread_create(&th, NULL, check_thread_func, NULL) == 0) {
102+
pthread_detach(th);
103+
} else {
104+
pthread_mutex_lock(&check_mutex);
105+
check_state = 0;
106+
pthread_mutex_unlock(&check_mutex);
107+
}
108+
} else {
109+
pthread_mutex_unlock(&check_mutex);
110+
}
111+
}
112+
113+
/* Returns 1 if a check is in progress (curl running in background). */
114+
int updater_is_checking(void) {
115+
int s;
116+
pthread_mutex_lock(&check_mutex);
117+
s = (check_state == 1);
118+
pthread_mutex_unlock(&check_mutex);
119+
return s;
120+
}
121+
122+
/* Legacy blocking API; prefer updater_check_async for HTTP handlers. */
123+
int updater_check(void) {
124+
int rc = do_check();
125+
pthread_mutex_lock(&check_mutex);
126+
check_state = 2;
127+
if (rc != 0) latest_version[0] = '\0';
128+
pthread_mutex_unlock(&check_mutex);
129+
return rc;
130+
}
131+
91132
const char *updater_get_latest_version(void) {
92-
return checked ? latest_version : NULL;
133+
const char *v = NULL;
134+
pthread_mutex_lock(&check_mutex);
135+
if (check_state == 2 && latest_version[0]) v = latest_version;
136+
pthread_mutex_unlock(&check_mutex);
137+
return v;
93138
}
94139

95140
int updater_is_update_available(void) {
96-
if (!checked) return 0;
97-
return updater_compare_versions(latest_version, version_get_string()) > 0;
141+
const char *lv;
142+
int avail = 0;
143+
pthread_mutex_lock(&check_mutex);
144+
lv = (check_state == 2 && latest_version[0]) ? latest_version : NULL;
145+
pthread_mutex_unlock(&check_mutex);
146+
if (lv) avail = updater_compare_versions(lv, version_get_string()) > 0;
147+
return avail;
98148
}
99149

100150
static char apply_status[256] = "No update attempted";

host/updater.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@
1212
int updater_compare_versions(const char *a, const char *b);
1313

1414
/*
15-
* Check GitHub for the latest release tag.
16-
* Uses curl (must be installed) to query the GitHub Releases API.
17-
* Stores result internally; subsequent calls return the cached value
18-
* until updater_check() is called again.
19-
* Returns 0 on success, -1 on failure.
15+
* Check GitHub for the latest release tag (blocking; blocks up to 10s).
16+
* Prefer updater_check_async() for HTTP handlers.
2017
*/
2118
int updater_check(void);
2219

20+
/* #119: Non-blocking. Starts background check if idle; returns immediately. */
21+
void updater_check_async(void);
22+
23+
/* Returns 1 if a check is in progress. */
24+
int updater_is_checking(void);
25+
2326
/* Returns the cached latest version string, or NULL if never checked. */
2427
const char *updater_get_latest_version(void);
2528

host/web_server.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,15 +1404,17 @@ struct http_response web_server_handle_api_check_update(const struct http_reques
14041404
web_server_strcpy_safe(response.content_type, "application/json", sizeof(response.content_type));
14051405
response.status_code = 200;
14061406

1407-
updater_check();
1407+
/* #119: Non-blocking - don't block HTTP handler for curl timeout */
1408+
updater_check_async();
14081409
latest = updater_get_latest_version();
14091410

14101411
snprintf(json, sizeof(json),
14111412
"{\"current_version\":\"%s\",\"latest_version\":\"%s\","
1412-
"\"update_available\":%s,\"git_hash\":\"%s\"}",
1413+
"\"update_available\":%s,\"checking\":%s,\"git_hash\":\"%s\"}",
14131414
version_get_string(),
1414-
latest ? latest : "unknown",
1415+
latest ? latest : (updater_is_checking() ? "" : "unknown"),
14151416
updater_is_update_available() ? "true" : "false",
1417+
updater_is_checking() ? "true" : "false",
14161418
version_get_git_hash());
14171419
web_server_strcpy_safe(response.body, json, sizeof(response.body));
14181420
return response;

0 commit comments

Comments
 (0)