Index: pjproject/trunk/pjlib/src/pj/timer.c
===================================================================
--- pjproject/trunk/pjlib/src/pj/timer.c	(revision 5971)
+++ pjproject/trunk/pjlib/src/pj/timer.c	(revision 6058)
@@ -47,4 +47,13 @@
 #define DEFAULT_MAX_TIMED_OUT_PER_POLL  (64)
 
+/* Enable this to raise assertion in order to catch bug of timer entry
+ * which has been deallocated without being cancelled. If disabled,
+ * the timer heap will simply remove the destroyed entry (and print log)
+ * and resume normally.
+ * This setting only works if PJ_TIMER_HEAP_USE_COPY is enabled.
+ */
+#define ASSERT_IF_ENTRY_DESTROYED (PJ_TIMER_HEAP_USE_COPY? 0: 0)
+
+
 enum
 {
@@ -54,4 +63,51 @@
 };
 
+#if PJ_TIMER_HEAP_USE_COPY
+
+/* Duplicate/copy of the timer entry. */
+typedef struct pj_timer_entry_dup
+{
+    /**
+     * The duplicate copy.
+     */
+    pj_timer_entry  dup;		
+    
+    /**
+     * Pointer of the original timer entry.
+     */
+    pj_timer_entry *entry;
+
+    /** 
+     * The future time when the timer expires, which the value is updated
+     * by timer heap when the timer is scheduled.
+     */
+    pj_time_val     _timer_value;
+
+    /**
+     * Internal: the group lock used by this entry, set when
+     * pj_timer_heap_schedule_w_lock() is used.
+     */
+    pj_grp_lock_t  *_grp_lock;
+
+#if PJ_TIMER_DEBUG
+    const char	   *src_file;
+    int		    src_line;
+#endif
+
+} pj_timer_entry_dup;
+
+#define GET_TIMER(ht, node) &ht->timer_dups[node->_timer_id]
+#define GET_ENTRY(node) node->entry
+#define GET_FIELD(node, _timer_id) node->dup._timer_id
+
+#else
+
+typedef pj_timer_entry pj_timer_entry_dup;
+
+#define GET_TIMER(ht, node) node
+#define GET_ENTRY(node) node
+#define GET_FIELD(node, _timer_id) node->_timer_id
+
+#endif
 
 /**
@@ -84,5 +140,5 @@
      * array.
      */
-    pj_timer_entry **heap;
+    pj_timer_entry_dup **heap;
 
     /**
@@ -97,4 +153,9 @@
      */
     pj_timer_id_t *timer_ids;
+    
+    /**
+     * An array of timer entry copies.
+     */
+    pj_timer_entry_dup *timer_dups;
 
     /**
@@ -127,5 +188,5 @@
 
 static void copy_node( pj_timer_heap_t *ht, pj_size_t slot, 
-		       pj_timer_entry *moved_node )
+		       pj_timer_entry_dup *moved_node )
 {
     PJ_CHECK_STACK();
@@ -135,5 +196,5 @@
     
     // Update the corresponding slot in the parallel <timer_ids_> array.
-    ht->timer_ids[moved_node->_timer_id] = (int)slot;
+    ht->timer_ids[GET_FIELD(moved_node, _timer_id)] = (int)slot;
 }
 
@@ -165,5 +226,5 @@
 
 
-static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry *moved_node,
+static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node,
                         size_t slot, size_t child)
 {
@@ -175,11 +236,15 @@
     {
 	// Choose the smaller of the two children.
-	if (child + 1 < ht->cur_size
-	    && PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value, ht->heap[child]->_timer_value))
+	if (child + 1 < ht->cur_size &&
+	    PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value,
+	    		   ht->heap[child]->_timer_value))
+	{
 	    child++;
+	}
 	
 	// Perform a <copy> if the child has a larger timeout value than
 	// the <moved_node>.
-	if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value, moved_node->_timer_value))
+	if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value,
+			   moved_node->_timer_value))
         {
 	    copy_node( ht, slot, ht->heap[child]);
@@ -195,5 +260,5 @@
 }
 
-static void reheap_up( pj_timer_heap_t *ht, pj_timer_entry *moved_node,
+static void reheap_up( pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node,
 		       size_t slot, size_t parent)
 {
@@ -204,5 +269,6 @@
 	// If the parent node is greater than the <moved_node> we need
 	// to copy it down.
-	if (PJ_TIME_VAL_LT(moved_node->_timer_value, ht->heap[parent]->_timer_value))
+	if (PJ_TIME_VAL_LT(moved_node->_timer_value,
+			   ht->heap[parent]->_timer_value))
         {
 	    copy_node(ht, slot, ht->heap[parent]);
@@ -220,10 +286,10 @@
 
 
-static pj_timer_entry * remove_node( pj_timer_heap_t *ht, size_t slot)
-{
-    pj_timer_entry *removed_node = ht->heap[slot];
+static pj_timer_entry_dup * remove_node( pj_timer_heap_t *ht, size_t slot)
+{
+    pj_timer_entry_dup *removed_node = ht->heap[slot];
     
     // Return this timer id to the freelist.
-    push_freelist( ht, removed_node->_timer_id );
+    push_freelist( ht, GET_FIELD(removed_node, _timer_id) );
     
     // Decrement the size of the heap by one since we're removing the
@@ -232,5 +298,23 @@
     
     // Set the ID
-    removed_node->_timer_id = -1;
+    if (GET_FIELD(removed_node, _timer_id) !=
+    	GET_ENTRY(removed_node)->_timer_id)
+    {
+	    PJ_LOG(3,(THIS_FILE, "Bug! Trying to remove entry %p from %s "
+	    			 "line %d, which has been deallocated "
+	    			 "without being cancelled",
+		  		 GET_ENTRY(removed_node),
+#if PJ_TIMER_DEBUG
+		  		 removed_node->src_file,
+		  		 removed_node->src_line));
+#else
+		  		 "N/A", 0));
+#endif
+#if ASSERT_IF_ENTRY_DESTROYED
+    	pj_assert(removed_node->dup._timer_id==removed_node->entry->_timer_id);
+#endif
+    }
+    GET_ENTRY(removed_node)->_timer_id = -1;
+    GET_FIELD(removed_node, _timer_id) = -1;
 
     // Only try to reheapify if we're not deleting the last entry.
@@ -239,5 +323,5 @@
     {
 	pj_size_t parent;
-	pj_timer_entry *moved_node = ht->heap[ht->cur_size];
+	pj_timer_entry_dup *moved_node = ht->heap[ht->cur_size];
 	
 	// Move the end node to the location being removed and update
@@ -249,8 +333,11 @@
 	parent = HEAP_PARENT (slot);
 	
-	if (PJ_TIME_VAL_GTE(moved_node->_timer_value, ht->heap[parent]->_timer_value))
+	if (PJ_TIME_VAL_GTE(moved_node->_timer_value,
+			    ht->heap[parent]->_timer_value))
+	{
 	    reheap_down( ht, moved_node, slot, HEAP_LEFT(slot));
-	else
+	} else {
 	    reheap_up( ht, moved_node, slot, parent);
+	}
     }
     
@@ -258,19 +345,45 @@
 }
 
-static void grow_heap(pj_timer_heap_t *ht)
+static pj_status_t grow_heap(pj_timer_heap_t *ht)
 {
     // All the containers will double in size from max_size_
     size_t new_size = ht->max_size * 2;
+    pj_timer_entry_dup *new_timer_dups = 0;
     pj_timer_id_t *new_timer_ids;
     pj_size_t i;
     
+    PJ_LOG(6,(THIS_FILE, "Growing heap size from %d to %d",
+		  	 ht->max_size, new_size));
+    
     // First grow the heap itself.
     
-    pj_timer_entry **new_heap = 0;
-    
-    new_heap = (pj_timer_entry**) 
-    	       pj_pool_alloc(ht->pool, sizeof(pj_timer_entry*) * new_size);
-    memcpy(new_heap, ht->heap, ht->max_size * sizeof(pj_timer_entry*));
-    //delete [] this->heap_;
+    pj_timer_entry_dup **new_heap = 0;
+    
+    new_heap = (pj_timer_entry_dup**) 
+    	       pj_pool_calloc(ht->pool, new_size, sizeof(pj_timer_entry_dup*));
+    if (!new_heap)
+    	return PJ_ENOMEM;
+    
+#if PJ_TIMER_HEAP_USE_COPY
+    // Grow the array of timer copies.
+    
+    new_timer_dups = (pj_timer_entry_dup*) 
+    	       	     pj_pool_alloc(ht->pool,
+    	       	     		   sizeof(pj_timer_entry_dup) * new_size);
+    if (!new_timer_dups)
+    	return PJ_ENOMEM;
+
+    memcpy(new_timer_dups, ht->timer_dups,
+    	   ht->max_size * sizeof(pj_timer_entry_dup));
+    for (i = 0; i < ht->cur_size; i++) {
+    	int idx = ht->heap[i] - ht->timer_dups;
+        // Point to the address in the new array
+        pj_assert(idx >= 0 && idx < ht->max_size);
+    	new_heap[i] = &new_timer_dups[idx];
+    }
+    ht->timer_dups = new_timer_dups;
+#else
+    memcpy(new_heap, ht->heap, ht->max_size * sizeof(pj_timer_entry *));
+#endif
     ht->heap = new_heap;
     
@@ -280,5 +393,7 @@
     new_timer_ids = (pj_timer_id_t*)
     		    pj_pool_alloc(ht->pool, new_size * sizeof(pj_timer_id_t));
-    
+    if (!new_timer_ids)
+	return PJ_ENOMEM;
+
     memcpy( new_timer_ids, ht->timer_ids, ht->max_size * sizeof(pj_timer_id_t));
     
@@ -291,13 +406,33 @@
     
     ht->max_size = new_size;
-}
-
-static void insert_node(pj_timer_heap_t *ht, pj_timer_entry *new_node)
-{
-    if (ht->cur_size + 2 >= ht->max_size)
-	grow_heap(ht);
-    
-    reheap_up( ht, new_node, ht->cur_size, HEAP_PARENT(ht->cur_size));
+    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t insert_node(pj_timer_heap_t *ht,
+			       pj_timer_entry *new_node,
+			       const pj_time_val *future_time)
+{
+    pj_timer_entry_dup *timer_copy;
+
+    if (ht->cur_size + 2 >= ht->max_size) {
+	pj_status_t status = grow_heap(ht);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    timer_copy = GET_TIMER(ht, new_node);
+#if PJ_TIMER_HEAP_USE_COPY    
+    // Create a duplicate of the timer entry.
+    pj_bzero(timer_copy, sizeof(*timer_copy));
+    pj_memcpy(&timer_copy->dup, new_node, sizeof(*new_node));
+    timer_copy->entry = new_node;
+#endif
+    timer_copy->_timer_value = *future_time;
+
+    reheap_up( ht, timer_copy, ht->cur_size, HEAP_PARENT(ht->cur_size));
     ht->cur_size++;
+
+    return PJ_SUCCESS;
 }
 
@@ -312,7 +447,6 @@
 	// Set the entry
 	entry->_timer_id = pop_freelist(ht);
-	entry->_timer_value = *future_time;
-	insert_node( ht, entry);
-	return 0;
+
+	return insert_node( ht, entry, future_time );
     }
     else
@@ -325,36 +459,34 @@
 		   unsigned flags)
 {
-  long timer_node_slot;
-
-  PJ_CHECK_STACK();
-
-  // Check to see if the timer_id is out of range
-  if (entry->_timer_id < 0 || (pj_size_t)entry->_timer_id > ht->max_size) {
-    entry->_timer_id = -1;
-    return 0;
-  }
-
-  timer_node_slot = ht->timer_ids[entry->_timer_id];
-
-  if (timer_node_slot < 0) { // Check to see if timer_id is still valid.
-    entry->_timer_id = -1;
-    return 0;
-  }
-
-  if (entry != ht->heap[timer_node_slot])
-    {
-      if ((flags & F_DONT_ASSERT) == 0)
-	  pj_assert(entry == ht->heap[timer_node_slot]);
-      entry->_timer_id = -1;
-      return 0;
-    }
-  else
-    {
-      remove_node( ht, timer_node_slot);
-
-      if ((flags & F_DONT_CALL) == 0)
-        // Call the close hook.
-	(*ht->callback)(ht, entry);
-      return 1;
+    long timer_node_slot;
+
+    PJ_CHECK_STACK();
+
+    // Check to see if the timer_id is out of range
+    if (entry->_timer_id < 0 || (pj_size_t)entry->_timer_id > ht->max_size) {
+	entry->_timer_id = -1;
+    	return 0;
+    }
+
+    timer_node_slot = ht->timer_ids[entry->_timer_id];
+
+    if (timer_node_slot < 0) { // Check to see if timer_id is still valid.
+    	entry->_timer_id = -1;
+    	return 0;
+    }
+
+    if (entry != GET_ENTRY(ht->heap[timer_node_slot])) {
+	if ((flags & F_DONT_ASSERT) == 0)
+	    pj_assert(entry == GET_ENTRY(ht->heap[timer_node_slot]));
+        entry->_timer_id = -1;
+      	return 0;
+    } else {
+      	remove_node( ht, timer_node_slot);
+
+      	if ((flags & F_DONT_CALL) == 0) {
+            // Call the close hook.
+	    (*ht->callback)(ht, entry);
+	}
+      	return 1;
     }
 }
@@ -369,5 +501,6 @@
            sizeof(pj_timer_heap_t) + 
            /* size of each entry: */
-           (count+2) * (sizeof(pj_timer_entry*)+sizeof(pj_timer_id_t)) +
+           (count+2) * (sizeof(pj_timer_entry_dup*)+sizeof(pj_timer_id_t)+
+           sizeof(pj_timer_entry_dup)) +
            /* lock, pool etc: */
            132;
@@ -392,5 +525,5 @@
 
     /* Allocate timer heap data structure from the pool */
-    ht = PJ_POOL_ALLOC_T(pool, pj_timer_heap_t);
+    ht = PJ_POOL_ZALLOC_T(pool, pj_timer_heap_t);
     if (!ht)
         return PJ_ENOMEM;
@@ -408,8 +541,16 @@
 
     // Create the heap array.
-    ht->heap = (pj_timer_entry**)
-    	       pj_pool_alloc(pool, sizeof(pj_timer_entry*) * size);
+    ht->heap = (pj_timer_entry_dup**)
+    	       pj_pool_calloc(pool, size, sizeof(pj_timer_entry_dup*));
     if (!ht->heap)
         return PJ_ENOMEM;
+
+#if PJ_TIMER_HEAP_USE_COPY
+    // Create the timer entry copies array.
+    ht->timer_dups = (pj_timer_entry_dup*)
+    	       	     pj_pool_alloc(pool, sizeof(pj_timer_entry_dup) * size);
+    if (!ht->timer_dups)
+        return PJ_ENOMEM;
+#endif
 
     // Create the parallel
@@ -468,5 +609,7 @@
     entry->user_data = user_data;
     entry->cb = cb;
+#if !PJ_TIMER_HEAP_USE_COPY
     entry->_grp_lock = NULL;
+#endif
 
     return entry;
@@ -505,8 +648,4 @@
     //PJ_ASSERT_RETURN(entry->_timer_id < 1, PJ_EINVALIDOP);
 
-#if PJ_TIMER_DEBUG
-    entry->src_file = src_file;
-    entry->src_line = src_line;
-#endif
     pj_gettickcount(&expires);
     PJ_TIME_VAL_ADD(expires, *delay);
@@ -517,5 +656,5 @@
     if (pj_timer_entry_running(entry)) {
 	unlock_timer_heap(ht);
-	PJ_LOG(3,(THIS_FILE, "Bug! Rescheduling outstanding entry (%p)",
+	PJ_LOG(3,(THIS_FILE, "Warning! Rescheduling outstanding entry (%p)",
 		  entry));
 	return PJ_EINVALIDOP;
@@ -524,10 +663,16 @@
     status = schedule_entry(ht, entry, &expires);
     if (status == PJ_SUCCESS) {
+    	pj_timer_entry_dup *timer_copy = GET_TIMER(ht, entry);
+
 	if (set_id)
-	    entry->id = id_val;
-	entry->_grp_lock = grp_lock;
-	if (entry->_grp_lock) {
-	    pj_grp_lock_add_ref(entry->_grp_lock);
+	    GET_FIELD(timer_copy, id) = entry->id = id_val;
+	timer_copy->_grp_lock = grp_lock;
+	if (timer_copy->_grp_lock) {
+	    pj_grp_lock_add_ref(timer_copy->_grp_lock);
 	}
+#if PJ_TIMER_DEBUG
+    	timer_copy->src_file = src_file;
+   	timer_copy->src_line = src_line;
+#endif
     }
     unlock_timer_heap(ht);
@@ -584,4 +729,6 @@
 			int id_val)
 {
+    pj_timer_entry_dup *timer_copy;
+    pj_grp_lock_t *grp_lock;
     int count;
 
@@ -589,4 +736,7 @@
 
     lock_timer_heap(ht);
+    timer_copy = GET_TIMER(ht, entry);
+    grp_lock = timer_copy->_grp_lock;
+
     count = cancel(ht, entry, flags | F_DONT_CALL);
     if (count > 0) {
@@ -595,7 +745,5 @@
 	    entry->id = id_val;
 	}
-	if (entry->_grp_lock) {
-	    pj_grp_lock_t *grp_lock = entry->_grp_lock;
-	    entry->_grp_lock = NULL;
+	if (grp_lock) {
 	    pj_grp_lock_dec_ref(grp_lock);
 	}
@@ -641,9 +789,11 @@
             count < ht->max_entries_per_poll ) 
     {
-	pj_timer_entry *node = remove_node(ht, 0);
+	pj_timer_entry_dup *node = remove_node(ht, 0);
+	pj_timer_entry *entry = GET_ENTRY(node);
 	/* Avoid re-use of this timer until the callback is done. */
 	///Not necessary, even causes problem (see also #2176).
 	///pj_timer_id_t node_timer_id = pop_freelist(ht);
 	pj_grp_lock_t *grp_lock;
+	pj_bool_t valid = PJ_TRUE;
 
 	++count;
@@ -651,4 +801,21 @@
 	grp_lock = node->_grp_lock;
 	node->_grp_lock = NULL;
+	if (GET_FIELD(node, cb) != entry->cb ||
+	    GET_FIELD(node, user_data) != entry->user_data)
+	{
+	    valid = PJ_FALSE;
+	    PJ_LOG(3,(THIS_FILE, "Bug! Polling entry %p from %s line %d has "
+	    			 "been deallocated without being cancelled",
+		  		 GET_ENTRY(node),
+#if PJ_TIMER_DEBUG
+		  		 node->src_file, node->src_line));
+#else
+		  		 "N/A", 0));
+#endif
+#if ASSERT_IF_ENTRY_DESTROYED
+	    pj_assert(node->dup.cb == entry->cb);
+	    pj_assert(node->dup.user_data == entry->user_data);
+#endif
+	}
 
 	unlock_timer_heap(ht);
@@ -656,8 +823,8 @@
 	PJ_RACE_ME(5);
 
-	if (node->cb)
-	    (*node->cb)(ht, node);
-
-	if (grp_lock)
+	if (valid && entry->cb)
+	    (*entry->cb)(ht, entry);
+
+	if (valid && grp_lock)
 	    pj_grp_lock_dec_ref(grp_lock);
 
@@ -720,5 +887,5 @@
 
 	for (i=0; i<(unsigned)ht->cur_size; ++i) {
-	    pj_timer_entry *e = ht->heap[i];
+	    pj_timer_entry_dup *e = ht->heap[i];
 	    pj_time_val delta;
 
@@ -731,5 +898,5 @@
 
 	    PJ_LOG(3,(THIS_FILE, "    %d\t%d\t%d.%03d\t%s:%d",
-		      e->_timer_id, e->id,
+		      GET_FIELD(e, _timer_id), GET_FIELD(e, id),
 		      (int)delta.sec, (int)delta.msec,
 		      e->src_file, e->src_line));
