diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /erts/emulator/beam/erl_bif_timer.c | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'erts/emulator/beam/erl_bif_timer.c')
-rw-r--r-- | erts/emulator/beam/erl_bif_timer.c | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/erts/emulator/beam/erl_bif_timer.c b/erts/emulator/beam/erl_bif_timer.c new file mode 100644 index 0000000000..172bb37952 --- /dev/null +++ b/erts/emulator/beam/erl_bif_timer.c @@ -0,0 +1,701 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2005-2009. 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% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "erl_bif_timer.h" +#include "global.h" +#include "bif.h" +#include "error.h" +#include "big.h" + +/**************************************************************************** +** BIF Timer support +****************************************************************************/ + +#define BTM_FLG_SL_TIMER (((Uint32) 1) << 0) +#define BTM_FLG_CANCELED (((Uint32) 1) << 1) +#define BTM_FLG_HEAD (((Uint32) 1) << 2) +#define BTM_FLG_BYNAME (((Uint32) 1) << 3) +#define BTM_FLG_WRAP (((Uint32) 1) << 4) + +struct ErtsBifTimer_ { + struct { + union { + ErtsBifTimer **head; + ErtsBifTimer *prev; + } u; + ErtsBifTimer *next; + } tab; + union { + Eterm name; + struct { + ErtsBifTimer *prev; + ErtsBifTimer *next; + Process *ess; + } proc; + } receiver; + ErlTimer tm; + ErlHeapFragment* bp; + Uint32 flags; + Eterm message; + Uint32 ref_numbers[ERTS_REF_NUMBERS]; +}; + +#ifdef SMALL_MEMORY +#define TIMER_HASH_VEC_SZ 3331 +#define BTM_PREALC_SZ 10 +#else +#define TIMER_HASH_VEC_SZ 10007 +#define BTM_PREALC_SZ 100 +#endif +static ErtsBifTimer **bif_timer_tab; +static Uint no_bif_timers; + + +static erts_smp_rwmtx_t bif_timer_lock; + +#define erts_smp_safe_btm_rwlock(P, L) \ + safe_btm_lock((P), (L), 1) +#define erts_smp_safe_btm_rlock(P, L) \ + safe_btm_lock((P), (L), 0) +#define erts_smp_btm_rwlock() \ + erts_smp_rwmtx_rwlock(&bif_timer_lock) +#define erts_smp_btm_tryrwlock() \ + erts_smp_rwmtx_tryrwlock(&bif_timer_lock) +#define erts_smp_btm_rwunlock() \ + erts_smp_rwmtx_rwunlock(&bif_timer_lock) +#define erts_smp_btm_rlock() \ + erts_smp_rwmtx_rlock(&bif_timer_lock) +#define erts_smp_btm_tryrlock() \ + erts_smp_rwmtx_tryrlock(&bif_timer_lock) +#define erts_smp_btm_runlock() \ + erts_smp_rwmtx_runlock(&bif_timer_lock) +#define erts_smp_btm_lock_init() \ + erts_smp_rwmtx_init(&bif_timer_lock, "bif_timers") + + +static ERTS_INLINE int +safe_btm_lock(Process *c_p, ErtsProcLocks c_p_locks, int rw_lock) +{ + ASSERT(c_p && c_p_locks); +#ifdef ERTS_SMP + if ((rw_lock ? erts_smp_btm_tryrwlock() : erts_smp_btm_tryrlock()) != EBUSY) + return 0; + erts_smp_proc_unlock(c_p, c_p_locks); + if (rw_lock) + erts_smp_btm_rwlock(); + else + erts_smp_btm_rlock(); + erts_smp_proc_lock(c_p, c_p_locks); + if (ERTS_PROC_IS_EXITING(c_p)) { + if (rw_lock) + erts_smp_btm_rwunlock(); + else + erts_smp_btm_runlock(); + return 1; + } +#endif + return 0; +} + +ERTS_SCHED_PREF_PALLOC_IMPL(btm_pre, ErtsBifTimer, BTM_PREALC_SZ) + +static ERTS_INLINE int +get_index(Uint32 *ref_numbers, Uint32 len) +{ + Uint32 hash; + /* len can potentially be larger than ERTS_REF_NUMBERS + if it has visited another node... */ + if (len > ERTS_REF_NUMBERS) + len = ERTS_REF_NUMBERS; + +#if ERTS_REF_NUMBERS != 3 +#error "ERTS_REF_NUMBERS changed. Update me..." +#endif + switch (len) { + case 3: if (!ref_numbers[2]) len = 2; + case 2: if (!ref_numbers[1]) len = 1; + default: break; + } + + ASSERT(1 <= len && len <= ERTS_REF_NUMBERS); + + hash = block_hash((byte *) ref_numbers, len * sizeof(Uint32), 0x08d12e65); + return (int) (hash % ((Uint32) TIMER_HASH_VEC_SZ)); +} + +static Eterm +create_ref(Uint *hp, Uint32 *ref_numbers, Uint32 len) +{ + Uint32 *datap; + int i; + + + if (len > ERTS_MAX_REF_NUMBERS) { + /* Such large refs should no be able to appear in the emulator */ + erl_exit(1, "%s:%d: Internal error\n", __FILE__, __LINE__); + } + +#ifdef ARCH_64 + hp[0] = make_ref_thing_header(len/2 + 1); + datap = (Uint32 *) &hp[1]; + *(datap++) = len; +#else + hp[0] = make_ref_thing_header(len); + datap = (Uint32 *) &hp[1]; +#endif + + for (i = 0; i < len; i++) + datap[i] = ref_numbers[i]; + + return make_internal_ref(hp); +} + +static int +eq_non_standard_ref_numbers(Uint32 *rn1, Uint32 len1, Uint32 *rn2, Uint32 len2) +{ +#ifdef ARCH_64 +#define MAX_REF_HEAP_SZ (1+(ERTS_MAX_REF_NUMBERS/2+1)) +#else +#define MAX_REF_HEAP_SZ (1+ERTS_MAX_REF_NUMBERS) +#endif + Uint r1_hp[MAX_REF_HEAP_SZ]; + Uint r2_hp[MAX_REF_HEAP_SZ]; + + return eq(create_ref(r1_hp, rn1, len1), create_ref(r2_hp, rn2, len2)); +#undef MAX_REF_HEAP_SZ +} + +static ERTS_INLINE int +eq_ref_numbers(Uint32 *rn1, Uint32 len1, Uint32 *rn2, Uint32 len2) +{ + int res; + if (len1 != ERTS_REF_NUMBERS || len2 != ERTS_REF_NUMBERS) { + /* Can potentially happen, but will never... */ + return eq_non_standard_ref_numbers(rn1, len1, rn2, len2); + } + +#if ERTS_REF_NUMBERS != 3 +#error "ERTS_REF_NUMBERS changed. Update me..." +#endif + res = rn1[0] == rn2[0] && rn1[1] == rn2[1] && rn1[2] == rn2[2]; + + ASSERT(res + ? eq_non_standard_ref_numbers(rn1, len1, rn2, len2) + : !eq_non_standard_ref_numbers(rn1, len1, rn2, len2)); + + return res; +} + +static ERTS_INLINE ErtsBifTimer * +tab_find(Eterm ref) +{ + Uint32 *ref_numbers = internal_ref_numbers(ref); + Uint32 ref_numbers_len = internal_ref_no_of_numbers(ref); + int ix = get_index(ref_numbers, ref_numbers_len); + ErtsBifTimer* btm; + + for (btm = bif_timer_tab[ix]; btm; btm = btm->tab.next) + if (eq_ref_numbers(ref_numbers, ref_numbers_len, + btm->ref_numbers, ERTS_REF_NUMBERS)) + return btm; + return NULL; +} + +static ERTS_INLINE void +tab_remove(ErtsBifTimer* btm) +{ + if (btm->flags & BTM_FLG_HEAD) { + *btm->tab.u.head = btm->tab.next; + if (btm->tab.next) { + btm->tab.next->flags |= BTM_FLG_HEAD; + btm->tab.next->tab.u.head = btm->tab.u.head; + } + } + else { + btm->tab.u.prev->tab.next = btm->tab.next; + if (btm->tab.next) + btm->tab.next->tab.u.prev = btm->tab.u.prev; + } + btm->flags |= BTM_FLG_CANCELED; + ASSERT(no_bif_timers > 0); + no_bif_timers--; +} + +static ERTS_INLINE void +tab_insert(ErtsBifTimer* btm) +{ + int ix = get_index(btm->ref_numbers, ERTS_REF_NUMBERS); + ErtsBifTimer* btm_list = bif_timer_tab[ix]; + + if (btm_list) { + btm_list->flags &= ~BTM_FLG_HEAD; + btm_list->tab.u.prev = btm; + } + + btm->flags |= BTM_FLG_HEAD; + btm->tab.u.head = &bif_timer_tab[ix]; + btm->tab.next = btm_list; + bif_timer_tab[ix] = btm; + no_bif_timers++; +} + +static ERTS_INLINE void +link_proc(Process *p, ErtsBifTimer* btm) +{ + btm->receiver.proc.ess = p; + btm->receiver.proc.prev = NULL; + btm->receiver.proc.next = p->bif_timers; + if (p->bif_timers) + p->bif_timers->receiver.proc.prev = btm; + p->bif_timers = btm; +} + +static ERTS_INLINE void +unlink_proc(ErtsBifTimer* btm) +{ + if (btm->receiver.proc.prev) + btm->receiver.proc.prev->receiver.proc.next = btm->receiver.proc.next; + else + btm->receiver.proc.ess->bif_timers = btm->receiver.proc.next; + if (btm->receiver.proc.next) + btm->receiver.proc.next->receiver.proc.prev = btm->receiver.proc.prev; +} + +static void +bif_timer_cleanup(ErtsBifTimer* btm) +{ + ASSERT(btm); + + if (btm->bp) + free_message_buffer(btm->bp); + + if (!btm_pre_free(btm)) { + if (btm->flags & BTM_FLG_SL_TIMER) + erts_free(ERTS_ALC_T_SL_BIF_TIMER, (void *) btm); + else + erts_free(ERTS_ALC_T_LL_BIF_TIMER, (void *) btm); + } +} + +static void +bif_timer_timeout(ErtsBifTimer* btm) +{ + ASSERT(btm); + + + erts_smp_btm_rwlock(); + + if (btm->flags & BTM_FLG_CANCELED) { + /* + * A concurrent cancel is ongoing. Do not send the timeout message, + * but cleanup here since the cancel call-back won't be called. + */ +#ifndef ERTS_SMP + ASSERT(0); +#endif + } + else { + ErtsProcLocks rp_locks = 0; + Process* rp; + + tab_remove(btm); + + ASSERT(!erts_get_current_process()); + + if (btm->flags & BTM_FLG_BYNAME) + rp = erts_whereis_process(NULL,0,btm->receiver.name,0,ERTS_P2P_FLG_SMP_INC_REFC); + else { + rp = btm->receiver.proc.ess; + erts_smp_proc_inc_refc(rp); + unlink_proc(btm); + } + + if (rp) { + Eterm message; + ErlHeapFragment *bp; + + bp = btm->bp; + btm->bp = NULL; /* Prevent cleanup of message buffer... */ + + if (!(btm->flags & BTM_FLG_WRAP)) + message = btm->message; + else { +#if ERTS_REF_NUMBERS != 3 +#error "ERTS_REF_NUMBERS changed. Update me..." +#endif + Eterm ref; + Uint *hp; + Uint wrap_size = REF_THING_SIZE + 4; + message = btm->message; + + if (!bp) { + ErlOffHeap *ohp; + ASSERT(is_immed(message)); + hp = erts_alloc_message_heap(wrap_size, + &bp, + &ohp, + rp, + &rp_locks); + } else { + Eterm old_size = bp->size; + bp = erts_resize_message_buffer(bp, old_size + wrap_size, + &message, 1); + hp = &bp->mem[0] + old_size; + } + + write_ref_thing(hp, + btm->ref_numbers[0], + btm->ref_numbers[1], + btm->ref_numbers[2]); + ref = make_internal_ref(hp); + hp += REF_THING_SIZE; + message = TUPLE3(hp, am_timeout, ref, message); + } + + erts_queue_message(rp, &rp_locks, bp, message, NIL); + erts_smp_proc_unlock(rp, rp_locks); + erts_smp_proc_dec_refc(rp); + } + } + + erts_smp_btm_rwunlock(); + + bif_timer_cleanup(btm); +} + +static Eterm +setup_bif_timer(Uint32 xflags, + Process *c_p, + Eterm time, + Eterm receiver, + Eterm message) +{ + Process *rp; + ErtsBifTimer* btm; + Uint timeout; + Eterm ref; + Uint32 *ref_numbers; + + if (!term_to_Uint(time, &timeout)) + return THE_NON_VALUE; +#ifdef ARCH_64 + if ((timeout >> 32) != 0) + return THE_NON_VALUE; +#endif + if (is_not_internal_pid(receiver) && is_not_atom(receiver)) + return THE_NON_VALUE; + + ref = erts_make_ref(c_p); + + if (is_atom(receiver)) + rp = NULL; + else { + rp = erts_pid2proc(c_p, ERTS_PROC_LOCK_MAIN, + receiver, ERTS_PROC_LOCK_MSGQ); + if (!rp) + return ref; + } + + if (timeout < ERTS_ALC_MIN_LONG_LIVED_TIME) { + if (timeout < 1000) { + btm = btm_pre_alloc(); + if (!btm) + goto sl_timer_alloc; + btm->flags = 0; + } + else { + sl_timer_alloc: + btm = (ErtsBifTimer *) erts_alloc(ERTS_ALC_T_SL_BIF_TIMER, + sizeof(ErtsBifTimer)); + btm->flags = BTM_FLG_SL_TIMER; + } + } + else { + btm = (ErtsBifTimer *) erts_alloc(ERTS_ALC_T_LL_BIF_TIMER, + sizeof(ErtsBifTimer)); + btm->flags = 0; + } + + if (rp) { + link_proc(rp, btm); + erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_MSGQ); + } + else { + ASSERT(is_atom(receiver)); + btm->receiver.name = receiver; + btm->flags |= BTM_FLG_BYNAME; + } + + btm->flags |= xflags; + + ref_numbers = internal_ref_numbers(ref); + ASSERT(internal_ref_no_of_numbers(ref) == 3); +#if ERTS_REF_NUMBERS != 3 +#error "ERTS_REF_NUMBERS changed. Update me..." +#endif + btm->ref_numbers[0] = ref_numbers[0]; + btm->ref_numbers[1] = ref_numbers[1]; + btm->ref_numbers[2] = ref_numbers[2]; + + ASSERT(eq_ref_numbers(btm->ref_numbers, ERTS_REF_NUMBERS, + ref_numbers, ERTS_REF_NUMBERS)); + + if (is_immed(message)) { + btm->bp = NULL; + btm->message = message; + } + else { + ErlHeapFragment* bp; + Eterm* hp; + Uint size; + + size = size_object(message); + btm->bp = bp = new_message_buffer(size); + hp = bp->mem; + btm->message = copy_struct(message, size, &hp, &bp->off_heap); + } + + tab_insert(btm); + ASSERT(btm == tab_find(ref)); + btm->tm.active = 0; /* MUST be initalized */ + erl_set_timer(&btm->tm, + (ErlTimeoutProc) bif_timer_timeout, + (ErlCancelProc) bif_timer_cleanup, + (void *) btm, + timeout); + return ref; +} + +/* send_after(Time, Pid, Message) -> Ref */ +BIF_RETTYPE send_after_3(BIF_ALIST_3) +{ + Eterm res; + + if (erts_smp_safe_btm_rwlock(BIF_P, ERTS_PROC_LOCK_MAIN)) + ERTS_BIF_EXITED(BIF_P); + + res = setup_bif_timer(0, BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3); + + erts_smp_btm_rwunlock(); + + if (is_non_value(res)) { + BIF_ERROR(BIF_P, BADARG); + } + else { + ASSERT(is_internal_ref(res)); + BIF_RET(res); + } +} + +/* start_timer(Time, Pid, Message) -> Ref */ +BIF_RETTYPE start_timer_3(BIF_ALIST_3) +{ + Eterm res; + + if (erts_smp_safe_btm_rwlock(BIF_P, ERTS_PROC_LOCK_MAIN)) + ERTS_BIF_EXITED(BIF_P); + + res = setup_bif_timer(BTM_FLG_WRAP, BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3); + + erts_smp_btm_rwunlock(); + + if (is_non_value(res)) { + BIF_ERROR(BIF_P, BADARG); + } + else { + ASSERT(is_internal_ref(res)); + BIF_RET(res); + } +} + +/* cancel_timer(Ref) -> false | RemainingTime */ +BIF_RETTYPE cancel_timer_1(BIF_ALIST_1) +{ + Eterm res; + ErtsBifTimer *btm; + + if (is_not_internal_ref(BIF_ARG_1)) { + if (is_ref(BIF_ARG_1)) { + BIF_RET(am_false); + } + BIF_ERROR(BIF_P, BADARG); + } + + if (erts_smp_safe_btm_rwlock(BIF_P, ERTS_PROC_LOCK_MAIN)) + ERTS_BIF_EXITED(BIF_P); + + btm = tab_find(BIF_ARG_1); + if (!btm || btm->flags & BTM_FLG_CANCELED) { + erts_smp_btm_rwunlock(); + res = am_false; + } + else { + Uint left = time_left(&btm->tm); + if (!(btm->flags & BTM_FLG_BYNAME)) { + erts_smp_proc_lock(btm->receiver.proc.ess, ERTS_PROC_LOCK_MSGQ); + unlink_proc(btm); + erts_smp_proc_unlock(btm->receiver.proc.ess, ERTS_PROC_LOCK_MSGQ); + } + tab_remove(btm); + ASSERT(!tab_find(BIF_ARG_1)); + erl_cancel_timer(&btm->tm); + erts_smp_btm_rwunlock(); + res = erts_make_integer(left, BIF_P); + } + + BIF_RET(res); +} + +/* read_timer(Ref) -> false | RemainingTime */ +BIF_RETTYPE read_timer_1(BIF_ALIST_1) +{ + Eterm res; + ErtsBifTimer *btm; + + if (is_not_internal_ref(BIF_ARG_1)) { + if (is_ref(BIF_ARG_1)) { + BIF_RET(am_false); + } + BIF_ERROR(BIF_P, BADARG); + } + + if (erts_smp_safe_btm_rlock(BIF_P, ERTS_PROC_LOCK_MAIN)) + ERTS_BIF_EXITED(BIF_P); + + btm = tab_find(BIF_ARG_1); + if (!btm || btm->flags & BTM_FLG_CANCELED) { + res = am_false; + } + else { + Uint left = time_left(&btm->tm); + res = erts_make_integer(left, BIF_P); + } + + erts_smp_btm_runlock(); + + BIF_RET(res); +} + +void +erts_print_bif_timer_info(int to, void *to_arg) +{ + int i; + int lock = !ERTS_IS_CRASH_DUMPING; + + if (lock) + erts_smp_btm_rlock(); + + for (i = 0; i < TIMER_HASH_VEC_SZ; i++) { + ErtsBifTimer *btm; + for (btm = bif_timer_tab[i]; btm; btm = btm->tab.next) { + Eterm receiver = (btm->flags & BTM_FLG_BYNAME + ? btm->receiver.name + : btm->receiver.proc.ess->id); + erts_print(to, to_arg, "=timer:%T\n", receiver); + erts_print(to, to_arg, "Message: %T\n", btm->message); + erts_print(to, to_arg, "Time left: %d ms\n", time_left(&btm->tm)); + } + } + + if (lock) + erts_smp_btm_runlock(); +} + + +void +erts_cancel_bif_timers(Process *p, ErtsProcLocks plocks) +{ + ErtsBifTimer *btm; + + if (erts_smp_btm_tryrwlock() == EBUSY) { + erts_smp_proc_unlock(p, plocks); + erts_smp_btm_rwlock(); + erts_smp_proc_lock(p, plocks); + } + + btm = p->bif_timers; + while (btm) { + ErtsBifTimer *tmp_btm; + ASSERT(!(btm->flags & BTM_FLG_CANCELED)); + tab_remove(btm); + tmp_btm = btm; + btm = btm->receiver.proc.next; + erl_cancel_timer(&tmp_btm->tm); + } + + p->bif_timers = NULL; + + erts_smp_btm_rwunlock(); +} + +void erts_bif_timer_init(void) +{ + int i; + no_bif_timers = 0; + init_btm_pre_alloc(); + erts_smp_btm_lock_init(); + bif_timer_tab = erts_alloc(ERTS_ALC_T_BIF_TIMER_TABLE, + sizeof(ErtsBifTimer *)*TIMER_HASH_VEC_SZ); + for (i = 0; i < TIMER_HASH_VEC_SZ; ++i) + bif_timer_tab[i] = NULL; +} + +Uint +erts_bif_timer_memory_size(void) +{ + Uint res; + int lock = !ERTS_IS_CRASH_DUMPING; + + if (lock) + erts_smp_btm_rlock(); + + res = (sizeof(ErtsBifTimer *)*TIMER_HASH_VEC_SZ + + no_bif_timers*sizeof(ErtsBifTimer)); + + if (lock) + erts_smp_btm_runlock(); + + return res; +} + + +void +erts_bif_timer_foreach(void (*func)(Eterm, Eterm, ErlHeapFragment *, void *), + void *arg) +{ + int i; + + ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); + + for (i = 0; i < TIMER_HASH_VEC_SZ; i++) { + ErtsBifTimer *btm; + for (btm = bif_timer_tab[i]; btm; btm = btm->tab.next) { + (*func)((btm->flags & BTM_FLG_BYNAME + ? btm->receiver.name + : btm->receiver.proc.ess->id), + btm->message, + btm->bp, + arg); + } + } +} |