blob: 212f04a8cf4fb230b4d4c6ea2392692f3ce77d6a [file] [log] [blame]
Benny Prijono9033e312005-11-21 02:08:39 +00001/* $Id$ */
2/*
3 * Copyright (C)2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pj/timer.h>
20#include <pj/pool.h>
21#include <pj/os.h>
22#include <pj/string.h>
23#include <pj/assert.h>
24#include <pj/errno.h>
25#include <pj/lock.h>
26
27#define HEAP_PARENT(X) (X == 0 ? 0 : (((X) - 1) / 2))
28#define HEAP_LEFT(X) (((X)+(X))+1)
29
30
31#define DEFAULT_MAX_TIMED_OUT_PER_POLL (64)
32
33
34/**
35 * The implementation of timer heap.
36 */
37struct pj_timer_heap_t
38{
39 /** Pool from which the timer heap resize will get the storage from */
40 pj_pool_t *pool;
41
42 /** Maximum size of the heap. */
43 pj_size_t max_size;
44
45 /** Current size of the heap. */
46 pj_size_t cur_size;
47
48 /** Max timed out entries to process per poll. */
49 unsigned max_entries_per_poll;
50
51 /** Lock object. */
52 pj_lock_t *lock;
53
54 /** Autodelete lock. */
55 pj_bool_t auto_delete_lock;
56
57 /**
58 * Current contents of the Heap, which is organized as a "heap" of
59 * pj_timer_entry *'s. In this context, a heap is a "partially
60 * ordered, almost complete" binary tree, which is stored in an
61 * array.
62 */
63 pj_timer_entry **heap;
64
65 /**
66 * An array of "pointers" that allows each pj_timer_entry in the
67 * <heap_> to be located in O(1) time. Basically, <timer_id_[i]>
68 * contains the slot in the <heap_> array where an pj_timer_entry
69 * with timer id <i> resides. Thus, the timer id passed back from
70 * <schedule_entry> is really an slot into the <timer_ids> array. The
71 * <timer_ids_> array serves two purposes: negative values are
72 * treated as "pointers" for the <freelist_>, whereas positive
73 * values are treated as "pointers" into the <heap_> array.
74 */
75 pj_timer_id_t *timer_ids;
76
77 /**
78 * "Pointer" to the first element in the freelist contained within
79 * the <timer_ids_> array, which is organized as a stack.
80 */
81 pj_timer_id_t timer_ids_freelist;
82
83 /** Callback to be called when a timer expires. */
84 pj_timer_heap_callback *callback;
85
86};
87
88
89
90PJ_INLINE(void) lock_timer_heap( pj_timer_heap_t *ht )
91{
92 if (ht->lock) {
93 pj_lock_acquire(ht->lock);
94 }
95}
96
97PJ_INLINE(void) unlock_timer_heap( pj_timer_heap_t *ht )
98{
99 if (ht->lock) {
100 pj_lock_release(ht->lock);
101 }
102}
103
104
105static void copy_node( pj_timer_heap_t *ht, int slot, pj_timer_entry *moved_node )
106{
107 PJ_CHECK_STACK();
108
109 // Insert <moved_node> into its new location in the heap.
110 ht->heap[slot] = moved_node;
111
112 // Update the corresponding slot in the parallel <timer_ids_> array.
113 ht->timer_ids[moved_node->_timer_id] = slot;
114}
115
116static pj_timer_id_t pop_freelist( pj_timer_heap_t *ht )
117{
118 // We need to truncate this to <int> for backwards compatibility.
119 pj_timer_id_t new_id = ht->timer_ids_freelist;
120
121 PJ_CHECK_STACK();
122
123 // The freelist values in the <timer_ids_> are negative, so we need
124 // to negate them to get the next freelist "pointer."
125 ht->timer_ids_freelist =
126 -ht->timer_ids[ht->timer_ids_freelist];
127
128 return new_id;
129
130}
131
132static void push_freelist (pj_timer_heap_t *ht, pj_timer_id_t old_id)
133{
134 PJ_CHECK_STACK();
135
136 // The freelist values in the <timer_ids_> are negative, so we need
137 // to negate them to get the next freelist "pointer."
138 ht->timer_ids[old_id] = -ht->timer_ids_freelist;
139 ht->timer_ids_freelist = old_id;
140}
141
142
143static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry *moved_node,
144 size_t slot, size_t child)
145{
146 PJ_CHECK_STACK();
147
148 // Restore the heap property after a deletion.
149
150 while (child < ht->cur_size)
151 {
152 // Choose the smaller of the two children.
153 if (child + 1 < ht->cur_size
154 && PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value, ht->heap[child]->_timer_value))
155 child++;
156
157 // Perform a <copy> if the child has a larger timeout value than
158 // the <moved_node>.
159 if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value, moved_node->_timer_value))
160 {
161 copy_node( ht, slot, ht->heap[child]);
162 slot = child;
163 child = HEAP_LEFT(child);
164 }
165 else
166 // We've found our location in the heap.
167 break;
168 }
169
170 copy_node( ht, slot, moved_node);
171}
172
173static void reheap_up( pj_timer_heap_t *ht, pj_timer_entry *moved_node,
174 size_t slot, size_t parent)
175{
176 // Restore the heap property after an insertion.
177
178 while (slot > 0)
179 {
180 // If the parent node is greater than the <moved_node> we need
181 // to copy it down.
182 if (PJ_TIME_VAL_LT(moved_node->_timer_value, ht->heap[parent]->_timer_value))
183 {
184 copy_node(ht, slot, ht->heap[parent]);
185 slot = parent;
186 parent = HEAP_PARENT(slot);
187 }
188 else
189 break;
190 }
191
192 // Insert the new node into its proper resting place in the heap and
193 // update the corresponding slot in the parallel <timer_ids> array.
194 copy_node(ht, slot, moved_node);
195}
196
197
198static pj_timer_entry * remove_node( pj_timer_heap_t *ht, size_t slot)
199{
200 pj_timer_entry *removed_node = ht->heap[slot];
201
202 // Return this timer id to the freelist.
203 push_freelist( ht, removed_node->_timer_id );
204
205 // Decrement the size of the heap by one since we're removing the
206 // "slot"th node.
207 ht->cur_size--;
208
209 // Set the ID
210 removed_node->_timer_id = -1;
211
212 // Only try to reheapify if we're not deleting the last entry.
213
214 if (slot < ht->cur_size)
215 {
216 int parent;
217 pj_timer_entry *moved_node = ht->heap[ht->cur_size];
218
219 // Move the end node to the location being removed and update
220 // the corresponding slot in the parallel <timer_ids> array.
221 copy_node( ht, slot, moved_node);
222
223 // If the <moved_node->time_value_> is great than or equal its
224 // parent it needs be moved down the heap.
225 parent = HEAP_PARENT (slot);
226
227 if (PJ_TIME_VAL_GTE(moved_node->_timer_value, ht->heap[parent]->_timer_value))
228 reheap_down( ht, moved_node, slot, HEAP_LEFT(slot));
229 else
230 reheap_up( ht, moved_node, slot, parent);
231 }
232
233 return removed_node;
234}
235
236static void grow_heap(pj_timer_heap_t *ht)
237{
238 // All the containers will double in size from max_size_
239 size_t new_size = ht->max_size * 2;
240 pj_timer_id_t *new_timer_ids;
241 pj_size_t i;
242
243 // First grow the heap itself.
244
245 pj_timer_entry **new_heap = 0;
246
247 new_heap = pj_pool_alloc(ht->pool, sizeof(pj_timer_entry*) * new_size);
248 memcpy(new_heap, ht->heap, ht->max_size * sizeof(pj_timer_entry*));
249 //delete [] this->heap_;
250 ht->heap = new_heap;
251
252 // Grow the array of timer ids.
253
254 new_timer_ids = 0;
255 new_timer_ids = pj_pool_alloc(ht->pool, new_size * sizeof(pj_timer_id_t));
256
257 memcpy( new_timer_ids, ht->timer_ids, ht->max_size * sizeof(pj_timer_id_t));
258
259 //delete [] timer_ids_;
260 ht->timer_ids = new_timer_ids;
261
262 // And add the new elements to the end of the "freelist".
263 for (i = ht->max_size; i < new_size; i++)
264 ht->timer_ids[i] = -((pj_timer_id_t) (i + 1));
265
266 ht->max_size = new_size;
267}
268
269static void insert_node(pj_timer_heap_t *ht, pj_timer_entry *new_node)
270{
271 if (ht->cur_size + 2 >= ht->max_size)
272 grow_heap(ht);
273
274 reheap_up( ht, new_node, ht->cur_size, HEAP_PARENT(ht->cur_size));
275 ht->cur_size++;
276}
277
278
279static pj_status_t schedule_entry( pj_timer_heap_t *ht,
280 pj_timer_entry *entry,
281 const pj_time_val *future_time )
282{
283 if (ht->cur_size < ht->max_size)
284 {
285 // Obtain the next unique sequence number.
286 // Set the entry
287 entry->_timer_id = pop_freelist(ht);
288 entry->_timer_value = *future_time;
289 insert_node( ht, entry);
290 return 0;
291 }
292 else
293 return -1;
294}
295
296
297static int cancel( pj_timer_heap_t *ht,
298 pj_timer_entry *entry,
299 int dont_call)
300{
301 long timer_node_slot;
302
303 PJ_CHECK_STACK();
304
305 // Check to see if the timer_id is out of range
306 if (entry->_timer_id < 0 || (pj_size_t)entry->_timer_id > ht->max_size)
307 return 0;
308
309 timer_node_slot = ht->timer_ids[entry->_timer_id];
310
311 if (timer_node_slot < 0) // Check to see if timer_id is still valid.
312 return 0;
313
314 if (entry != ht->heap[timer_node_slot])
315 {
316 pj_assert(entry == ht->heap[timer_node_slot]);
317 return 0;
318 }
319 else
320 {
321 remove_node( ht, timer_node_slot);
322
323 if (dont_call == 0)
324 // Call the close hook.
325 (*ht->callback)(ht, entry);
326 return 1;
327 }
328}
329
330
331/*
332 * Calculate memory size required to create a timer heap.
333 */
334PJ_DEF(pj_size_t) pj_timer_heap_mem_size(pj_size_t count)
335{
336 return /* size of the timer heap itself: */
337 sizeof(pj_timer_heap_t) +
338 /* size of each entry: */
339 (count+2) * (sizeof(pj_timer_entry*)+sizeof(pj_timer_id_t)) +
340 /* lock, pool etc: */
341 132;
342}
343
344/*
345 * Create a new timer heap.
346 */
347PJ_DEF(pj_status_t) pj_timer_heap_create( pj_pool_t *pool,
348 pj_size_t size,
349 pj_timer_heap_t **p_heap)
350{
351 pj_timer_heap_t *ht;
352 pj_size_t i;
353
354 PJ_ASSERT_RETURN(pool && p_heap, PJ_EINVAL);
355
356 *p_heap = NULL;
357
358 /* Magic? */
359 size += 2;
360
361 /* Allocate timer heap data structure from the pool */
362 ht = pj_pool_alloc(pool, sizeof(pj_timer_heap_t));
363 if (!ht)
364 return PJ_ENOMEM;
365
366 /* Initialize timer heap sizes */
367 ht->max_size = size;
368 ht->cur_size = 0;
369 ht->max_entries_per_poll = DEFAULT_MAX_TIMED_OUT_PER_POLL;
370 ht->timer_ids_freelist = 1;
371 ht->pool = pool;
372
373 /* Lock. */
374 ht->lock = NULL;
375 ht->auto_delete_lock = 0;
376
377 // Create the heap array.
378 ht->heap = pj_pool_alloc(pool, sizeof(pj_timer_entry*) * size);
379 if (!ht->heap)
380 return PJ_ENOMEM;
381
382 // Create the parallel
383 ht->timer_ids = pj_pool_alloc( pool, sizeof(pj_timer_id_t) * size);
384 if (!ht->timer_ids)
385 return PJ_ENOMEM;
386
387 // Initialize the "freelist," which uses negative values to
388 // distinguish freelist elements from "pointers" into the <heap_>
389 // array.
390 for (i=0; i<size; ++i)
391 ht->timer_ids[i] = -((pj_timer_id_t) (i + 1));
392
393 *p_heap = ht;
394 return PJ_SUCCESS;
395}
396
397PJ_DEF(void) pj_timer_heap_destroy( pj_timer_heap_t *ht )
398{
399 if (ht->lock && ht->auto_delete_lock) {
400 pj_lock_destroy(ht->lock);
401 ht->lock = NULL;
402 }
403}
404
405PJ_DEF(void) pj_timer_heap_set_lock( pj_timer_heap_t *ht,
406 pj_lock_t *lock,
407 pj_bool_t auto_del )
408{
409 if (ht->lock && ht->auto_delete_lock)
410 pj_lock_destroy(ht->lock);
411
412 ht->lock = lock;
413 ht->auto_delete_lock = auto_del;
414}
415
416
417PJ_DEF(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht,
418 unsigned count )
419{
420 unsigned old_count = ht->max_entries_per_poll;
421 ht->max_entries_per_poll = count;
422 return old_count;
423}
424
425PJ_DEF(pj_timer_entry*) pj_timer_entry_init( pj_timer_entry *entry,
426 int id,
427 void *user_data,
428 pj_timer_heap_callback *cb )
429{
430 pj_assert(entry && cb);
431
432 entry->id = id;
433 entry->user_data = user_data;
434 entry->cb = cb;
435
436 return entry;
437}
438
439PJ_DEF(pj_status_t) pj_timer_heap_schedule( pj_timer_heap_t *ht,
440 pj_timer_entry *entry,
441 const pj_time_val *delay)
442{
443 pj_status_t status;
444 pj_time_val expires;
445
446 PJ_ASSERT_RETURN(ht && entry && delay, PJ_EINVAL);
447
448 pj_gettimeofday(&expires);
449 PJ_TIME_VAL_ADD(expires, *delay);
450
451 lock_timer_heap(ht);
452 status = schedule_entry(ht, entry, &expires);
453 unlock_timer_heap(ht);
454
455 return status;
456}
457
458PJ_DEF(int) pj_timer_heap_cancel( pj_timer_heap_t *ht,
459 pj_timer_entry *entry)
460{
461 int count;
462
463 PJ_ASSERT_RETURN(ht && entry, PJ_EINVAL);
464
465 lock_timer_heap(ht);
466 count = cancel(ht, entry, 1);
467 unlock_timer_heap(ht);
468
469 return count;
470}
471
472PJ_DEF(unsigned) pj_timer_heap_poll( pj_timer_heap_t *ht,
473 pj_time_val *next_delay )
474{
475 pj_time_val now;
476 unsigned count;
477
478 PJ_ASSERT_RETURN(ht, 0);
479
480 if (!ht->cur_size && next_delay) {
481 next_delay->sec = next_delay->msec = PJ_MAXINT32;
482 return 0;
483 }
484
485 count = 0;
486 pj_gettimeofday(&now);
487
488 lock_timer_heap(ht);
489 while ( ht->cur_size &&
490 PJ_TIME_VAL_LTE(ht->heap[0]->_timer_value, now) &&
491 count < ht->max_entries_per_poll )
492 {
493 pj_timer_entry *node = remove_node(ht, 0);
494 ++count;
495
496 unlock_timer_heap(ht);
497 (*node->cb)(ht, node);
498 lock_timer_heap(ht);
499 }
500 if (ht->cur_size && next_delay) {
501 *next_delay = ht->heap[0]->_timer_value;
502 PJ_TIME_VAL_SUB(*next_delay, now);
503 } else if (next_delay) {
504 next_delay->sec = next_delay->msec = PJ_MAXINT32;
505 }
506 unlock_timer_heap(ht);
507
508 return count;
509}
510
511PJ_DEF(pj_size_t) pj_timer_heap_count( pj_timer_heap_t *ht )
512{
513 PJ_ASSERT_RETURN(ht, 0);
514
515 return ht->cur_size;
516}
517
518PJ_DEF(pj_status_t) pj_timer_heap_earliest_time( pj_timer_heap_t * ht,
519 pj_time_val *timeval)
520{
521 pj_assert(ht->cur_size != 0);
522 if (ht->cur_size == 0)
523 return PJ_ENOTFOUND;
524
525 lock_timer_heap(ht);
526 *timeval = ht->heap[0]->_timer_value;
527 unlock_timer_heap(ht);
528
529 return PJ_SUCCESS;
530}
531