/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2018. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ /* * Manage registered processes. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "sys.h" #include "erl_vm.h" #include "global.h" #include "hash.h" #include "atom.h" #include "register.h" static Hash process_reg; #define PREG_HASH_SIZE 10 #define REG_HASH(term) ((HashValue) atom_val(term)) static erts_rwmtx_t regtab_rwmtx; #define reg_try_read_lock() erts_rwmtx_tryrlock(®tab_rwmtx) #define reg_try_write_lock() erts_rwmtx_tryrwlock(®tab_rwmtx) #define reg_read_lock() erts_rwmtx_rlock(®tab_rwmtx) #define reg_write_lock() erts_rwmtx_rwlock(®tab_rwmtx) #define reg_read_unlock() erts_rwmtx_runlock(®tab_rwmtx) #define reg_write_unlock() erts_rwmtx_rwunlock(®tab_rwmtx) static ERTS_INLINE void reg_safe_read_lock(Process *c_p, ErtsProcLocks *c_p_locks) { if (*c_p_locks) { ASSERT(c_p); ASSERT(c_p_locks); ASSERT(*c_p_locks); if (reg_try_read_lock() != EBUSY) { #ifdef ERTS_ENABLE_LOCK_CHECK erts_proc_lc_might_unlock(c_p, *c_p_locks); #endif return; } /* Release process locks in order to avoid deadlock */ erts_proc_unlock(c_p, *c_p_locks); *c_p_locks = 0; } reg_read_lock(); } static ERTS_INLINE void reg_safe_write_lock(Process *c_p, ErtsProcLocks *c_p_locks) { if (*c_p_locks) { ASSERT(c_p); ASSERT(c_p_locks); ASSERT(*c_p_locks); if (reg_try_write_lock() != EBUSY) { #ifdef ERTS_ENABLE_LOCK_CHECK erts_proc_lc_might_unlock(c_p, *c_p_locks); #endif return; } /* Release process locks in order to avoid deadlock */ erts_proc_unlock(c_p, *c_p_locks); *c_p_locks = 0; } reg_write_lock(); } static ERTS_INLINE int is_proc_alive(Process *p) { return !ERTS_PROC_IS_EXITING(p); } void register_info(fmtfn_t to, void *to_arg) { int lock = !ERTS_IS_CRASH_DUMPING; if (lock) reg_read_lock(); hash_info(to, to_arg, &process_reg); if (lock) reg_read_unlock(); } static HashValue reg_hash(RegProc* obj) { return REG_HASH(obj->name); } static int reg_cmp(RegProc *tmpl, RegProc *obj) { return tmpl->name != obj->name; } static RegProc* reg_alloc(RegProc *tmpl) { RegProc* obj = (RegProc*) erts_alloc(ERTS_ALC_T_REG_PROC, sizeof(RegProc)); if (!obj) { erts_exit(ERTS_ERROR_EXIT, "Can't allocate %d bytes of memory\n", sizeof(RegProc)); } obj->name = tmpl->name; obj->p = tmpl->p; obj->pt = tmpl->pt; return obj; } static void reg_free(RegProc *obj) { erts_free(ERTS_ALC_T_REG_PROC, (void*) obj); } void init_register_table(void) { HashFunctions f; erts_rwmtx_opt_t rwmtx_opt = ERTS_RWMTX_OPT_DEFAULT_INITER; rwmtx_opt.type = ERTS_RWMTX_TYPE_FREQUENT_READ; rwmtx_opt.lived = ERTS_RWMTX_LONG_LIVED; erts_rwmtx_init_opt(®tab_rwmtx, &rwmtx_opt, "reg_tab", NIL, ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); f.hash = (H_FUN) reg_hash; f.cmp = (HCMP_FUN) reg_cmp; f.alloc = (HALLOC_FUN) reg_alloc; f.free = (HFREE_FUN) reg_free; f.meta_alloc = (HMALLOC_FUN) erts_alloc; f.meta_free = (HMFREE_FUN) erts_free; f.meta_print = (HMPRINT_FUN) erts_print; hash_init(ERTS_ALC_T_REG_TABLE, &process_reg, "process_reg", PREG_HASH_SIZE, f); } /* * Register a process or port (can't be registered twice). * Returns 0 if name, process or port is already registered. * * When smp support is enabled: * * Assumes that main lock is locked (and only main lock) * on c_p. * */ int erts_register_name(Process *c_p, Eterm name, Eterm id) { int res = 0; Process *proc = NULL; Port *port = NULL; RegProc r, *rp; ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(c_p); if (is_not_atom(name) || name == am_undefined) return res; if (c_p->common.id == id) /* A very common case I think... */ proc = c_p; else { if (is_not_internal_pid(id) && is_not_internal_port(id)) return res; erts_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN); if (is_internal_port(id)) { port = erts_id2port(id); if (!port) goto done; } } { ErtsProcLocks proc_locks = proc ? ERTS_PROC_LOCK_MAIN : 0; reg_safe_write_lock(proc, &proc_locks); if (proc && !proc_locks) erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); } if (is_internal_pid(id)) { if (!proc) proc = erts_pid2proc(NULL, 0, id, ERTS_PROC_LOCK_MAIN); r.p = proc; if (!proc) goto done; if (proc->common.u.alive.reg) goto done; r.pt = NULL; } else { ASSERT(!INVALID_PORT(port, id)); ERTS_LC_ASSERT(erts_lc_is_port_locked(port)); r.pt = port; if (r.pt->common.u.alive.reg) goto done; r.p = NULL; } r.name = name; rp = (RegProc*) hash_put(&process_reg, (void*) &r); if (proc && rp->p == proc) { if (IS_TRACED_FL(proc, F_TRACE_PROCS)) { trace_proc(proc, ERTS_PROC_LOCK_MAIN, proc, am_register, name); } proc->common.u.alive.reg = rp; } else if (port && rp->pt == port) { if (IS_TRACED_FL(port, F_TRACE_PORTS)) { trace_port(port, am_register, name); } port->common.u.alive.reg = rp; } if ((rp->p && rp->p->common.id == id) || (rp->pt && rp->pt->common.id == id)) { res = 1; } done: reg_write_unlock(); if (port) erts_port_release(port); if (c_p != proc) { if (proc) erts_proc_unlock(proc, ERTS_PROC_LOCK_MAIN); erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); } return res; } /* * * When smp support is enabled: * * Assumes that main lock is locked (and only main lock) * on c_p. * * * am_undefined is returned if c_p became exiting. */ Eterm erts_whereis_name_to_id(Process *c_p, Eterm name) { Eterm res = am_undefined; HashValue hval; int ix; HashBucket* b; ErtsProcLocks c_p_locks = 0; if (c_p) { c_p_locks = ERTS_PROC_LOCK_MAIN; ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(c_p); } reg_safe_read_lock(c_p, &c_p_locks); if (c_p && !c_p_locks) erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); hval = REG_HASH(name); ix = hval % process_reg.size; b = process_reg.bucket[ix]; /* * Note: We have inlined the code from hash.c for speed. */ while (b) { RegProc* rp = (RegProc *) b; if (rp->name == name) { /* * SMP NOTE: No need to lock registered entity since it cannot * be removed without acquiring write reg lock and id on entity * is read only. */ if (rp->p) res = rp->p->common.id; else if (rp->pt) res = rp->pt->common.id; break; } b = b->next; } reg_read_unlock(); ASSERT(is_internal_pid(res) || is_internal_port(res) || res==am_undefined); return res; } void erts_whereis_name(Process *c_p, ErtsProcLocks c_p_locks, Eterm name, Process** proc, ErtsProcLocks need_locks, int flags, Port** port, int lock_port) { RegProc* rp = NULL; HashValue hval; int ix; HashBucket* b; ErtsProcLocks current_c_p_locks; Port *pending_port = NULL; if (!c_p) c_p_locks = 0; current_c_p_locks = c_p_locks; restart: reg_safe_read_lock(c_p, ¤t_c_p_locks); /* Locked locks: * - port lock on pending_port if pending_port != NULL * - read reg lock * - current_c_p_locks (either c_p_locks or 0) on c_p */ hval = REG_HASH(name); ix = hval % process_reg.size; b = process_reg.bucket[ix]; /* * Note: We have inlined the code from hash.c for speed. */ while (b) { if (((RegProc *) b)->name == name) { rp = (RegProc *) b; break; } b = b->next; } if (proc) { if (!rp) *proc = NULL; else { if (!rp->p) *proc = NULL; else { if (need_locks) { erts_proc_safelock(c_p, current_c_p_locks, c_p_locks, rp->p, 0, need_locks); current_c_p_locks = c_p_locks; } if ((flags & ERTS_P2P_FLG_ALLOW_OTHER_X) || is_proc_alive(rp->p)) *proc = rp->p; else { if (need_locks) erts_proc_unlock(rp->p, need_locks); *proc = NULL; } } if (*proc && (flags & ERTS_P2P_FLG_INC_REFC)) erts_proc_inc_refc(*proc); } } if (port) { if (!rp || !rp->pt) *port = NULL; else { if (lock_port) { if (pending_port == rp->pt) pending_port = NULL; else { if (pending_port) { /* Ahh! Registered port changed while reg lock was unlocked... */ erts_port_release(pending_port); pending_port = NULL; } if (erts_port_trylock(rp->pt) == EBUSY) { Eterm id = rp->pt->common.id; /* id read only... */ /* Unlock all locks, acquire port lock, and restart... */ if (current_c_p_locks) { erts_proc_unlock(c_p, current_c_p_locks); current_c_p_locks = 0; } reg_read_unlock(); pending_port = erts_id2port(id); goto restart; } } ERTS_LC_ASSERT(erts_lc_is_port_locked(rp->pt)); } *port = rp->pt; } } if (c_p && !current_c_p_locks) erts_proc_lock(c_p, c_p_locks); if (pending_port) erts_port_release(pending_port); reg_read_unlock(); } Process * erts_whereis_process(Process *c_p, ErtsProcLocks c_p_locks, Eterm name, ErtsProcLocks need_locks, int flags) { Process *proc; erts_whereis_name(c_p, c_p_locks, name, &proc, need_locks, flags, NULL, 0); return proc; } /* * Unregister a name * Return 0 if not registered * Otherwise returns 1 * */ int erts_unregister_name(Process *c_p, ErtsProcLocks c_p_locks, Port *c_prt, Eterm name) { int res = 0; RegProc r, *rp; Port *port = c_prt; ErtsProcLocks current_c_p_locks = 0; /* * SMP note: If 'c_prt != NULL' and 'c_prt->reg->name == name', * we are *not* allowed to temporarily release the lock * on c_prt. */ if (!c_p) { c_p_locks = 0; } current_c_p_locks = c_p_locks; restart: reg_safe_write_lock(c_p, ¤t_c_p_locks); r.name = name; if (is_non_value(name)) { /* Unregister current process name */ ASSERT(c_p); if (current_c_p_locks != c_p_locks) { erts_proc_lock(c_p, c_p_locks); current_c_p_locks = c_p_locks; } if (c_p->common.u.alive.reg) { r.name = c_p->common.u.alive.reg->name; } else { /* Name got unregistered while main lock was released */ res = 0; goto done; } } if ((rp = (RegProc*) hash_get(&process_reg, (void*) &r)) != NULL) { if (rp->pt) { if (port != rp->pt) { if (port) { ASSERT(port != c_prt); erts_port_release(port); port = NULL; } if (erts_port_trylock(rp->pt) == EBUSY) { Eterm id = rp->pt->common.id; /* id read only... */ /* Unlock all locks, acquire port lock, and restart... */ if (current_c_p_locks) { erts_proc_unlock(c_p, current_c_p_locks); current_c_p_locks = 0; } reg_write_unlock(); port = erts_id2port(id); goto restart; } port = rp->pt; } ASSERT(rp->pt == port); ERTS_LC_ASSERT(erts_lc_is_port_locked(port)); rp->pt->common.u.alive.reg = NULL; if (IS_TRACED_FL(port, F_TRACE_PORTS)) { if (current_c_p_locks) { erts_proc_unlock(c_p, current_c_p_locks); current_c_p_locks = 0; } trace_port(port, am_unregister, r.name); } } else if (rp->p) { erts_proc_safelock(c_p, current_c_p_locks, c_p_locks, rp->p, (c_p == rp->p) ? current_c_p_locks : 0, ERTS_PROC_LOCK_MAIN); current_c_p_locks = c_p_locks; rp->p->common.u.alive.reg = NULL; if (IS_TRACED_FL(rp->p, F_TRACE_PROCS)) { trace_proc(rp->p, (c_p == rp->p) ? c_p_locks : ERTS_PROC_LOCK_MAIN, rp->p, am_unregister, r.name); } if (rp->p != c_p) { erts_proc_unlock(rp->p, ERTS_PROC_LOCK_MAIN); } } hash_erase(&process_reg, (void*) &r); res = 1; } done: reg_write_unlock(); if (c_prt != port) { if (port) { erts_port_release(port); } if (c_prt) { erts_port_lock(c_prt); } } if (c_p && !current_c_p_locks) { erts_proc_lock(c_p, c_p_locks); } return res; } int process_reg_size(void) { int size; int lock = !ERTS_IS_CRASH_DUMPING; if (lock) reg_read_lock(); size = process_reg.size; if (lock) reg_read_unlock(); return size; } int process_reg_sz(void) { int sz; int lock = !ERTS_IS_CRASH_DUMPING; if (lock) reg_read_lock(); sz = hash_table_sz(&process_reg); if (lock) reg_read_unlock(); return sz; } /**********************************************************************/ #include "bif.h" /* return a list of the registered processes */ BIF_RETTYPE registered_0(BIF_ALIST_0) { int i; Eterm res; Uint need; Eterm* hp; HashBucket **bucket; ErtsProcLocks proc_locks = ERTS_PROC_LOCK_MAIN; ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(BIF_P); reg_safe_read_lock(BIF_P, &proc_locks); if (!proc_locks) erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); bucket = process_reg.bucket; /* work out how much heap we need & maybe garb, by scanning through the registered process table */ need = 0; for (i = 0; i < process_reg.size; i++) { HashBucket *b = bucket[i]; while (b != NULL) { need += 2; b = b->next; } } if (need == 0) { reg_read_unlock(); BIF_RET(NIL); } hp = HAlloc(BIF_P, need); /* scan through again and make the list */ res = NIL; for (i = 0; i < process_reg.size; i++) { HashBucket *b = bucket[i]; while (b != NULL) { RegProc *reg = (RegProc *) b; res = CONS(hp, reg->name, res); hp += 2; b = b->next; } } reg_read_unlock(); BIF_RET(res); }