/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2013. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * %CopyrightEnd% */ /* * TIMING WHEEL * * Timeouts kept in an wheel. A timeout is measured relative to the * current slot (tiw_pos) in the wheel, and inserted at slot * (tiw_pos + timeout) % TIW_SIZE. Each timeout also has a count * equal to timeout/TIW_SIZE, which is needed since the time axis * is wrapped arount the wheel. * * Several slots may be processed in one operation. If the number of * slots is greater that the wheel size, the wheel is only traversed * once, * * The following example shows a time axis where there is one timeout * at each "tick", and where 1, 2, 3 ... wheel slots are released in * one operation. The notation "= min_timeout_pos) { true_min_timeout = 0; break; } p = tiw[tiw_pos_ix]; while (p) { ErtsMonotonicTime timeout_pos; ASSERT(p != p->next); timeout_pos = p->timeout_pos; if (min_timeout_pos > timeout_pos) { min_timeout_pos = timeout_pos; if (min_timeout_pos <= slot_timeout_pos) goto found_next; } p = p->next; } tiw_pos_ix++; if (tiw_pos_ix == TIW_SIZE) tiw_pos_ix = 0; } while (start_ix != tiw_pos_ix); found_next: min_timeout = ERTS_CLKTCKS_TO_MONOTONIC(min_timeout_pos); if (min_timeout != next_timeout_time) set_next_timeout(min_timeout, true_min_timeout); return min_timeout; } static void remove_timer(ErlTimer *p) { /* first */ if (!p->prev) { tiw[p->slot] = p->next; if(p->next) p->next->prev = NULL; } else { p->prev->next = p->next; } /* last */ if (!p->next) { if (p->prev) p->prev->next = NULL; } else { p->next->prev = p->prev; } p->next = NULL; p->prev = NULL; /* Make sure cancel callback isn't called */ p->active = 0; tiw_nto--; } ErtsMonotonicTime erts_check_next_timeout_time(ErtsMonotonicTime max_search_time) { ErtsMonotonicTime next, curr; curr = erts_get_monotonic_time(); erts_smp_mtx_lock(&tiw_lock); next = find_next_timeout(curr, max_search_time); erts_smp_mtx_unlock(&tiw_lock); return next; } #ifndef DEBUG #define ERTS_DBG_CHK_SAFE_TO_SKIP_TO(TO) ((void) 0) #else #define ERTS_DBG_CHK_SAFE_TO_SKIP_TO(TO) debug_check_safe_to_skip_to((TO)) static void debug_check_safe_to_skip_to(ErtsMonotonicTime skip_to_pos) { int slots, ix; ErlTimer *tmr; ErtsMonotonicTime tmp; ix = (int) (tiw_pos & (TIW_SIZE-1)); tmp = skip_to_pos - tiw_pos; ASSERT(tmp >= 0); if (tmp < (ErtsMonotonicTime) TIW_SIZE) slots = (int) tmp; else slots = TIW_SIZE; while (slots > 0) { tmr = tiw[ix]; while (tmr) { ASSERT(tmr->timeout_pos > skip_to_pos); tmr = tmr->next; } ix++; if (ix == TIW_SIZE) ix = 0; slots--; } } #endif void erts_bump_timers(ErtsMonotonicTime curr_time) { int tiw_pos_ix, slots; ErlTimer *p, *timeout_head, **timeout_tail; ErtsMonotonicTime bump_to, tmp_slots; if (erts_smp_atomic32_cmpxchg_nob(&is_bumping, 1, 0) != 0) return; /* Another thread is currently bumping... */ bump_to = ERTS_MONOTONIC_TO_CLKTCKS(curr_time); erts_smp_mtx_lock(&tiw_lock); if (tiw_pos >= bump_to) { timeout_head = NULL; goto done; } /* Don't want others here while we are bumping... */ set_next_timeout(curr_time + ERTS_MONOTONIC_DAY, 0); if (!tiw_at_once.head) { timeout_head = NULL; timeout_tail = &timeout_head; } else { ASSERT(tiw_nto >= tiw_at_once.nto); timeout_head = tiw_at_once.head; timeout_tail = tiw_at_once.tail; tiw_nto -= tiw_at_once.nto; tiw_at_once.head = NULL; tiw_at_once.tail = &tiw_at_once.head; tiw_at_once.nto = 0; } if (tiw_nto == 0) { ERTS_DBG_CHK_SAFE_TO_SKIP_TO(bump_to); tiw_pos = bump_to; goto done; } if (true_next_timeout_time) { ErtsMonotonicTime skip_until_pos; /* * No need inspecting slots where we know no timeouts * to trigger should reside. */ skip_until_pos = ERTS_MONOTONIC_TO_CLKTCKS(next_timeout_time); if (skip_until_pos > bump_to) skip_until_pos = bump_to; ERTS_DBG_CHK_SAFE_TO_SKIP_TO(skip_until_pos); ASSERT(skip_until_pos > tiw_pos); tiw_pos = skip_until_pos - 1; } tiw_pos_ix = (int) ((tiw_pos+1) & (TIW_SIZE-1)); tmp_slots = (bump_to - tiw_pos); if (tmp_slots < (ErtsMonotonicTime) TIW_SIZE) slots = (int) tmp_slots; else slots = TIW_SIZE; while (slots > 0) { p = tiw[tiw_pos_ix]; while (p) { ErlTimer *next = p->next; ASSERT(p != next); if (p->timeout_pos <= bump_to) { /* we have a timeout */ /* Remove from list */ remove_timer(p); *timeout_tail = p; /* Insert in timeout queue */ timeout_tail = &p->next; } p = next; } tiw_pos_ix++; if (tiw_pos_ix == TIW_SIZE) tiw_pos_ix = 0; slots--; } ASSERT(tmp_slots >= (ErtsMonotonicTime) TIW_SIZE || tiw_pos_ix == (int) ((bump_to+1) & (TIW_SIZE-1))); tiw_pos = bump_to; /* Search at most two seconds ahead... */ (void) find_next_timeout(curr_time, ERTS_SEC_TO_MONOTONIC(2)); done: erts_smp_mtx_unlock(&tiw_lock); erts_smp_atomic32_set_nob(&is_bumping, 0); /* Call timedout timers callbacks */ while (timeout_head) { p = timeout_head; timeout_head = p->next; /* Here comes hairy use of the timer fields! * They are reset without having the lock. * It is assumed that no code but this will * accesses any field until the ->timeout * callback is called. */ ASSERT(p->timeout_pos <= bump_to); p->next = NULL; p->prev = NULL; p->slot = 0; (*p->timeout)(p->arg); } } Uint erts_timer_wheel_memory_size(void) { return (Uint) TIW_SIZE * sizeof(ErlTimer*); } /* this routine links the time cells into a free list at the start and sets the time queue as empty */ void erts_init_time(int time_correction, ErtsTimeWarpMode time_warp_mode) { ErtsMonotonicTime mtime; int i, itime; /* system dependent init; must be done before do_time_init() if timer thread is enabled */ itime = erts_init_time_sup(time_correction, time_warp_mode); #ifdef TIW_ITIME_IS_CONSTANT if (itime != TIW_ITIME) { erl_exit(ERTS_ABORT_EXIT, "timer resolution mismatch %d != %d", itime, TIW_ITIME); } #else tiw_itime = itime; #endif erts_smp_atomic32_init_nob(&is_bumping, 0); erts_smp_mtx_init(&tiw_lock, "timer_wheel"); tiw = (ErlTimer**) erts_alloc(ERTS_ALC_T_TIMER_WHEEL, TIW_SIZE * sizeof(ErlTimer*)); for(i = 0; i < TIW_SIZE; i++) tiw[i] = NULL; mtime = erts_get_monotonic_time(); tiw_pos = ERTS_MONOTONIC_TO_CLKTCKS(mtime); tiw_nto = 0; tiw_at_once.head = NULL; tiw_at_once.tail = &tiw_at_once.head; tiw_at_once.nto = 0; init_next_timeout(mtime + ERTS_MONOTONIC_DAY); erts_late_init_time_sup(); } /* ** Insert a process into the time queue, with a timeout 't' */ static ErtsMonotonicTime insert_timer(ErlTimer* p, ErtsMonotonicTime curr_time, ErtsMonotonicTime to) { ErtsMonotonicTime timeout_time, timeout_pos; if (to == 0) { timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time); tiw_nto++; tiw_at_once.nto++; *tiw_at_once.tail = p; p->next = NULL; p->timeout_pos = timeout_pos; timeout_time = ERTS_CLKTCKS_TO_MONOTONIC(timeout_pos); } else { int tm; ErtsMonotonicTime ticks; ticks = ERTS_MSEC_TO_CLKTCKS(to); timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time - 1) + 1 + ticks; /* calculate slot */ tm = (int) (timeout_pos & (TIW_SIZE-1)); p->slot = (Uint) tm; /* insert at head of list at slot */ p->next = tiw[tm]; p->prev = NULL; if (p->next != NULL) p->next->prev = p; tiw[tm] = p; tiw_nto++; timeout_time = ERTS_CLKTCKS_TO_MONOTONIC(timeout_pos); p->timeout_pos = timeout_pos; ASSERT(ERTS_MSEC_TO_MONOTONIC(to) <= timeout_time - curr_time); ASSERT(timeout_time - curr_time < ERTS_MSEC_TO_MONOTONIC(to) + ERTS_CLKTCKS_TO_MONOTONIC(1)); } if (timeout_time < next_timeout_time) set_next_timeout(timeout_time, 1); return timeout_time; } void erts_set_timer(ErlTimer* p, ErlTimeoutProc timeout, ErlCancelProc cancel, void* arg, Uint t) { #ifdef ERTS_SMP ErtsMonotonicTime timeout_time; #endif ErtsMonotonicTime current_time = erts_get_monotonic_time(); erts_smp_mtx_lock(&tiw_lock); if (p->active) { /* XXX assert ? */ erts_smp_mtx_unlock(&tiw_lock); return; } p->timeout = timeout; p->cancel = cancel; p->arg = arg; p->active = 1; #ifdef ERTS_SMP timeout_time = #else (void) #endif insert_timer(p, current_time, (ErtsMonotonicTime) t); erts_smp_mtx_unlock(&tiw_lock); #if defined(ERTS_SMP) erts_sys_schedule_interrupt_timed(1, timeout_time); #endif } void erts_cancel_timer(ErlTimer* p) { erts_smp_mtx_lock(&tiw_lock); if (!p->active) { /* allow repeated cancel (drivers) */ erts_smp_mtx_unlock(&tiw_lock); return; } remove_timer(p); p->slot = 0; if (p->cancel != NULL) { erts_smp_mtx_unlock(&tiw_lock); (*p->cancel)(p->arg); return; } erts_smp_mtx_unlock(&tiw_lock); } /* Returns the amount of time left in ms until the timer 'p' is triggered. 0 is returned if 'p' isn't active. 0 is returned also if the timer is overdue (i.e., would have triggered immediately if it hadn't been cancelled). */ Uint erts_time_left(ErlTimer *p) { ErtsMonotonicTime current_time, timeout_time; erts_smp_mtx_lock(&tiw_lock); if (!p->active) { erts_smp_mtx_unlock(&tiw_lock); return 0; } timeout_time = ERTS_CLKTCKS_TO_MONOTONIC(p->timeout_pos); erts_smp_mtx_unlock(&tiw_lock); current_time = erts_get_monotonic_time(); if (timeout_time <= current_time) return 0; return (Uint) ERTS_MONOTONIC_TO_MSEC(timeout_time - current_time); } #ifdef DEBUG void erts_p_slpq(void) { ErtsMonotonicTime current_time = erts_get_monotonic_time(); int i; ErlTimer* p; erts_smp_mtx_lock(&tiw_lock); /* print the whole wheel, starting at the current position */ erts_printf("\ncurrent time = %bps tiw_pos = %d tiw_nto %d\n", current_time, tiw_pos, tiw_nto); i = tiw_pos; if (tiw[i] != NULL) { erts_printf("%d:\n", i); for(p = tiw[i]; p != NULL; p = p->next) { erts_printf(" (timeout time %bps, slot %d)\n", ERTS_CLKTCKS_TO_MONOTONIC(p->timeout_pos), p->slot); } } for(i = ((i+1) & (TIW_SIZE-1)); i != (tiw_pos & (TIW_SIZE-1)); i = ((i+1) & (TIW_SIZE-1))) { if (tiw[i] != NULL) { erts_printf("%d:\n", i); for(p = tiw[i]; p != NULL; p = p->next) { erts_printf(" (timeout time %bps, slot %d)\n", ERTS_CLKTCKS_TO_MONOTONIC(p->timeout_pos), p->slot); } } } erts_smp_mtx_unlock(&tiw_lock); } #endif /* DEBUG */