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>
14SM_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
25bool Monitor = false; /* use monitoring? */
26static unsigned int Mon_exec_time = 0;
27
28/* mutex protects Mon_cur_ctx, Mon_ctx_head, and ctx_start */
29static smutex_t Mon_mutex;
30static 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
48static SMFICTX_PTR Mon_cur_ctx = NULL;
49static 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
61int
62smfi_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
79static void *
80mi_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
150int
151mi_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
183int
184mi_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
211int
212mi_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