Line | Count | Source |
1 | | /* |
2 | | * Task management functions. |
3 | | * |
4 | | * Copyright 2000-2009 Willy Tarreau <w@1wt.eu> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #include <string.h> |
14 | | |
15 | | #include <import/eb32tree.h> |
16 | | |
17 | | #include <haproxy/api.h> |
18 | | #include <haproxy/activity.h> |
19 | | #include <haproxy/cfgparse.h> |
20 | | #include <haproxy/clock.h> |
21 | | #include <haproxy/fd.h> |
22 | | #include <haproxy/list.h> |
23 | | #include <haproxy/pool.h> |
24 | | #include <haproxy/task.h> |
25 | | #include <haproxy/tools.h> |
26 | | |
27 | | extern struct task *process_stream(struct task *t, void *context, unsigned int state); |
28 | | extern void stream_update_timings(struct task *t, uint64_t lat, uint64_t cpu); |
29 | | |
30 | | DECLARE_TYPED_POOL(pool_head_task, "task", struct task, 0, 64); |
31 | | DECLARE_TYPED_POOL(pool_head_tasklet, "tasklet", struct tasklet, 0, 64); |
32 | | |
33 | | /* This is the memory pool containing all the signal structs. These |
34 | | * struct are used to store each required signal between two tasks. |
35 | | */ |
36 | | DECLARE_TYPED_POOL(pool_head_notification, "notification", struct notification); |
37 | | |
38 | | /* used to detect if the scheduler looks stuck (for warnings) */ |
39 | | static struct { |
40 | | int sched_stuck THREAD_ALIGNED(); |
41 | | } sched_ctx[MAX_THREADS]; |
42 | | |
43 | | #if !defined(HA_CAS_IS_8B) && !defined(HA_HAVE_CAS_DW) |
44 | | __decl_thread(HA_SPINLOCK_T task_state_tid); |
45 | | #endif |
46 | | |
47 | | /* Flags the task <t> for immediate destruction and puts it into its first |
48 | | * thread's shared tasklet list if not yet queued/running. This will bypass |
49 | | * the priority scheduling and make the task show up as fast as possible in |
50 | | * the other thread's queue. Note that this operation isn't idempotent and is |
51 | | * not supposed to be run on the same task from multiple threads at once. It's |
52 | | * the caller's responsibility to make sure it is the only one able to kill the |
53 | | * task. |
54 | | */ |
55 | | void task_kill(struct task *t) |
56 | 0 | { |
57 | 0 | unsigned int state = t->state; |
58 | 0 | unsigned int thr; |
59 | |
|
60 | 0 | BUG_ON(state & TASK_KILLED); |
61 | |
|
62 | 0 | while (1) { |
63 | 0 | while (state & (TASK_RUNNING | TASK_QUEUED)) { |
64 | | /* task already in the queue and about to be executed, |
65 | | * or even currently running. Just add the flag and be |
66 | | * done with it, the process loop will detect it and kill |
67 | | * it. The CAS will fail if we arrive too late. |
68 | | */ |
69 | 0 | if (_HA_ATOMIC_CAS(&t->state, &state, state | TASK_KILLED)) |
70 | 0 | return; |
71 | 0 | } |
72 | | |
73 | | /* We'll have to wake it up, but we must also secure it so that |
74 | | * it doesn't vanish under us. TASK_QUEUED guarantees nobody will |
75 | | * add past us. |
76 | | */ |
77 | 0 | if (_HA_ATOMIC_CAS(&t->state, &state, state | TASK_QUEUED | TASK_KILLED)) { |
78 | | /* Bypass the tree and go directly into the shared tasklet list. |
79 | | * Note: that's a task so it must be accounted for as such. Pick |
80 | | * the task's first thread for the job. |
81 | | */ |
82 | 0 | thr = __task_get_current_owner(t->tid); |
83 | 0 | if (thr == -1) |
84 | 0 | thr = tid; |
85 | | |
86 | | /* Beware: tasks that have never run don't have their ->list empty yet! */ |
87 | 0 | MT_LIST_APPEND(&ha_thread_ctx[thr].shared_tasklet_list, |
88 | 0 | list_to_mt_list(&((struct tasklet *)t)->list)); |
89 | 0 | _HA_ATOMIC_INC(&ha_thread_ctx[thr].rq_total); |
90 | 0 | _HA_ATOMIC_INC(&ha_thread_ctx[thr].tasks_in_list); |
91 | 0 | wake_thread(thr); |
92 | 0 | return; |
93 | 0 | } |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | | /* Equivalent of task_kill for tasklets. Mark the tasklet <t> for destruction. |
98 | | * It will be deleted on the next scheduler invocation. This function is |
99 | | * thread-safe : a thread can kill a tasklet of another thread. |
100 | | */ |
101 | | void tasklet_kill(struct tasklet *t) |
102 | 0 | { |
103 | 0 | unsigned int state = t->state; |
104 | 0 | unsigned int thr; |
105 | |
|
106 | 0 | BUG_ON(state & TASK_KILLED); |
107 | |
|
108 | 0 | while (1) { |
109 | 0 | while (state & (TASK_QUEUED)) { |
110 | | /* Tasklet already in the list ready to be executed. Add |
111 | | * the killed flag and wait for the process loop to |
112 | | * detect it. |
113 | | */ |
114 | 0 | if (_HA_ATOMIC_CAS(&t->state, &state, state | TASK_KILLED)) |
115 | 0 | return; |
116 | 0 | } |
117 | | |
118 | | /* Mark the tasklet as killed and wake the thread to process it |
119 | | * as soon as possible. |
120 | | */ |
121 | 0 | if (_HA_ATOMIC_CAS(&t->state, &state, state | TASK_QUEUED | TASK_KILLED)) { |
122 | 0 | thr = t->tid >= 0 ? t->tid : tid; |
123 | 0 | MT_LIST_APPEND(&ha_thread_ctx[thr].shared_tasklet_list, |
124 | 0 | list_to_mt_list(&t->list)); |
125 | 0 | _HA_ATOMIC_INC(&ha_thread_ctx[thr].rq_total); |
126 | 0 | wake_thread(thr); |
127 | 0 | return; |
128 | 0 | } |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | /* Do not call this one, please use tasklet_wakeup_here() instead, as this one |
133 | | * is the slow path of tasklet_wakeup_here() which performs some preliminary |
134 | | * checks and sets TASK_QUEUED before calling this one. It is only permitted to |
135 | | * call this function for tasks/tasklets that are not bound (->tid==-1) or that |
136 | | * are bound to the local thread (->tid==tid || ->tid==-2-tid). |
137 | | */ |
138 | | void __tasklet_wakeup_here(struct tasklet *tl) |
139 | 0 | { |
140 | 0 | BUG_ON_HOT(tl->tid != -1 && tl->tid != tid && tl->tid != -2 - tid); |
141 | |
|
142 | 0 | if (_HA_ATOMIC_LOAD(&th_ctx->flags) & TH_FL_TASK_PROFILING) |
143 | 0 | tl->wake_date = now_mono_time(); |
144 | | |
145 | | /* this tasklet runs on the caller thread */ |
146 | 0 | if (tl->state & TASK_HEAVY) { |
147 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_HEAVY], &tl->list); |
148 | 0 | th_ctx->tl_class_mask |= 1 << TL_HEAVY; |
149 | 0 | } |
150 | 0 | else if (unlikely(tl->state & TASK_RT)) { |
151 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_RT], &tl->list); |
152 | 0 | th_ctx->tl_class_mask |= 1 << TL_RT; |
153 | 0 | } |
154 | 0 | else if (tl->state & TASK_SELF_WAKING) { |
155 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_BULK], &tl->list); |
156 | 0 | th_ctx->tl_class_mask |= 1 << TL_BULK; |
157 | 0 | } |
158 | 0 | else if ((struct task *)tl == th_ctx->current && !(tl->state & TASK_WOKEN_ANY)) { |
159 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_BULK], &tl->list); |
160 | 0 | th_ctx->tl_class_mask |= 1 << TL_BULK; |
161 | 0 | } |
162 | 0 | else if (th_ctx->current_queue < 0) { |
163 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_URGENT], &tl->list); |
164 | 0 | th_ctx->tl_class_mask |= 1 << TL_URGENT; |
165 | 0 | } |
166 | 0 | else { |
167 | 0 | LIST_APPEND(&th_ctx->tasklets[TL_NORMAL], &tl->list); |
168 | 0 | th_ctx->tl_class_mask |= 1 << TL_NORMAL; |
169 | 0 | } |
170 | 0 | _HA_ATOMIC_INC(&th_ctx->rq_total); |
171 | 0 | } |
172 | | |
173 | | /* Do not call this one, please use tasklet_wakeup_on() instead, as this one is |
174 | | * the slow path of tasklet_wakeup_on() which performs some preliminary checks |
175 | | * and sets TASK_QUEUED before calling this one. |
176 | | */ |
177 | | void __tasklet_wakeup_on(struct tasklet *tl, int thr) |
178 | 0 | { |
179 | 0 | BUG_ON_HOT(thr < 0); |
180 | |
|
181 | 0 | if (_HA_ATOMIC_LOAD(&ha_thread_ctx[thr].flags) & TH_FL_TASK_PROFILING) |
182 | 0 | tl->wake_date = now_mono_time(); |
183 | |
|
184 | 0 | MT_LIST_APPEND(&ha_thread_ctx[thr].shared_tasklet_list, list_to_mt_list(&tl->list)); |
185 | 0 | _HA_ATOMIC_INC(&ha_thread_ctx[thr].rq_total); |
186 | 0 | wake_thread(thr); |
187 | 0 | } |
188 | | |
189 | | /* Do not call this one, please use tasklet_wakeup_after_on() instead, as this one is |
190 | | * the slow path of tasklet_wakeup_after() which performs some preliminary checks |
191 | | * and sets TASK_QUEUED before calling this one. |
192 | | */ |
193 | | struct list *__tasklet_wakeup_after(struct list *head, struct tasklet *tl) |
194 | 0 | { |
195 | 0 | BUG_ON(tl->tid >= 0 && tid != tl->tid); |
196 | | |
197 | | /* this tasklet runs on the caller thread */ |
198 | 0 | if (!head) { |
199 | 0 | __tasklet_wakeup_here(tl); |
200 | 0 | } |
201 | 0 | else { |
202 | 0 | if (_HA_ATOMIC_LOAD(&th_ctx->flags) & TH_FL_TASK_PROFILING) |
203 | 0 | tl->wake_date = now_mono_time(); |
204 | 0 | LIST_APPEND(head, &tl->list); |
205 | 0 | _HA_ATOMIC_INC(&th_ctx->rq_total); |
206 | 0 | } |
207 | 0 | return &tl->list; |
208 | 0 | } |
209 | | |
210 | | /* Puts the task <t> in run queue at a position depending on t->nice. <t> is |
211 | | * returned. The nice value assigns boosts in 32th of the run queue size. A |
212 | | * nice value of -1024 sets the task to -tasks_run_queue*32, while a nice value |
213 | | * of 1024 sets the task to tasks_run_queue*32. The state flags are cleared, so |
214 | | * the caller will have to set its flags after this call. |
215 | | * The task must not already be in the run queue. If unsure, use the safer |
216 | | * task_wakeup() function. |
217 | | */ |
218 | | void __task_wakeup(struct task *t) |
219 | 0 | { |
220 | 0 | struct eb_root *root = &th_ctx->rqueue; |
221 | | /* |
222 | | * At this point the task tid should always be set to the relevant |
223 | | * thread, so we can just use __task_get_current_owner(); |
224 | | */ |
225 | 0 | int thr __maybe_unused = __task_get_current_owner(t->tid); |
226 | |
|
227 | 0 | BUG_ON(t->tid == -1); |
228 | |
|
229 | 0 | if (unlikely(_HA_ATOMIC_LOAD(&t->state) & TASK_RT)) { |
230 | | /* real-time tasks must be super rare; they are woken up as tasklets. */ |
231 | 0 | if (thr < 0 || thr == tid) |
232 | 0 | __tasklet_wakeup_here((struct tasklet *)t); |
233 | 0 | else |
234 | 0 | __tasklet_wakeup_on((struct tasklet *)t, thr); |
235 | 0 | return; |
236 | 0 | } |
237 | | |
238 | | #ifdef USE_THREAD |
239 | | if (thr != tid) { |
240 | | root = &ha_thread_ctx[thr].rqueue_shared; |
241 | | |
242 | | _HA_ATOMIC_INC(&ha_thread_ctx[thr].rq_total); |
243 | | HA_SPIN_LOCK(TASK_RQ_LOCK, &ha_thread_ctx[thr].rqsh_lock); |
244 | | |
245 | | t->rq.key = _HA_ATOMIC_ADD_FETCH(&ha_thread_ctx[thr].rqueue_ticks, 1); |
246 | | __ha_barrier_store(); |
247 | | } else |
248 | | #endif |
249 | 0 | { |
250 | 0 | _HA_ATOMIC_INC(&th_ctx->rq_total); |
251 | 0 | t->rq.key = _HA_ATOMIC_ADD_FETCH(&th_ctx->rqueue_ticks, 1); |
252 | 0 | } |
253 | |
|
254 | 0 | if (likely(t->nice)) { |
255 | 0 | int offset; |
256 | |
|
257 | 0 | _HA_ATOMIC_INC(&ha_thread_info[thr].tg_ctx->niced_tasks); |
258 | 0 | offset = t->nice * (int)global.tune.runqueue_depth; |
259 | 0 | t->rq.key += offset; |
260 | 0 | } |
261 | |
|
262 | 0 | if (_HA_ATOMIC_LOAD(&th_ctx->flags) & TH_FL_TASK_PROFILING) |
263 | 0 | t->wake_date = now_mono_time(); |
264 | |
|
265 | 0 | eb32_insert(root, &t->rq); |
266 | |
|
267 | | #ifdef USE_THREAD |
268 | | if (thr != tid) { |
269 | | HA_SPIN_UNLOCK(TASK_RQ_LOCK, &ha_thread_ctx[thr].rqsh_lock); |
270 | | |
271 | | /* If all threads that are supposed to handle this task are sleeping, |
272 | | * wake one. |
273 | | */ |
274 | | wake_thread(thr); |
275 | | } |
276 | | #endif |
277 | 0 | return; |
278 | 0 | } |
279 | | |
280 | | /* |
281 | | * __task_queue() |
282 | | * |
283 | | * Inserts a task into wait queue <wq> at the position given by its expiration |
284 | | * date. It does not matter if the task was already in the wait queue or not, |
285 | | * as it will be unlinked. The task MUST NOT have an infinite expiration timer. |
286 | | * Last, tasks must not be queued further than the end of the tree, which is |
287 | | * between <now_ms> and <now_ms> + 2^31 ms (now+24days in 32bit). |
288 | | * |
289 | | * This function should not be used directly, it is meant to be called by the |
290 | | * inline version of task_queue() which performs a few cheap preliminary tests |
291 | | * before deciding to call __task_queue(). Moreover this function doesn't care |
292 | | * at all about locking so the caller must be careful when deciding whether to |
293 | | * lock or not around this call. |
294 | | */ |
295 | | void __task_queue(struct task *task) |
296 | 0 | { |
297 | 0 | int old_state, new_state; |
298 | 0 | int old_tid; |
299 | 0 | int cur_owner; |
300 | | |
301 | | /* if this happens the process is doomed anyway, so better catch it now |
302 | | * so that we have the caller in the stack. |
303 | | */ |
304 | 0 | BUG_ON(task->expire == TICK_ETERNITY); |
305 | |
|
306 | 0 | do { |
307 | 0 | new_state = old_state = _HA_ATOMIC_LOAD(&task->state); |
308 | 0 | if (old_state & TASK_KILLED) |
309 | 0 | return; |
310 | 0 | old_tid = _HA_ATOMIC_LOAD(&task->tid); |
311 | 0 | cur_owner = __task_get_current_owner(old_tid); |
312 | 0 | if (old_tid != -1 && cur_owner != tid) |
313 | 0 | new_state |= TASK_WOKEN_WQ; |
314 | 0 | } while (!(__task_set_state_and_tid(task, old_tid, __task_get_new_tid_field(old_tid), old_state, new_state))); |
315 | | |
316 | 0 | if (cur_owner != tid && cur_owner != -1) { |
317 | | /* |
318 | | * If the task has already been woken up to be added in the |
319 | | * wait queue, nothing left to do, the target thread will |
320 | | * eventually do the right thing. |
321 | | */ |
322 | 0 | if (!(old_state & TASK_WOKEN_WQ)) |
323 | 0 | _task_wakeup(task, 0, NULL); |
324 | 0 | return; |
325 | 0 | } |
326 | | |
327 | 0 | if (likely(task_in_wq(task))) |
328 | 0 | __task_unlink_wq(task); |
329 | | |
330 | | /* the task is not in the queue now */ |
331 | 0 | task->wq.key = task->expire; |
332 | | #ifdef DEBUG_CHECK_INVALID_EXPIRATION_DATES |
333 | | if (tick_is_lt(task->wq.key, now_ms)) |
334 | | /* we're queuing too far away or in the past (most likely) */ |
335 | | return; |
336 | | #endif |
337 | |
|
338 | 0 | eb32_insert(&th_ctx->timers, &task->wq); |
339 | 0 | } |
340 | | |
341 | | /* |
342 | | * Extract all expired timers from the timer queue, and wakes up all |
343 | | * associated tasks. |
344 | | */ |
345 | | void wake_expired_tasks() |
346 | 0 | { |
347 | 0 | struct thread_ctx * const tt = th_ctx; // thread's tasks |
348 | 0 | int max_processed = global.tune.runqueue_depth; |
349 | 0 | struct task *task; |
350 | 0 | struct eb32_node *eb; |
351 | |
|
352 | 0 | while (1) { |
353 | 0 | if (max_processed-- <= 0) |
354 | 0 | goto leave; |
355 | | |
356 | 0 | eb = eb32_lookup_ge(&tt->timers, now_ms - TIMER_LOOK_BACK); |
357 | 0 | if (!eb) { |
358 | | /* we might have reached the end of the tree, typically because |
359 | | * <now_ms> is in the first half and we're first scanning the last |
360 | | * half. Let's loop back to the beginning of the tree now. |
361 | | */ |
362 | 0 | eb = eb32_first(&tt->timers); |
363 | 0 | if (likely(!eb)) |
364 | 0 | break; |
365 | 0 | } |
366 | | |
367 | | /* It is possible that this task was left at an earlier place in the |
368 | | * tree because a recent call to task_queue() has not moved it. This |
369 | | * happens when the new expiration date is later than the old one. |
370 | | * Since it is very unlikely that we reach a timeout anyway, it's a |
371 | | * lot cheaper to proceed like this because we almost never update |
372 | | * the tree. We may also find disabled expiration dates there. Since |
373 | | * we have detached the task from the tree, we simply call task_queue |
374 | | * to take care of this. Note that we might occasionally requeue it at |
375 | | * the same place, before <eb>, so we have to check if this happens, |
376 | | * and adjust <eb>, otherwise we may skip it which is not what we want. |
377 | | * We may also not requeue the task (and not point eb at it) if its |
378 | | * expiration time is not set. We also make sure we leave the real |
379 | | * expiration date for the next task in the queue so that when calling |
380 | | * next_timer_expiry() we're guaranteed to see the next real date and |
381 | | * not the next apparent date. This is in order to avoid useless |
382 | | * wakeups. |
383 | | */ |
384 | | |
385 | 0 | task = eb32_entry(eb, struct task, wq); |
386 | 0 | if (tick_is_expired(task->expire, now_ms)) { |
387 | 0 | int set_running = 0; |
388 | | |
389 | | /* expired task, wake it up */ |
390 | 0 | __task_unlink_wq(task); |
391 | | /* |
392 | | * If it's a shared task, see whether we should hand it |
393 | | * to a less loaded thread. |
394 | | */ |
395 | 0 | if (unlikely(task->tid < 0) && global.nbthread > 1) { |
396 | 0 | int attempts = MIN(global.nbthread, 3); |
397 | 0 | while (attempts-- > 0) { |
398 | 0 | uint new_tid = statistical_prng_range(global.nbthread); |
399 | |
|
400 | 0 | if (new_tid == tid) |
401 | 0 | continue; |
402 | | |
403 | 0 | ASSUME(new_tid < MAX_THREADS); |
404 | 0 | if (ha_thread_ctx[new_tid].rq_total * 2 < th_ctx->rq_total) { |
405 | 0 | int cur_state; |
406 | 0 | do { |
407 | 0 | cur_state = _HA_ATOMIC_LOAD(&task->state); |
408 | | /* |
409 | | * Okay the task is already in our runqueue, |
410 | | * or somebody owns the |
411 | | * TASK_RUNNING flag because |
412 | | * it is calling task_schedule(), give up. |
413 | | */ |
414 | 0 | if (cur_state & (TASK_QUEUED | TASK_RUNNING)) |
415 | 0 | break; |
416 | | /* |
417 | | * Make sure we have TASK_RUNNING set |
418 | | * so that the task don't |
419 | | * immediately run on the |
420 | | * new thread and gets |
421 | | * freed. |
422 | | */ |
423 | 0 | if (__task_set_state_and_tid(task, task->tid, -2 - new_tid, cur_state, cur_state | TASK_RUNNING)) { |
424 | 0 | set_running = 1; |
425 | 0 | break; |
426 | 0 | } |
427 | 0 | } while (1); |
428 | 0 | break; |
429 | 0 | } |
430 | 0 | } |
431 | 0 | } |
432 | 0 | if (set_running) |
433 | 0 | task_drop_running(task, TASK_WOKEN_TIMER); |
434 | 0 | else |
435 | 0 | _task_wakeup(task, TASK_WOKEN_TIMER, 0); |
436 | 0 | } |
437 | 0 | else if (task->expire != eb->key) { |
438 | | /* task is not expired but its key doesn't match so let's |
439 | | * update it and skip to next apparently expired task. |
440 | | */ |
441 | 0 | __task_unlink_wq(task); |
442 | 0 | if (tick_isset(task->expire)) |
443 | 0 | __task_queue(task); |
444 | 0 | } |
445 | 0 | else { |
446 | | /* task not expired and correctly placed. It may not be eternal. */ |
447 | 0 | BUG_ON(task->expire == TICK_ETERNITY); |
448 | 0 | break; |
449 | 0 | } |
450 | 0 | } |
451 | 0 | leave: |
452 | 0 | return; |
453 | 0 | } |
454 | | |
455 | | /* Checks the next timer for the current thread by looking into its own timer |
456 | | * list. It may return TICK_ETERNITY if no timer is present. |
457 | | * Note that the next timer might very well be slightly in the past. |
458 | | */ |
459 | | int next_timer_expiry() |
460 | 0 | { |
461 | 0 | struct thread_ctx * const tt = th_ctx; // thread's tasks |
462 | 0 | struct eb32_node *eb; |
463 | 0 | int ret = TICK_ETERNITY; |
464 | | |
465 | | /* first check in the thread-local timers */ |
466 | 0 | eb = eb32_lookup_ge(&tt->timers, now_ms - TIMER_LOOK_BACK); |
467 | 0 | if (!eb) { |
468 | | /* we might have reached the end of the tree, typically because |
469 | | * <now_ms> is in the first half and we're first scanning the last |
470 | | * half. Let's loop back to the beginning of the tree now. |
471 | | */ |
472 | 0 | eb = eb32_first(&tt->timers); |
473 | 0 | } |
474 | |
|
475 | 0 | if (eb) |
476 | 0 | ret = eb->key; |
477 | |
|
478 | 0 | return ret; |
479 | 0 | } |
480 | | |
481 | | /* Walks over tasklet lists th_ctx->tasklets[0..TL_CLASSES-1] and run at most |
482 | | * budget[TL_*] of them. Returns the number of entries effectively processed |
483 | | * (tasks and tasklets merged). The count of tasks in the list for the current |
484 | | * thread is adjusted. |
485 | | */ |
486 | | unsigned int run_tasks_from_lists(unsigned int budgets[]) |
487 | 0 | { |
488 | 0 | struct task *(*process)(struct task *t, void *ctx, unsigned int state); |
489 | 0 | struct list *tl_queues = th_ctx->tasklets; |
490 | 0 | struct task *t; |
491 | 0 | uint8_t budget_mask = (1 << TL_CLASSES) - 1; |
492 | 0 | struct sched_activity *profile_entry = NULL; |
493 | 0 | unsigned int done = 0; |
494 | 0 | unsigned int queue; |
495 | 0 | unsigned int state; |
496 | 0 | void *ctx; |
497 | |
|
498 | 0 | for (queue = 0; queue < TL_CLASSES;) { |
499 | 0 | th_ctx->current_queue = queue; |
500 | | |
501 | | /* global.tune.sched.low-latency is set */ |
502 | 0 | if (global.tune.options & GTUNE_SCHED_LOW_LATENCY) { |
503 | 0 | if (unlikely(th_ctx->tl_class_mask & budget_mask & ((1 << queue) - 1))) { |
504 | | /* a lower queue index has tasks again and still has a |
505 | | * budget to run them. Let's switch to it now. |
506 | | */ |
507 | 0 | queue = (th_ctx->tl_class_mask & 1) ? 0 : |
508 | 0 | (th_ctx->tl_class_mask & 2) ? 1 : |
509 | 0 | (th_ctx->tl_class_mask & 4) ? 2 : 3; |
510 | 0 | continue; |
511 | 0 | } |
512 | | |
513 | 0 | if (unlikely(queue > TL_URGENT && |
514 | 0 | budget_mask & (1 << TL_URGENT) && |
515 | 0 | !MT_LIST_ISEMPTY(&th_ctx->shared_tasklet_list))) { |
516 | | /* an urgent tasklet arrived from another thread */ |
517 | 0 | break; |
518 | 0 | } |
519 | | |
520 | 0 | if (unlikely(queue > TL_NORMAL && |
521 | 0 | budget_mask & (1 << TL_NORMAL) && |
522 | 0 | (!eb_is_empty(&th_ctx->rqueue) || !eb_is_empty(&th_ctx->rqueue_shared)))) { |
523 | | /* a task was woken up by a bulk tasklet or another thread */ |
524 | 0 | break; |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | 0 | if (LIST_ISEMPTY(&tl_queues[queue])) { |
529 | 0 | th_ctx->tl_class_mask &= ~(1 << queue); |
530 | 0 | queue++; |
531 | 0 | continue; |
532 | 0 | } |
533 | | |
534 | 0 | if (!budgets[queue]) { |
535 | 0 | budget_mask &= ~(1 << queue); |
536 | 0 | queue++; |
537 | 0 | continue; |
538 | 0 | } |
539 | | |
540 | 0 | t = (struct task *)LIST_ELEM(tl_queues[queue].n, struct tasklet *, list); |
541 | | |
542 | | /* check if this task has already run during this loop */ |
543 | 0 | if ((uint16_t)t->last_run == (uint16_t)activity[tid].loops) { |
544 | 0 | budget_mask &= ~(1 << queue); |
545 | 0 | queue++; |
546 | 0 | continue; |
547 | 0 | } |
548 | 0 | t->last_run = activity[tid].loops; |
549 | 0 | ctx = t->context; |
550 | 0 | process = t->process; |
551 | 0 | t->calls++; |
552 | |
|
553 | 0 | budgets[queue]--; |
554 | 0 | activity[tid].ctxsw++; |
555 | |
|
556 | 0 | th_ctx->lock_wait_total = 0; |
557 | 0 | th_ctx->mem_wait_total = 0; |
558 | 0 | th_ctx->locked_total = 0; |
559 | 0 | th_ctx->sched_wake_date = t->wake_date; |
560 | 0 | if (th_ctx->sched_wake_date || (t->state & TASK_F_WANTS_TIME)) { |
561 | | /* take the most accurate clock we have, either |
562 | | * mono_time() or last now_ns (monotonic but only |
563 | | * incremented once per poll loop). |
564 | | */ |
565 | 0 | th_ctx->sched_call_date = now_mono_time(); |
566 | 0 | if (unlikely(!th_ctx->sched_call_date)) |
567 | 0 | th_ctx->sched_call_date = now_ns; |
568 | 0 | } |
569 | |
|
570 | 0 | if (th_ctx->sched_wake_date) { |
571 | 0 | t->wake_date = 0; |
572 | 0 | profile_entry = sched_activity_entry(sched_activity, t->process, t->caller); |
573 | 0 | th_ctx->sched_profile_entry = profile_entry; |
574 | 0 | HA_ATOMIC_ADD(&profile_entry->lat_time, (uint32_t)(th_ctx->sched_call_date - th_ctx->sched_wake_date)); |
575 | 0 | HA_ATOMIC_INC(&profile_entry->calls); |
576 | 0 | } |
577 | |
|
578 | 0 | __ha_barrier_store(); |
579 | |
|
580 | 0 | th_ctx->current = t; |
581 | 0 | _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_STUCK); // this thread is still running |
582 | |
|
583 | 0 | _HA_ATOMIC_DEC(&th_ctx->rq_total); |
584 | 0 | LIST_DEL_INIT(&((struct tasklet *)t)->list); |
585 | 0 | __ha_barrier_store(); |
586 | | |
587 | | |
588 | | /* We must be the exclusive owner of the TASK_RUNNING bit, and |
589 | | * have to be careful that the task is not being manipulated on |
590 | | * another thread finding it expired in wake_expired_tasks(). |
591 | | * The TASK_RUNNING bit will be set during these operations, |
592 | | * they are extremely rare and do not last long so the best to |
593 | | * do here is to wait. |
594 | | */ |
595 | 0 | state = _HA_ATOMIC_LOAD(&t->state); |
596 | 0 | do { |
597 | 0 | while (unlikely(state & TASK_RUNNING)) { |
598 | 0 | __ha_cpu_relax(); |
599 | 0 | state = _HA_ATOMIC_LOAD(&t->state); |
600 | 0 | } |
601 | 0 | } while (!_HA_ATOMIC_CAS(&t->state, &state, (state & TASK_PERSISTENT) | TASK_RUNNING)); |
602 | |
|
603 | 0 | __ha_barrier_atomic_store(); |
604 | | |
605 | | /* keep the task counter up to date */ |
606 | 0 | if (!(state & TASK_F_TASKLET)) |
607 | 0 | _HA_ATOMIC_DEC(&ha_thread_ctx[tid].tasks_in_list); |
608 | | |
609 | | /* From this point, we know that the task or tasklet was properly |
610 | | * dequeued, flagged and accounted for. Let's now check if it was |
611 | | * killed. If TASK_KILLED arrived before we've read the state, we |
612 | | * directly free the task/tasklet. Otherwise for tasks it will be |
613 | | * seen after processing and it's freed on the exit path. |
614 | | */ |
615 | |
|
616 | 0 | if (unlikely((state & TASK_KILLED) || process == NULL)) { |
617 | | /* Task or tasklet has been killed, let's remove it */ |
618 | 0 | if (state & TASK_F_TASKLET) |
619 | 0 | pool_free(pool_head_tasklet, t); |
620 | 0 | else { |
621 | 0 | task_unlink_wq(t); |
622 | 0 | __task_free(t); |
623 | 0 | } |
624 | | /* We don't want max_processed to be decremented if |
625 | | * we're just freeing a destroyed task, we should only |
626 | | * do so if we really ran a task. |
627 | | */ |
628 | 0 | goto next; |
629 | 0 | } |
630 | | |
631 | 0 | if (state & TASK_WOKEN_WQ) { |
632 | | /* We should add this task to our wait queue */ |
633 | 0 | task_queue(t); |
634 | | /* |
635 | | * If this is the only reason the task got scheduled, |
636 | | * then we don't actually have ot run it. |
637 | | */ |
638 | 0 | if ((state & TASK_WOKEN_ANY) == TASK_WOKEN_WQ) { |
639 | 0 | task_drop_running(t, 0); |
640 | 0 | goto next; |
641 | 0 | } |
642 | 0 | state &= ~TASK_WOKEN_WQ; |
643 | 0 | } |
644 | | /* OK now the task or tasklet is well alive and is going to be run */ |
645 | 0 | if (state & TASK_F_TASKLET) { |
646 | | /* this is a tasklet */ |
647 | |
|
648 | 0 | t = EXEC_CTX_WITH_RET(EXEC_CTX_MAKE(TH_EX_CTX_TASK, process), |
649 | 0 | process(t, ctx, state)); |
650 | 0 | if (t != NULL) |
651 | 0 | _HA_ATOMIC_AND(&t->state, ~TASK_RUNNING); |
652 | 0 | } else { |
653 | | /* This is a regular task */ |
654 | |
|
655 | 0 | if (process == process_stream) |
656 | 0 | t = EXEC_CTX_WITH_RET(EXEC_CTX_MAKE(TH_EX_CTX_TASK, process_stream), |
657 | 0 | process_stream(t, ctx, state)); |
658 | 0 | else |
659 | 0 | t = EXEC_CTX_WITH_RET(EXEC_CTX_MAKE(TH_EX_CTX_TASK, process), |
660 | 0 | process(t, ctx, state)); |
661 | | |
662 | | /* If there is a pending state, we have to wake up the task |
663 | | * immediately, else we defer it into wait queue. |
664 | | */ |
665 | 0 | if (t != NULL) { |
666 | 0 | state = _HA_ATOMIC_LOAD(&t->state); |
667 | 0 | if (unlikely(state & TASK_KILLED)) { |
668 | 0 | task_unlink_wq(t); |
669 | 0 | __task_free(t); |
670 | 0 | } |
671 | 0 | else { |
672 | 0 | if (__task_get_current_owner(t->tid) == tid) |
673 | 0 | task_queue(t); |
674 | 0 | task_drop_running(t, 0); |
675 | 0 | } |
676 | 0 | } |
677 | 0 | } |
678 | 0 | done++; |
679 | 0 | next: |
680 | 0 | th_ctx->current = NULL; |
681 | 0 | sched_ctx[tid].sched_stuck = 0; // scheduler is not stuck (don't warn) |
682 | 0 | __ha_barrier_store(); |
683 | | |
684 | | /* stats are only registered for non-zero wake dates */ |
685 | 0 | if (unlikely(th_ctx->sched_wake_date)) { |
686 | 0 | HA_ATOMIC_ADD(&profile_entry->cpu_time, (uint32_t)(now_mono_time() - th_ctx->sched_call_date)); |
687 | 0 | if (th_ctx->lock_wait_total) |
688 | 0 | HA_ATOMIC_ADD(&profile_entry->lkw_time, th_ctx->lock_wait_total); |
689 | 0 | if (th_ctx->mem_wait_total) |
690 | 0 | HA_ATOMIC_ADD(&profile_entry->mem_time, th_ctx->mem_wait_total); |
691 | 0 | if (th_ctx->locked_total) |
692 | 0 | HA_ATOMIC_ADD(&profile_entry->lkd_time, th_ctx->locked_total); |
693 | 0 | } |
694 | 0 | } |
695 | 0 | th_ctx->current_queue = -1; |
696 | 0 | th_ctx->sched_wake_date = TICK_ETERNITY; |
697 | |
|
698 | 0 | return done; |
699 | 0 | } |
700 | | |
701 | | /* The run queue is chronologically sorted in a tree. An insertion counter is |
702 | | * used to assign a position to each task. This counter may be combined with |
703 | | * other variables (eg: nice value) to set the final position in the tree. The |
704 | | * counter may wrap without a problem, of course. We then limit the number of |
705 | | * tasks processed to 200 in any case, so that general latency remains low and |
706 | | * so that task positions have a chance to be considered. The function scans |
707 | | * both the global and local run queues and picks the most urgent task between |
708 | | * the two. We need to grab the global runqueue lock to touch it so it's taken |
709 | | * on the very first access to the global run queue and is released as soon as |
710 | | * it reaches the end. |
711 | | * |
712 | | * The function adjusts <next> if a new event is closer. |
713 | | */ |
714 | | void process_runnable_tasks() |
715 | 0 | { |
716 | 0 | struct thread_ctx * const tt = th_ctx; |
717 | 0 | struct eb32_node *lrq; // next local run queue entry |
718 | 0 | struct eb32_node *grq; // next global run queue entry |
719 | 0 | struct task *t; |
720 | 0 | const unsigned int default_weights[TL_CLASSES] = { |
721 | 0 | [TL_RT] = 1, // never more than 1 RT task at once |
722 | 0 | [TL_URGENT] = 64, // ~50% of CPU bandwidth for I/O |
723 | 0 | [TL_NORMAL] = 60, // ~47% of CPU bandwidth for tasks |
724 | 0 | [TL_BULK] = 4, // ~3% of CPU bandwidth for self-wakers |
725 | 0 | [TL_HEAVY] = 1, // never more than 1 heavy task at once |
726 | 0 | }; |
727 | 0 | unsigned int max[TL_CLASSES]; // max to be run per class |
728 | 0 | unsigned int max_total; // sum of max above |
729 | 0 | struct mt_list *tmp_list; |
730 | 0 | unsigned int queue; |
731 | 0 | int max_processed; |
732 | 0 | int lpicked, gpicked; |
733 | 0 | int rt_queued = 0; |
734 | 0 | int heavy_queued = 0; |
735 | 0 | int budget, done; |
736 | |
|
737 | 0 | _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_STUCK); // this thread is still running |
738 | |
|
739 | 0 | swrate_add_peak_local(&th_ctx->rq_tot_peak, RQ_LOAD_SAMPLES, th_ctx->rq_total); |
740 | |
|
741 | 0 | if (!thread_has_tasks()) { |
742 | 0 | activity[tid].empty_rq++; |
743 | 0 | return; |
744 | 0 | } |
745 | | |
746 | 0 | max_processed = global.tune.runqueue_depth; |
747 | |
|
748 | 0 | if (likely(tg_ctx->niced_tasks)) |
749 | 0 | max_processed = (max_processed + 3) / 4; |
750 | |
|
751 | 0 | if (max_processed < th_ctx->rq_total && th_ctx->rq_total <= 2*max_processed) { |
752 | | /* If the run queue exceeds the budget by up to 50%, let's cut it |
753 | | * into two identical halves to improve latency. |
754 | | */ |
755 | 0 | max_processed = th_ctx->rq_total / 2; |
756 | 0 | } |
757 | |
|
758 | 0 | not_done_yet: |
759 | 0 | max[TL_URGENT] = max[TL_NORMAL] = max[TL_BULK] = 0; |
760 | | |
761 | | /* RT tasklets list may be processed at most once */ |
762 | 0 | if (!rt_queued) { |
763 | 0 | if ((tt->tl_class_mask & (1 << TL_RT))) { |
764 | 0 | max[TL_RT] = default_weights[TL_RT]; |
765 | 0 | rt_queued = 1; |
766 | 0 | } |
767 | 0 | else |
768 | 0 | max[TL_RT] = 0; |
769 | 0 | } |
770 | | |
771 | | /* urgent tasklets list gets a default weight of ~50% */ |
772 | 0 | if ((tt->tl_class_mask & (1 << TL_URGENT)) || |
773 | 0 | !MT_LIST_ISEMPTY(&tt->shared_tasklet_list)) |
774 | 0 | max[TL_URGENT] = default_weights[TL_URGENT]; |
775 | | |
776 | | /* normal tasklets list gets a default weight of ~47% */ |
777 | 0 | if ((tt->tl_class_mask & (1 << TL_NORMAL)) || |
778 | 0 | !eb_is_empty(&th_ctx->rqueue) || !eb_is_empty(&th_ctx->rqueue_shared)) |
779 | 0 | max[TL_NORMAL] = default_weights[TL_NORMAL]; |
780 | | |
781 | | /* bulk tasklets list gets a default weight of ~3% */ |
782 | 0 | if ((tt->tl_class_mask & (1 << TL_BULK))) |
783 | 0 | max[TL_BULK] = default_weights[TL_BULK]; |
784 | | |
785 | | /* heavy tasks are processed only once and never refilled in a |
786 | | * call round. That budget is not lost either as we don't reset |
787 | | * it unless consumed. |
788 | | */ |
789 | 0 | if (!heavy_queued) { |
790 | 0 | if ((tt->tl_class_mask & (1 << TL_HEAVY))) |
791 | 0 | max[TL_HEAVY] = default_weights[TL_HEAVY]; |
792 | 0 | else |
793 | 0 | max[TL_HEAVY] = 0; |
794 | 0 | heavy_queued = 1; |
795 | 0 | } |
796 | | |
797 | | /* Now compute a fair share of the weights. Total may slightly exceed |
798 | | * 100% due to rounding, this is not a problem. Note that while in |
799 | | * theory the sum cannot be NULL as we cannot get there without tasklets |
800 | | * to process, in practice it seldom happens when multiple writers |
801 | | * conflict and rollback on MT_LIST_TRY_APPEND(shared_tasklet_list), causing |
802 | | * a first MT_LIST_ISEMPTY() to succeed for thread_has_task() and the |
803 | | * one above to finally fail. This is extremely rare and not a problem. |
804 | | */ |
805 | 0 | max_total = max[TL_RT] + max[TL_URGENT] + max[TL_NORMAL] + max[TL_BULK] + max[TL_HEAVY]; |
806 | 0 | if (!max_total) |
807 | 0 | goto leave; |
808 | | |
809 | 0 | for (queue = 0; queue < TL_CLASSES; queue++) |
810 | 0 | max[queue] = ((unsigned)max_processed * max[queue] + max_total - 1) / max_total; |
811 | | |
812 | | /* The RT queue must never process more than one task at once */ |
813 | 0 | if (max[TL_RT] > 1) |
814 | 0 | max[TL_RT] = 1; |
815 | | |
816 | | /* The heavy queue must never process more than very few tasks at once |
817 | | * anyway. We set the limit to 1 if running on low_latency scheduling, |
818 | | * given that we know that other values can have an impact on latency |
819 | | * (~500us end-to-end connection achieved at 130kcps in SSL), 1 + one |
820 | | * per 1024 tasks if there is at least one non-heavy task while still |
821 | | * respecting the ratios above, or 1 + one per 128 tasks if only heavy |
822 | | * tasks are present. This allows to drain excess SSL handshakes more |
823 | | * efficiently if the queue becomes congested. |
824 | | */ |
825 | 0 | if (max[TL_HEAVY] > 1) { |
826 | 0 | if (global.tune.options & GTUNE_SCHED_LOW_LATENCY) |
827 | 0 | budget = 1; |
828 | 0 | else if (tt->tl_class_mask & ~(1 << TL_HEAVY)) |
829 | 0 | budget = 1 + tt->rq_total / 1024; |
830 | 0 | else |
831 | 0 | budget = 1 + tt->rq_total / 128; |
832 | |
|
833 | 0 | if (max[TL_HEAVY] > budget) |
834 | 0 | max[TL_HEAVY] = budget; |
835 | 0 | } |
836 | |
|
837 | 0 | lrq = grq = NULL; |
838 | | |
839 | | /* pick up to max[TL_NORMAL] regular tasks from prio-ordered run queues */ |
840 | | /* Note: the grq lock is always held when grq is not null */ |
841 | 0 | lpicked = gpicked = 0; |
842 | 0 | budget = max[TL_NORMAL] - tt->tasks_in_list; |
843 | 0 | while (lpicked + gpicked < budget && (!rt_queued || !(global.tune.options & GTUNE_SCHED_LOW_LATENCY))) { |
844 | 0 | if (!eb_is_empty(&th_ctx->rqueue_shared) && !grq) { |
845 | | #ifdef USE_THREAD |
846 | | HA_SPIN_LOCK(TASK_RQ_LOCK, &th_ctx->rqsh_lock); |
847 | | grq = eb32_lookup_ge(&th_ctx->rqueue_shared, _HA_ATOMIC_LOAD(&tt->rqueue_ticks) - TIMER_LOOK_BACK); |
848 | | if (unlikely(!grq)) { |
849 | | grq = eb32_first(&th_ctx->rqueue_shared); |
850 | | if (!grq) |
851 | | HA_SPIN_UNLOCK(TASK_RQ_LOCK, &th_ctx->rqsh_lock); |
852 | | } |
853 | | #endif |
854 | 0 | } |
855 | | |
856 | | /* If a global task is available for this thread, it's in grq |
857 | | * now and the global RQ is locked. |
858 | | */ |
859 | |
|
860 | 0 | if (!lrq) { |
861 | 0 | lrq = eb32_lookup_ge(&tt->rqueue, _HA_ATOMIC_LOAD(&tt->rqueue_ticks) - TIMER_LOOK_BACK); |
862 | 0 | if (unlikely(!lrq)) |
863 | 0 | lrq = eb32_first(&tt->rqueue); |
864 | 0 | } |
865 | |
|
866 | 0 | if (!lrq && !grq) |
867 | 0 | break; |
868 | | |
869 | 0 | if (likely(!grq || (lrq && (int)(lrq->key - grq->key) <= 0))) { |
870 | 0 | t = eb32_entry(lrq, struct task, rq); |
871 | 0 | lrq = eb32_next(lrq); |
872 | 0 | eb32_delete(&t->rq); |
873 | 0 | lpicked++; |
874 | 0 | } |
875 | | #ifdef USE_THREAD |
876 | | else { |
877 | | t = eb32_entry(grq, struct task, rq); |
878 | | grq = eb32_next(grq); |
879 | | eb32_delete(&t->rq); |
880 | | |
881 | | if (unlikely(!grq)) { |
882 | | grq = eb32_first(&th_ctx->rqueue_shared); |
883 | | if (!grq) |
884 | | HA_SPIN_UNLOCK(TASK_RQ_LOCK, &th_ctx->rqsh_lock); |
885 | | } |
886 | | gpicked++; |
887 | | } |
888 | | #endif |
889 | 0 | if (t->nice) |
890 | 0 | _HA_ATOMIC_DEC(&tg_ctx->niced_tasks); |
891 | | |
892 | | /* Add it to the local task list */ |
893 | 0 | LIST_APPEND(&tt->tasklets[TL_NORMAL], &((struct tasklet *)t)->list); |
894 | 0 | } |
895 | | |
896 | | /* release the rqueue lock */ |
897 | 0 | if (grq) { |
898 | 0 | HA_SPIN_UNLOCK(TASK_RQ_LOCK, &th_ctx->rqsh_lock); |
899 | 0 | grq = NULL; |
900 | 0 | } |
901 | |
|
902 | 0 | if (lpicked + gpicked) { |
903 | 0 | tt->tl_class_mask |= 1 << TL_NORMAL; |
904 | 0 | _HA_ATOMIC_ADD(&tt->tasks_in_list, lpicked + gpicked); |
905 | 0 | activity[tid].tasksw += lpicked + gpicked; |
906 | 0 | } |
907 | | |
908 | | /* Merge the list of tasklets waken up by other threads to the |
909 | | * main list. |
910 | | */ |
911 | 0 | tmp_list = MT_LIST_BEHEAD(&tt->shared_tasklet_list); |
912 | 0 | if (tmp_list) { |
913 | 0 | LIST_SPLICE_END_DETACHED(&tt->tasklets[TL_URGENT], (struct list *)tmp_list); |
914 | 0 | if (!LIST_ISEMPTY(&tt->tasklets[TL_URGENT])) |
915 | 0 | tt->tl_class_mask |= 1 << TL_URGENT; |
916 | 0 | } |
917 | | |
918 | | /* execute tasklets in each queue */ |
919 | 0 | done = run_tasks_from_lists(max); |
920 | 0 | max_processed -= done; |
921 | | |
922 | | /* some tasks may have woken other ones up */ |
923 | 0 | if (done && max_processed > 0 && !rt_queued && thread_has_tasks()) |
924 | 0 | goto not_done_yet; |
925 | | |
926 | 0 | leave: |
927 | 0 | if (tt->tl_class_mask) |
928 | 0 | activity[tid].long_rq++; |
929 | 0 | } |
930 | | |
931 | | /* Pings the scheduler to verify that tasks continue running for thread <thr>. |
932 | | * Returns 1 if the scheduler made progress since last call, 0 if it looks |
933 | | * stuck. It marks it as stuck for next visit. |
934 | | */ |
935 | | int is_sched_alive(int thr) |
936 | 0 | { |
937 | 0 | return !HA_ATOMIC_XCHG(&sched_ctx[thr].sched_stuck, 1); |
938 | 0 | } |
939 | | |
940 | | /* |
941 | | * Delete every tasks before running the master polling loop |
942 | | */ |
943 | | void mworker_cleantasks() |
944 | 0 | { |
945 | 0 | struct task *t; |
946 | 0 | int i; |
947 | 0 | struct eb32_node *tmp_wq = NULL; |
948 | 0 | struct eb32_node *tmp_rq = NULL; |
949 | |
|
950 | | #ifdef USE_THREAD |
951 | | /* cleanup the global run queue */ |
952 | | tmp_rq = eb32_first(&th_ctx->rqueue_shared); |
953 | | while (tmp_rq) { |
954 | | t = eb32_entry(tmp_rq, struct task, rq); |
955 | | tmp_rq = eb32_next(tmp_rq); |
956 | | task_destroy(t); |
957 | | } |
958 | | #endif |
959 | | /* clean the per thread run queue */ |
960 | 0 | for (i = 0; i < global.nbthread; i++) { |
961 | 0 | tmp_rq = eb32_first(&ha_thread_ctx[i].rqueue); |
962 | 0 | while (tmp_rq) { |
963 | 0 | t = eb32_entry(tmp_rq, struct task, rq); |
964 | 0 | tmp_rq = eb32_next(tmp_rq); |
965 | 0 | task_destroy(t); |
966 | 0 | } |
967 | | /* cleanup the per thread timers queue */ |
968 | 0 | tmp_wq = eb32_first(&ha_thread_ctx[i].timers); |
969 | 0 | while (tmp_wq) { |
970 | 0 | t = eb32_entry(tmp_wq, struct task, wq); |
971 | 0 | tmp_wq = eb32_next(tmp_wq); |
972 | 0 | task_destroy(t); |
973 | 0 | } |
974 | 0 | } |
975 | 0 | } |
976 | | |
977 | | /* perform minimal initializations */ |
978 | | static void init_task() |
979 | 0 | { |
980 | 0 | int i, q; |
981 | |
|
982 | 0 | for (i = 0; i < MAX_THREADS; i++) { |
983 | 0 | for (q = 0; q < TL_CLASSES; q++) |
984 | 0 | LIST_INIT(&ha_thread_ctx[i].tasklets[q]); |
985 | 0 | MT_LIST_INIT(&ha_thread_ctx[i].shared_tasklet_list); |
986 | 0 | } |
987 | 0 | } |
988 | | |
989 | | /* config parser for global "tune.sched.low-latency", accepts "on" or "off" */ |
990 | | static int cfg_parse_tune_sched_low_latency(char **args, int section_type, struct proxy *curpx, |
991 | | const struct proxy *defpx, const char *file, int line, |
992 | | char **err) |
993 | 0 | { |
994 | 0 | if (too_many_args(1, args, err, NULL)) |
995 | 0 | return -1; |
996 | | |
997 | 0 | if (strcmp(args[1], "on") == 0) |
998 | 0 | global.tune.options |= GTUNE_SCHED_LOW_LATENCY; |
999 | 0 | else if (strcmp(args[1], "off") == 0) |
1000 | 0 | global.tune.options &= ~GTUNE_SCHED_LOW_LATENCY; |
1001 | 0 | else { |
1002 | 0 | memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); |
1003 | 0 | return -1; |
1004 | 0 | } |
1005 | 0 | return 0; |
1006 | 0 | } |
1007 | | |
1008 | | /* config keyword parsers */ |
1009 | | static struct cfg_kw_list cfg_kws = {ILH, { |
1010 | | { CFG_GLOBAL, "tune.sched.low-latency", cfg_parse_tune_sched_low_latency }, |
1011 | | { 0, NULL, NULL } |
1012 | | }}; |
1013 | | |
1014 | | INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); |
1015 | | INITCALL0(STG_PREPARE, init_task); |
1016 | | |
1017 | | /* |
1018 | | * Local variables: |
1019 | | * c-indent-level: 8 |
1020 | | * c-basic-offset: 8 |
1021 | | * End: |
1022 | | */ |