diff --git a/lib/thread.c b/lib/thread.c index 97448894e5..43bae242c6 100644 --- a/lib/thread.c +++ b/lib/thread.c @@ -706,6 +706,7 @@ thread_get (struct thread_master *m, u_char type, thread->func = func; thread->arg = arg; thread->index = -1; + thread->yield = THREAD_YIELD_TIME_SLOT; /* default */ strip_funcname (thread->funcname, funcname); @@ -1198,7 +1199,8 @@ thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) return timeval_elapsed (now->real, start->real); } -/* We should aim to yield after THREAD_YIELD_TIME_SLOT milliseconds. +/* We should aim to yield after yield milliseconds, which defaults + to THREAD_YIELD_TIME_SLOT . Note: we are using real (wall clock) time for this calculation. It could be argued that CPU time may make more sense in certain contexts. The things to consider are whether the thread may have @@ -1212,7 +1214,13 @@ thread_should_yield (struct thread *thread) { quagga_get_relative (NULL); return (timeval_elapsed(relative_time, thread->real) > - THREAD_YIELD_TIME_SLOT); + thread->yield); +} + +void +thread_set_yield_time (struct thread *thread, unsigned long yield_time) +{ + thread->yield = yield_time; } void diff --git a/lib/thread.h b/lib/thread.h index bc7eaef447..f47dc92350 100644 --- a/lib/thread.h +++ b/lib/thread.h @@ -85,6 +85,7 @@ struct thread int index; /* used for timers to store position in queue */ struct timeval real; struct cpu_thread_history *hist; /* cache pointer to cpu_history */ + unsigned long yield; /* yield time in us */ char funcname[FUNCNAME_LEN]; }; @@ -209,6 +210,8 @@ extern void thread_call (struct thread *); extern unsigned long thread_timer_remain_second (struct thread *); extern int thread_should_yield (struct thread *); extern unsigned long timeval_elapsed (struct timeval a, struct timeval b); +/* set yield time for thread */ +extern void thread_set_yield_time (struct thread *, unsigned long); /* Internal libzebra exports */ extern void thread_getrusage (RUSAGE_T *); diff --git a/lib/workqueue.c b/lib/workqueue.c index 61643bf814..a7f4b70716 100644 --- a/lib/workqueue.c +++ b/lib/workqueue.c @@ -84,6 +84,7 @@ work_queue_new (struct thread_master *m, const char *queue_name) /* Default values, can be overriden by caller */ new->spec.hold = WORK_QUEUE_DEFAULT_HOLD; + new->spec.yield = THREAD_YIELD_TIME_SLOT; return new; } @@ -113,6 +114,9 @@ work_queue_schedule (struct work_queue *wq, unsigned int delay) { wq->thread = thread_add_background (wq->master, work_queue_run, wq, delay); + /* set thread yield time, if needed */ + if (wq->thread && wq->spec.yield != THREAD_YIELD_TIME_SLOT) + thread_set_yield_time (wq->thread, wq->spec.yield); return 1; } else @@ -174,27 +178,27 @@ DEFUN(show_work_queues, struct work_queue *wq; vty_out (vty, - "%c %8s %5s %8s %21s%s", - ' ', "List","(ms) ","Q. Runs","Cycle Counts ", + "%c %8s %5s %8s %8s %21s%s", + ' ', "List","(ms) ","Q. Runs","Yields","Cycle Counts ", VTY_NEWLINE); vty_out (vty, - "%c %8s %5s %8s %7s %6s %6s %s%s", + "%c %8s %5s %8s %8s %7s %6s %8s %6s %s%s", 'P', "Items", "Hold", - "Total", - "Best","Gran.","Avg.", + "Total","Total", + "Best","Gran.","Total","Avg.", "Name", VTY_NEWLINE); for (ALL_LIST_ELEMENTS_RO ((&work_queues), node, wq)) { - vty_out (vty,"%c %8d %5d %8ld %7d %6d %6u %s%s", + vty_out (vty,"%c %8d %5d %8ld %8ld %7d %6d %8ld %6u %s%s", (CHECK_FLAG (wq->flags, WQ_UNPLUGGED) ? ' ' : 'P'), listcount (wq->items), wq->spec.hold, - wq->runs, - wq->cycles.best, wq->cycles.granularity, + wq->runs, wq->yields, + wq->cycles.best, wq->cycles.granularity, wq->cycles.total, (wq->runs) ? (unsigned int) (wq->cycles.total / wq->runs) : 0, wq->name, @@ -250,7 +254,8 @@ work_queue_run (struct thread *thread) assert (wq && wq->items); /* calculate cycle granularity: - * list iteration == 1 cycle + * list iteration == 1 run + * listnode processing == 1 cycle * granularity == # cycles between checks whether we should yield. * * granularity should be > 0, and can increase slowly after each run to @@ -309,6 +314,14 @@ work_queue_run (struct thread *thread) { item->ran--; work_queue_item_requeue (wq, node); + /* If a single node is being used with a meta-queue (e.g., zebra), + * update the next node as we don't want to exit the thread and + * reschedule it after every node. By definition, WQ_REQUEUE is + * meant to continue the processing; the yield logic will kick in + * to terminate the thread when time has exceeded. + */ + if (nnode == NULL) + nnode = node; break; } case WQ_RETRY_NOW: @@ -346,7 +359,7 @@ stats: /* we yielded, check whether granularity should be reduced */ if (yielded && (cycles < wq->cycles.granularity)) { - wq->cycles.granularity = ((cycles > 0) ? cycles + wq->cycles.granularity = ((cycles > 0) ? cycles : WORK_QUEUE_MIN_GRANULARITY); } /* otherwise, should granularity increase? */ @@ -354,7 +367,7 @@ stats: { if (cycles > wq->cycles.best) wq->cycles.best = cycles; - + /* along with yielded check, provides hysteresis for granularity */ if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR * WQ_HYSTERESIS_FACTOR)) @@ -366,6 +379,8 @@ stats: wq->runs++; wq->cycles.total += cycles; + if (yielded) + wq->yields++; #if 0 printf ("%s: cycles %d, new: best %d, worst %d\n", diff --git a/lib/workqueue.h b/lib/workqueue.h index f59499a0a7..7d9a877d10 100644 --- a/lib/workqueue.h +++ b/lib/workqueue.h @@ -84,11 +84,14 @@ struct work_queue unsigned int max_retries; unsigned int hold; /* hold time for first run, in ms */ + + unsigned long yield; /* yield time in us for associated thread */ } spec; /* remaining fields should be opaque to users */ struct list *items; /* queue item list */ unsigned long runs; /* runs count */ + unsigned long yields; /* yields count */ struct { unsigned int best;