diff --git a/src/builder/b-nspawn.c b/src/builder/b-nspawn.c
index ad60019..3022362 100644
--- a/src/builder/b-nspawn.c
+++ b/src/builder/b-nspawn.c
@@ -512,6 +512,7 @@ saib_spawn_script(struct sai_nspawn *ns)
const char *env[] = {
"PATH=/usr/local/bin:/usr/bin:/bin",
"LANG=en_US.UTF-8",
+ "TERM=xterm-256color",
NULL
};
char one_step[4096];
diff --git a/src/builder/b-ws-server.c b/src/builder/b-ws-server.c
index 2f1e344..272e84e 100644
--- a/src/builder/b-ws-server.c
+++ b/src/builder/b-ws-server.c
@@ -186,7 +186,22 @@ saib_m_rx(void *userobj, const uint8_t *in, size_t len, int flags)
can = (sai_cancel_t *)a.dest;
- lwsl_notice("%s: received task cancel for %s\n", __func__, can->task_uuid);
+ lwsl_notice("%s: received task cancel for %s, erase %d\n", __func__, can->task_uuid, can->erase);
+
+ if (can->erase) {
+#if !defined(WIN32)
+ if (write(builder.pipe_master_wr, can->task_uuid,
+ LWS_POSIX_LENGTH_CAST(strlen(can->task_uuid))) != (ssize_t)strlen(can->task_uuid))
+ lwsl_err("%s: failed to write to deletion worker\n",
+ __func__);
+#else
+ DWORD written;
+ if (!WriteFile(builder.pipe_master_wr_win, can->task_uuid,
+ (DWORD)strlen(can->task_uuid), &written, NULL))
+ lwsl_err("%s: failed to write to deletion worker\n",
+ __func__);
+#endif
+ }
lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1,
builder.sai_plat_owner.head) {
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 222cedc..d316703 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -268,6 +268,7 @@ typedef struct sai_rejection {
typedef struct sai_cancel {
struct lws_dll2 list;
char task_uuid[65];
+ unsigned int erase;
} sai_cancel_t;
/*
@@ -718,7 +719,7 @@ extern const lws_struct_map_t
lsm_plat_list[1],
lsm_schema_map_plat[1],
lsm_task_rej[4],
- lsm_task_cancel[1],
+ lsm_task_cancel[2],
lsm_schema_json_map_can[1],
lsm_schema_json_map_task[1],
lsm_schema_json_map_event[1],
diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c
index 85b6f9a..1c9dd09 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -203,6 +203,7 @@ const lws_struct_map_t lsm_schema_json_task_rej[] = {
const lws_struct_map_t lsm_task_cancel[] = {
LSM_CARRAY (sai_cancel_t, task_uuid, "task_uuid"),
+ LSM_UNSIGNED (sai_cancel_t, erase, "erase"),
};
const lws_struct_map_t lsm_rebuild[] = {
diff --git a/src/server/s-central.c b/src/server/s-central.c
index 122204b..aff0f0c 100644
--- a/src/server/s-central.c
+++ b/src/server/s-central.c
@@ -186,6 +186,82 @@ sais_ensure_tables(struct vhd *vhd)
}
void
+sais_central_gc_deleted_events_cb(lws_sorted_usec_list_t *sul)
+{
+ struct vhd *vhd = lws_container_of(sul, struct vhd, sul_gc_events);
+ char q[128], event_uuid[65], esc[96];
+ sqlite3_stmt *sm, *tsm;
+ sqlite3 *pdb = NULL;
+
+ /* Find one event that is in SAIES_DELETED (7) state */
+ lws_snprintf(q, sizeof(q), "SELECT uuid FROM events WHERE state = %d LIMIT 1", SAIES_DELETED);
+
+ if (sqlite3_prepare_v2(vhd->server.pdb, q, -1, &sm, NULL) != SQLITE_OK)
+ return;
+
+ if (sqlite3_step(sm) != SQLITE_ROW) {
+ sqlite3_finalize(sm);
+ return; /* Nothing to GC */
+ }
+
+ lws_strncpy(event_uuid, (const char *)sqlite3_column_text(sm, 0), sizeof(event_uuid));
+ sqlite3_finalize(sm);
+
+ if (sai_event_db_ensure_open(vhd->context, &vhd->sqlite3_cache,
+ vhd->sqlite3_path_lhs, event_uuid, 0, &pdb))
+ return;
+
+ /* Pick 1 task that is stopped to garbage collect */
+ /* 0=WAITING, 3=SUCCESS, 4=FAIL, 5=CANCELLED */
+ lws_snprintf(q, sizeof(q), "SELECT uuid FROM tasks WHERE state IN (0, 3, 4, 5) LIMIT 1");
+
+ if (sqlite3_prepare_v2(pdb, q, -1, &tsm, NULL) == SQLITE_OK) {
+ if (sqlite3_step(tsm) == SQLITE_ROW) {
+ const char *u = (const char *)sqlite3_column_text(tsm, 0);
+ if (u) {
+ char tu[65];
+ lws_strncpy(tu, u, sizeof(tu));
+ lws_sql_purify(esc, tu, sizeof(esc));
+
+ sais_task_cancel(vhd, tu, 1);
+
+ lws_snprintf(q, sizeof(q), "DELETE FROM logs WHERE task_uuid='%s'", esc);
+ sqlite3_exec(pdb, q, NULL, NULL, NULL);
+
+ lws_snprintf(q, sizeof(q), "DELETE FROM artifacts WHERE task_uuid='%s'", esc);
+ sqlite3_exec(pdb, q, NULL, NULL, NULL);
+
+ lws_snprintf(q, sizeof(q), "DELETE FROM tasks WHERE uuid='%s'", esc);
+ sqlite3_exec(pdb, q, NULL, NULL, NULL);
+ }
+ }
+ sqlite3_finalize(tsm);
+ }
+
+ /* Check if the event DB is completely empty of tasks now */
+ int remaining = 1; /* Assume 1 just in case query fails */
+ if (sqlite3_prepare_v2(pdb, "SELECT count(*) FROM tasks", -1, &tsm, NULL) == SQLITE_OK) {
+ if (sqlite3_step(tsm) == SQLITE_ROW)
+ remaining = sqlite3_column_int(tsm, 0);
+ sqlite3_finalize(tsm);
+ }
+ sai_event_db_close(&vhd->sqlite3_cache, &pdb);
+
+ if (remaining == 0) {
+ /* Everything is gone, we can delete the logical event and unlink the sqlite files safely */
+ lws_sql_purify(esc, event_uuid, sizeof(esc));
+ lws_snprintf(q, sizeof(q), "DELETE FROM events WHERE uuid='%s'", esc);
+ sqlite3_exec(vhd->server.pdb, q, NULL, NULL, NULL);
+ sai_event_db_delete_database(vhd->sqlite3_path_lhs, event_uuid);
+ lwsl_notice("%s: Completed GC for deleted event %s\n", __func__, event_uuid);
+ }
+
+ /* Keep yielding and re-scheduling as long as there's still work to do on this or other events */
+ lws_sul_schedule(vhd->context, 0, &vhd->sul_gc_events,
+ sais_central_gc_deleted_events_cb, 10 * LWS_US_PER_MS); /* 10ms for next task */
+}
+
+void
sais_central_cb(lws_sorted_usec_list_t *sul)
{
struct vhd *vhd = lws_container_of(sul, struct vhd, sul_central);
@@ -214,6 +290,10 @@ sais_central_cb(lws_sorted_usec_list_t *sul)
sais_prune_inflight_list(vhd);
sais_platforms_with_tasks_pending(vhd);
+ if (!vhd->sul_gc_events.list.owner)
+ lws_sul_schedule(context, 0, &vhd->sul_gc_events,
+ sais_central_gc_deleted_events_cb, 10 * LWS_US_PER_MS);
+
/* check again in 1s */
lws_sul_schedule(context, 0, &vhd->sul_central, sais_central_cb,
diff --git a/src/server/s-private.h b/src/server/s-private.h
index f3b3f7a..0f31e8a 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -231,6 +231,7 @@ struct vhd {
lws_sorted_usec_list_t sul_logcache;
lws_sorted_usec_list_t sul_central; /* background task allocation sul */
lws_sorted_usec_list_t sul_activity; /* activity broadcast sul */
+ lws_sorted_usec_list_t sul_gc_events; /* incremental GC of deleted events */
lws_usec_t last_check_abandoned_tasks;
@@ -292,13 +293,16 @@ sais_central_cb(lws_sorted_usec_list_t *sul);
void
sais_activity_cb(lws_sorted_usec_list_t *sul);
+void
+sais_central_gc_deleted_events_cb(lws_sorted_usec_list_t *sul);
+
sai_db_result_t
sais_task_clear_build_and_logs(struct vhd *vhd, const char *task_uuid, int from_rejection);
sai_db_result_t
sais_task_rebuild_last_step(struct vhd *vhd, const char *task_uuid);
int
-sais_task_cancel(struct vhd *vhd, const char *task_uuid);
+sais_task_cancel(struct vhd *vhd, const char *task_uuid, int erase);
int
sais_allocate_task(struct vhd *vhd, struct pss *pss, sai_plat_t *cb,
@@ -400,7 +404,7 @@ void
sais_get_task_metrics_estimates(struct vhd *vhd, sai_task_t *task);
int
-sais_task_cancel(struct vhd *vhd, const char *task_uuid);
+sais_task_cancel(struct vhd *vhd, const char *task_uuid, int erase);
int
sais_task_stop_on_builders(struct vhd *vhd, const char *task_uuid);
diff --git a/src/server/s-task-helpers.c b/src/server/s-task-helpers.c
index f2cfec6..cf07ca8 100644
--- a/src/server/s-task-helpers.c
+++ b/src/server/s-task-helpers.c
@@ -418,7 +418,7 @@ sais_task_pause(struct vhd *vhd, const char *task_uuid)
}
int
-sais_task_cancel(struct vhd *vhd, const char *task_uuid)
+sais_task_cancel(struct vhd *vhd, const char *task_uuid, int erase)
{
sai_cancel_t *can;
@@ -438,6 +438,7 @@ sais_task_cancel(struct vhd *vhd, const char *task_uuid)
memset(can, 0, sizeof(*can));
lws_strncpy(can->task_uuid, task_uuid, sizeof(can->task_uuid));
+ can->erase = (unsigned int)erase;
lws_dll2_add_tail(&can->list, &pss->task_cancel_owner);
@@ -526,6 +527,7 @@ sais_task_stop_on_builders(struct vhd *vhd, const char *task_uuid)
memset(can, 0, sizeof(*can));
lws_strncpy(can->task_uuid, task_uuid, sizeof(can->task_uuid));
+ can->erase = 0;
lws_dll2_add_tail(&can->list, &pss_match->task_cancel_owner);
lws_callback_on_writable(pss_match->wsi);
diff --git a/src/server/s-task.c b/src/server/s-task.c
index 901fa09..a040e58 100644
--- a/src/server/s-task.c
+++ b/src/server/s-task.c
@@ -172,7 +172,7 @@ sais_task_pending(struct vhd *vhd, struct pss *pss, sai_plat_t *cb,
const char *platform)
{
struct lwsac *ac = NULL, *failed_ac = NULL;
- char esc_plat[96], pf[2048], query[384];
+ char esc_plat[96], esc_bname[128], pf[2048], query[384];
lws_dll2_owner_t o, failed_tasks_owner;
typedef struct sai_failed_task_info {
lws_dll2_t list;
@@ -184,6 +184,7 @@ sais_task_pending(struct vhd *vhd, struct pss *pss, sai_plat_t *cb,
int n;
lws_sql_purify(esc_plat, platform, sizeof(esc_plat));
+ lws_sql_purify(esc_bname, cb->name, sizeof(esc_bname));
assert(platform);
/*
@@ -233,7 +234,9 @@ sais_task_pending(struct vhd *vhd, struct pss *pss, sai_plat_t *cb,
*/
lws_snprintf(query, sizeof(query), "select count(state) from tasks where "
- "(state = 0 or state = 9) and platform = '%s'", esc_plat);
+ "(state = 0 or state = 9) and platform = '%s' and "
+ "(builder_name IS NULL or builder_name = '' or builder_name = '%s')",
+ esc_plat, esc_bname);
m = sqlite3_exec(pdb, query, sql3_get_integer_cb, &pending_count, NULL);
if (m != SQLITE_OK) {
@@ -365,8 +368,9 @@ sais_task_pending(struct vhd *vhd, struct pss *pss, sai_plat_t *cb,
lws_sql_purify(esc_taskname, fti->taskname, sizeof(esc_taskname));
lws_snprintf(pf, sizeof(pf),
" and (state == 0 or state == 9) and "
- "(platform == '%s') and (taskname == '%s')",
- esc_plat, esc_taskname);
+ "(platform == '%s') and (taskname == '%s') and "
+ "(builder_name IS NULL or builder_name == '' or builder_name == '%s')",
+ esc_plat, esc_taskname, esc_bname);
lwsac_free(&pss->ac_alloc_task);
lws_dll2_owner_clear(&owner);
@@ -395,8 +399,9 @@ next1: ;
/* We have fallen back to doing tasks earliest-first */
lws_snprintf(pf, sizeof(pf),
- " and (state = 0 or state = 9) and (platform = '%s')",
- esc_plat);
+ " and (state = 0 or state = 9) and (platform = '%s') and "
+ "(builder_name IS NULL or builder_name = '' or builder_name = '%s')",
+ esc_plat, esc_bname);
lwsac_free(&pss->ac_alloc_task);
lws_dll2_owner_t owner;
diff --git a/src/server/s-webops.c b/src/server/s-webops.c
index f2f9444..496ec83 100644
--- a/src/server/s-webops.c
+++ b/src/server/s-webops.c
@@ -272,65 +272,27 @@ sai_db_result_t
sais_event_delete(struct vhd *vhd, const char *event_uuid)
{
char qu[128], esc[96], pre[LWS_PRE + 128];
- struct lwsac *ac = NULL;
lws_wsmsg_info_t info;
sqlite3 *pdb = NULL;
- lws_dll2_owner_t o;
char *err = NULL;
+ sqlite3_stmt *sm;
size_t len;
int ret;
- if (sai_event_db_ensure_open(vhd->context, &vhd->sqlite3_cache,
- vhd->sqlite3_path_lhs, event_uuid, 0, &pdb) == 0) {
- if (lws_struct_sq3_deserialize(pdb, NULL, NULL,
- lsm_schema_sq3_map_task,
- &o, &ac, 0, 999) >= 0) {
-
- ret = sqlite3_exec(pdb, "BEGIN TRANSACTION", NULL, NULL, &err);
- if (ret != SQLITE_OK) {
- sai_event_db_close(&vhd->sqlite3_cache, &pdb);
- lwsac_free(&ac);
- if (ret == SQLITE_BUSY)
- return SAI_DB_RESULT_BUSY;
- return SAI_DB_RESULT_ERROR;
- }
-
- lws_start_foreach_dll(struct lws_dll2 *, p, o.head) {
- sai_task_t *t = lws_container_of(p, sai_task_t, list);
-
- if (t->state != SAIES_WAITING &&
- t->state != SAIES_SUCCESS &&
- t->state != SAIES_FAIL &&
- t->state != SAIES_CANCELLED)
- sais_task_cancel(vhd, t->uuid);
-
- } lws_end_foreach_dll(p);
-
- ret = sqlite3_exec(pdb, "END TRANSACTION", NULL, NULL, &err);
- if (ret != SQLITE_OK) {
- sai_event_db_close(&vhd->sqlite3_cache, &pdb);
- lwsac_free(&ac);
- if (ret == SQLITE_BUSY)
- return SAI_DB_RESULT_BUSY;
- return SAI_DB_RESULT_ERROR;
- }
- }
- sai_event_db_close(&vhd->sqlite3_cache, &pdb);
- lwsac_free(&ac);
- }
-
lws_sql_purify(esc, event_uuid, sizeof(esc));
- lws_snprintf(qu, sizeof(qu), "delete from events where uuid='%s'", esc);
+
+ /* 1. Mark event as SAIES_DELETED immediately so it disappears from UI */
+ lws_snprintf(qu, sizeof(qu), "update events set state=%d where uuid='%s'", SAIES_DELETED, esc);
ret = sqlite3_exec(vhd->server.pdb, qu, NULL, NULL, &err);
if (ret != SQLITE_OK) {
if (ret == SQLITE_BUSY)
return SAI_DB_RESULT_BUSY;
- lwsl_err("%s: evdel uuid %s, sq3 err %s\n", __func__, esc, err);
+ lwsl_err("%s: evdel mark uuid %s, sq3 err %s\n", __func__, esc, err);
sqlite3_free(err);
return SAI_DB_RESULT_ERROR;
}
- sai_event_db_delete_database(vhd->sqlite3_path_lhs, event_uuid);
+ /* 2. Broadcast change to UI */
sais_eventchange(vhd->h_ss_websrv, event_uuid, SAIES_DELETED);
len = (size_t)lws_snprintf(pre + LWS_PRE, sizeof(pre) - LWS_PRE,
@@ -347,6 +309,28 @@ sais_event_delete(struct vhd *vhd, const char *event_uuid)
return SAI_DB_RESULT_ERROR;
}
+ /* 3. Drop active builders gracefully without loading huge JSON objects */
+ if (sai_event_db_ensure_open(vhd->context, &vhd->sqlite3_cache,
+ vhd->sqlite3_path_lhs, event_uuid, 0, &pdb) == 0) {
+ lws_snprintf(qu, sizeof(qu), "SELECT uuid FROM tasks WHERE state != 0 AND state != 3 AND state != 4 AND state != 5");
+ if (sqlite3_prepare_v2(pdb, qu, -1, &sm, NULL) == SQLITE_OK) {
+ while (sqlite3_step(sm) == SQLITE_ROW) {
+ const unsigned char *task_uuid = sqlite3_column_text(sm, 0);
+ if (task_uuid)
+ sais_task_cancel(vhd, (const char *)task_uuid, 0);
+ }
+ sqlite3_finalize(sm);
+ }
+ sai_event_db_close(&vhd->sqlite3_cache, &pdb);
+ }
+
+ /*
+ * Incrementally garbage collect the tasks later in sais_central_cb,
+ * which eventually drops the DB and erases the event row entirely.
+ */
+ lws_sul_schedule(vhd->context, 0, &vhd->sul_gc_events,
+ sais_central_gc_deleted_events_cb, 1);
+
/*
* Recompute startable task platforms and broadcast to all sai-power,
* after there has been a change in tasks
diff --git a/src/server/s-ws-web.c b/src/server/s-ws-web.c
index 70377d9..5311f09 100644
--- a/src/server/s-ws-web.c
+++ b/src/server/s-ws-web.c
@@ -597,7 +597,7 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
if (sais_validate_id(ei->event_hash, SAI_TASKID_LEN))
goto soft_error;
- sais_task_cancel(m->vhd, ei->event_hash);
+ sais_task_cancel(m->vhd, ei->event_hash, 0);
break;
diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c
index fef69ef..fb45c88 100644
--- a/src/web/w-ws-browser.c
+++ b/src/web/w-ws-browser.c
@@ -887,17 +887,17 @@ saiw_browser_queue_overview(struct vhd *vhd, struct pss *pss)
if (pss->specific_project[0]) {
lws_sql_purify(esc, pss->specific_project, sizeof(esc) - 1);
- lws_snprintf(filt, sizeof(filt), " and repo_name=\"%s\"", esc);
+ lws_snprintf(filt, sizeof(filt), " and state != %d and repo_name=\"%s\"", SAIES_DELETED, esc);
n = -1;
+ } else {
+ lws_snprintf(filt, sizeof(filt), " and state != %d", SAIES_DELETED);
}
unsigned int total_events = 0;
{
char q[256];
sqlite3_stmt *stmt;
- lws_snprintf(q, sizeof(q), "SELECT COUNT(*) FROM events%s%s",
- filt[0] ? " WHERE " : "",
- filt[0] ? filt + 5 : "");
+ lws_snprintf(q, sizeof(q), "SELECT COUNT(*) FROM events WHERE %s", filt + 5);
if (sqlite3_prepare_v2(vhd->pdb, q, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW)
total_events = (unsigned int)sqlite3_column_int(stmt, 0);