13544Sjbeck /*
23544Sjbeck * Copyright (c) 2006 Sendmail, Inc. and its suppliers.
33544Sjbeck * All rights reserved.
43544Sjbeck *
53544Sjbeck * By using this file, you agree to the terms and conditions set
63544Sjbeck * forth in the LICENSE file which can be found at the top level of
73544Sjbeck * the sendmail distribution.
83544Sjbeck *
93544Sjbeck */
103544Sjbeck
113544Sjbeck #pragma ident "%Z%%M% %I% %E% SMI"
123544Sjbeck
13*5402Sjbeck #include <sm/gen.h>
14*5402Sjbeck SM_RCSID("@(#)$Id: monitor.c,v 8.7 2007/04/23 16:26:28 ca Exp $")
153544Sjbeck #include "libmilter.h"
163544Sjbeck
173544Sjbeck #if _FFR_THREAD_MONITOR
183544Sjbeck
193544Sjbeck /*
203544Sjbeck ** Thread Monitoring
213544Sjbeck ** Todo: more error checking (return code from function calls)
223544Sjbeck ** add comments.
233544Sjbeck */
243544Sjbeck
253544Sjbeck bool Monitor = false; /* use monitoring? */
263544Sjbeck static unsigned int Mon_exec_time = 0;
273544Sjbeck
283544Sjbeck /* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */
293544Sjbeck static smutex_t Mon_mutex;
303544Sjbeck static scond_t Mon_cv;
313544Sjbeck
323544Sjbeck /*
333544Sjbeck ** Current ctx to monitor.
343544Sjbeck ** Invariant:
353544Sjbeck ** Mon_cur_ctx == NULL || Mon_cur_ctx is thread which was started the longest
363544Sjbeck ** time ago.
373544Sjbeck **
383544Sjbeck ** Basically the entries in the list are ordered by time because new
393544Sjbeck ** entries are appended at the end. However, due to the concurrent
403544Sjbeck ** execution (multi-threaded) and no guaranteed order of wakeups
413544Sjbeck ** after a mutex_lock() attempt, the order might not be strict,
423544Sjbeck ** i.e., if the list contains e1 and e2 (in that order) then
433544Sjbeck ** the the start time of e2 can be (slightly) smaller than that of e1.
443544Sjbeck ** However, this slight inaccurracy should not matter for the proper
453544Sjbeck ** working of this algorithm.
463544Sjbeck */
473544Sjbeck
483544Sjbeck static SMFICTX_PTR Mon_cur_ctx = NULL;
493544Sjbeck static smfi_hd_T Mon_ctx_head; /* head of the linked list of active contexts */
503544Sjbeck
513544Sjbeck /*
523544Sjbeck ** SMFI_SET_MAX_EXEC_TIME -- set maximum execution time for a thread
533544Sjbeck **
543544Sjbeck ** Parameters:
553544Sjbeck ** tm -- maximum execution time for a thread
563544Sjbeck **
573544Sjbeck ** Returns:
583544Sjbeck ** MI_SUCCESS
593544Sjbeck */
603544Sjbeck
613544Sjbeck int
smfi_set_max_exec_time(tm)623544Sjbeck smfi_set_max_exec_time(tm)
633544Sjbeck unsigned int tm;
643544Sjbeck {
653544Sjbeck Mon_exec_time = tm;
663544Sjbeck return MI_SUCCESS;
673544Sjbeck }
683544Sjbeck
693544Sjbeck /*
703544Sjbeck ** MI_MONITOR_THREAD -- monitoring thread
713544Sjbeck **
723544Sjbeck ** Parameters:
733544Sjbeck ** arg -- ignored (required by pthread_create())
743544Sjbeck **
753544Sjbeck ** Returns:
763544Sjbeck ** NULL on termination.
773544Sjbeck */
783544Sjbeck
793544Sjbeck static void *
mi_monitor_thread(arg)803544Sjbeck mi_monitor_thread(arg)
813544Sjbeck void *arg;
823544Sjbeck {
833544Sjbeck sthread_t tid;
843544Sjbeck int r;
853544Sjbeck time_t now, end;
863544Sjbeck
873544Sjbeck SM_ASSERT(Monitor);
883544Sjbeck SM_ASSERT(Mon_exec_time > 0);
893544Sjbeck tid = (sthread_t) sthread_get_id();
903544Sjbeck if (pthread_detach(tid) != 0)
913544Sjbeck {
923544Sjbeck /* log an error */
933544Sjbeck return (void *)1;
943544Sjbeck }
953544Sjbeck
963544Sjbeck /*
973544Sjbeck ** NOTE: this is "flow through" code,
983544Sjbeck ** do NOT use do { } while ("break" is used here!)
993544Sjbeck */
1003544Sjbeck
1013544Sjbeck #define MON_CHK_STOP \
1023544Sjbeck now = time(NULL); \
1033544Sjbeck end = Mon_cur_ctx->ctx_start + Mon_exec_time; \
1043544Sjbeck if (now > end) \
1053544Sjbeck { \
1063544Sjbeck smi_log(SMI_LOG_ERR, \
1073544Sjbeck "WARNING: monitor timeout triggered, now=%ld, end=%ld, tid=%ld, state=0x%x",\
1083544Sjbeck (long) now, (long) end, \
1093544Sjbeck (long) Mon_cur_ctx->ctx_id, Mon_cur_ctx->ctx_state);\
1103544Sjbeck mi_stop_milters(MILTER_STOP); \
1113544Sjbeck break; \
1123544Sjbeck }
1133544Sjbeck
1143544Sjbeck (void) smutex_lock(&Mon_mutex);
1153544Sjbeck while (mi_stop() == MILTER_CONT)
1163544Sjbeck {
1173544Sjbeck if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
1183544Sjbeck {
1193544Sjbeck struct timespec abstime;
1203544Sjbeck
1213544Sjbeck MON_CHK_STOP;
1223544Sjbeck abstime.tv_sec = end;
1233544Sjbeck abstime.tv_nsec = 0;
1243544Sjbeck r = pthread_cond_timedwait(&Mon_cv, &Mon_mutex,
1253544Sjbeck &abstime);
1263544Sjbeck }
1273544Sjbeck else
1283544Sjbeck r = pthread_cond_wait(&Mon_cv, &Mon_mutex);
1293544Sjbeck if (mi_stop() != MILTER_CONT)
1303544Sjbeck break;
1313544Sjbeck if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
1323544Sjbeck {
1333544Sjbeck MON_CHK_STOP;
1343544Sjbeck }
1353544Sjbeck }
1363544Sjbeck (void) smutex_unlock(&Mon_mutex);
1373544Sjbeck
1383544Sjbeck return NULL;
1393544Sjbeck }
1403544Sjbeck
1413544Sjbeck /*
1423544Sjbeck ** MI_MONITOR_INIT -- initialize monitoring thread
1433544Sjbeck **
1443544Sjbeck ** Parameters: none
1453544Sjbeck **
1463544Sjbeck ** Returns:
1473544Sjbeck ** MI_SUCCESS/MI_FAILURE
1483544Sjbeck */
1493544Sjbeck
1503544Sjbeck int
mi_monitor_init()1513544Sjbeck mi_monitor_init()
1523544Sjbeck {
1533544Sjbeck int r;
1543544Sjbeck sthread_t tid;
1553544Sjbeck
1563544Sjbeck SM_ASSERT(!Monitor);
1573544Sjbeck if (Mon_exec_time <= 0)
1583544Sjbeck return MI_SUCCESS;
1593544Sjbeck Monitor = true;
1603544Sjbeck if (!smutex_init(&Mon_mutex))
1613544Sjbeck return MI_FAILURE;
1623544Sjbeck if (scond_init(&Mon_cv) != 0)
1633544Sjbeck return MI_FAILURE;
1643544Sjbeck SM_TAILQ_INIT(&Mon_ctx_head);
1653544Sjbeck
1663544Sjbeck r = thread_create(&tid, mi_monitor_thread, (void *)NULL);
1673544Sjbeck if (r != 0)
1683544Sjbeck return r;
1693544Sjbeck return MI_SUCCESS;
1703544Sjbeck }
1713544Sjbeck
1723544Sjbeck /*
1733544Sjbeck ** MI_MONITOR_WORK_BEGIN -- record start of thread execution
1743544Sjbeck **
1753544Sjbeck ** Parameters:
1763544Sjbeck ** ctx -- session context
1773544Sjbeck ** cmd -- milter command char
1783544Sjbeck **
1793544Sjbeck ** Returns:
1803544Sjbeck ** 0
1813544Sjbeck */
1823544Sjbeck
1833544Sjbeck int
mi_monitor_work_begin(ctx,cmd)1843544Sjbeck mi_monitor_work_begin(ctx, cmd)
1853544Sjbeck SMFICTX_PTR ctx;
1863544Sjbeck int cmd;
1873544Sjbeck {
1883544Sjbeck (void) smutex_lock(&Mon_mutex);
1893544Sjbeck if (NULL == Mon_cur_ctx)
1903544Sjbeck {
1913544Sjbeck Mon_cur_ctx = ctx;
1923544Sjbeck (void) scond_signal(&Mon_cv);
1933544Sjbeck }
1943544Sjbeck ctx->ctx_start = time(NULL);
1953544Sjbeck SM_TAILQ_INSERT_TAIL(&Mon_ctx_head, ctx, ctx_mon_link);
1963544Sjbeck (void) smutex_unlock(&Mon_mutex);
1973544Sjbeck return 0;
1983544Sjbeck }
1993544Sjbeck
2003544Sjbeck /*
2013544Sjbeck ** MI_MONITOR_WORK_END -- record end of thread execution
2023544Sjbeck **
2033544Sjbeck ** Parameters:
2043544Sjbeck ** ctx -- session context
2053544Sjbeck ** cmd -- milter command char
2063544Sjbeck **
2073544Sjbeck ** Returns:
2083544Sjbeck ** 0
2093544Sjbeck */
2103544Sjbeck
2113544Sjbeck int
mi_monitor_work_end(ctx,cmd)2123544Sjbeck mi_monitor_work_end(ctx, cmd)
2133544Sjbeck SMFICTX_PTR ctx;
2143544Sjbeck int cmd;
2153544Sjbeck {
2163544Sjbeck (void) smutex_lock(&Mon_mutex);
2173544Sjbeck ctx->ctx_start = 0;
2183544Sjbeck SM_TAILQ_REMOVE(&Mon_ctx_head, ctx, ctx_mon_link);
2193544Sjbeck if (Mon_cur_ctx == ctx)
2203544Sjbeck {
2213544Sjbeck if (SM_TAILQ_EMPTY(&Mon_ctx_head))
2223544Sjbeck Mon_cur_ctx = NULL;
2233544Sjbeck else
2243544Sjbeck Mon_cur_ctx = SM_TAILQ_FIRST(&Mon_ctx_head);
2253544Sjbeck }
2263544Sjbeck (void) smutex_unlock(&Mon_mutex);
2273544Sjbeck return 0;
2283544Sjbeck }
2293544Sjbeck #endif /* _FFR_THREAD_MONITOR */
230