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 <sm/gen.h>
12 SM_RCSID("@(#)$Id: monitor.c,v 8.7 2007/04/23 16:26:28 ca Exp $")
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
smfi_set_max_exec_time(tm)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 *
mi_monitor_thread(arg)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
mi_monitor_init()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
mi_monitor_work_begin(ctx,cmd)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
mi_monitor_work_end(ctx,cmd)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