1 /*
2  *  Copyright (c) 2006 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  *
9  */
10 
11 #pragma ident	"%Z%%M%	%I%	%E% SMI"
12 
13 #include <sm/gen.h>
14 SM_RCSID("@(#)$Id: monitor.c,v 8.7 2007/04/23 16:26:28 ca Exp $")
15 #include "libmilter.h"
16 
17 #if _FFR_THREAD_MONITOR
18 
19 /*
20 **  Thread Monitoring
21 **  Todo: more error checking (return code from function calls)
22 **  add comments.
23 */
24 
25 bool Monitor = false; /* use monitoring? */
26 static unsigned int Mon_exec_time = 0;
27 
28 /* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */
29 static smutex_t Mon_mutex;
30 static scond_t Mon_cv;
31 
32 /*
33 **  Current ctx to monitor.
34 **  Invariant:
35 **  Mon_cur_ctx == NULL || Mon_cur_ctx is thread which was started the longest
36 **	time ago.
37 **
38 **  Basically the entries in the list are ordered by time because new
39 **	entries are appended at the end. However, due to the concurrent
40 **	execution (multi-threaded) and no guaranteed order of wakeups
41 **	after a mutex_lock() attempt, the order might not be strict,
42 **	i.e., if the list contains e1 and e2 (in that order) then
43 **	the the start time of e2 can be (slightly) smaller than that of e1.
44 **	However, this slight inaccurracy should not matter for the proper
45 **	working of this algorithm.
46 */
47 
48 static SMFICTX_PTR Mon_cur_ctx = NULL;
49 static smfi_hd_T Mon_ctx_head; /* head of the linked list of active contexts */
50 
51 /*
52 **  SMFI_SET_MAX_EXEC_TIME -- set maximum execution time for a thread
53 **
54 **	Parameters:
55 **		tm -- maximum execution time for a thread
56 **
57 **	Returns:
58 **		MI_SUCCESS
59 */
60 
61 int
62 smfi_set_max_exec_time(tm)
63 	unsigned int tm;
64 {
65 	Mon_exec_time = tm;
66 	return MI_SUCCESS;
67 }
68 
69 /*
70 **  MI_MONITOR_THREAD -- monitoring thread
71 **
72 **	Parameters:
73 **		arg -- ignored (required by pthread_create())
74 **
75 **	Returns:
76 **		NULL on termination.
77 */
78 
79 static void *
80 mi_monitor_thread(arg)
81 	void *arg;
82 {
83 	sthread_t tid;
84 	int r;
85 	time_t now, end;
86 
87 	SM_ASSERT(Monitor);
88 	SM_ASSERT(Mon_exec_time > 0);
89 	tid = (sthread_t) sthread_get_id();
90 	if (pthread_detach(tid) != 0)
91 	{
92 		/* log an error */
93 		return (void *)1;
94 	}
95 
96 /*
97 **  NOTE: this is "flow through" code,
98 **  do NOT use do { } while ("break" is used here!)
99 */
100 
101 #define MON_CHK_STOP							\
102 	now = time(NULL);						\
103 	end = Mon_cur_ctx->ctx_start + Mon_exec_time;			\
104 	if (now > end)							\
105 	{								\
106 		smi_log(SMI_LOG_ERR,					\
107 			"WARNING: monitor timeout triggered, now=%ld, end=%ld, tid=%ld, state=0x%x",\
108 			(long) now, (long) end,				\
109 			(long) Mon_cur_ctx->ctx_id, Mon_cur_ctx->ctx_state);\
110 		mi_stop_milters(MILTER_STOP);				\
111 		break;							\
112 	}
113 
114 	(void) smutex_lock(&Mon_mutex);
115 	while (mi_stop() == MILTER_CONT)
116 	{
117 		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
118 		{
119 			struct timespec abstime;
120 
121 			MON_CHK_STOP;
122 			abstime.tv_sec = end;
123 			abstime.tv_nsec = 0;
124 			r = pthread_cond_timedwait(&Mon_cv, &Mon_mutex,
125 					&abstime);
126 		}
127 		else
128 			r = pthread_cond_wait(&Mon_cv, &Mon_mutex);
129 		if (mi_stop() != MILTER_CONT)
130 			break;
131 		if (Mon_cur_ctx != NULL && Mon_cur_ctx->ctx_start > 0)
132 		{
133 			MON_CHK_STOP;
134 		}
135 	}
136 	(void) smutex_unlock(&Mon_mutex);
137 
138 	return NULL;
139 }
140 
141 /*
142 **  MI_MONITOR_INIT -- initialize monitoring thread
143 **
144 **	Parameters: none
145 **
146 **	Returns:
147 **		MI_SUCCESS/MI_FAILURE
148 */
149 
150 int
151 mi_monitor_init()
152 {
153 	int r;
154 	sthread_t tid;
155 
156 	SM_ASSERT(!Monitor);
157 	if (Mon_exec_time <= 0)
158 		return MI_SUCCESS;
159 	Monitor = true;
160 	if (!smutex_init(&Mon_mutex))
161 		return MI_FAILURE;
162 	if (scond_init(&Mon_cv) != 0)
163 		return MI_FAILURE;
164 	SM_TAILQ_INIT(&Mon_ctx_head);
165 
166 	r = thread_create(&tid, mi_monitor_thread, (void *)NULL);
167 	if (r != 0)
168 		return r;
169 	return MI_SUCCESS;
170 }
171 
172 /*
173 **  MI_MONITOR_WORK_BEGIN -- record start of thread execution
174 **
175 **	Parameters:
176 **		ctx -- session context
177 **		cmd -- milter command char
178 **
179 **	Returns:
180 **		0
181 */
182 
183 int
184 mi_monitor_work_begin(ctx, cmd)
185 	SMFICTX_PTR ctx;
186 	int cmd;
187 {
188 	(void) smutex_lock(&Mon_mutex);
189 	if (NULL == Mon_cur_ctx)
190 	{
191 		Mon_cur_ctx = ctx;
192 		(void) scond_signal(&Mon_cv);
193 	}
194 	ctx->ctx_start = time(NULL);
195 	SM_TAILQ_INSERT_TAIL(&Mon_ctx_head, ctx, ctx_mon_link);
196 	(void) smutex_unlock(&Mon_mutex);
197 	return 0;
198 }
199 
200 /*
201 **  MI_MONITOR_WORK_END -- record end of thread execution
202 **
203 **	Parameters:
204 **		ctx -- session context
205 **		cmd -- milter command char
206 **
207 **	Returns:
208 **		0
209 */
210 
211 int
212 mi_monitor_work_end(ctx, cmd)
213 	SMFICTX_PTR ctx;
214 	int cmd;
215 {
216 	(void) smutex_lock(&Mon_mutex);
217 	ctx->ctx_start = 0;
218 	SM_TAILQ_REMOVE(&Mon_ctx_head, ctx, ctx_mon_link);
219 	if (Mon_cur_ctx == ctx)
220 	{
221 		if (SM_TAILQ_EMPTY(&Mon_ctx_head))
222 			Mon_cur_ctx = NULL;
223 		else
224 			Mon_cur_ctx = SM_TAILQ_FIRST(&Mon_ctx_head);
225 	}
226 	(void) smutex_unlock(&Mon_mutex);
227 	return 0;
228 }
229 #endif /* _FFR_THREAD_MONITOR */
230