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