/* * Copyright (c) 2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ #include SM_RCSID("@(#)$Id: monitor.c,v 8.7 2007/04/23 16:26:28 ca Exp $") #include "libmilter.h" #if _FFR_THREAD_MONITOR /* ** Thread Monitoring ** Todo: more error checking (return code from function calls) ** add comments. */ bool Monitor = false; /* use monitoring? */ static unsigned int Mon_exec_time = 0; /* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */ static smutex_t Mon_mutex; static scond_t Mon_cv; /* ** Current ctx to monitor. ** Invariant: ** Mon_cur_ctx == NULL || Mon_cur_ctx is thread which was started the longest ** time ago. ** ** Basically the entries in the list are ordered by time because new ** entries are appended at the end. However, due to the concurrent ** execution (multi-threaded) and no guaranteed order of wakeups ** after a mutex_lock() attempt, the order might not be strict, ** i.e., if the list contains e1 and e2 (in that order) then ** the the start time of e2 can be (slightly) smaller than that of e1. ** However, this slight inaccurracy should not matter for the proper ** working of this algorithm. */ static SMFICTX_PTR Mon_cur_ctx = NULL; static smfi_hd_T Mon_ctx_head; /* head of the linked list of active contexts */ /* ** SMFI_SET_MAX_EXEC_TIME -- set maximum execution time for a thread ** ** Parameters: ** tm -- maximum execution time for a thread ** ** Returns: ** MI_SUCCESS */ int smfi_set_max_exec_time(tm) unsigned int tm; { Mon_exec_time = tm; return MI_SUCCESS; } /* ** MI_MONITOR_THREAD -- monitoring thread ** ** Parameters: ** arg -- ignored (required by pthread_create()) ** ** Returns: ** NULL on termination. */ static void * mi_monitor_thread(arg) void *arg; { sthread_t tid; int r; time_t now, end; SM_ASSERT(Monitor); SM_ASSERT(Mon_exec_time > 0); tid = (sthread_t) sthread_get_id(); if (pthread_detach(tid) != 0) { /* log an error */ return (void *)1; } /* ** NOTE: this is "flow through" code, ** do NOT use do { } while ("break" is used here!) */ #define MON_CHK_STOP \ now = time(NULL); \ end = Mon_cur_ctx->ctx_start + Mon_exec_time; \ if (now > end) \ { \ smi_log(SMI_LOG_ERR, \ "WARNING: monitor timeout triggered, now=%ld, end=%ld, tid=%ld, state=0x%x",\ (long) now, (long) end, \ (long) Mon_cur_ctx->ctx_id, Mon_cur_ctx->ctx_state);\ mi_stop_milters(MILTER_STOP); \ break; \ } (void) smutex_lock(&Mon_mutex); while (mi_stop() == MILTER_CONT) { if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0) { struct timespec abstime; MON_CHK_STOP; abstime.tv_sec = end; abstime.tv_nsec = 0; r = pthread_cond_timedwait(&Mon_cv, &Mon_mutex, &abstime); } else r = pthread_cond_wait(&Mon_cv, &Mon_mutex); if (mi_stop() != MILTER_CONT) break; if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0) { MON_CHK_STOP; } } (void) smutex_unlock(&Mon_mutex); return NULL; } /* ** MI_MONITOR_INIT -- initialize monitoring thread ** ** Parameters: none ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int mi_monitor_init() { int r; sthread_t tid; SM_ASSERT(!Monitor); if (Mon_exec_time <= 0) return MI_SUCCESS; Monitor = true; if (!smutex_init(&Mon_mutex)) return MI_FAILURE; if (scond_init(&Mon_cv) != 0) return MI_FAILURE; SM_TAILQ_INIT(&Mon_ctx_head); r = thread_create(&tid, mi_monitor_thread, (void *)NULL); if (r != 0) return r; return MI_SUCCESS; } /* ** MI_MONITOR_WORK_BEGIN -- record start of thread execution ** ** Parameters: ** ctx -- session context ** cmd -- milter command char ** ** Returns: ** 0 */ int mi_monitor_work_begin(ctx, cmd) SMFICTX_PTR ctx; int cmd; { (void) smutex_lock(&Mon_mutex); if (NULL == Mon_cur_ctx) { Mon_cur_ctx = ctx; (void) scond_signal(&Mon_cv); } ctx->ctx_start = time(NULL); SM_TAILQ_INSERT_TAIL(&Mon_ctx_head, ctx, ctx_mon_link); (void) smutex_unlock(&Mon_mutex); return 0; } /* ** MI_MONITOR_WORK_END -- record end of thread execution ** ** Parameters: ** ctx -- session context ** cmd -- milter command char ** ** Returns: ** 0 */ int mi_monitor_work_end(ctx, cmd) SMFICTX_PTR ctx; int cmd; { (void) smutex_lock(&Mon_mutex); ctx->ctx_start = 0; SM_TAILQ_REMOVE(&Mon_ctx_head, ctx, ctx_mon_link); if (Mon_cur_ctx == ctx) { if (SM_TAILQ_EMPTY(&Mon_ctx_head)) Mon_cur_ctx = NULL; else Mon_cur_ctx = SM_TAILQ_FIRST(&Mon_ctx_head); } (void) smutex_unlock(&Mon_mutex); return 0; } #endif /* _FFR_THREAD_MONITOR */