]>
git.proxmox.com Git - mirror_frr.git/blob - lib/thread.c
1 /* Thread management routine
2 * Copyright (C) 1998, 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
4 * This file is part of GNU Zebra.
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
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.
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
33 struct timeval recent_time
;
34 static struct hash
*cpu_record
= NULL
;
36 /* Struct timeval's tv_usec one second value. */
37 #define TIMER_SECOND_MICRO 1000000L
39 /* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO).
40 And change negative values to 0. */
42 timeval_adjust (struct timeval a
)
44 while (a
.tv_usec
>= TIMER_SECOND_MICRO
)
46 a
.tv_usec
-= TIMER_SECOND_MICRO
;
52 a
.tv_usec
+= TIMER_SECOND_MICRO
;
57 /* Change negative timeouts to 0. */
58 a
.tv_sec
= a
.tv_usec
= 0;
64 timeval_subtract (struct timeval a
, struct timeval b
)
68 ret
.tv_usec
= a
.tv_usec
- b
.tv_usec
;
69 ret
.tv_sec
= a
.tv_sec
- b
.tv_sec
;
71 return timeval_adjust (ret
);
75 timeval_cmp (struct timeval a
, struct timeval b
)
77 return (a
.tv_sec
== b
.tv_sec
78 ? a
.tv_usec
- b
.tv_usec
: a
.tv_sec
- b
.tv_sec
);
82 timeval_elapsed (struct timeval a
, struct timeval b
)
84 return (((a
.tv_sec
- b
.tv_sec
) * TIMER_SECOND_MICRO
)
85 + (a
.tv_usec
- b
.tv_usec
));
89 cpu_record_hash_key (struct cpu_thread_history
*a
)
91 return (uintptr_t) a
->func
;
95 cpu_record_hash_cmp (struct cpu_thread_history
*a
,
96 struct cpu_thread_history
*b
)
98 return a
->func
== b
->func
;
102 cpu_record_hash_alloc (struct cpu_thread_history
*a
)
104 struct cpu_thread_history
*new;
105 new = XCALLOC (MTYPE_THREAD_STATS
, sizeof (struct cpu_thread_history
));
107 new->funcname
= XSTRDUP(MTYPE_THREAD_FUNCNAME
, a
->funcname
);
112 vty_out_cpu_thread_history(struct vty
* vty
,
113 struct cpu_thread_history
*a
)
116 vty_out(vty
, "%7ld.%03ld %9d %8ld %9ld %8ld %9ld",
117 a
->cpu
.total
/1000, a
->cpu
.total
%1000, a
->total_calls
,
118 a
->cpu
.total
/a
->total_calls
, a
->cpu
.max
,
119 a
->real
.total
/a
->total_calls
, a
->real
.max
);
121 vty_out(vty
, "%7ld.%03ld %9d %8ld %9ld",
122 a
->real
.total
/1000, a
->real
.total
%1000, a
->total_calls
,
123 a
->real
.total
/a
->total_calls
, a
->real
.max
);
125 vty_out(vty
, " %c%c%c%c%c%c %s%s",
126 a
->types
& (1 << THREAD_READ
) ? 'R':' ',
127 a
->types
& (1 << THREAD_WRITE
) ? 'W':' ',
128 a
->types
& (1 << THREAD_TIMER
) ? 'T':' ',
129 a
->types
& (1 << THREAD_EVENT
) ? 'E':' ',
130 a
->types
& (1 << THREAD_EXECUTE
) ? 'X':' ',
131 a
->types
& (1 << THREAD_BACKGROUND
) ? 'B' : ' ',
132 a
->funcname
, VTY_NEWLINE
);
136 cpu_record_hash_print(struct hash_backet
*bucket
,
139 struct cpu_thread_history
*totals
= args
[0];
140 struct vty
*vty
= args
[1];
141 unsigned char *filter
= args
[2];
142 struct cpu_thread_history
*a
= bucket
->data
;
145 if ( !(a
->types
& *filter
) )
147 vty_out_cpu_thread_history(vty
,a
);
148 totals
->total_calls
+= a
->total_calls
;
149 totals
->real
.total
+= a
->real
.total
;
150 if (totals
->real
.max
< a
->real
.max
)
151 totals
->real
.max
= a
->real
.max
;
153 totals
->cpu
.total
+= a
->cpu
.total
;
154 if (totals
->cpu
.max
< a
->cpu
.max
)
155 totals
->cpu
.max
= a
->cpu
.max
;
160 cpu_record_print(struct vty
*vty
, unsigned char filter
)
162 struct cpu_thread_history tmp
;
163 void *args
[3] = {&tmp
, vty
, &filter
};
165 memset(&tmp
, 0, sizeof tmp
);
166 tmp
.funcname
= "TOTAL";
170 vty_out(vty
, "%21s %18s %18s%s",
171 "", "CPU (user+system):", "Real (wall-clock):", VTY_NEWLINE
);
173 vty_out(vty
, "Runtime(ms) Invoked Avg uSec Max uSecs");
175 vty_out(vty
, " Avg uSec Max uSecs");
177 vty_out(vty
, " Type Thread%s", VTY_NEWLINE
);
178 hash_iterate(cpu_record
,
179 (void(*)(struct hash_backet
*,void*))cpu_record_hash_print
,
182 if (tmp
.total_calls
> 0)
183 vty_out_cpu_thread_history(vty
, &tmp
);
186 DEFUN(show_thread_cpu
,
188 "show thread cpu [FILTER]",
190 "Thread information\n"
192 "Display filter (rwtexb)\n")
195 unsigned char filter
= 0xff;
200 while (argv
[0][i
] != '\0')
202 switch ( argv
[0][i
] )
206 filter
|= (1 << THREAD_READ
);
210 filter
|= (1 << THREAD_WRITE
);
214 filter
|= (1 << THREAD_TIMER
);
218 filter
|= (1 << THREAD_EVENT
);
222 filter
|= (1 << THREAD_EXECUTE
);
226 filter
|= (1 << THREAD_BACKGROUND
);
235 vty_out(vty
, "Invalid filter \"%s\" specified,"
236 " must contain at least one of 'RWTEXB'%s",
237 argv
[0], VTY_NEWLINE
);
242 cpu_record_print(vty
, filter
);
246 /* List allocation and head/tail print out. */
248 thread_list_debug (struct thread_list
*list
)
250 printf ("count [%d] head [%p] tail [%p]\n",
251 list
->count
, list
->head
, list
->tail
);
254 /* Debug print for thread_master. */
255 static void __attribute__ ((unused
))
256 thread_master_debug (struct thread_master
*m
)
258 printf ("-----------\n");
259 printf ("readlist : ");
260 thread_list_debug (&m
->read
);
261 printf ("writelist : ");
262 thread_list_debug (&m
->write
);
263 printf ("timerlist : ");
264 thread_list_debug (&m
->timer
);
265 printf ("eventlist : ");
266 thread_list_debug (&m
->event
);
267 printf ("unuselist : ");
268 thread_list_debug (&m
->unuse
);
269 printf ("bgndlist : ");
270 thread_list_debug (&m
->background
);
271 printf ("total alloc: [%ld]\n", m
->alloc
);
272 printf ("-----------\n");
275 /* Allocate new thread master. */
276 struct thread_master
*
277 thread_master_create ()
279 if (cpu_record
== NULL
)
281 = hash_create_size (1011, (unsigned int (*) (void *))cpu_record_hash_key
,
282 (int (*) (void *, void *))cpu_record_hash_cmp
);
284 return (struct thread_master
*) XCALLOC (MTYPE_THREAD_MASTER
,
285 sizeof (struct thread_master
));
288 /* Add a new thread to the list. */
290 thread_list_add (struct thread_list
*list
, struct thread
*thread
)
293 thread
->prev
= list
->tail
;
295 list
->tail
->next
= thread
;
302 /* Add a new thread just before the point. */
304 thread_list_add_before (struct thread_list
*list
,
305 struct thread
*point
,
306 struct thread
*thread
)
308 thread
->next
= point
;
309 thread
->prev
= point
->prev
;
311 point
->prev
->next
= thread
;
314 point
->prev
= thread
;
318 /* Delete a thread from the list. */
319 static struct thread
*
320 thread_list_delete (struct thread_list
*list
, struct thread
*thread
)
323 thread
->next
->prev
= thread
->prev
;
325 list
->tail
= thread
->prev
;
327 thread
->prev
->next
= thread
->next
;
329 list
->head
= thread
->next
;
330 thread
->next
= thread
->prev
= NULL
;
335 /* Move thread to unuse list. */
337 thread_add_unuse (struct thread_master
*m
, struct thread
*thread
)
339 assert (m
!= NULL
&& thread
!= NULL
);
340 assert (thread
->next
== NULL
);
341 assert (thread
->prev
== NULL
);
342 assert (thread
->type
== THREAD_UNUSED
);
343 thread_list_add (&m
->unuse
, thread
);
344 /* XXX: Should we deallocate funcname here? */
347 /* Free all unused thread. */
349 thread_list_free (struct thread_master
*m
, struct thread_list
*list
)
354 for (t
= list
->head
; t
; t
= next
)
357 XFREE (MTYPE_THREAD_FUNCNAME
, t
->funcname
);
358 XFREE (MTYPE_THREAD
, t
);
364 /* Stop thread scheduler. */
366 thread_master_free (struct thread_master
*m
)
368 thread_list_free (m
, &m
->read
);
369 thread_list_free (m
, &m
->write
);
370 thread_list_free (m
, &m
->timer
);
371 thread_list_free (m
, &m
->event
);
372 thread_list_free (m
, &m
->ready
);
373 thread_list_free (m
, &m
->unuse
);
374 thread_list_free (m
, &m
->background
);
376 XFREE (MTYPE_THREAD_MASTER
, m
);
379 /* Thread list is empty or not. */
381 thread_empty (struct thread_list
*list
)
383 return list
->head
? 0 : 1;
386 /* Delete top of the list and return it. */
387 static struct thread
*
388 thread_trim_head (struct thread_list
*list
)
390 if (!thread_empty (list
))
391 return thread_list_delete (list
, list
->head
);
395 /* Return remain time in second. */
397 thread_timer_remain_second (struct thread
*thread
)
399 gettimeofday (&recent_time
, NULL
);
401 if (thread
->u
.sands
.tv_sec
- recent_time
.tv_sec
> 0)
402 return thread
->u
.sands
.tv_sec
- recent_time
.tv_sec
;
407 /* Trim blankspace and "()"s */
409 strip_funcname (const char *funcname
)
412 char tmp
, *ret
, *e
, *b
= buff
;
414 strncpy(buff
, funcname
, sizeof(buff
));
415 buff
[ sizeof(buff
) -1] = '\0';
416 e
= buff
+strlen(buff
) -1;
418 /* Wont work for funcname == "Word (explanation)" */
420 while (*b
== ' ' || *b
== '(')
422 while (*e
== ' ' || *e
== ')')
428 ret
= XSTRDUP (MTYPE_THREAD_FUNCNAME
, b
);
434 /* Get new thread. */
435 static struct thread
*
436 thread_get (struct thread_master
*m
, u_char type
,
437 int (*func
) (struct thread
*), void *arg
, const char* funcname
)
439 struct thread
*thread
;
441 if (!thread_empty (&m
->unuse
))
443 thread
= thread_trim_head (&m
->unuse
);
444 if (thread
->funcname
)
445 XFREE(MTYPE_THREAD_FUNCNAME
, thread
->funcname
);
449 thread
= XCALLOC (MTYPE_THREAD
, sizeof (struct thread
));
453 thread
->add_type
= type
;
458 thread
->funcname
= strip_funcname(funcname
);
463 /* Add new read thread. */
465 funcname_thread_add_read (struct thread_master
*m
,
466 int (*func
) (struct thread
*), void *arg
, int fd
, const char* funcname
)
468 struct thread
*thread
;
472 if (FD_ISSET (fd
, &m
->readfd
))
474 zlog (NULL
, LOG_WARNING
, "There is already read fd [%d]", fd
);
478 thread
= thread_get (m
, THREAD_READ
, func
, arg
, funcname
);
479 FD_SET (fd
, &m
->readfd
);
481 thread_list_add (&m
->read
, thread
);
486 /* Add new write thread. */
488 funcname_thread_add_write (struct thread_master
*m
,
489 int (*func
) (struct thread
*), void *arg
, int fd
, const char* funcname
)
491 struct thread
*thread
;
495 if (FD_ISSET (fd
, &m
->writefd
))
497 zlog (NULL
, LOG_WARNING
, "There is already write fd [%d]", fd
);
501 thread
= thread_get (m
, THREAD_WRITE
, func
, arg
, funcname
);
502 FD_SET (fd
, &m
->writefd
);
504 thread_list_add (&m
->write
, thread
);
509 static struct thread
*
510 funcname_thread_add_timer_timeval (struct thread_master
*m
,
511 int (*func
) (struct thread
*),
514 struct timeval
*time_relative
,
515 const char* funcname
)
517 struct thread
*thread
;
518 struct timeval alarm_time
;
519 struct thread_list
*list
;
524 assert (type
== THREAD_TIMER
|| type
== THREAD_BACKGROUND
);
525 assert (time_relative
);
527 list
= ((type
== THREAD_TIMER
) ? &m
->timer
: &m
->background
);
528 thread
= thread_get (m
, type
, func
, arg
, funcname
);
530 /* Do we need jitter here? */
531 gettimeofday (&recent_time
, NULL
);
532 alarm_time
.tv_sec
= recent_time
.tv_sec
+ time_relative
->tv_sec
;
533 alarm_time
.tv_usec
= recent_time
.tv_usec
+ time_relative
->tv_usec
;
534 thread
->u
.sands
= timeval_adjust(alarm_time
);
536 /* Sort by timeval. */
537 for (tt
= list
->head
; tt
; tt
= tt
->next
)
538 if (timeval_cmp (thread
->u
.sands
, tt
->u
.sands
) <= 0)
542 thread_list_add_before (list
, tt
, thread
);
544 thread_list_add (list
, thread
);
550 /* Add timer event thread. */
552 funcname_thread_add_timer (struct thread_master
*m
,
553 int (*func
) (struct thread
*),
554 void *arg
, long timer
, const char* funcname
)
563 return funcname_thread_add_timer_timeval (m
, func
, THREAD_TIMER
, arg
,
567 /* Add timer event thread with "millisecond" resolution */
569 funcname_thread_add_timer_msec (struct thread_master
*m
,
570 int (*func
) (struct thread
*),
571 void *arg
, long timer
, const char* funcname
)
577 trel
.tv_sec
= timer
/ 1000;
578 trel
.tv_usec
= 1000*(timer
% 1000);
580 return funcname_thread_add_timer_timeval (m
, func
, THREAD_TIMER
,
581 arg
, &trel
, funcname
);
584 /* Add a background thread, with an optional millisec delay */
586 funcname_thread_add_background (struct thread_master
*m
,
587 int (*func
) (struct thread
*),
588 void *arg
, long delay
,
589 const char *funcname
)
597 trel
.tv_sec
= delay
/ 1000;
598 trel
.tv_usec
= 1000*(delay
% 1000);
606 return funcname_thread_add_timer_timeval (m
, func
, THREAD_BACKGROUND
,
607 arg
, &trel
, funcname
);
610 /* Add simple event thread. */
612 funcname_thread_add_event (struct thread_master
*m
,
613 int (*func
) (struct thread
*), void *arg
, int val
, const char* funcname
)
615 struct thread
*thread
;
619 thread
= thread_get (m
, THREAD_EVENT
, func
, arg
, funcname
);
621 thread_list_add (&m
->event
, thread
);
626 /* Cancel thread from scheduler. */
628 thread_cancel (struct thread
*thread
)
630 struct thread_list
*list
;
632 switch (thread
->type
)
635 assert (FD_ISSET (thread
->u
.fd
, &thread
->master
->readfd
));
636 FD_CLR (thread
->u
.fd
, &thread
->master
->readfd
);
637 list
= &thread
->master
->read
;
640 assert (FD_ISSET (thread
->u
.fd
, &thread
->master
->writefd
));
641 FD_CLR (thread
->u
.fd
, &thread
->master
->writefd
);
642 list
= &thread
->master
->write
;
645 list
= &thread
->master
->timer
;
648 list
= &thread
->master
->event
;
651 list
= &thread
->master
->ready
;
653 case THREAD_BACKGROUND
:
654 list
= &thread
->master
->background
;
660 thread_list_delete (list
, thread
);
661 thread
->type
= THREAD_UNUSED
;
662 thread_add_unuse (thread
->master
, thread
);
665 /* Delete all events which has argument value arg. */
667 thread_cancel_event (struct thread_master
*m
, void *arg
)
669 unsigned int ret
= 0;
670 struct thread
*thread
;
672 thread
= m
->event
.head
;
683 thread_list_delete (&m
->event
, t
);
684 t
->type
= THREAD_UNUSED
;
685 thread_add_unuse (m
, t
);
691 static struct timeval
*
692 thread_timer_wait (struct thread_list
*tlist
, struct timeval
*timer_val
)
694 if (!thread_empty (tlist
))
696 *timer_val
= timeval_subtract (tlist
->head
->u
.sands
, recent_time
);
702 static struct thread
*
703 thread_run (struct thread_master
*m
, struct thread
*thread
,
704 struct thread
*fetch
)
707 thread
->type
= THREAD_UNUSED
;
708 thread_add_unuse (m
, thread
);
713 thread_process_fd (struct thread_list
*list
, fd_set
*fdset
, fd_set
*mfdset
)
715 struct thread
*thread
;
721 for (thread
= list
->head
; thread
; thread
= next
)
725 if (FD_ISSET (THREAD_FD (thread
), fdset
))
727 assert (FD_ISSET (THREAD_FD (thread
), mfdset
));
728 FD_CLR(THREAD_FD (thread
), mfdset
);
729 thread_list_delete (list
, thread
);
730 thread_list_add (&thread
->master
->ready
, thread
);
731 thread
->type
= THREAD_READY
;
738 /* Add all timers that have popped to the ready list. */
740 thread_timer_process (struct thread_list
*list
, struct timeval
*timenow
)
742 struct thread
*thread
;
743 unsigned int ready
= 0;
745 for (thread
= list
->head
; thread
; thread
= thread
->next
)
747 if (timeval_cmp (*timenow
, thread
->u
.sands
) < 0)
749 thread_list_delete (list
, thread
);
750 thread
->type
= THREAD_READY
;
751 thread_list_add (&thread
->master
->ready
, thread
);
757 /* Fetch next ready thread. */
759 thread_fetch (struct thread_master
*m
, struct thread
*fetch
)
761 struct thread
*thread
;
765 struct timeval timer_val
;
766 struct timeval timer_val_bg
;
767 struct timeval
*timer_wait
;
768 struct timeval
*timer_wait_bg
;
774 /* Signals are highest priority */
775 quagga_sigevent_process ();
777 /* Normal event are the next highest priority. */
778 if ((thread
= thread_trim_head (&m
->event
)) != NULL
)
779 return thread_run (m
, thread
, fetch
);
781 /* If there are any ready threads from previous scheduler runs,
782 * process top of them.
784 if ((thread
= thread_trim_head (&m
->ready
)) != NULL
)
785 return thread_run (m
, thread
, fetch
);
787 /* Structure copy. */
789 writefd
= m
->writefd
;
790 exceptfd
= m
->exceptfd
;
792 /* Calculate select wait timer if nothing else to do */
793 gettimeofday (&recent_time
, NULL
);
794 timer_wait
= thread_timer_wait (&m
->timer
, &timer_val
);
795 timer_wait_bg
= thread_timer_wait (&m
->background
, &timer_val_bg
);
798 (!timer_wait
|| (timeval_cmp (*timer_wait
, *timer_wait_bg
) > 0)))
799 timer_wait
= timer_wait_bg
;
801 num
= select (FD_SETSIZE
, &readfd
, &writefd
, &exceptfd
, timer_wait
);
803 /* Signals should get quick treatment */
807 continue; /* signal received - process it */
808 zlog_warn ("select() error: %s", safe_strerror (errno
));
812 /* Check foreground timers. Historically, they have had higher
813 priority than I/O threads, so let's push them onto the ready
814 list in front of the I/O threads. */
815 gettimeofday (&recent_time
, NULL
);
816 thread_timer_process (&m
->timer
, &recent_time
);
818 /* Got IO, process it */
821 /* Normal priority read thead. */
822 thread_process_fd (&m
->read
, &readfd
, &m
->readfd
);
824 thread_process_fd (&m
->write
, &writefd
, &m
->writefd
);
828 /* If any threads were made ready above (I/O or foreground timer),
829 perhaps we should avoid adding background timers to the ready
830 list at this time. If this is code is uncommented, then background
831 timer threads will not run unless there is nothing else to do. */
832 if ((thread
= thread_trim_head (&m
->ready
)) != NULL
)
833 return thread_run (m
, thread
, fetch
);
836 /* Background timer/events, lowest priority */
837 thread_timer_process (&m
->background
, &recent_time
);
839 if ((thread
= thread_trim_head (&m
->ready
)) != NULL
)
840 return thread_run (m
, thread
, fetch
);
845 thread_consumed_time (RUSAGE_T
*now
, RUSAGE_T
*start
, unsigned long *cputime
)
848 /* This is 'user + sys' time. */
849 *cputime
= timeval_elapsed (now
->cpu
.ru_utime
, start
->cpu
.ru_utime
) +
850 timeval_elapsed (now
->cpu
.ru_stime
, start
->cpu
.ru_stime
);
853 #endif /* HAVE_RUSAGE */
854 return timeval_elapsed (now
->real
, start
->real
);
857 /* We should aim to yield after THREAD_YIELD_TIME_SLOT milliseconds.
858 Note: we are using real (wall clock) time for this calculation.
859 It could be argued that CPU time may make more sense in certain
860 contexts. The things to consider are whether the thread may have
861 blocked (in which case wall time increases, but CPU time does not),
862 or whether the system is heavily loaded with other processes competing
863 for CPU time. On balance, wall clock time seems to make sense.
864 Plus it has the added benefit that gettimeofday should be faster
865 than calling getrusage. */
867 thread_should_yield (struct thread
*thread
)
869 gettimeofday(&recent_time
, NULL
);
870 return (timeval_elapsed(recent_time
, thread
->ru
.real
) >
871 THREAD_YIELD_TIME_SLOT
);
874 /* We check thread consumed time. If the system has getrusage, we'll
875 use that to get in-depth stats on the performance of the thread in addition
876 to wall clock time stats from gettimeofday. */
878 thread_call (struct thread
*thread
)
880 unsigned long realtime
, cputime
;
883 /* Cache a pointer to the relevant cpu history thread, if the thread
884 * does not have it yet.
886 * Callers submitting 'dummy threads' hence must take care that
887 * thread->cpu is NULL
891 struct cpu_thread_history tmp
;
893 tmp
.func
= thread
->func
;
894 tmp
.funcname
= thread
->funcname
;
896 thread
->hist
= hash_get (cpu_record
, &tmp
,
897 (void * (*) (void *))cpu_record_hash_alloc
);
900 GETRUSAGE (&thread
->ru
);
902 (*thread
->func
) (thread
);
906 realtime
= thread_consumed_time (&ru
, &thread
->ru
, &cputime
);
907 thread
->hist
->real
.total
+= realtime
;
908 if (thread
->hist
->real
.max
< realtime
)
909 thread
->hist
->real
.max
= realtime
;
911 thread
->hist
->cpu
.total
+= cputime
;
912 if (thread
->hist
->cpu
.max
< cputime
)
913 thread
->hist
->cpu
.max
= cputime
;
916 ++(thread
->hist
->total_calls
);
917 thread
->hist
->types
|= (1 << thread
->add_type
);
919 #ifdef CONSUMED_TIME_CHECK
920 if (realtime
> CONSUMED_TIME_CHECK
)
923 * We have a CPU Hog on our hands.
924 * Whinge about it now, so we're aware this is yet another task
927 zlog_warn ("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)",
929 (unsigned long) thread
->func
,
930 realtime
/1000, cputime
/1000);
932 #endif /* CONSUMED_TIME_CHECK */
937 funcname_thread_execute (struct thread_master
*m
,
938 int (*func
)(struct thread
*),
941 const char* funcname
)
945 memset (&dummy
, 0, sizeof (struct thread
));
947 dummy
.type
= THREAD_EVENT
;
948 dummy
.add_type
= THREAD_EXECUTE
;
953 dummy
.funcname
= strip_funcname (funcname
);
954 thread_call (&dummy
);
956 XFREE (MTYPE_THREAD_FUNCNAME
, dummy
.funcname
);