Skip to content

Commit 4f5fddc

Browse files
committed
afpd: reap idle Spotlight queries to prevent resource leaks
Clients that disconnect or crash without sending closeQueryForContext leave server-side query objects allocated indefinitely. Add a MAX_SL_QUERY_IDLE_TIME (60 s) constant and slq_idle_cleanup(), which scans the active query list on every Spotlight RPC and cancels or destroys any query not polled within the idle window. The idle timer (slq_time) is reset on each fetchQueryResultsForContext call so actively-polled queries are not reaped. The cleanup runs alongside the existing slq_cancelled_cleanup() in afp_spotlight_rpc().
1 parent b5a7471 commit 4f5fddc

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

etc/afpd/spotlight.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "etc/spotlight/sparql_parser.h"
4848

4949
#define MAX_SL_RESULTS 20
50+
#define MAX_SL_QUERY_IDLE_TIME 60
5051

5152
struct timeval convert_timespec_to_timeval(const struct timespec ts)
5253
{
@@ -599,6 +600,60 @@ static void slq_cancelled_cleanup(void)
599600
return;
600601
}
601602

603+
/*!
604+
* @brief Cancel or destroy queries idle longer than MAX_SL_QUERY_IDLE_TIME
605+
*
606+
* Called on every Spotlight RPC to reap queries abandoned by clients that
607+
* disconnected or crashed without sending closeQueryForContext.
608+
*/
609+
static void slq_idle_cleanup(void)
610+
{
611+
struct list_head *p;
612+
slq_t *q = NULL;
613+
time_t now = time(NULL);
614+
bool found;
615+
616+
/*
617+
* list_for_each is not safe for deletion, so restart after each
618+
* mutation. The active query list is short and bounded, so the
619+
* overhead is negligible.
620+
*/
621+
do {
622+
found = false;
623+
list_for_each(p, &sl_queries) {
624+
q = list_entry(p, slq_t, slq_list);
625+
626+
if (q->slq_time > now || now - q->slq_time < MAX_SL_QUERY_IDLE_TIME) {
627+
continue;
628+
}
629+
630+
LOG(log_debug, logtype_sl,
631+
"ctx1: %" PRIx64 ", ctx2: %" PRIx64 ": idle timeout, state: %s",
632+
q->slq_ctx1, q->slq_ctx2,
633+
slq_state_names[q->slq_state].state_name);
634+
635+
switch (q->slq_state) {
636+
case SLQ_STATE_DONE:
637+
case SLQ_STATE_FULL:
638+
case SLQ_STATE_ERROR:
639+
slq_destroy(q);
640+
break;
641+
642+
case SLQ_STATE_RUNNING:
643+
case SLQ_STATE_RESULTS:
644+
slq_cancel(q);
645+
break;
646+
647+
default:
648+
break;
649+
}
650+
651+
found = true;
652+
break;
653+
}
654+
} while (found);
655+
}
656+
602657
static void slq_dump(void)
603658
{
604659
struct list_head *p;
@@ -1016,6 +1071,9 @@ static int sl_rpc_fetchQueryResultsForContext(const AFPObj *obj,
10161071
EC_FAIL;
10171072
}
10181073

1074+
/* Reset idle timer: client is still actively polling this query */
1075+
slq->slq_time = time(NULL);
1076+
10191077
switch (slq->slq_state) {
10201078
case SLQ_STATE_RUNNING:
10211079
case SLQ_STATE_RESULTS:
@@ -1412,6 +1470,7 @@ int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen,
14121470
}
14131471

14141472
slq_cancelled_cleanup();
1473+
slq_idle_cleanup();
14151474
ibuf += 2;
14161475
ibuflen -= 2;
14171476
vid = SVAL(ibuf, 0);

include/atalk/spotlight.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ typedef struct _slq_t {
9494
AFPObj *slq_obj; /*!< global AFPObj handle */
9595
const struct vol *slq_vol; /*!< volume handle */
9696
char *slq_scope; /*!< search scope */
97-
time_t slq_time; /*!< timestamp received query */
97+
time_t slq_time; /*!< last client activity timestamp */
9898
uint64_t slq_ctx1; /*!< client context 1 */
9999
uint64_t slq_ctx2; /*!< client context 2 */
100100
sl_array_t *slq_reqinfo; /*!< array with requested metadata */

0 commit comments

Comments
 (0)