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