使用範例
- 在遍歷的時候進行ets處理,如刪除,更新,插入字段內容等
- 配合ets:first和ets:next的迭代器使用
實踐例子
- mnesia的使用
- httpc的使用
- snmpm_net 使用
- dets
- ets自己的foldr使用
作用
可以將ets表鎖住,避免ets數據修改導致遍歷數據錯誤
具體的使用用法
- 使用時:ets:safe_fixtable(Tab, true)
- 使用完畢後:ets:safe_fixtable(Tab, false)
源碼設計
BIF_RETTYPE ets_safe_fixtable_2(BIF_ALIST_2)
{
DbTable *tb;
db_lock_kind_t kind;
#ifdef HARDDEBUG
erts_fprintf(stderr,
"ets:safe_fixtable(%T,%T); Process: %T, initial: %T:%T/%bpu\n",
BIF_ARG_1, BIF_ARG_2, BIF_P->common.id,
BIF_P->u.initial[0], BIF_P->u.initial[1], BIF_P->u.initial[2]);
#endif
kind = (BIF_ARG_2 == am_true) ? LCK_READ : LCK_WRITE_REC;
DB_BIF_GET_TABLE(tb, DB_READ, kind, BIF_ets_safe_fixtable_2);
if (BIF_ARG_2 == am_true) {
fix_table_locked(BIF_P, tb);
}
else if (BIF_ARG_2 == am_false) {
if (IS_FIXED(tb)) {
unfix_table_locked(BIF_P, tb, &kind);
}
}
else {
db_unlock(tb, kind);
BIF_ERROR(BIF_P, BADARG);
}
db_unlock(tb, kind);
BIF_RET(am_true);
}
/* SMP note: table only need to be LCK_READ locked */
static void fix_table_locked(Process* p, DbTable* tb)
{
DbFixation *fix;
int use_locks = !DB_LOCK_FREE(tb);
if (use_locks)
erts_mtx_lock(&tb->common.fixlock);
erts_refc_inc(&tb->common.fix_count,1);
fix = tb->common.fixing_procs;
if (fix == NULL) {
tb->common.time.monotonic
= erts_get_monotonic_time(erts_proc_sched_data(p));
tb->common.time.offset = erts_get_time_offset();
}
else {
fix = fixing_procs_rbt_lookup(fix, p);
if (fix) {
ASSERT(fixed_tabs_find(NULL, fix));
++(fix->counter);
if (use_locks)
erts_mtx_unlock(&tb->common.fixlock);
return;
}
}
fix = (DbFixation *) erts_db_alloc(ERTS_ALC_T_DB_FIXATION,
tb, sizeof(DbFixation));
ERTS_ETS_MISC_MEM_ADD(sizeof(DbFixation));
fix->tabs.btid = tb->common.btid;
erts_refc_inc(&fix->tabs.btid->intern.refc, 2);
fix->procs.p = p;
fix->counter = 1;
fixing_procs_rbt_insert(&tb->common.fixing_procs, fix);
if (use_locks)
erts_mtx_unlock(&tb->common.fixlock);
p->flags |= F_USING_DB;
fixed_tabs_insert(p, fix);
}
static void fixed_tabs_insert(Process* p, DbFixation* fix)
{
DbFixation* first = erts_psd_get(p, ERTS_PSD_ETS_FIXED_TABLES);
if (!first) {
fix->tabs.next = fix->tabs.prev = fix;
erts_psd_set(p, ERTS_PSD_ETS_FIXED_TABLES, fix);
}
else {
ASSERT(!fixed_tabs_find(first, fix));
fix->tabs.prev = first->tabs.prev;
fix->tabs.next = first;
fix->tabs.prev->tabs.next = fix;
first->tabs.prev = fix;
}
}
/* SMP note: May re-lock table
*/
static void unfix_table_locked(Process* p, DbTable* tb,
db_lock_kind_t* kind_p)
{
DbFixation* fix;
int use_locks = !DB_LOCK_FREE(tb);
if (use_locks)
erts_mtx_lock(&tb->common.fixlock);
fix = fixing_procs_rbt_lookup(tb->common.fixing_procs, p);
if (fix) {
erts_refc_dec(&tb->common.fix_count,0);
--(fix->counter);
ASSERT(fix->counter >= 0);
if (fix->counter == 0) {
fixing_procs_rbt_delete(&tb->common.fixing_procs, fix);
if (use_locks)
erts_mtx_unlock(&tb->common.fixlock);
fixed_tabs_delete(p, fix);
erts_refc_dec(&fix->tabs.btid->intern.refc, 1);
erts_db_free(ERTS_ALC_T_DB_FIXATION,
tb, (void *) fix, sizeof(DbFixation));
ERTS_ETS_MISC_MEM_ADD(-sizeof(DbFixation));
goto unlocked;
}
}
if (use_locks)
erts_mtx_unlock(&tb->common.fixlock);
unlocked:
if (!IS_FIXED(tb) && IS_HASH_TABLE(tb->common.status)
&& erts_atomic_read_nob(&tb->hash.fixdel) != (erts_aint_t)NULL) {
if (*kind_p == LCK_READ && tb->common.is_thread_safe) {
/* Must have write lock while purging pseudo-deleted (OTP-8166) */
if (use_locks) {
erts_rwmtx_runlock(&tb->common.rwlock);
erts_rwmtx_rwlock(&tb->common.rwlock);
}
*kind_p = LCK_WRITE;
if (tb->common.status & (DB_DELETE|DB_BUSY))
return;
}
db_unfix_table_hash(&(tb->hash));
}
}
SWord db_unfix_table_hash(DbTableHash *tb)
{
FixedDeletion* fixdel;
SWord work = 0;
ERTS_LC_ASSERT(IS_TAB_WLOCKED(tb)
|| (erts_lc_rwmtx_is_rlocked(&tb->common.rwlock)
&& !tb->common.is_thread_safe));
restart:
fixdel = (FixedDeletion*) erts_atomic_xchg_mb(&tb->fixdel,
(erts_aint_t) NULL);
while (fixdel) {
FixedDeletion *free_me;
do {
HashDbTerm **bp;
HashDbTerm *b;
HashDbTerm *free_us = NULL;
erts_rwmtx_t* lck;
lck = WLOCK_HASH(tb, fixdel->slot);
if (IS_FIXED(tb)) { /* interrupted by fixer */
WUNLOCK_HASH(lck);
restore_fixdel(tb,fixdel);
if (!IS_FIXED(tb)) {
goto restart; /* unfixed again! */
}
return work;
}
if (fixdel->slot < NACTIVE(tb)) {
bp = &BUCKET(tb, fixdel->slot);
b = *bp;
while (b != NULL) {
if (is_pseudo_deleted(b)) {
HashDbTerm* nxt = b->next;
b->next = free_us;
free_us = b;
work++;
b = *bp = nxt;
} else {
bp = &b->next;
b = b->next;
}
}
}
/* else slot has been joined and purged by shrink() */
WUNLOCK_HASH(lck);
free_term_list(tb, free_us);
}while (fixdel->all && fixdel->slot-- > 0);
free_me = fixdel;
fixdel = fixdel->next;
free_fixdel(tb, free_me);
work++;
}
/* ToDo: Maybe try grow/shrink the table as well */
return work;
}