1058561cbSjbeck /*
2058561cbSjbeck  *  Copyright (c) 2006 Sendmail, Inc. and its suppliers.
3058561cbSjbeck  *	All rights reserved.
4058561cbSjbeck  *
5058561cbSjbeck  * By using this file, you agree to the terms and conditions set
6058561cbSjbeck  * forth in the LICENSE file which can be found at the top level of
7058561cbSjbeck  * the sendmail distribution.
8058561cbSjbeck  *
9058561cbSjbeck  */
10058561cbSjbeck 
11*7800901eSjbeck #include <sm/gen.h>
12*7800901eSjbeck SM_RCSID("@(#)$Id: monitor.c,v 8.7 2007/04/23 16:26:28 ca Exp $")
13058561cbSjbeck #include "libmilter.h"
14058561cbSjbeck 
15058561cbSjbeck #if _FFR_THREAD_MONITOR
16058561cbSjbeck 
17058561cbSjbeck /*
18058561cbSjbeck **  Thread Monitoring
19058561cbSjbeck **  Todo: more error checking (return code from function calls)
20058561cbSjbeck **  add comments.
21058561cbSjbeck */
22058561cbSjbeck 
23058561cbSjbeck bool Monitor = false; /* use monitoring? */
24058561cbSjbeck static unsigned int Mon_exec_time = 0;
25058561cbSjbeck 
26058561cbSjbeck /* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */
27058561cbSjbeck static smutex_t Mon_mutex;
28058561cbSjbeck static scond_t Mon_cv;
29058561cbSjbeck 
30058561cbSjbeck /*
31058561cbSjbeck **  Current ctx to monitor.
32058561cbSjbeck **  Invariant:
33058561cbSjbeck **  Mon_cur_ctx == NULL || Mon_cur_ctx is thread which was started the longest
34058561cbSjbeck **	time ago.
35058561cbSjbeck **
36058561cbSjbeck **  Basically the entries in the list are ordered by time because new
37058561cbSjbeck **	entries are appended at the end. However, due to the concurrent
38058561cbSjbeck **	execution (multi-threaded) and no guaranteed order of wakeups
39058561cbSjbeck **	after a mutex_lock() attempt, the order might not be strict,
40058561cbSjbeck **	i.e., if the list contains e1 and e2 (in that order) then
41058561cbSjbeck **	the the start time of e2 can be (slightly) smaller than that of e1.
42058561cbSjbeck **	However, this slight inaccurracy should not matter for the proper
43058561cbSjbeck **	working of this algorithm.
44058561cbSjbeck */
45058561cbSjbeck 
46058561cbSjbeck static SMFICTX_PTR Mon_cur_ctx = NULL;
47058561cbSjbeck static smfi_hd_T Mon_ctx_head; /* head of the linked list of active contexts */
48058561cbSjbeck 
49058561cbSjbeck /*
50058561cbSjbeck **  SMFI_SET_MAX_EXEC_TIME -- set maximum execution time for a thread
51058561cbSjbeck **
52058561cbSjbeck **	Parameters:
53058561cbSjbeck **		tm -- maximum execution time for a thread
54058561cbSjbeck **
55058561cbSjbeck **	Returns:
56058561cbSjbeck **		MI_SUCCESS
57058561cbSjbeck */
58058561cbSjbeck 
59058561cbSjbeck int
smfi_set_max_exec_time(tm)60058561cbSjbeck smfi_set_max_exec_time(tm)
61058561cbSjbeck 	unsigned int tm;
62058561cbSjbeck {
63058561cbSjbeck 	Mon_exec_time = tm;
64058561cbSjbeck 	return MI_SUCCESS;
65058561cbSjbeck }
66058561cbSjbeck 
67058561cbSjbeck /*
68058561cbSjbeck **  MI_MONITOR_THREAD -- monitoring thread
69058561cbSjbeck **
70058561cbSjbeck **	Parameters:
71058561cbSjbeck **		arg -- ignored (required by pthread_create())
72058561cbSjbeck **
73058561cbSjbeck **	Returns:
74058561cbSjbeck **		NULL on termination.
75058561cbSjbeck */
76058561cbSjbeck 
77058561cbSjbeck static void *
mi_monitor_thread(arg)78058561cbSjbeck mi_monitor_thread(arg)
79058561cbSjbeck 	void *arg;
80058561cbSjbeck {
81058561cbSjbeck 	sthread_t tid;
82058561cbSjbeck 	int r;
83058561cbSjbeck 	time_t now, end;
84058561cbSjbeck 
85058561cbSjbeck 	SM_ASSERT(Monitor);
86058561cbSjbeck 	SM_ASSERT(Mon_exec_time > 0);
87058561cbSjbeck 	tid = (sthread_t) sthread_get_id();
88058561cbSjbeck 	if (pthread_detach(tid) != 0)
89058561cbSjbeck 	{
90058561cbSjbeck 		/* log an error */
91058561cbSjbeck 		return (void *)1;
92058561cbSjbeck 	}
93058561cbSjbeck 
94058561cbSjbeck /*
95058561cbSjbeck **  NOTE: this is "flow through" code,
96058561cbSjbeck **  do NOT use do { } while ("break" is used here!)
97058561cbSjbeck */
98058561cbSjbeck 
99058561cbSjbeck #define MON_CHK_STOP							\
100058561cbSjbeck 	now = time(NULL);						\
101058561cbSjbeck 	end = Mon_cur_ctx->ctx_start + Mon_exec_time;			\
102058561cbSjbeck 	if (now > end)							\
103058561cbSjbeck 	{								\
104058561cbSjbeck 		smi_log(SMI_LOG_ERR,					\
105058561cbSjbeck 			"WARNING: monitor timeout triggered, now=%ld, end=%ld, tid=%ld, state=0x%x",\
106058561cbSjbeck 			(long) now, (long) end,				\
107058561cbSjbeck 			(long) Mon_cur_ctx->ctx_id, Mon_cur_ctx->ctx_state);\
108058561cbSjbeck 		mi_stop_milters(MILTER_STOP);				\
109058561cbSjbeck 		break;							\
110058561cbSjbeck 	}
111058561cbSjbeck 
112058561cbSjbeck 	(void) smutex_lock(&Mon_mutex);
113058561cbSjbeck 	while (mi_stop() == MILTER_CONT)
114058561cbSjbeck 	{
115058561cbSjbeck 		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
116058561cbSjbeck 		{
117058561cbSjbeck 			struct timespec abstime;
118058561cbSjbeck 
119058561cbSjbeck 			MON_CHK_STOP;
120058561cbSjbeck 			abstime.tv_sec = end;
121058561cbSjbeck 			abstime.tv_nsec = 0;
122058561cbSjbeck 			r = pthread_cond_timedwait(&Mon_cv, &Mon_mutex,
123058561cbSjbeck 					&abstime);
124058561cbSjbeck 		}
125058561cbSjbeck 		else
126058561cbSjbeck 			r = pthread_cond_wait(&Mon_cv, &Mon_mutex);
127058561cbSjbeck 		if (mi_stop() != MILTER_CONT)
128058561cbSjbeck 			break;
129058561cbSjbeck 		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
130058561cbSjbeck 		{
131058561cbSjbeck 			MON_CHK_STOP;
132058561cbSjbeck 		}
133058561cbSjbeck 	}
134058561cbSjbeck 	(void) smutex_unlock(&Mon_mutex);
135058561cbSjbeck 
136058561cbSjbeck 	return NULL;
137058561cbSjbeck }
138058561cbSjbeck 
139058561cbSjbeck /*
140058561cbSjbeck **  MI_MONITOR_INIT -- initialize monitoring thread
141058561cbSjbeck **
142058561cbSjbeck **	Parameters: none
143058561cbSjbeck **
144058561cbSjbeck **	Returns:
145058561cbSjbeck **		MI_SUCCESS/MI_FAILURE
146058561cbSjbeck */
147058561cbSjbeck 
148058561cbSjbeck int
mi_monitor_init()149058561cbSjbeck mi_monitor_init()
150058561cbSjbeck {
151058561cbSjbeck 	int r;
152058561cbSjbeck 	sthread_t tid;
153058561cbSjbeck 
154058561cbSjbeck 	SM_ASSERT(!Monitor);
155058561cbSjbeck 	if (Mon_exec_time <= 0)
156058561cbSjbeck 		return MI_SUCCESS;
157058561cbSjbeck 	Monitor = true;
158058561cbSjbeck 	if (!smutex_init(&Mon_mutex))
159058561cbSjbeck 		return MI_FAILURE;
160058561cbSjbeck 	if (scond_init(&Mon_cv) != 0)
161058561cbSjbeck 		return MI_FAILURE;
162058561cbSjbeck 	SM_TAILQ_INIT(&Mon_ctx_head);
163058561cbSjbeck 
164058561cbSjbeck 	r = thread_create(&tid, mi_monitor_thread, (void *)NULL);
165058561cbSjbeck 	if (r != 0)
166058561cbSjbeck 		return r;
167058561cbSjbeck 	return MI_SUCCESS;
168058561cbSjbeck }
169058561cbSjbeck 
170058561cbSjbeck /*
171058561cbSjbeck **  MI_MONITOR_WORK_BEGIN -- record start of thread execution
172058561cbSjbeck **
173058561cbSjbeck **	Parameters:
174058561cbSjbeck **		ctx -- session context
175058561cbSjbeck **		cmd -- milter command char
176058561cbSjbeck **
177058561cbSjbeck **	Returns:
178058561cbSjbeck **		0
179058561cbSjbeck */
180058561cbSjbeck 
181058561cbSjbeck int
mi_monitor_work_begin(ctx,cmd)182058561cbSjbeck mi_monitor_work_begin(ctx, cmd)
183058561cbSjbeck 	SMFICTX_PTR ctx;
184058561cbSjbeck 	int cmd;
185058561cbSjbeck {
186058561cbSjbeck 	(void) smutex_lock(&Mon_mutex);
187058561cbSjbeck 	if (NULL == Mon_cur_ctx)
188058561cbSjbeck 	{
189058561cbSjbeck 		Mon_cur_ctx = ctx;
190058561cbSjbeck 		(void) scond_signal(&Mon_cv);
191058561cbSjbeck 	}
192058561cbSjbeck 	ctx->ctx_start = time(NULL);
193058561cbSjbeck 	SM_TAILQ_INSERT_TAIL(&Mon_ctx_head, ctx, ctx_mon_link);
194058561cbSjbeck 	(void) smutex_unlock(&Mon_mutex);
195058561cbSjbeck 	return 0;
196058561cbSjbeck }
197058561cbSjbeck 
198058561cbSjbeck /*
199058561cbSjbeck **  MI_MONITOR_WORK_END -- record end of thread execution
200058561cbSjbeck **
201058561cbSjbeck **	Parameters:
202058561cbSjbeck **		ctx -- session context
203058561cbSjbeck **		cmd -- milter command char
204058561cbSjbeck **
205058561cbSjbeck **	Returns:
206058561cbSjbeck **		0
207058561cbSjbeck */
208058561cbSjbeck 
209058561cbSjbeck int
mi_monitor_work_end(ctx,cmd)210058561cbSjbeck mi_monitor_work_end(ctx, cmd)
211058561cbSjbeck 	SMFICTX_PTR ctx;
212058561cbSjbeck 	int cmd;
213058561cbSjbeck {
214058561cbSjbeck 	(void) smutex_lock(&Mon_mutex);
215058561cbSjbeck 	ctx->ctx_start = 0;
216058561cbSjbeck 	SM_TAILQ_REMOVE(&Mon_ctx_head, ctx, ctx_mon_link);
217058561cbSjbeck 	if (Mon_cur_ctx == ctx)
218058561cbSjbeck 	{
219058561cbSjbeck 		if (SM_TAILQ_EMPTY(&Mon_ctx_head))
220058561cbSjbeck 			Mon_cur_ctx = NULL;
221058561cbSjbeck 		else
222058561cbSjbeck 			Mon_cur_ctx = SM_TAILQ_FIRST(&Mon_ctx_head);
223058561cbSjbeck 	}
224058561cbSjbeck 	(void) smutex_unlock(&Mon_mutex);
225058561cbSjbeck 	return 0;
226058561cbSjbeck }
227058561cbSjbeck #endif /* _FFR_THREAD_MONITOR */
228