blob: 260e8c8e492c39198e0ae61c491edff488ebfb61 [file] [log] [blame]
paul718e3742002-12-13 20:15:29 +00001/* Thread management routine
2 * Copyright (C) 1998, 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GNU Zebra; see the file COPYING. If not, write to the Free
18 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 * 02111-1307, USA.
20 */
21
22/* #define DEBUG */
23
24#include <zebra.h>
25
26#include "thread.h"
27#include "memory.h"
28#include "log.h"
paule04ab742003-01-17 23:47:00 +000029#include "hash.h"
30#include "command.h"
paul05c447d2004-07-22 19:14:27 +000031#include "sigevent.h"
paule04ab742003-01-17 23:47:00 +000032
Paul Jakmadb9c0df2006-08-27 06:44:02 +000033/* Recent absolute time of day */
ajs8b70d0b2005-04-28 01:31:13 +000034struct timeval recent_time;
Paul Jakmadb9c0df2006-08-27 06:44:02 +000035static struct timeval last_recent_time;
36/* Relative time, since startup */
37static struct timeval relative_time;
38static struct timeval relative_time_base;
39/* init flag */
40static unsigned short timers_inited;
41
paule04ab742003-01-17 23:47:00 +000042static struct hash *cpu_record = NULL;
paul718e3742002-12-13 20:15:29 +000043
44/* Struct timeval's tv_usec one second value. */
45#define TIMER_SECOND_MICRO 1000000L
46
ajs8b70d0b2005-04-28 01:31:13 +000047/* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO).
48 And change negative values to 0. */
paula48b4e62005-04-22 00:43:47 +000049static struct timeval
paul718e3742002-12-13 20:15:29 +000050timeval_adjust (struct timeval a)
51{
52 while (a.tv_usec >= TIMER_SECOND_MICRO)
53 {
54 a.tv_usec -= TIMER_SECOND_MICRO;
55 a.tv_sec++;
56 }
57
58 while (a.tv_usec < 0)
59 {
60 a.tv_usec += TIMER_SECOND_MICRO;
61 a.tv_sec--;
62 }
63
64 if (a.tv_sec < 0)
ajs8b70d0b2005-04-28 01:31:13 +000065 /* Change negative timeouts to 0. */
66 a.tv_sec = a.tv_usec = 0;
paul718e3742002-12-13 20:15:29 +000067
68 return a;
69}
70
71static struct timeval
72timeval_subtract (struct timeval a, struct timeval b)
73{
74 struct timeval ret;
75
76 ret.tv_usec = a.tv_usec - b.tv_usec;
77 ret.tv_sec = a.tv_sec - b.tv_sec;
78
79 return timeval_adjust (ret);
80}
81
ajs8b70d0b2005-04-28 01:31:13 +000082static long
paul718e3742002-12-13 20:15:29 +000083timeval_cmp (struct timeval a, struct timeval b)
84{
85 return (a.tv_sec == b.tv_sec
86 ? a.tv_usec - b.tv_usec : a.tv_sec - b.tv_sec);
87}
88
89static unsigned long
90timeval_elapsed (struct timeval a, struct timeval b)
91{
92 return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO)
93 + (a.tv_usec - b.tv_usec));
94}
95
Paul Jakmadb9c0df2006-08-27 06:44:02 +000096#ifndef HAVE_CLOCK_MONOTONIC
97static void
98quagga_gettimeofday_relative_adjust (void)
99{
100 struct timeval diff;
101 if (timeval_cmp (recent_time, last_recent_time) < 0)
102 {
103 relative_time.tv_sec++;
104 relative_time.tv_usec = 0;
105 }
106 else
107 {
108 diff = timeval_subtract (recent_time, last_recent_time);
109 relative_time.tv_sec += diff.tv_sec;
110 relative_time.tv_usec += diff.tv_usec;
111 relative_time = timeval_adjust (relative_time);
112 }
113 last_recent_time = recent_time;
114}
115#endif /* !HAVE_CLOCK_MONOTONIC */
116
117/* gettimeofday wrapper, to keep recent_time updated */
118static int
119quagga_gettimeofday (struct timeval *tv)
120{
121 int ret;
122
123 assert (tv);
124
125 if (!(ret = gettimeofday (&recent_time, NULL)))
126 {
127 /* init... */
128 if (!timers_inited)
129 {
130 relative_time_base = last_recent_time = recent_time;
131 timers_inited = 1;
132 }
133 /* avoid copy if user passed recent_time pointer.. */
134 if (tv != &recent_time)
135 *tv = recent_time;
136 return 0;
137 }
138 return ret;
139}
140
141static int
142quagga_get_relative (struct timeval *tv)
143{
144 int ret;
145
146#ifdef HAVE_CLOCK_MONOTONIC
147 {
148 struct timespec tp;
149 if (!(ret = clock_gettime (CLOCK_MONOTONIC, &tp)))
150 {
151 relative_time.tv_sec = tp.tv_sec;
152 relative_time.tv_usec = tp.tv_nsec / 1000;
153 }
154 }
155#else /* !HAVE_CLOCK_MONOTONIC */
156 if (!(ret = quagga_gettimeofday (&recent_time)))
157 quagga_gettimeofday_relative_adjust();
158#endif /* HAVE_CLOCK_MONOTONIC */
159
160 if (tv)
161 *tv = relative_time;
162
163 return ret;
164}
165
166/* Get absolute time stamp, but in terms of the internal timer
167 * Could be wrong, but at least won't go back.
168 */
169static void
170quagga_real_stabilised (struct timeval *tv)
171{
172 *tv = relative_time_base;
173 tv->tv_sec += relative_time.tv_sec;
174 tv->tv_usec += relative_time.tv_usec;
175 *tv = timeval_adjust (*tv);
176}
177
178/* Exported Quagga timestamp function.
179 * Modelled on POSIX clock_gettime.
180 */
181int
182quagga_gettime (enum quagga_clkid clkid, struct timeval *tv)
183{
184 switch (clkid)
185 {
186 case QUAGGA_CLK_REALTIME:
187 return quagga_gettimeofday (tv);
188 case QUAGGA_CLK_MONOTONIC:
189 return quagga_get_relative (tv);
190 case QUAGGA_CLK_REALTIME_STABILISED:
191 quagga_real_stabilised (tv);
192 return 0;
193 default:
194 errno = EINVAL;
195 return -1;
196 }
197}
198
199/* time_t value in terms of stabilised absolute time.
200 * replacement for POSIX time()
201 */
202time_t
203quagga_time (time_t *t)
204{
205 struct timeval tv;
206 quagga_real_stabilised (&tv);
207 if (t)
208 *t = tv.tv_sec;
209 return tv.tv_sec;
210}
211
212/* Public export of recent_relative_time by value */
213struct timeval
214recent_relative_time (void)
215{
216 return relative_time;
217}
218
paula48b4e62005-04-22 00:43:47 +0000219static unsigned int
paule04ab742003-01-17 23:47:00 +0000220cpu_record_hash_key (struct cpu_thread_history *a)
221{
paul8cc41982005-05-06 21:25:49 +0000222 return (uintptr_t) a->func;
paule04ab742003-01-17 23:47:00 +0000223}
224
225static int
Stephen Hemmingerffe11cf2008-08-14 16:25:25 +0100226cpu_record_hash_cmp (const struct cpu_thread_history *a,
227 const struct cpu_thread_history *b)
paule04ab742003-01-17 23:47:00 +0000228{
229 return a->func == b->func;
230}
231
paul8cc41982005-05-06 21:25:49 +0000232static void *
paule04ab742003-01-17 23:47:00 +0000233cpu_record_hash_alloc (struct cpu_thread_history *a)
234{
235 struct cpu_thread_history *new;
paul039b9572004-10-31 16:43:17 +0000236 new = XCALLOC (MTYPE_THREAD_STATS, sizeof (struct cpu_thread_history));
paule04ab742003-01-17 23:47:00 +0000237 new->func = a->func;
paul9d11a192004-10-31 16:19:24 +0000238 new->funcname = XSTRDUP(MTYPE_THREAD_FUNCNAME, a->funcname);
paule04ab742003-01-17 23:47:00 +0000239 return new;
240}
241
242static inline void
243vty_out_cpu_thread_history(struct vty* vty,
244 struct cpu_thread_history *a)
245{
ajs8b70d0b2005-04-28 01:31:13 +0000246#ifdef HAVE_RUSAGE
247 vty_out(vty, "%7ld.%03ld %9d %8ld %9ld %8ld %9ld",
248 a->cpu.total/1000, a->cpu.total%1000, a->total_calls,
249 a->cpu.total/a->total_calls, a->cpu.max,
250 a->real.total/a->total_calls, a->real.max);
251#else
252 vty_out(vty, "%7ld.%03ld %9d %8ld %9ld",
253 a->real.total/1000, a->real.total%1000, a->total_calls,
254 a->real.total/a->total_calls, a->real.max);
255#endif
256 vty_out(vty, " %c%c%c%c%c%c %s%s",
paule04ab742003-01-17 23:47:00 +0000257 a->types & (1 << THREAD_READ) ? 'R':' ',
258 a->types & (1 << THREAD_WRITE) ? 'W':' ',
259 a->types & (1 << THREAD_TIMER) ? 'T':' ',
260 a->types & (1 << THREAD_EVENT) ? 'E':' ',
261 a->types & (1 << THREAD_EXECUTE) ? 'X':' ',
paula48b4e62005-04-22 00:43:47 +0000262 a->types & (1 << THREAD_BACKGROUND) ? 'B' : ' ',
paule04ab742003-01-17 23:47:00 +0000263 a->funcname, VTY_NEWLINE);
264}
265
266static void
267cpu_record_hash_print(struct hash_backet *bucket,
268 void *args[])
269{
270 struct cpu_thread_history *totals = args[0];
271 struct vty *vty = args[1];
272 unsigned char *filter = args[2];
273 struct cpu_thread_history *a = bucket->data;
paula48b4e62005-04-22 00:43:47 +0000274
paule04ab742003-01-17 23:47:00 +0000275 a = bucket->data;
276 if ( !(a->types & *filter) )
277 return;
278 vty_out_cpu_thread_history(vty,a);
paule04ab742003-01-17 23:47:00 +0000279 totals->total_calls += a->total_calls;
ajs8b70d0b2005-04-28 01:31:13 +0000280 totals->real.total += a->real.total;
281 if (totals->real.max < a->real.max)
282 totals->real.max = a->real.max;
283#ifdef HAVE_RUSAGE
284 totals->cpu.total += a->cpu.total;
285 if (totals->cpu.max < a->cpu.max)
286 totals->cpu.max = a->cpu.max;
287#endif
paule04ab742003-01-17 23:47:00 +0000288}
289
290static void
291cpu_record_print(struct vty *vty, unsigned char filter)
292{
293 struct cpu_thread_history tmp;
294 void *args[3] = {&tmp, vty, &filter};
295
296 memset(&tmp, 0, sizeof tmp);
297 tmp.funcname = "TOTAL";
298 tmp.types = filter;
299
ajs8b70d0b2005-04-28 01:31:13 +0000300#ifdef HAVE_RUSAGE
301 vty_out(vty, "%21s %18s %18s%s",
302 "", "CPU (user+system):", "Real (wall-clock):", VTY_NEWLINE);
303#endif
304 vty_out(vty, "Runtime(ms) Invoked Avg uSec Max uSecs");
305#ifdef HAVE_RUSAGE
306 vty_out(vty, " Avg uSec Max uSecs");
307#endif
308 vty_out(vty, " Type Thread%s", VTY_NEWLINE);
paule04ab742003-01-17 23:47:00 +0000309 hash_iterate(cpu_record,
310 (void(*)(struct hash_backet*,void*))cpu_record_hash_print,
311 args);
312
313 if (tmp.total_calls > 0)
314 vty_out_cpu_thread_history(vty, &tmp);
315}
316
317DEFUN(show_thread_cpu,
318 show_thread_cpu_cmd,
319 "show thread cpu [FILTER]",
320 SHOW_STR
321 "Thread information\n"
322 "Thread CPU usage\n"
paula48b4e62005-04-22 00:43:47 +0000323 "Display filter (rwtexb)\n")
paule04ab742003-01-17 23:47:00 +0000324{
325 int i = 0;
326 unsigned char filter = 0xff;
327
328 if (argc > 0)
329 {
330 filter = 0;
331 while (argv[0][i] != '\0')
332 {
333 switch ( argv[0][i] )
334 {
335 case 'r':
336 case 'R':
337 filter |= (1 << THREAD_READ);
338 break;
339 case 'w':
340 case 'W':
341 filter |= (1 << THREAD_WRITE);
342 break;
343 case 't':
344 case 'T':
345 filter |= (1 << THREAD_TIMER);
346 break;
347 case 'e':
348 case 'E':
349 filter |= (1 << THREAD_EVENT);
350 break;
351 case 'x':
352 case 'X':
353 filter |= (1 << THREAD_EXECUTE);
354 break;
paula48b4e62005-04-22 00:43:47 +0000355 case 'b':
356 case 'B':
357 filter |= (1 << THREAD_BACKGROUND);
358 break;
paule04ab742003-01-17 23:47:00 +0000359 default:
360 break;
361 }
362 ++i;
363 }
364 if (filter == 0)
365 {
paula48b4e62005-04-22 00:43:47 +0000366 vty_out(vty, "Invalid filter \"%s\" specified,"
367 " must contain at least one of 'RWTEXB'%s",
paule04ab742003-01-17 23:47:00 +0000368 argv[0], VTY_NEWLINE);
369 return CMD_WARNING;
370 }
371 }
372
373 cpu_record_print(vty, filter);
374 return CMD_SUCCESS;
375}
376
paul718e3742002-12-13 20:15:29 +0000377/* List allocation and head/tail print out. */
378static void
379thread_list_debug (struct thread_list *list)
380{
381 printf ("count [%d] head [%p] tail [%p]\n",
382 list->count, list->head, list->tail);
383}
384
385/* Debug print for thread_master. */
paul8cc41982005-05-06 21:25:49 +0000386static void __attribute__ ((unused))
paul718e3742002-12-13 20:15:29 +0000387thread_master_debug (struct thread_master *m)
388{
389 printf ("-----------\n");
390 printf ("readlist : ");
391 thread_list_debug (&m->read);
392 printf ("writelist : ");
393 thread_list_debug (&m->write);
394 printf ("timerlist : ");
395 thread_list_debug (&m->timer);
396 printf ("eventlist : ");
397 thread_list_debug (&m->event);
398 printf ("unuselist : ");
399 thread_list_debug (&m->unuse);
paula48b4e62005-04-22 00:43:47 +0000400 printf ("bgndlist : ");
401 thread_list_debug (&m->background);
paul718e3742002-12-13 20:15:29 +0000402 printf ("total alloc: [%ld]\n", m->alloc);
403 printf ("-----------\n");
404}
405
406/* Allocate new thread master. */
407struct thread_master *
408thread_master_create ()
409{
paule04ab742003-01-17 23:47:00 +0000410 if (cpu_record == NULL)
paul8cc41982005-05-06 21:25:49 +0000411 cpu_record
412 = hash_create_size (1011, (unsigned int (*) (void *))cpu_record_hash_key,
Stephen Hemmingerffe11cf2008-08-14 16:25:25 +0100413 (int (*) (const void *, const void *))cpu_record_hash_cmp);
paula48b4e62005-04-22 00:43:47 +0000414
paul718e3742002-12-13 20:15:29 +0000415 return (struct thread_master *) XCALLOC (MTYPE_THREAD_MASTER,
416 sizeof (struct thread_master));
417}
418
419/* Add a new thread to the list. */
420static void
421thread_list_add (struct thread_list *list, struct thread *thread)
422{
423 thread->next = NULL;
424 thread->prev = list->tail;
425 if (list->tail)
426 list->tail->next = thread;
427 else
428 list->head = thread;
429 list->tail = thread;
430 list->count++;
431}
432
433/* Add a new thread just before the point. */
434static void
435thread_list_add_before (struct thread_list *list,
436 struct thread *point,
437 struct thread *thread)
438{
439 thread->next = point;
440 thread->prev = point->prev;
441 if (point->prev)
442 point->prev->next = thread;
443 else
444 list->head = thread;
445 point->prev = thread;
446 list->count++;
447}
448
449/* Delete a thread from the list. */
450static struct thread *
451thread_list_delete (struct thread_list *list, struct thread *thread)
452{
453 if (thread->next)
454 thread->next->prev = thread->prev;
455 else
456 list->tail = thread->prev;
457 if (thread->prev)
458 thread->prev->next = thread->next;
459 else
460 list->head = thread->next;
461 thread->next = thread->prev = NULL;
462 list->count--;
463 return thread;
464}
465
466/* Move thread to unuse list. */
467static void
468thread_add_unuse (struct thread_master *m, struct thread *thread)
469{
paula48b4e62005-04-22 00:43:47 +0000470 assert (m != NULL && thread != NULL);
paul718e3742002-12-13 20:15:29 +0000471 assert (thread->next == NULL);
472 assert (thread->prev == NULL);
473 assert (thread->type == THREAD_UNUSED);
474 thread_list_add (&m->unuse, thread);
paul9d11a192004-10-31 16:19:24 +0000475 /* XXX: Should we deallocate funcname here? */
paul718e3742002-12-13 20:15:29 +0000476}
477
478/* Free all unused thread. */
479static void
480thread_list_free (struct thread_master *m, struct thread_list *list)
481{
482 struct thread *t;
483 struct thread *next;
484
485 for (t = list->head; t; t = next)
486 {
487 next = t->next;
paul9d11a192004-10-31 16:19:24 +0000488 XFREE (MTYPE_THREAD_FUNCNAME, t->funcname);
paul718e3742002-12-13 20:15:29 +0000489 XFREE (MTYPE_THREAD, t);
490 list->count--;
491 m->alloc--;
492 }
493}
494
495/* Stop thread scheduler. */
496void
497thread_master_free (struct thread_master *m)
498{
499 thread_list_free (m, &m->read);
500 thread_list_free (m, &m->write);
501 thread_list_free (m, &m->timer);
502 thread_list_free (m, &m->event);
503 thread_list_free (m, &m->ready);
504 thread_list_free (m, &m->unuse);
paula48b4e62005-04-22 00:43:47 +0000505 thread_list_free (m, &m->background);
506
paul718e3742002-12-13 20:15:29 +0000507 XFREE (MTYPE_THREAD_MASTER, m);
508}
509
paul8cc41982005-05-06 21:25:49 +0000510/* Thread list is empty or not. */
511static inline int
512thread_empty (struct thread_list *list)
513{
514 return list->head ? 0 : 1;
515}
516
paul718e3742002-12-13 20:15:29 +0000517/* Delete top of the list and return it. */
518static struct thread *
519thread_trim_head (struct thread_list *list)
520{
paul8cc41982005-05-06 21:25:49 +0000521 if (!thread_empty (list))
paul718e3742002-12-13 20:15:29 +0000522 return thread_list_delete (list, list->head);
523 return NULL;
524}
525
paul718e3742002-12-13 20:15:29 +0000526/* Return remain time in second. */
527unsigned long
528thread_timer_remain_second (struct thread *thread)
529{
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000530 quagga_get_relative (NULL);
531
532 if (thread->u.sands.tv_sec - relative_time.tv_sec > 0)
533 return thread->u.sands.tv_sec - relative_time.tv_sec;
paul718e3742002-12-13 20:15:29 +0000534 else
535 return 0;
536}
537
paule04ab742003-01-17 23:47:00 +0000538/* Trim blankspace and "()"s */
539static char *
hasso8c328f12004-10-05 21:01:23 +0000540strip_funcname (const char *funcname)
paule04ab742003-01-17 23:47:00 +0000541{
542 char buff[100];
543 char tmp, *ret, *e, *b = buff;
544
545 strncpy(buff, funcname, sizeof(buff));
546 buff[ sizeof(buff) -1] = '\0';
547 e = buff +strlen(buff) -1;
548
549 /* Wont work for funcname == "Word (explanation)" */
550
551 while (*b == ' ' || *b == '(')
552 ++b;
553 while (*e == ' ' || *e == ')')
554 --e;
555 e++;
556
557 tmp = *e;
558 *e = '\0';
paul9d11a192004-10-31 16:19:24 +0000559 ret = XSTRDUP (MTYPE_THREAD_FUNCNAME, b);
paule04ab742003-01-17 23:47:00 +0000560 *e = tmp;
561
562 return ret;
563}
564
paul718e3742002-12-13 20:15:29 +0000565/* Get new thread. */
566static struct thread *
567thread_get (struct thread_master *m, u_char type,
hasso8c328f12004-10-05 21:01:23 +0000568 int (*func) (struct thread *), void *arg, const char* funcname)
paul718e3742002-12-13 20:15:29 +0000569{
570 struct thread *thread;
571
paul8cc41982005-05-06 21:25:49 +0000572 if (!thread_empty (&m->unuse))
paul2946f652003-03-27 23:48:24 +0000573 {
574 thread = thread_trim_head (&m->unuse);
575 if (thread->funcname)
paul9d11a192004-10-31 16:19:24 +0000576 XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname);
paul2946f652003-03-27 23:48:24 +0000577 }
paul718e3742002-12-13 20:15:29 +0000578 else
579 {
580 thread = XCALLOC (MTYPE_THREAD, sizeof (struct thread));
581 m->alloc++;
582 }
583 thread->type = type;
paule04ab742003-01-17 23:47:00 +0000584 thread->add_type = type;
paul718e3742002-12-13 20:15:29 +0000585 thread->master = m;
586 thread->func = func;
587 thread->arg = arg;
588
paule04ab742003-01-17 23:47:00 +0000589 thread->funcname = strip_funcname(funcname);
590
paul718e3742002-12-13 20:15:29 +0000591 return thread;
592}
593
594/* Add new read thread. */
595struct thread *
paule04ab742003-01-17 23:47:00 +0000596funcname_thread_add_read (struct thread_master *m,
hasso8c328f12004-10-05 21:01:23 +0000597 int (*func) (struct thread *), void *arg, int fd, const char* funcname)
paul718e3742002-12-13 20:15:29 +0000598{
599 struct thread *thread;
600
601 assert (m != NULL);
602
603 if (FD_ISSET (fd, &m->readfd))
604 {
605 zlog (NULL, LOG_WARNING, "There is already read fd [%d]", fd);
606 return NULL;
607 }
608
paule04ab742003-01-17 23:47:00 +0000609 thread = thread_get (m, THREAD_READ, func, arg, funcname);
paul718e3742002-12-13 20:15:29 +0000610 FD_SET (fd, &m->readfd);
611 thread->u.fd = fd;
612 thread_list_add (&m->read, thread);
613
614 return thread;
615}
616
617/* Add new write thread. */
618struct thread *
paule04ab742003-01-17 23:47:00 +0000619funcname_thread_add_write (struct thread_master *m,
hasso8c328f12004-10-05 21:01:23 +0000620 int (*func) (struct thread *), void *arg, int fd, const char* funcname)
paul718e3742002-12-13 20:15:29 +0000621{
622 struct thread *thread;
623
624 assert (m != NULL);
625
626 if (FD_ISSET (fd, &m->writefd))
627 {
628 zlog (NULL, LOG_WARNING, "There is already write fd [%d]", fd);
629 return NULL;
630 }
631
paule04ab742003-01-17 23:47:00 +0000632 thread = thread_get (m, THREAD_WRITE, func, arg, funcname);
paul718e3742002-12-13 20:15:29 +0000633 FD_SET (fd, &m->writefd);
634 thread->u.fd = fd;
635 thread_list_add (&m->write, thread);
636
637 return thread;
638}
639
paul98c91ac2004-10-05 14:57:50 +0000640static struct thread *
641funcname_thread_add_timer_timeval (struct thread_master *m,
642 int (*func) (struct thread *),
paula48b4e62005-04-22 00:43:47 +0000643 int type,
paul98c91ac2004-10-05 14:57:50 +0000644 void *arg,
645 struct timeval *time_relative,
hasso8c328f12004-10-05 21:01:23 +0000646 const char* funcname)
paul718e3742002-12-13 20:15:29 +0000647{
paul718e3742002-12-13 20:15:29 +0000648 struct thread *thread;
paula48b4e62005-04-22 00:43:47 +0000649 struct thread_list *list;
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000650 struct timeval alarm_time;
paul718e3742002-12-13 20:15:29 +0000651 struct thread *tt;
paul718e3742002-12-13 20:15:29 +0000652
653 assert (m != NULL);
654
ajs8b70d0b2005-04-28 01:31:13 +0000655 assert (type == THREAD_TIMER || type == THREAD_BACKGROUND);
paula48b4e62005-04-22 00:43:47 +0000656 assert (time_relative);
657
ajs8b70d0b2005-04-28 01:31:13 +0000658 list = ((type == THREAD_TIMER) ? &m->timer : &m->background);
paula48b4e62005-04-22 00:43:47 +0000659 thread = thread_get (m, type, func, arg, funcname);
paul718e3742002-12-13 20:15:29 +0000660
661 /* Do we need jitter here? */
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000662 quagga_gettimeofday (&recent_time);
663 alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec;
664 alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec;
ajs8b70d0b2005-04-28 01:31:13 +0000665 thread->u.sands = timeval_adjust(alarm_time);
paul718e3742002-12-13 20:15:29 +0000666
667 /* Sort by timeval. */
paula48b4e62005-04-22 00:43:47 +0000668 for (tt = list->head; tt; tt = tt->next)
paul718e3742002-12-13 20:15:29 +0000669 if (timeval_cmp (thread->u.sands, tt->u.sands) <= 0)
670 break;
671
672 if (tt)
paula48b4e62005-04-22 00:43:47 +0000673 thread_list_add_before (list, tt, thread);
paul718e3742002-12-13 20:15:29 +0000674 else
paula48b4e62005-04-22 00:43:47 +0000675 thread_list_add (list, thread);
paul718e3742002-12-13 20:15:29 +0000676
677 return thread;
678}
679
paul98c91ac2004-10-05 14:57:50 +0000680
681/* Add timer event thread. */
jardin9e867fe2003-12-23 08:56:18 +0000682struct thread *
paul98c91ac2004-10-05 14:57:50 +0000683funcname_thread_add_timer (struct thread_master *m,
684 int (*func) (struct thread *),
hasso8c328f12004-10-05 21:01:23 +0000685 void *arg, long timer, const char* funcname)
jardin9e867fe2003-12-23 08:56:18 +0000686{
paul98c91ac2004-10-05 14:57:50 +0000687 struct timeval trel;
jardin9e867fe2003-12-23 08:56:18 +0000688
689 assert (m != NULL);
690
paul9076fbd2004-10-11 09:40:58 +0000691 trel.tv_sec = timer;
paul98c91ac2004-10-05 14:57:50 +0000692 trel.tv_usec = 0;
693
paula48b4e62005-04-22 00:43:47 +0000694 return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER, arg,
695 &trel, funcname);
paul98c91ac2004-10-05 14:57:50 +0000696}
697
698/* Add timer event thread with "millisecond" resolution */
699struct thread *
700funcname_thread_add_timer_msec (struct thread_master *m,
701 int (*func) (struct thread *),
hasso8c328f12004-10-05 21:01:23 +0000702 void *arg, long timer, const char* funcname)
paul98c91ac2004-10-05 14:57:50 +0000703{
704 struct timeval trel;
705
706 assert (m != NULL);
jardin9e867fe2003-12-23 08:56:18 +0000707
ajsaf04bd72004-12-28 17:00:12 +0000708 trel.tv_sec = timer / 1000;
709 trel.tv_usec = 1000*(timer % 1000);
jardin9e867fe2003-12-23 08:56:18 +0000710
paula48b4e62005-04-22 00:43:47 +0000711 return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER,
712 arg, &trel, funcname);
713}
714
715/* Add a background thread, with an optional millisec delay */
716struct thread *
717funcname_thread_add_background (struct thread_master *m,
718 int (*func) (struct thread *),
719 void *arg, long delay,
720 const char *funcname)
721{
722 struct timeval trel;
723
724 assert (m != NULL);
725
726 if (delay)
727 {
728 trel.tv_sec = delay / 1000;
729 trel.tv_usec = 1000*(delay % 1000);
730 }
731 else
732 {
733 trel.tv_sec = 0;
734 trel.tv_usec = 0;
735 }
736
737 return funcname_thread_add_timer_timeval (m, func, THREAD_BACKGROUND,
738 arg, &trel, funcname);
jardin9e867fe2003-12-23 08:56:18 +0000739}
740
paul718e3742002-12-13 20:15:29 +0000741/* Add simple event thread. */
742struct thread *
paule04ab742003-01-17 23:47:00 +0000743funcname_thread_add_event (struct thread_master *m,
hasso8c328f12004-10-05 21:01:23 +0000744 int (*func) (struct thread *), void *arg, int val, const char* funcname)
paul718e3742002-12-13 20:15:29 +0000745{
746 struct thread *thread;
747
748 assert (m != NULL);
749
paule04ab742003-01-17 23:47:00 +0000750 thread = thread_get (m, THREAD_EVENT, func, arg, funcname);
paul718e3742002-12-13 20:15:29 +0000751 thread->u.val = val;
752 thread_list_add (&m->event, thread);
753
754 return thread;
755}
756
757/* Cancel thread from scheduler. */
758void
759thread_cancel (struct thread *thread)
760{
paula48b4e62005-04-22 00:43:47 +0000761 struct thread_list *list;
762
paul718e3742002-12-13 20:15:29 +0000763 switch (thread->type)
764 {
765 case THREAD_READ:
766 assert (FD_ISSET (thread->u.fd, &thread->master->readfd));
767 FD_CLR (thread->u.fd, &thread->master->readfd);
paula48b4e62005-04-22 00:43:47 +0000768 list = &thread->master->read;
paul718e3742002-12-13 20:15:29 +0000769 break;
770 case THREAD_WRITE:
771 assert (FD_ISSET (thread->u.fd, &thread->master->writefd));
772 FD_CLR (thread->u.fd, &thread->master->writefd);
paula48b4e62005-04-22 00:43:47 +0000773 list = &thread->master->write;
paul718e3742002-12-13 20:15:29 +0000774 break;
775 case THREAD_TIMER:
paula48b4e62005-04-22 00:43:47 +0000776 list = &thread->master->timer;
paul718e3742002-12-13 20:15:29 +0000777 break;
778 case THREAD_EVENT:
paula48b4e62005-04-22 00:43:47 +0000779 list = &thread->master->event;
paul718e3742002-12-13 20:15:29 +0000780 break;
781 case THREAD_READY:
paula48b4e62005-04-22 00:43:47 +0000782 list = &thread->master->ready;
paul718e3742002-12-13 20:15:29 +0000783 break;
paula48b4e62005-04-22 00:43:47 +0000784 case THREAD_BACKGROUND:
785 list = &thread->master->background;
ajs8b70d0b2005-04-28 01:31:13 +0000786 break;
paul718e3742002-12-13 20:15:29 +0000787 default:
paula48b4e62005-04-22 00:43:47 +0000788 return;
paul718e3742002-12-13 20:15:29 +0000789 break;
790 }
paula48b4e62005-04-22 00:43:47 +0000791 thread_list_delete (list, thread);
paul718e3742002-12-13 20:15:29 +0000792 thread->type = THREAD_UNUSED;
793 thread_add_unuse (thread->master, thread);
794}
795
796/* Delete all events which has argument value arg. */
pauldc818072005-05-19 01:30:53 +0000797unsigned int
paul718e3742002-12-13 20:15:29 +0000798thread_cancel_event (struct thread_master *m, void *arg)
799{
pauldc818072005-05-19 01:30:53 +0000800 unsigned int ret = 0;
paul718e3742002-12-13 20:15:29 +0000801 struct thread *thread;
802
803 thread = m->event.head;
804 while (thread)
805 {
806 struct thread *t;
807
808 t = thread;
809 thread = t->next;
810
811 if (t->arg == arg)
paula48b4e62005-04-22 00:43:47 +0000812 {
pauldc818072005-05-19 01:30:53 +0000813 ret++;
paula48b4e62005-04-22 00:43:47 +0000814 thread_list_delete (&m->event, t);
815 t->type = THREAD_UNUSED;
816 thread_add_unuse (m, t);
817 }
paul718e3742002-12-13 20:15:29 +0000818 }
pauldc818072005-05-19 01:30:53 +0000819 return ret;
paul718e3742002-12-13 20:15:29 +0000820}
821
paula48b4e62005-04-22 00:43:47 +0000822static struct timeval *
823thread_timer_wait (struct thread_list *tlist, struct timeval *timer_val)
paul718e3742002-12-13 20:15:29 +0000824{
paul8cc41982005-05-06 21:25:49 +0000825 if (!thread_empty (tlist))
paul718e3742002-12-13 20:15:29 +0000826 {
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000827 *timer_val = timeval_subtract (tlist->head->u.sands, relative_time);
paul718e3742002-12-13 20:15:29 +0000828 return timer_val;
829 }
830 return NULL;
831}
paul718e3742002-12-13 20:15:29 +0000832
paul8cc41982005-05-06 21:25:49 +0000833static struct thread *
paul718e3742002-12-13 20:15:29 +0000834thread_run (struct thread_master *m, struct thread *thread,
835 struct thread *fetch)
836{
837 *fetch = *thread;
838 thread->type = THREAD_UNUSED;
839 thread_add_unuse (m, thread);
840 return fetch;
841}
842
paula48b4e62005-04-22 00:43:47 +0000843static int
844thread_process_fd (struct thread_list *list, fd_set *fdset, fd_set *mfdset)
paul718e3742002-12-13 20:15:29 +0000845{
846 struct thread *thread;
847 struct thread *next;
848 int ready = 0;
paula48b4e62005-04-22 00:43:47 +0000849
850 assert (list);
851
paul718e3742002-12-13 20:15:29 +0000852 for (thread = list->head; thread; thread = next)
853 {
854 next = thread->next;
855
856 if (FD_ISSET (THREAD_FD (thread), fdset))
paula48b4e62005-04-22 00:43:47 +0000857 {
858 assert (FD_ISSET (THREAD_FD (thread), mfdset));
859 FD_CLR(THREAD_FD (thread), mfdset);
860 thread_list_delete (list, thread);
861 thread_list_add (&thread->master->ready, thread);
862 thread->type = THREAD_READY;
863 ready++;
864 }
paul718e3742002-12-13 20:15:29 +0000865 }
866 return ready;
867}
868
ajs8b70d0b2005-04-28 01:31:13 +0000869/* Add all timers that have popped to the ready list. */
paula48b4e62005-04-22 00:43:47 +0000870static unsigned int
871thread_timer_process (struct thread_list *list, struct timeval *timenow)
872{
873 struct thread *thread;
874 unsigned int ready = 0;
875
paula48b4e62005-04-22 00:43:47 +0000876 for (thread = list->head; thread; thread = thread->next)
ajs8b70d0b2005-04-28 01:31:13 +0000877 {
878 if (timeval_cmp (*timenow, thread->u.sands) < 0)
879 return ready;
880 thread_list_delete (list, thread);
881 thread->type = THREAD_READY;
882 thread_list_add (&thread->master->ready, thread);
883 ready++;
884 }
paula48b4e62005-04-22 00:43:47 +0000885 return ready;
886}
887
paul718e3742002-12-13 20:15:29 +0000888/* Fetch next ready thread. */
889struct thread *
890thread_fetch (struct thread_master *m, struct thread *fetch)
891{
paul718e3742002-12-13 20:15:29 +0000892 struct thread *thread;
893 fd_set readfd;
894 fd_set writefd;
895 fd_set exceptfd;
paul718e3742002-12-13 20:15:29 +0000896 struct timeval timer_val;
paula48b4e62005-04-22 00:43:47 +0000897 struct timeval timer_val_bg;
paul718e3742002-12-13 20:15:29 +0000898 struct timeval *timer_wait;
paula48b4e62005-04-22 00:43:47 +0000899 struct timeval *timer_wait_bg;
paul718e3742002-12-13 20:15:29 +0000900
901 while (1)
902 {
paula48b4e62005-04-22 00:43:47 +0000903 int num = 0;
paula48b4e62005-04-22 00:43:47 +0000904
paul05c447d2004-07-22 19:14:27 +0000905 /* Signals are highest priority */
906 quagga_sigevent_process ();
907
908 /* Normal event are the next highest priority. */
paul718e3742002-12-13 20:15:29 +0000909 if ((thread = thread_trim_head (&m->event)) != NULL)
paul05c447d2004-07-22 19:14:27 +0000910 return thread_run (m, thread, fetch);
paula48b4e62005-04-22 00:43:47 +0000911
paula48b4e62005-04-22 00:43:47 +0000912 /* If there are any ready threads from previous scheduler runs,
913 * process top of them.
914 */
paul718e3742002-12-13 20:15:29 +0000915 if ((thread = thread_trim_head (&m->ready)) != NULL)
paul05c447d2004-07-22 19:14:27 +0000916 return thread_run (m, thread, fetch);
paula48b4e62005-04-22 00:43:47 +0000917
paul718e3742002-12-13 20:15:29 +0000918 /* Structure copy. */
919 readfd = m->readfd;
920 writefd = m->writefd;
921 exceptfd = m->exceptfd;
paula48b4e62005-04-22 00:43:47 +0000922
923 /* Calculate select wait timer if nothing else to do */
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000924 quagga_get_relative (NULL);
ajs8b70d0b2005-04-28 01:31:13 +0000925 timer_wait = thread_timer_wait (&m->timer, &timer_val);
926 timer_wait_bg = thread_timer_wait (&m->background, &timer_val_bg);
927
928 if (timer_wait_bg &&
929 (!timer_wait || (timeval_cmp (*timer_wait, *timer_wait_bg) > 0)))
930 timer_wait = timer_wait_bg;
paula48b4e62005-04-22 00:43:47 +0000931
paul718e3742002-12-13 20:15:29 +0000932 num = select (FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
paula48b4e62005-04-22 00:43:47 +0000933
934 /* Signals should get quick treatment */
paul718e3742002-12-13 20:15:29 +0000935 if (num < 0)
paul05c447d2004-07-22 19:14:27 +0000936 {
937 if (errno == EINTR)
paula48b4e62005-04-22 00:43:47 +0000938 continue; /* signal received - process it */
ajs6099b3b2004-11-20 02:06:59 +0000939 zlog_warn ("select() error: %s", safe_strerror (errno));
paul05c447d2004-07-22 19:14:27 +0000940 return NULL;
941 }
ajs8b70d0b2005-04-28 01:31:13 +0000942
943 /* Check foreground timers. Historically, they have had higher
944 priority than I/O threads, so let's push them onto the ready
945 list in front of the I/O threads. */
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000946 quagga_get_relative (NULL);
947 thread_timer_process (&m->timer, &relative_time);
paula48b4e62005-04-22 00:43:47 +0000948
949 /* Got IO, process it */
950 if (num > 0)
951 {
952 /* Normal priority read thead. */
ajs8b70d0b2005-04-28 01:31:13 +0000953 thread_process_fd (&m->read, &readfd, &m->readfd);
paula48b4e62005-04-22 00:43:47 +0000954 /* Write thead. */
ajs8b70d0b2005-04-28 01:31:13 +0000955 thread_process_fd (&m->write, &writefd, &m->writefd);
paula48b4e62005-04-22 00:43:47 +0000956 }
ajs8b70d0b2005-04-28 01:31:13 +0000957
958#if 0
959 /* If any threads were made ready above (I/O or foreground timer),
960 perhaps we should avoid adding background timers to the ready
961 list at this time. If this is code is uncommented, then background
962 timer threads will not run unless there is nothing else to do. */
963 if ((thread = thread_trim_head (&m->ready)) != NULL)
964 return thread_run (m, thread, fetch);
965#endif
966
paula48b4e62005-04-22 00:43:47 +0000967 /* Background timer/events, lowest priority */
Paul Jakmadb9c0df2006-08-27 06:44:02 +0000968 thread_timer_process (&m->background, &relative_time);
paula48b4e62005-04-22 00:43:47 +0000969
ajs8b70d0b2005-04-28 01:31:13 +0000970 if ((thread = thread_trim_head (&m->ready)) != NULL)
paul05c447d2004-07-22 19:14:27 +0000971 return thread_run (m, thread, fetch);
paul718e3742002-12-13 20:15:29 +0000972 }
973}
974
ajs924b9222005-04-16 17:11:24 +0000975unsigned long
ajs8b70d0b2005-04-28 01:31:13 +0000976thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime)
paul718e3742002-12-13 20:15:29 +0000977{
paul718e3742002-12-13 20:15:29 +0000978#ifdef HAVE_RUSAGE
979 /* This is 'user + sys' time. */
ajs8b70d0b2005-04-28 01:31:13 +0000980 *cputime = timeval_elapsed (now->cpu.ru_utime, start->cpu.ru_utime) +
981 timeval_elapsed (now->cpu.ru_stime, start->cpu.ru_stime);
paul718e3742002-12-13 20:15:29 +0000982#else
ajs8b70d0b2005-04-28 01:31:13 +0000983 *cputime = 0;
paul718e3742002-12-13 20:15:29 +0000984#endif /* HAVE_RUSAGE */
ajs8b70d0b2005-04-28 01:31:13 +0000985 return timeval_elapsed (now->real, start->real);
paul718e3742002-12-13 20:15:29 +0000986}
987
ajs8b70d0b2005-04-28 01:31:13 +0000988/* We should aim to yield after THREAD_YIELD_TIME_SLOT milliseconds.
989 Note: we are using real (wall clock) time for this calculation.
990 It could be argued that CPU time may make more sense in certain
991 contexts. The things to consider are whether the thread may have
992 blocked (in which case wall time increases, but CPU time does not),
993 or whether the system is heavily loaded with other processes competing
994 for CPU time. On balance, wall clock time seems to make sense.
995 Plus it has the added benefit that gettimeofday should be faster
996 than calling getrusage. */
paul718e3742002-12-13 20:15:29 +0000997int
998thread_should_yield (struct thread *thread)
999{
Paul Jakmadb9c0df2006-08-27 06:44:02 +00001000 quagga_get_relative (NULL);
1001 return (timeval_elapsed(relative_time, thread->ru.real) >
ajs8b70d0b2005-04-28 01:31:13 +00001002 THREAD_YIELD_TIME_SLOT);
paul718e3742002-12-13 20:15:29 +00001003}
1004
Paul Jakmadb9c0df2006-08-27 06:44:02 +00001005void
1006thread_getrusage (RUSAGE_T *r)
1007{
1008 quagga_get_relative (NULL);
1009#ifdef HAVE_RUSAGE
1010 getrusage(RUSAGE_SELF, &(r->cpu));
1011#endif
1012 r->real = relative_time;
1013
1014#ifdef HAVE_CLOCK_MONOTONIC
1015 /* quagga_get_relative() only updates recent_time if gettimeofday
1016 * based, not when using CLOCK_MONOTONIC. As we export recent_time
1017 * and guarantee to update it before threads are run...
1018 */
1019 quagga_gettimeofday(&recent_time);
1020#endif /* HAVE_CLOCK_MONOTONIC */
1021}
1022
paul718e3742002-12-13 20:15:29 +00001023/* We check thread consumed time. If the system has getrusage, we'll
ajs8b70d0b2005-04-28 01:31:13 +00001024 use that to get in-depth stats on the performance of the thread in addition
1025 to wall clock time stats from gettimeofday. */
paul718e3742002-12-13 20:15:29 +00001026void
1027thread_call (struct thread *thread)
1028{
ajs8b70d0b2005-04-28 01:31:13 +00001029 unsigned long realtime, cputime;
paul718e3742002-12-13 20:15:29 +00001030 RUSAGE_T ru;
Paul Jakmacc8b13a2006-07-25 20:40:40 +00001031
1032 /* Cache a pointer to the relevant cpu history thread, if the thread
1033 * does not have it yet.
1034 *
1035 * Callers submitting 'dummy threads' hence must take care that
1036 * thread->cpu is NULL
1037 */
1038 if (!thread->hist)
1039 {
1040 struct cpu_thread_history tmp;
1041
1042 tmp.func = thread->func;
1043 tmp.funcname = thread->funcname;
1044
1045 thread->hist = hash_get (cpu_record, &tmp,
1046 (void * (*) (void *))cpu_record_hash_alloc);
1047 }
paul718e3742002-12-13 20:15:29 +00001048
1049 GETRUSAGE (&thread->ru);
1050
1051 (*thread->func) (thread);
1052
1053 GETRUSAGE (&ru);
1054
ajs8b70d0b2005-04-28 01:31:13 +00001055 realtime = thread_consumed_time (&ru, &thread->ru, &cputime);
Paul Jakmacc8b13a2006-07-25 20:40:40 +00001056 thread->hist->real.total += realtime;
1057 if (thread->hist->real.max < realtime)
1058 thread->hist->real.max = realtime;
ajs8b70d0b2005-04-28 01:31:13 +00001059#ifdef HAVE_RUSAGE
Paul Jakmacc8b13a2006-07-25 20:40:40 +00001060 thread->hist->cpu.total += cputime;
1061 if (thread->hist->cpu.max < cputime)
1062 thread->hist->cpu.max = cputime;
ajs8b70d0b2005-04-28 01:31:13 +00001063#endif
paule04ab742003-01-17 23:47:00 +00001064
Paul Jakmacc8b13a2006-07-25 20:40:40 +00001065 ++(thread->hist->total_calls);
1066 thread->hist->types |= (1 << thread->add_type);
paul718e3742002-12-13 20:15:29 +00001067
ajs924b9222005-04-16 17:11:24 +00001068#ifdef CONSUMED_TIME_CHECK
ajs8b70d0b2005-04-28 01:31:13 +00001069 if (realtime > CONSUMED_TIME_CHECK)
paul718e3742002-12-13 20:15:29 +00001070 {
1071 /*
1072 * We have a CPU Hog on our hands.
1073 * Whinge about it now, so we're aware this is yet another task
1074 * to fix.
1075 */
ajs8b70d0b2005-04-28 01:31:13 +00001076 zlog_warn ("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)",
ajs924b9222005-04-16 17:11:24 +00001077 thread->funcname,
1078 (unsigned long) thread->func,
ajs8b70d0b2005-04-28 01:31:13 +00001079 realtime/1000, cputime/1000);
paul718e3742002-12-13 20:15:29 +00001080 }
ajs924b9222005-04-16 17:11:24 +00001081#endif /* CONSUMED_TIME_CHECK */
paul718e3742002-12-13 20:15:29 +00001082}
1083
1084/* Execute thread */
1085struct thread *
paule04ab742003-01-17 23:47:00 +00001086funcname_thread_execute (struct thread_master *m,
paul718e3742002-12-13 20:15:29 +00001087 int (*func)(struct thread *),
1088 void *arg,
paule04ab742003-01-17 23:47:00 +00001089 int val,
hasso8c328f12004-10-05 21:01:23 +00001090 const char* funcname)
paul718e3742002-12-13 20:15:29 +00001091{
1092 struct thread dummy;
1093
1094 memset (&dummy, 0, sizeof (struct thread));
1095
1096 dummy.type = THREAD_EVENT;
paule04ab742003-01-17 23:47:00 +00001097 dummy.add_type = THREAD_EXECUTE;
paul718e3742002-12-13 20:15:29 +00001098 dummy.master = NULL;
1099 dummy.func = func;
1100 dummy.arg = arg;
1101 dummy.u.val = val;
paule04ab742003-01-17 23:47:00 +00001102 dummy.funcname = strip_funcname (funcname);
paul718e3742002-12-13 20:15:29 +00001103 thread_call (&dummy);
1104
paul9d11a192004-10-31 16:19:24 +00001105 XFREE (MTYPE_THREAD_FUNCNAME, dummy.funcname);
paul2946f652003-03-27 23:48:24 +00001106
paul718e3742002-12-13 20:15:29 +00001107 return NULL;
1108}