diff options
author | Kjell Winblad <kjellwinblad@gmail.com> | 2019-08-15 16:43:49 +0200 |
---|---|---|
committer | Kjell Winblad <kjellwinblad@gmail.com> | 2019-12-18 10:00:29 +0100 |
commit | d5d89208521cc0d025ed47c6ea8419ec7aadea48 (patch) | |
tree | 56189a7a4680d43574232ab0099c097f421cd641 /erts | |
parent | eb26b2465228324272e4a3df6c517d8c55283939 (diff) | |
download | erlang-d5d89208521cc0d025ed47c6ea8419ec7aadea48.tar.gz |
Make ets:insert/2 and ets:insert_new/2 yield
The `ets:insert/2` and `ets:insert_new/2` functions did not yield even
when they were given long lists and a call to one of the functions did
only consume a single reduction. This commit fixes these problems.
The fix uses the "Yielding C Fun" tool to automatically generate
yieldable versions of C functions.
Diffstat (limited to 'erts')
-rw-r--r-- | erts/.gitignore | 1 | ||||
-rw-r--r-- | erts/configure.in | 3 | ||||
-rw-r--r-- | erts/emulator/Makefile.in | 25 | ||||
-rw-r--r-- | erts/emulator/beam/erl_alloc.types | 1 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db.c | 720 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db.h | 19 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_catree.c | 27 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_hash.c | 248 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_tree.c | 204 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_tree_util.h | 7 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_util.c | 57 | ||||
-rw-r--r-- | erts/emulator/beam/erl_db_util.h | 42 | ||||
-rw-r--r-- | erts/emulator/beam/erl_process.h | 18 | ||||
-rw-r--r-- | erts/emulator/beam/sys.h | 33 | ||||
-rw-r--r-- | erts/emulator/internal_doc/AutomaticYieldingOfCCode.md | 9 | ||||
-rw-r--r-- | erts/emulator/sys/unix/sys_uds.c | 2 | ||||
-rw-r--r-- | erts/lib_src/yielding_c_fun/GNUmakefile | 28 | ||||
-rw-r--r-- | erts/lib_src/yielding_c_fun/README.md | 2 | ||||
-rw-r--r-- | erts/lib_src/yielding_c_fun/main_target.mk | 4 |
19 files changed, 1272 insertions, 178 deletions
diff --git a/erts/.gitignore b/erts/.gitignore index e515dc8811..ab33d1507b 100644 --- a/erts/.gitignore +++ b/erts/.gitignore @@ -21,3 +21,4 @@ /emulator/test/*_no_opt_SUITE.erl /emulator/pcre/pcre_exec_loop_break_cases.inc +/emulator/beam/erl_db_insert_list.inc diff --git a/erts/configure.in b/erts/configure.in index d35f8a02d9..2a5b171e36 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -585,7 +585,8 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[ #endif])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) - CFLAGS="-std=gnu99 $CFLAGS"]) + CFLAGS="-std=gnu99 $CFLAGS" + DEBUG_CFLAGS="-std=gnu99 $DEBUG_CFLAGS"]) AC_MSG_CHECKING([CFLAGS for -O switch]) case "$CFLAGS" in diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index ef8854f827..27ecdcb382 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -457,6 +457,8 @@ endif include zlib/zlib.mk include pcre/pcre.mk +YCF_SOURCE_DIR = $(ERL_TOP)/erts/lib_src/yielding_c_fun +include $(ERL_TOP)/erts/lib_src/yielding_c_fun/main_target.mk $(ERTS_LIB): $(V_at)cd $(ERTS_LIB_DIR) && $(MAKE) $(TYPE) @@ -470,6 +472,10 @@ clean: $(RM) -r pcre/obj/$(TARGET) $(PCRE_GENINC) $(RM) -r zlib/obj/$(TARGET) $(RM) -r bin/$(TARGET) + $(RM) $(YCF_EXECUTABLE) + $(RM) $(YCF_SOURCE_DIR)/*.o + $(RM) $(YCF_SOURCE_DIR)/lib/tiny_regex_c/*.o + $(RM) $(YCF_SOURCE_DIR)/lib/simple_c_gc/*.o cd $(ERTS_LIB_DIR) && $(MAKE) clean .PHONY: docs @@ -621,7 +627,24 @@ $(TTF_DIR)/driver_tab.c: Makefile.in utils/make_driver_tab $(gen_verbose)LANG=C $(PERL) utils/make_driver_tab -o $@ -nifs $(NIF_OBJS) $(STATIC_NIF_LIBS) -drivers $(DRV_OBJS) $(STATIC_DRIVER_LIBS) GENERATE += $(TTF_DIR)/driver_tab.c - +beam/erl_db_insert_list.inc: $(YCF_EXECUTABLE) beam/erl_db.c + $(gen_verbose)$(YCF_EXECUTABLE) \ + -yield \ + -static_aux_funs \ + -only_yielding_funs \ + -output_file_name beam/erl_db_insert_list.inc \ + -f ets_insert_2_list_check \ + -f ets_insert_new_2_list_has_member \ + -f ets_insert_2_list_from_p_heap \ + -f ets_insert_2_list_destroy_copied_dbterms \ + -f ets_insert_2_list_copy_term_list \ + -f ets_insert_new_2_dbterm_list_has_member \ + -f ets_insert_2_list_insert_db_term_list \ + -f ets_insert_2_list \ + -fnoauto ets_insert_2_list_lock_tbl \ + beam/erl_db.c + +GENERATE += beam/erl_db_insert_list.inc # Preloaded code. # diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types index 2341da8630..3d14e86445 100644 --- a/erts/emulator/beam/erl_alloc.types +++ b/erts/emulator/beam/erl_alloc.types @@ -196,6 +196,7 @@ type DB_TERM ETS ETS db_term type DB_PROC_CLEANUP SHORT_LIVED ETS db_proc_cleanup_state type ETS_ALL_REQ SHORT_LIVED ETS ets_all_request type ETS_CTRS ETS ETS ets_decentralized_ctrs +type ETS_I_LST_TRAP SHORT_LIVED ETS ets_insert_list_bif_trap_state type LOGGER_DSBUF TEMPORARY SYSTEM logger_dsbuf type TMP_DSBUF TEMPORARY SYSTEM tmp_dsbuf type INFO_DSBUF SYSTEM SYSTEM info_dsbuf diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index 67ed5fed1f..77c317b690 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -51,6 +51,12 @@ erts_atomic_t erts_ets_misc_mem_size; ** Utility macros */ +#ifdef DEBUG +#define IF_DEBUG_OR(DEBUG_EXP, NORMAL_EXP) (DEBUG_EXP) +#else +#define IF_DEBUG_OR(DEBUG_EXP, NORMAL_EXP) (NORMAL_EXP) +#endif + #define DB_BIF_GET_TABLE(TB, WHAT, KIND, BIF_IX) \ DB_GET_TABLE(TB, BIF_ARG_1, WHAT, KIND, BIF_IX, NULL, BIF_P) @@ -353,7 +359,9 @@ struct meta_name_tab_entry* meta_name_tab_bucket(Eterm name, typedef enum { LCK_READ=1, /* read only access */ LCK_WRITE=2, /* exclusive table write access */ - LCK_WRITE_REC=3 /* record write access */ + LCK_WRITE_REC=3, /* record write access */ + NOLCK_ACCESS=4 /* Used to access the table structure + without acquiring the table lock */ } db_lock_kind_t; extern DbTableMethod db_hash; @@ -621,7 +629,7 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind) if (kind == LCK_WRITE) { erts_rwmtx_rwlock(&tb->common.rwlock); tb->common.is_thread_safe = 1; - } else { + } else if (kind != NOLCK_ACCESS) { erts_rwmtx_rlock(&tb->common.rwlock); ASSERT(!tb->common.is_thread_safe); } @@ -633,6 +641,8 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind) case LCK_WRITE_REC: erts_rwmtx_rwlock(&tb->common.rwlock); break; + case NOLCK_ACCESS: + return; default: erts_rwmtx_rlock(&tb->common.rwlock); } @@ -642,7 +652,7 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind) static ERTS_INLINE void db_unlock(DbTable* tb, db_lock_kind_t kind) { - if (DB_LOCK_FREE(tb)) + if (DB_LOCK_FREE(tb) || kind == NOLCK_ACCESS) return; if (tb->common.type & DB_FINE_LOCKED) { if (kind == LCK_WRITE) { @@ -673,7 +683,10 @@ static ERTS_INLINE int db_is_exclusive(DbTable* tb, db_lock_kind_t kind) if (DB_LOCK_FREE(tb)) return 1; - return kind != LCK_READ && tb->common.is_thread_safe; + return + kind != LCK_READ && + kind != NOLCK_ACCESS && + tb->common.is_thread_safe; } static DbTable* handle_lacking_permission(Process* p, DbTable* tb, @@ -681,11 +694,29 @@ static DbTable* handle_lacking_permission(Process* p, DbTable* tb, Uint* freason_p) { if (tb->common.status & DB_BUSY) { + void* continuation_state; if (!db_is_exclusive(tb, kind)) { db_unlock(tb, kind); db_lock(tb, LCK_WRITE); } - delete_all_objects_continue(p, tb); + continuation_state = (void*)erts_atomic_read_nob(&tb->common.continuation_state); + if (continuation_state != NULL) { + const long iterations_per_red = 10; + const long reds = iterations_per_red * ERTS_BIF_REDS_LEFT(p); + long nr_of_reductions = + IF_DEBUG_OR(reds * 0.1 * erts_sched_local_random_float((Uint)freason_p), + reds); + const long init_reds = nr_of_reductions; + tb->common.continuation(&nr_of_reductions, + &continuation_state, + NULL); + if (continuation_state == NULL) { + erts_atomic_set_relb(&tb->common.continuation_state, (Sint)NULL); + } + BUMP_REDS(p, (init_reds - nr_of_reductions) / iterations_per_red); + } else { + delete_all_objects_continue(p, tb); + } db_unlock(tb, LCK_WRITE); tb = NULL; *freason_p = TRAP; @@ -724,9 +755,9 @@ DbTable* db_get_table_aux(Process *p, if (!meta_already_locked) erts_rwmtx_rlock(mtl); else { - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(mtl) - || erts_lc_rwmtx_is_rwlocked(mtl) - || META_DB_LOCK_FREE()); + ERTS_LC_ASSERT(META_DB_LOCK_FREE() + || erts_lc_rwmtx_is_rlocked(mtl) + || erts_lc_rwmtx_is_rwlocked(mtl)); } tb = NULL; if (bucket->pu.tb != NULL) { @@ -754,15 +785,28 @@ DbTable* db_get_table_aux(Process *p, if (tb) { db_lock(tb, kind); #ifdef ETS_DBG_FORCE_TRAP - if (erts_atomic_read_nob(&tb->common.dbg_force_trap) && - erts_atomic_add_read_nob(&tb->common.dbg_force_trap, 2) & 2) { - db_unlock(tb, kind); - tb = NULL; - *freason_p = TRAP; + if (erts_atomic_read_nob(&tb->common.dbg_force_trap)) { + Uint32 rand = erts_sched_local_random((Uint)&p); + if ( !(rand & 7) ) { + /* About 7 of 8 threads that are on the line above + will get here */ + if (erts_atomic_add_read_nob(&tb->common.dbg_force_trap, 2) & 2) { + db_unlock(tb, kind); + tb = NULL; + *freason_p = TRAP; + return tb; + } + } + + } - else #endif - if (ERTS_UNLIKELY(!(tb->common.status & what))) + if (ERTS_UNLIKELY(what != DB_READ_TBL_STRUCT + /* IMPORTANT: the above check is + necessary as the status field might + be in an intermediate state when + kind==NOLCK_ACCESS */ && + !(tb->common.status & what))) tb = handle_lacking_permission(p, tb, kind, freason_p); } else @@ -781,6 +825,17 @@ DbTable* db_get_table(Process *p, return db_get_table_aux(p, id, what, kind, 0, freason_p); } +static BIF_RETTYPE db_get_table_or_fail_return(DbTable **tb, /* out */ + Eterm table_id, + Uint32 what, + db_lock_kind_t kind, + Uint bif_ix, + Process* p) +{ + DB_GET_TABLE(*tb, table_id, what, kind, bif_ix, NULL, p); + return THE_NON_VALUE; +} + static int insert_named_tab(Eterm name_atom, DbTable* tb, int have_lock) { int ret = 0; @@ -863,7 +918,7 @@ static int remove_named_tab(DbTable *tb, int have_lock) db_lock(tb, LCK_WRITE); } - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(rwlock) || META_DB_LOCK_FREE()); + ERTS_LC_ASSERT(META_DB_LOCK_FREE() || erts_lc_rwmtx_is_rwlocked(rwlock)); if (bucket->pu.tb == NULL) { goto done; @@ -1384,6 +1439,491 @@ BIF_RETTYPE ets_update_counter_4(BIF_ALIST_4) return do_update_counter(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4); } +typedef enum { + ETS_INSERT_2_LIST_PROCESS_LOCAL, + ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK, + ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY, + ETS_INSERT_2_LIST_GLOBAL +} ets_insert_2_list_status; + +typedef struct { + ets_insert_2_list_status status; + BIF_RETTYPE destroy_return_value; + DbTable* tb; + void* continuation_state; + Binary* continuation_res_bin; +} ets_insert_2_list_info; + + +static ERTS_INLINE BIF_RETTYPE +ets_cret_to_return_value(Process* p, int cret) +{ + switch (cret) { + case DB_ERROR_NONE_FALSE: + BIF_RET(am_false); + case DB_ERROR_NONE: + BIF_RET(am_true); + case DB_ERROR_SYSRES: + BIF_ERROR(p, SYSTEM_LIMIT); + default: + BIF_ERROR(p, BADARG); + } +} + +/* + * >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + * >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + * + * Start of code section that Yielding C Fun (YCF) transforms + * + * The functions starting with the ets_insert_2_list and + * ets_insert_new_2 prefixes below are not called directly. YCF + * generates yieldable versions of these functions before "erl_db.c" is + * compiled. These generated functions are placed in the file + * "erl_db_insert_list.inc" which is included below. The generation of + * "erl_db_insert_list.inc" is defined in + * "$ERL_TOP/erts/emulator/Makefile.in". See + * "$ERL_TOP/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md" + * for more information about YCF. + * + * >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + * >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + */ +#define ON_DESTROY_STATE +#define YCF_SPECIAL_CODE_START(PARAM) +#define YCF_SPECIAL_CODE_END() +#define YCF_NR_OF_REDS_LEFT() 0 +#define YCF_SET_NR_OF_REDS_LEFT(NOT_USED) +#define YCF_YIELD() erts_exit(1, \ + "Called the normal version of ets_insert_2_list_lock_tbl.\n" \ + "But only the yieldable version of ets_insert_2_list_lock_tbl should be called\n") +#define YCF_GET_EXTRA_CONTEXT() NULL + +static int ets_insert_2_list_check(int keypos, Eterm list) +{ + Eterm lst = THE_NON_VALUE; + int i = 0; + for (lst = list; is_list(lst); lst = CDR(list_val(lst))) { + i++; + if (is_not_tuple(CAR(list_val(lst))) || + (arityval(*tuple_val(CAR(list_val(lst)))) < keypos)) { + return DB_ERROR_BADITEM; + } + } + if (lst != NIL) { + return DB_ERROR_BADITEM; + } + return DB_ERROR_NONE; +} + +static int ets_insert_new_2_list_has_member(DbTable* tb, Eterm list) +{ + Eterm lst; + Eterm lookup_ret; + DbTableMethod* meth = tb->common.meth; + for (lst = list; is_list(lst); lst = CDR(list_val(lst))) { + meth->db_member(tb, + TERM_GETKEY(tb,CAR(list_val(lst))), + &lookup_ret); + if (lookup_ret != am_false) { + return 1; + } + } + return 0; +} + +static int ets_insert_2_list_from_p_heap(DbTable* tb, Eterm list) +{ + Eterm lst; + DbTableMethod* meth = tb->common.meth; + int cret = DB_ERROR_NONE; + for (lst = list; is_list(lst); lst = CDR(list_val(lst))) { + cret = meth->db_put(tb, CAR(list_val(lst)), 0); + if (cret != DB_ERROR_NONE) + return cret; + } + return DB_ERROR_NONE; +} + +static void ets_insert_2_list_destroy_copied_dbterms(DbTableMethod* meth, + int compressed, + void* db_term_list) +{ + void* lst = db_term_list; + void* term = NULL; + while (lst != NULL) { + term = meth->db_dbterm_list_remove_first(&lst); + meth->db_free_dbterm(compressed, term); + } +} + +static void* ets_insert_2_list_copy_term_list(DbTableMethod* meth, + int compress, + int keypos, + Eterm list) +{ + void* db_term_list = NULL; + void *term; + Eterm lst; + for (lst = list; is_list(lst); lst = CDR(list_val(lst))) { + term = meth->db_eterm_to_dbterm(compress, + keypos, + CAR(list_val(lst))); + if (db_term_list != NULL) { + db_term_list = + meth->db_dbterm_list_prepend(db_term_list, + term); + } else { + db_term_list = term; + } + } + + return db_term_list; + + /* The following code will be executed if the calling process is + killed in the middle of the for loop above*/ + YCF_SPECIAL_CODE_START(ON_DESTROY_STATE); { + ets_insert_2_list_destroy_copied_dbterms(meth, + compress, + db_term_list); + } YCF_SPECIAL_CODE_END(); +} + +static int ets_insert_new_2_dbterm_list_has_member(DbTable* tb, void* db_term_list) +{ + Eterm lookup_ret; + DbTableMethod* meth = tb->common.meth; + void* lst = db_term_list; + void* term = NULL; + Eterm key; + while (lst != NULL) { + term = meth->db_dbterm_list_remove_first(&lst); + key = meth->db_get_dbterm_key(tb, term); + meth->db_member(tb, key, &lookup_ret); + if (lookup_ret != am_false) { + return 1; + } + } + return 0; +} + +static void ets_insert_2_list_insert_db_term_list(DbTable* tb, + void* list) +{ + void* lst = list; + void* term = NULL; + DbTableMethod* meth = tb->common.meth; + do { + term = meth->db_dbterm_list_remove_first(&lst); + meth->db_put_dbterm(tb, term, 0); + } while (lst != NULL); + return; +} + +static void ets_insert_2_list_lock_tbl(Eterm table_id, + Process* p, + Uint bif_ix, + ets_insert_2_list_status on_success_status) +{ + BIF_RETTYPE fail_ret; + DbTable* tb; + ets_insert_2_list_info *ctx; + do { + fail_ret = db_get_table_or_fail_return(&tb, + table_id, + DB_WRITE, + LCK_WRITE, + bif_ix, + p); + ctx = YCF_GET_EXTRA_CONTEXT(); + if (tb == NULL) { + if (p->freason == TRAP) { + ctx->status = ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK; + } else { + ctx->status = ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY; + ctx->destroy_return_value = fail_ret; + } + YCF_YIELD(); + } else { + ctx->status = on_success_status; + ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock)); + ASSERT(!(tb->common.status & DB_DELETE)); + } + } while (tb == NULL); +} + +ERTS_GCC_DIAG_OFF(unused-function) +static BIF_RETTYPE ets_insert_2_list(Process* p, + Eterm table_id, + DbTable *tb, + Eterm list, + int is_insert_new) +{ + int cret = DB_ERROR_NONE; + void* db_term_list = NULL; /* OBS: memory managements depends on that + db_term_list is initialized to NULL */ + const int without_trap_const = 2; + long nr_of_reductions_given = YCF_NR_OF_REDS_LEFT(); + long consumed_reds; + DbTableMethod* meth = tb->common.meth; + int compressed = tb->common.compress; + int keypos = tb->common.keypos; + Uint bif_ix = (is_insert_new ? BIF_ets_insert_new_2 : BIF_ets_insert_2); + /* tb should not be accessed after this point unless the table + lock is held as the table can get deleted while the function is + yielding */ + cret = ets_insert_2_list_check(keypos, list); + if (cret != DB_ERROR_NONE) { + return ets_cret_to_return_value(p, cret); + } + consumed_reds = nr_of_reductions_given - YCF_NR_OF_REDS_LEFT(); + if (consumed_reds < (nr_of_reductions_given/without_trap_const)) { + /* There is enough reductions left to do the inserts directly + from the heap without yielding */ + ets_insert_2_list_lock_tbl(table_id, p, bif_ix, ETS_INSERT_2_LIST_PROCESS_LOCAL); + /* Ensure that we will not yield while inserting from heap */ + YCF_SET_NR_OF_REDS_LEFT(YCF_MAX_NR_OF_REDS); + if (is_insert_new) { + if (ets_insert_new_2_list_has_member(tb, list)) { + cret = DB_ERROR_NONE_FALSE; + } else { + cret = ets_insert_2_list_from_p_heap(tb, list); + } + } else { + cret = ets_insert_2_list_from_p_heap(tb, list); + } + db_unlock(tb, LCK_WRITE); + YCF_SET_NR_OF_REDS_LEFT(consumed_reds - + (YCF_MAX_NR_OF_REDS - YCF_NR_OF_REDS_LEFT())); + return ets_cret_to_return_value(p, cret); + } + /* Copy term list from heap so that other processes can help */ + db_term_list = + ets_insert_2_list_copy_term_list(meth, compressed, keypos, list); + /* Lock table */ + ets_insert_2_list_lock_tbl(table_id, p, bif_ix, ETS_INSERT_2_LIST_GLOBAL); + /* The operation must complete after this point */ + if (is_insert_new) { + if (ets_insert_new_2_dbterm_list_has_member(tb, db_term_list)) { + ets_insert_2_list_destroy_copied_dbterms(meth, + compressed, + db_term_list); + cret = DB_ERROR_NONE_FALSE; + } else { + ets_insert_2_list_insert_db_term_list(tb, db_term_list); + } + } else { + ets_insert_2_list_insert_db_term_list(tb, db_term_list); + } + if (tb->common.continuation != NULL) { + /* Uninstall the continuation from the table struct */ + tb->common.continuation = NULL; + if (is_insert_new) { + int* result_ptr = + ERTS_MAGIC_BIN_DATA(tb->common.continuation_res_bin); + *result_ptr = cret; + erts_bin_release(tb->common.continuation_res_bin); + } + tb->common.status |= tb->common.type & (DB_PRIVATE|DB_PROTECTED|DB_PUBLIC); + tb->common.status &= ~DB_BUSY; + erts_atomic_set_relb(&tb->common.continuation_state, (Sint)NULL); + } + + return ets_cret_to_return_value(NULL, cret); + + /* The following code will be executed if the initiating process + is killed before an ets_insert_2_list_lock_tbl call has + succeeded */ + YCF_SPECIAL_CODE_START(ON_DESTROY_STATE); { + ets_insert_2_list_destroy_copied_dbterms(meth, + compressed, + db_term_list); + } YCF_SPECIAL_CODE_END(); +} +/* ERTS_GCC_DIAG_ON(unused-function) */ +/* gcc 5.4.0 produces unused-function warnings if the above line is + uncommented. This issue seems to be fixed in gcc 7.4.0. */ + +#undef YCF_YIELD +#undef YCF_GET_EXTRA_CONTEXT +#undef YCF_NR_OF_REDS_LEFT +#undef YCF_SET_NR_OF_REDS_LEFT +#undef ON_DESTROY_STATE +#undef YCF_SPECIAL_CODE_START +#undef YCF_SPECIAL_CODE_END +/* + * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + * + * End of code section that Yielding C Fun (YCF) transforms + * + * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + */ +#include "erl_db_insert_list.inc" + +static void* ets_insert_2_yield_alloc(size_t size, void* ctx) +{ + (void)ctx; + return erts_alloc(ERTS_ALC_T_ETS_I_LST_TRAP, size); +} + +static void ets_insert_2_yield_free(void* data, void* ctx) +{ + (void)ctx; + erts_free(ERTS_ALC_T_ETS_I_LST_TRAP, data); +} + +static int ets_insert_2_list_yield_dtor(Binary* bin) +{ + ets_insert_2_list_info* ctx = ERTS_MAGIC_BIN_DATA(bin); + if (ctx->status != ETS_INSERT_2_LIST_GLOBAL && + ctx->continuation_state != NULL) { + /* The operation has not been committed to the table and has + not completed*/ + ets_insert_2_list_ycf_gen_destroy(ctx->continuation_state); + } + return 1; +} + +static void ets_insert_2_list_continuation(long *reds_ptr, + void** state, + void* extra_context) +{ + ets_insert_2_list_ycf_gen_continue(reds_ptr, state, extra_context); +} + +static int db_insert_new_2_res_bin_dtor(Binary *context_bin) +{ + (void)context_bin; + return 1; +} + +static BIF_RETTYPE ets_insert_2_list_driver(Process* p, + Eterm tid, + Eterm list, + int is_insert_new) { + const Uint iterations_per_red = 10; + const long reds = iterations_per_red * ERTS_BIF_REDS_LEFT(p); + long nr_of_reductions = + IF_DEBUG_OR(reds * 0.1 * erts_sched_local_random_float((Uint)&p), + reds); + const long init_reds = nr_of_reductions; + ets_insert_2_list_info* ctx = NULL; + ets_insert_2_list_info ictx; + BIF_RETTYPE ret = THE_NON_VALUE; + Eterm state_mref = list; + Uint bix = (is_insert_new ? BIF_ets_insert_new_2 : BIF_ets_insert_2); + if (is_internal_magic_ref(state_mref)) { + Binary* state_bin = erts_magic_ref2bin(state_mref); + if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) != ets_insert_2_list_yield_dtor) { + BIF_ERROR(p, BADARG); + } + /* Continue a trapped call */ + erts_set_gc_state(p, 1); + ctx = ERTS_MAGIC_BIN_DATA(state_bin); + if (ctx->status == ETS_INSERT_2_LIST_GLOBAL) { + /* An operation that can be helped by other operations is + handled here */ + Uint freason__; + int cret = DB_ERROR_NONE; + DbTable* tb; + /* First check if another process has completed the + operation without acquiring the lock */ + if (NULL == (tb = db_get_table(p, tid, DB_READ_TBL_STRUCT, NOLCK_ACCESS, &freason__))) { + if (freason__ == TRAP){ + erts_set_gc_state(p, 0); + return db_bif_fail(p, freason__, bix, NULL); + } + } + if (tb != NULL && + (void*)erts_atomic_read_acqb(&tb->common.continuation_state) == + ctx->continuation_state) { + /* The lock has to be taken to complete the operation */ + if (NULL == (tb = db_get_table(p, tid, DB_WRITE, LCK_WRITE, &freason__))) { + if (freason__ == TRAP){ + erts_set_gc_state(p, 0); + return db_bif_fail(p, freason__, bix, NULL); + } + } + /* Must be done since the db_get_table call did not trap */ + if (tb != NULL) { + db_unlock(tb, LCK_WRITE); + } + } + if (is_insert_new) { + int* res = ERTS_MAGIC_BIN_DATA(ctx->continuation_res_bin); + cret = *res; + } + return ets_cret_to_return_value(NULL, cret); + } else { + ret = ets_insert_2_list_ycf_gen_continue(&nr_of_reductions, + &ctx->continuation_state, + ctx); + } + } else { + /* Start call */ + ictx.continuation_state = NULL; + ictx.status = ETS_INSERT_2_LIST_PROCESS_LOCAL; + ictx.tb = NULL; + ctx = &ictx; + DB_GET_TABLE(ctx->tb, tid, DB_READ_TBL_STRUCT, NOLCK_ACCESS, bix, NULL, p); + ret = ets_insert_2_list_ycf_gen_yielding(&nr_of_reductions, + &ctx->continuation_state, + ctx, + ets_insert_2_yield_alloc, + ets_insert_2_yield_free, + NULL, + 0, + NULL, + p, + tid, + ctx->tb, + list, + is_insert_new); + if (ctx->continuation_state != NULL) { + Binary* state_bin = erts_create_magic_binary(sizeof(ets_insert_2_list_info), + ets_insert_2_list_yield_dtor); + Eterm* hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE); + state_mref = erts_mk_magic_ref(&hp, &MSO(p), state_bin); + ctx = ERTS_MAGIC_BIN_DATA(state_bin); + *ctx = ictx; + } + } + BUMP_REDS(p, (init_reds - nr_of_reductions) / iterations_per_red); + if (ctx->status == ETS_INSERT_2_LIST_GLOBAL && + ctx->continuation_state != NULL && + ctx->tb->common.continuation == NULL) { + /* Install the continuation in the table structure so other + threads can help */ + if (is_insert_new) { + Binary* bin = + erts_create_magic_binary(sizeof(int), + db_insert_new_2_res_bin_dtor); + Eterm* hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE); + erts_mk_magic_ref(&hp, &MSO(p), bin); + erts_refc_inctest(&bin->intern.refc, 2); + ctx->tb->common.continuation_res_bin = bin; + ctx->continuation_res_bin = bin; + } + ctx->tb->common.continuation = ets_insert_2_list_continuation; + ctx->tb->common.status &= ~(DB_PRIVATE|DB_PROTECTED|DB_PUBLIC); + ctx->tb->common.status |= DB_BUSY; + erts_atomic_set_relb(&ctx->tb->common.continuation_state, + (Sint)ctx->continuation_state); + } + if (ctx->status == ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY) { + return ctx->destroy_return_value; + } + if (ctx->status == ETS_INSERT_2_LIST_GLOBAL) { + db_unlock(ctx->tb, LCK_WRITE); + } + if (ctx->continuation_state != NULL) { + erts_set_gc_state(p, 0); + BIF_TRAP2(&bif_trap_export[bix], p, tid, state_mref); + } + return ret; +} /* ** The put BIF @@ -1392,59 +1932,40 @@ BIF_RETTYPE ets_insert_2(BIF_ALIST_2) { DbTable* tb; int cret = DB_ERROR_NONE; - Eterm lst; + Eterm insert_term; DbTableMethod* meth; - db_lock_kind_t kind; - CHECK_TABLES(); - - /* Write lock table if more than one object to keep atomicity */ - kind = ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL) - ? LCK_WRITE : LCK_WRITE_REC); - - DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_2); - if (BIF_ARG_2 == NIL) { - db_unlock(tb, kind); + /* Check that the table exists */ + DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2); + db_unlock(tb, LCK_WRITE_REC); BIF_RET(am_true); - } - meth = tb->common.meth; - if (is_list(BIF_ARG_2)) { - for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) { - if (is_not_tuple(CAR(list_val(lst))) || - (arityval(*tuple_val(CAR(list_val(lst)))) < tb->common.keypos)) { - goto badarg; - } - } - if (lst != NIL) { - goto badarg; - } - for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) { - cret = meth->db_put(tb, CAR(list_val(lst)), 0); - if (cret != DB_ERROR_NONE) - break; - } + } if ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL) || + is_internal_magic_ref(BIF_ARG_2)) { + /* Handle list case */ + return ets_insert_2_list_driver(BIF_P, + BIF_ARG_1, + BIF_ARG_2, + 0); + } else if (is_list(BIF_ARG_2)) { + insert_term = CAR(list_val(BIF_ARG_2)); } else { - if (is_not_tuple(BIF_ARG_2) || - (arityval(*tuple_val(BIF_ARG_2)) < tb->common.keypos)) { - goto badarg; - } - cret = meth->db_put(tb, BIF_ARG_2, 0); + insert_term = BIF_ARG_2; } - db_unlock(tb, kind); - - switch (cret) { - case DB_ERROR_NONE: - BIF_RET(am_true); - case DB_ERROR_SYSRES: - BIF_ERROR(BIF_P, SYSTEM_LIMIT); - default: - BIF_ERROR(BIF_P, BADARG); + DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2); + + meth = tb->common.meth; + if (is_not_tuple(insert_term) || + (arityval(*tuple_val(insert_term)) < tb->common.keypos)) { + db_unlock(tb, LCK_WRITE_REC); + BIF_ERROR(BIF_P, BADARG); } - badarg: - db_unlock(tb, kind); - BIF_ERROR(BIF_P, BADARG); + cret = meth->db_put(tb, insert_term, 0); + + db_unlock(tb, LCK_WRITE_REC); + + return ets_cret_to_return_value(BIF_P, cret); } @@ -1458,69 +1979,37 @@ BIF_RETTYPE ets_insert_new_2(BIF_ALIST_2) Eterm ret = am_true; Eterm obj; db_lock_kind_t kind; - CHECK_TABLES(); - if (is_list(BIF_ARG_2)) { - if (CDR(list_val(BIF_ARG_2)) != NIL) { - Eterm lst; - Eterm lookup_ret; - DbTableMethod* meth; - - /* More than one object, use LCK_WRITE to keep atomicity */ - kind = LCK_WRITE; - DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_new_2); - - meth = tb->common.meth; - for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) { - if (is_not_tuple(CAR(list_val(lst))) - || (arityval(*tuple_val(CAR(list_val(lst)))) - < tb->common.keypos)) { - goto badarg; - } - } - if (lst != NIL) { - goto badarg; - } - for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) { - cret = meth->db_member(tb, TERM_GETKEY(tb,CAR(list_val(lst))), - &lookup_ret); - if ((cret != DB_ERROR_NONE) || (lookup_ret != am_false)) { - ret = am_false; - goto done; - } - } - - for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) { - cret = meth->db_put(tb,CAR(list_val(lst)), 0); - if (cret != DB_ERROR_NONE) - break; - } - goto done; - } - obj = CAR(list_val(BIF_ARG_2)); - } - else { - obj = BIF_ARG_2; + if (BIF_ARG_2 == NIL) { + /* Check that the table exists */ + DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2); + db_unlock(tb, LCK_WRITE_REC); + BIF_RET(am_true); + } if ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL) || + is_internal_magic_ref(BIF_ARG_2)) { + /* Handle list case */ + return ets_insert_2_list_driver(BIF_P, BIF_ARG_1, BIF_ARG_2, 1); + } else if (is_list(BIF_ARG_2)) { + obj = CAR(list_val(BIF_ARG_2)); + } else { + obj = BIF_ARG_2; } - /* Only one object (or NIL) - */ + + /* Only one object */ kind = LCK_WRITE_REC; DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_new_2); - if (BIF_ARG_2 == NIL) { - db_unlock(tb, kind); - BIF_RET(am_true); - } if (is_not_tuple(obj) || (arityval(*tuple_val(obj)) < tb->common.keypos)) { - goto badarg; + db_unlock(tb, kind); + BIF_ERROR(BIF_P, BADARG); } cret = tb->common.meth->db_put(tb, obj, 1); /* key_clash_fail */ -done: db_unlock(tb, kind); + switch (cret) { case DB_ERROR_NONE: BIF_RET(ret); @@ -1531,9 +2020,6 @@ done: default: BIF_ERROR(BIF_P, BADARG); } - badarg: - db_unlock(tb, kind); - BIF_ERROR(BIF_P, BADARG); } /* @@ -1800,6 +2286,8 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2) tb->common.status = status; tb->common.type = status; /* Note, 'type' is *read only* from now on... */ + tb->common.continuation = NULL; + erts_atomic_set_nob(&tb->common.continuation_state, (Sint)NULL); erts_refc_init(&tb->common.fix_count, 0); db_init_lock(tb, status & (DB_FINE_LOCKED|DB_FREQ_READ)); tb->common.keypos = keypos; @@ -2242,7 +2730,7 @@ static void delete_all_objects_continue(Process* p, DbTable* tb) SWord initial_reds = ERTS_BIF_REDS_LEFT(p); SWord reds = initial_reds; - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock) || DB_LOCK_FREE(tb)); + ERTS_LC_ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock)); if ((tb->common.status & (DB_DELETE|DB_BUSY)) != DB_BUSY) return; @@ -4207,7 +4695,7 @@ static SWord free_fixations_locked(Process* p, DbTable *tb) { struct free_fixations_ctx ctx; - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock) || DB_LOCK_FREE(tb)); + ERTS_LC_ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock)); ctx.p = p; ctx.tb = tb; diff --git a/erts/emulator/beam/erl_db.h b/erts/emulator/beam/erl_db.h index c604744687..6327c56625 100644 --- a/erts/emulator/beam/erl_db.h +++ b/erts/emulator/beam/erl_db.h @@ -159,13 +159,15 @@ extern erts_aint_t erts_ets_dbg_force_trap; */ #define ERTS_DB_ALC_MEM_UPDATE_(TAB, FREE_SZ, ALLOC_SZ) \ -do { \ - erts_aint_t sz__ = (((erts_aint_t) (ALLOC_SZ)) \ - - ((erts_aint_t) (FREE_SZ))); \ - ASSERT((TAB)); \ - erts_flxctr_add(&(TAB)->common.counters, \ - ERTS_DB_TABLE_MEM_COUNTER_ID, \ - sz__); \ +do { \ + if ((TAB) != NULL) { \ + erts_aint_t sz__ = (((erts_aint_t) (ALLOC_SZ)) \ + - ((erts_aint_t) (FREE_SZ))); \ + ASSERT((TAB)); \ + erts_flxctr_add(&(TAB)->common.counters, \ + ERTS_DB_TABLE_MEM_COUNTER_ID, \ + sz__); \ + } \ } while (0) #define ERTS_ETS_MISC_MEM_ADD(SZ) \ @@ -310,7 +312,8 @@ erts_db_free(ErtsAlcType_t type, DbTable *tab, void *ptr, Uint size) ASSERT(ptr != 0); ASSERT(size == ERTS_ALC_DBG_BLK_SZ(ptr)); ERTS_DB_ALC_MEM_UPDATE_(tab, size, 0); - ASSERT(((void *) tab) != ptr || + ASSERT(tab == NULL || + ((void *) tab) != ptr || tab->common.counters.is_decentralized || 0 == erts_flxctr_read_centralized(&tab->common.counters, ERTS_DB_TABLE_MEM_COUNTER_ID)); diff --git a/erts/emulator/beam/erl_db_catree.c b/erts/emulator/beam/erl_db_catree.c index d6e2f2c581..6f1acba6a4 100644 --- a/erts/emulator/beam/erl_db_catree.c +++ b/erts/emulator/beam/erl_db_catree.c @@ -160,6 +160,9 @@ db_lookup_dbterm_catree(Process *, DbTable *, Eterm key, Eterm obj, DbUpdateHandle*); static void db_finalize_dbterm_catree(int cret, DbUpdateHandle *); static int db_get_binary_info_catree(Process*, DbTable*, Eterm key, Eterm *ret); +static int db_put_dbterm_catree(DbTable* tbl, + void* obj, + int key_clash_fail); static void split_catree(DbTableCATree *tb, DbTableCATreeNode* ERTS_RESTRICT base, @@ -213,6 +216,12 @@ DbTableMethod db_catree = db_foreach_offheap_catree, db_lookup_dbterm_catree, db_finalize_dbterm_catree, + db_eterm_to_dbterm_tree_common, + db_dbterm_list_prepend_tree_common, + db_dbterm_list_remove_first_tree_common, + db_put_dbterm_catree, + db_free_dbterm_tree_common, + db_get_dbterm_key_tree_common, db_get_binary_info_catree, db_first_catree, /* raw_first same as first */ db_next_catree /* raw_next same as next */ @@ -1632,6 +1641,24 @@ static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret) return result; } +static int db_put_dbterm_catree(DbTable* tbl, + void* obj, + int key_clash_fail) +{ + TreeDbTerm *value_to_insert = obj; + DbTableCATree *tb = &tbl->catree; + Eterm key = GETKEY(tb, value_to_insert->dbterm.tpl); + FindBaseNode fbn; + DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn); + int result = db_put_dbterm_tree_common(&tb->common, + &node->u.base.root, + value_to_insert, + key_clash_fail, + NULL); + wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level); + return result; +} + static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail) { DbTableCATree *tb = &tbl->catree; diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c index b88e890b52..7ad3fe88fe 100644 --- a/erts/emulator/beam/erl_db_hash.c +++ b/erts/emulator/beam/erl_db_hash.c @@ -531,6 +531,14 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj, DbUpdateHandle* handle); static void db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle); +static void* db_eterm_to_dbterm_hash(int compress, int keypos, Eterm obj); +static void* db_dbterm_list_prepend_hash(void* list, void* db_term); +static void* db_dbterm_list_remove_first_hash(void** list); +static int db_put_dbterm_hash(DbTable* tb, + void* obj, + int key_clash_fail); +static void db_free_dbterm_hash(int compressed, void* obj); +static Eterm db_get_dbterm_key_hash(DbTable* tb, void* db_term); static int db_get_binary_info_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret); @@ -572,28 +580,55 @@ static ERTS_INLINE int has_key(DbTableHash* tb, HashDbTerm* b, } } -static ERTS_INLINE HashDbTerm* new_dbterm(DbTableHash* tb, Eterm obj) +static ERTS_INLINE HashDbTerm* new_dbterm_hash(DbTableCommon* tb, Eterm obj) { HashDbTerm* p; - if (tb->common.compress) { - p = db_store_term_comp(&tb->common, NULL, offsetof(HashDbTerm,dbterm), obj); + if (tb->compress) { + p = db_store_term_comp(tb, tb->keypos, NULL, offsetof(HashDbTerm,dbterm), obj); } else { - p = db_store_term(&tb->common, NULL, offsetof(HashDbTerm,dbterm), obj); + p = db_store_term(tb, NULL, offsetof(HashDbTerm,dbterm), obj); + } + return p; +} + +/* + * This function only differ from new_dbterm_hash in that it does not + * adjust the memory size of a given table. + */ +static ERTS_INLINE HashDbTerm* new_dbterm_hash_no_tab(int compress, int keypos, Eterm obj) +{ + HashDbTerm* p; + if (compress) { + p = db_store_term_comp(NULL, keypos, NULL, offsetof(HashDbTerm,dbterm), obj); + } else { + p = db_store_term(NULL, NULL, offsetof(HashDbTerm,dbterm), obj); } return p; } +static ERTS_INLINE HashDbTerm* new_dbterm(DbTableHash* tb, Eterm obj) +{ + return new_dbterm_hash(&tb->common, obj); +} + static ERTS_INLINE HashDbTerm* replace_dbterm(DbTableHash* tb, HashDbTerm* old, Eterm obj) { HashDbTerm* ret; ASSERT(old != NULL); if (tb->common.compress) { - ret = db_store_term_comp(&tb->common, &(old->dbterm), offsetof(HashDbTerm,dbterm), obj); + ret = db_store_term_comp(&tb->common, + tb->common.keypos, + &(old->dbterm), + offsetof(HashDbTerm,dbterm), + obj); } else { - ret = db_store_term(&tb->common, &(old->dbterm), offsetof(HashDbTerm,dbterm), obj); + ret = db_store_term(&tb->common, + &(old->dbterm), + offsetof(HashDbTerm,dbterm), + obj); } return ret; } @@ -635,6 +670,12 @@ DbTableMethod db_hash = db_foreach_offheap_hash, db_lookup_dbterm_hash, db_finalize_dbterm_hash, + db_eterm_to_dbterm_hash, + db_dbterm_list_prepend_hash, + db_dbterm_list_remove_first_hash, + db_put_dbterm_hash, + db_free_dbterm_hash, + db_get_dbterm_key_hash, db_get_binary_info_hash, db_raw_first_hash, db_raw_next_hash @@ -858,6 +899,158 @@ static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret) return DB_ERROR_NONE; } +static int db_eq_terms_comp(DbTableCommon* tb, DbTerm* a, DbTerm* b) +{ + ErlOffHeap tmp_offheap_a; + Eterm* allocp_a; + Eterm* hp_a; + Eterm tmp_a; + ErlOffHeap tmp_offheap_b; + Eterm* allocp_b; + Eterm* hp_b; + Eterm tmp_b; + int is_eq; + + ASSERT(tb->compress); + hp_a = allocp_a = erts_alloc(ERTS_ALC_T_TMP, b->size*sizeof(Eterm)); + tmp_offheap_a.first = NULL; + tmp_a = db_copy_from_comp(tb, a, &hp_a, &tmp_offheap_a); + + hp_b = allocp_b = erts_alloc(ERTS_ALC_T_TMP, b->size*sizeof(Eterm)); + tmp_offheap_b.first = NULL; + tmp_b = db_copy_from_comp(tb, b, &hp_b, &tmp_offheap_b); + + is_eq = eq(tmp_a,tmp_b); + erts_cleanup_offheap(&tmp_offheap_a); + erts_free(ERTS_ALC_T_TMP, allocp_a); + erts_cleanup_offheap(&tmp_offheap_b); + erts_free(ERTS_ALC_T_TMP, allocp_b); + return is_eq; +} + +static ERTS_INLINE int db_terms_eq(DbTableCommon* tb, DbTerm* a, DbTerm* b) +{ + if (!tb->compress) { + return EQ(make_tuple(a->tpl), make_tuple(b->tpl)); + } + else { + return db_eq_terms_comp(tb, a, b); + } +} + +static int db_put_dbterm_hash(DbTable* tbl, + void* ob, + int key_clash_fail) +{ + DbTableHash *tb = &tbl->hash; + HashValue hval; + int ix; + Eterm key; + HashDbTerm** bp; + HashDbTerm* b; + HashDbTerm* q; + DbTableHashLockAndCounter* lck_ctr; + int nitems; + int ret = DB_ERROR_NONE; + HashDbTerm *value_to_insert = ob; + Uint size_to_insert = db_term_size(tbl, value_to_insert, offsetof(HashDbTerm, dbterm)); + ERTS_DB_ALC_MEM_UPDATE_(tbl, 0, size_to_insert); + key = GETKEY(tb, value_to_insert->dbterm.tpl); + hval = MAKE_HASH(key); + value_to_insert->hvalue = hval; + lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb, hval); + ix = hash_to_ix(tb, hval); + bp = &BUCKET(tb, ix); + b = *bp; + + for (;;) { + if (b == NULL) { + goto Lnew; + } + if (has_key(tb,b,key,hval)) { + break; + } + bp = &b->next; + b = b->next; + } + /* Key found + */ + if (tb->common.status & DB_SET) { + HashDbTerm* bnext = b->next; + if (is_pseudo_deleted(b)) { + INC_NITEMS(tb, lck_ctr, hval); + b->pseudo_deleted = 0; + } + else if (key_clash_fail) { + ret = DB_ERROR_BADKEY; + goto Ldone; + } + value_to_insert->pseudo_deleted = b->pseudo_deleted; + free_term(tb, b); + q = value_to_insert; + q->next = bnext; + ASSERT(q->hvalue == hval); + *bp = q; + goto Ldone; + } + else if (key_clash_fail) { /* && (DB_BAG || DB_DUPLICATE_BAG) */ + q = b; + do { + if (!is_pseudo_deleted(q)) { + ret = DB_ERROR_BADKEY; + goto Ldone; + } + q = q->next; + }while (q != NULL && has_key(tb,q,key,hval)); + } + else if (tb->common.status & DB_BAG) { + HashDbTerm** qp = bp; + q = b; + do { + if (db_terms_eq(&tb->common, + &value_to_insert->dbterm, + &q->dbterm)) { + if (is_pseudo_deleted(q)) { + INC_NITEMS(tb, lck_ctr, hval); + q->pseudo_deleted = 0; + ASSERT(q->hvalue == hval); + if (q != b) { /* must move to preserve key insertion order */ + *qp = q->next; + q->next = b; + *bp = q; + } + } + free_term(tb, value_to_insert); + goto Ldone; + } + qp = &q->next; + q = *qp; + }while (q != NULL && has_key(tb,q,key,hval)); + } + /*else DB_DUPLICATE_BAG */ + +Lnew: + q = value_to_insert; + q->hvalue = hval; + q->pseudo_deleted = 0; + q->next = b; + *bp = q; + INC_NITEMS(tb, lck_ctr, hval); + nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval); + WUNLOCK_HASH_LCK_CTR(lck_ctr); + { + int nactive = NACTIVE(tb); + if (nitems > GROW_LIMIT(nactive) && !IS_FIXED(tb)) { + grow(tb, nitems); + } + } + return DB_ERROR_NONE; + +Ldone: + WUNLOCK_HASH_LCK_CTR(lck_ctr); + return ret; +} + int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail) { DbTableHash *tb = &tbl->hash; @@ -3651,6 +3844,49 @@ static int db_raw_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret) return DB_ERROR_NONE; } +static void* db_eterm_to_dbterm_hash(int compress, int keypos, Eterm obj) +{ + HashDbTerm* term = new_dbterm_hash_no_tab(compress, keypos, obj); + term->next = NULL; + return term; +} + +static void* db_dbterm_list_prepend_hash(void* list, void* db_term) +{ + HashDbTerm* l = list; + HashDbTerm* t = db_term; + t->next = l; + return t; +} + +static void* db_dbterm_list_remove_first_hash(void** list) +{ + if (*list == NULL) { + return NULL; + } else { + HashDbTerm* t = (*list); + HashDbTerm* l = t->next; + *list = l; + return t; + } +} + +/* + * Frees a HashDbTerm without updating the memory footprint of the + * table. + */ +static void db_free_dbterm_hash(int compressed, void* obj) +{ + HashDbTerm* p = obj; + db_free_term_no_tab(compressed, p, offsetof(HashDbTerm, dbterm)); +} + +static Eterm db_get_dbterm_key_hash(DbTable* tb, void* db_term) +{ + HashDbTerm *value_to_insert = db_term; + return GETKEY(tb, value_to_insert->dbterm.tpl); +} + /* For testing only */ Eterm erts_ets_hash_sizeof_ext_segtab(void) { diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c index 9f50733892..3ba9f7f86c 100644 --- a/erts/emulator/beam/erl_db_tree.c +++ b/erts/emulator/beam/erl_db_tree.c @@ -142,20 +142,33 @@ static ERTS_INLINE TreeDbTerm* new_dbterm(DbTableCommon *tb, Eterm obj) { TreeDbTerm* p; if (tb->compress) { - p = db_store_term_comp(tb, NULL, offsetof(TreeDbTerm,dbterm), obj); + p = db_store_term_comp(tb, tb->keypos, NULL, offsetof(TreeDbTerm,dbterm), obj); } else { p = db_store_term(tb, NULL, offsetof(TreeDbTerm,dbterm), obj); } return p; } + +static ERTS_INLINE TreeDbTerm* new_dbterm_no_tab(int compress, int keypos, Eterm obj) +{ + TreeDbTerm* p; + if (compress) { + p = db_store_term_comp(NULL, keypos, NULL, offsetof(TreeDbTerm,dbterm), obj); + } + else { + p = db_store_term(NULL, NULL, offsetof(TreeDbTerm,dbterm), obj); + } + return p; +} + static ERTS_INLINE TreeDbTerm* replace_dbterm(DbTableCommon *tb, TreeDbTerm* old, Eterm obj) { TreeDbTerm* p; ASSERT(old != NULL); if (tb->compress) { - p = db_store_term_comp(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj); + p = db_store_term_comp(tb, tb->keypos, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj); } else { p = db_store_term(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj); @@ -458,9 +471,10 @@ db_lookup_dbterm_tree(Process *, DbTable *, Eterm key, Eterm obj, DbUpdateHandle*); static void db_finalize_dbterm_tree(int cret, DbUpdateHandle *); - static int db_get_binary_info_tree(Process*, DbTable*, Eterm key, Eterm *ret); - +static int db_put_dbterm_tree(DbTable* tbl, /* [in out] */ + void* obj, + int key_clash_fail); /* ** Static variables @@ -503,6 +517,12 @@ DbTableMethod db_tree = db_foreach_offheap_tree, db_lookup_dbterm_tree, db_finalize_dbterm_tree, + db_eterm_to_dbterm_tree_common, + db_dbterm_list_prepend_tree_common, + db_dbterm_list_remove_first_tree_common, + db_put_dbterm_tree, + db_free_dbterm_tree_common, + db_get_dbterm_key_tree_common, db_get_binary_info_tree, db_first_tree, /* raw_first same as first */ db_next_tree /* raw_next same as next */ @@ -660,6 +680,138 @@ static ERTS_INLINE int cmp_key_eq(DbTableCommon* tb, Eterm key, TreeDbTerm* obj) return is_same(key, obj_key) || CMP(key, obj_key) == 0; } +/* + * This function differ to db_put_tree_common in that it inserts a TreeDbTerm + * instead of an Eterm. + */ +int db_put_dbterm_tree_common(DbTableCommon *tb, + TreeDbTerm **root, + TreeDbTerm *value_to_insert, + int key_clash_fail, + DbTableTree *stack_container) +{ + /* Non recursive insertion in AVL tree, building our own stack */ + TreeDbTerm **tstack[STACK_NEED]; + int tpos = 0; + int dstack[STACK_NEED+1]; + int dpos = 0; + int state = 0; + TreeDbTerm **this = root; + Sint c; + Eterm key; + int dir; + TreeDbTerm *p1, *p2, *p; + Uint size_to_insert = db_term_size((DbTable*)tb, value_to_insert, offsetof(TreeDbTerm, dbterm)); + ERTS_DB_ALC_MEM_UPDATE_((DbTable*)tb, 0, size_to_insert); + key = GETKEY(tb, value_to_insert->dbterm.tpl); + + reset_static_stack(stack_container); + + dstack[dpos++] = DIR_END; + for (;;) + if (!*this) { /* Found our place */ + state = 1; + INC_NITEMS(((DbTable*)tb)); + *this = value_to_insert; + (*this)->balance = 0; + (*this)->left = (*this)->right = NULL; + break; + } else if ((c = cmp_key(tb, key, *this)) < 0) { + /* go lefts */ + dstack[dpos++] = DIR_LEFT; + tstack[tpos++] = this; + this = &((*this)->left); + } else if (c > 0) { /* go right */ + dstack[dpos++] = DIR_RIGHT; + tstack[tpos++] = this; + this = &((*this)->right); + } else if (!key_clash_fail) { /* Equal key and this is a set, replace. */ + value_to_insert->balance = (*this)->balance; + value_to_insert->left = (*this)->left; + value_to_insert->right = (*this)->right; + free_term((DbTable*)tb, *this); + *this = value_to_insert; + break; + } else { + return DB_ERROR_BADKEY; /* key already exists */ + } + + while (state && ( dir = dstack[--dpos] ) != DIR_END) { + this = tstack[--tpos]; + p = *this; + if (dir == DIR_LEFT) { + switch (p->balance) { + case 1: + p->balance = 0; + state = 0; + break; + case 0: + p->balance = -1; + break; + case -1: /* The icky case */ + p1 = p->left; + if (p1->balance == -1) { /* Single LL rotation */ + p->left = p1->right; + p1->right = p; + p->balance = 0; + (*this) = p1; + } else { /* Double RR rotation */ + p2 = p1->right; + p1->right = p2->left; + p2->left = p1; + p->left = p2->right; + p2->right = p; + p->balance = (p2->balance == -1) ? +1 : 0; + p1->balance = (p2->balance == 1) ? -1 : 0; + (*this) = p2; + } + (*this)->balance = 0; + state = 0; + break; + } + } else { /* dir == DIR_RIGHT */ + switch (p->balance) { + case -1: + p->balance = 0; + state = 0; + break; + case 0: + p->balance = 1; + break; + case 1: + p1 = p->right; + if (p1->balance == 1) { /* Single RR rotation */ + p->right = p1->left; + p1->left = p; + p->balance = 0; + (*this) = p1; + } else { /* Double RL rotation */ + p2 = p1->left; + p1->left = p2->right; + p2->right = p1; + p->right = p2->left; + p2->left = p; + p->balance = (p2->balance == 1) ? -1 : 0; + p1->balance = (p2->balance == -1) ? 1 : 0; + (*this) = p2; + } + (*this)->balance = 0; + state = 0; + break; + } + } + } + return DB_ERROR_NONE; +} + +static int db_put_dbterm_tree(DbTable* tbl, /* [in out] */ + void* obj, + int key_clash_fail) /* DB_ERROR_BADKEY if key exists */ +{ + DbTableTree *tb = &tbl->tree; + return db_put_dbterm_tree_common(&tb->common, &tb->root, obj, key_clash_fail, tb); +} + int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj, int key_clash_fail, DbTableTree *stack_container) { @@ -3351,6 +3503,50 @@ Eterm db_binary_info_tree_common(Process* p, TreeDbTerm* this) } +void* db_eterm_to_dbterm_tree_common(int compress, int keypos, Eterm obj) +{ + TreeDbTerm* term = new_dbterm_no_tab(compress, keypos, obj); + term->left = NULL; + term->right = NULL; + return term; +} + +void* db_dbterm_list_prepend_tree_common(void *list, void *db_term) +{ + TreeDbTerm* l = list; + TreeDbTerm* t = db_term; + t->left = l; + return t; +} + +void* db_dbterm_list_remove_first_tree_common(void **list) +{ + if (*list == NULL) { + return NULL; + } else { + TreeDbTerm* t = (*list); + TreeDbTerm* l = t->left; + *list = l; + return t; + } +} + +/* + * Frees a TreeDbTerm without updating the memory footprint of the + * table. + */ +void db_free_dbterm_tree_common(int compressed, void* obj) +{ + TreeDbTerm* p = obj; + db_free_term_no_tab(compressed, p, offsetof(TreeDbTerm, dbterm)); +} + +Eterm db_get_dbterm_key_tree_common(DbTable* tb, void* db_term) +{ + TreeDbTerm *term = db_term; + return GETKEY(tb, term->dbterm.tpl); +} + /* * Traverse the tree with a callback function, used by db_match_xxx */ diff --git a/erts/emulator/beam/erl_db_tree_util.h b/erts/emulator/beam/erl_db_tree_util.h index 14afbd56f7..4d94b142c7 100644 --- a/erts/emulator/beam/erl_db_tree_util.h +++ b/erts/emulator/beam/erl_db_tree_util.h @@ -169,6 +169,13 @@ int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root, DbTableTree *stack_container); void db_finalize_dbterm_tree_common(int cret, DbUpdateHandle *handle, DbTableTree *stack_container); +void* db_eterm_to_dbterm_tree_common(int compress, int keypos, Eterm obj); +void* db_dbterm_list_prepend_tree_common(void* list, void* db_term); +void* db_dbterm_list_remove_first_tree_common(void **list); +int db_put_dbterm_tree_common(DbTableCommon *tb, TreeDbTerm **root, TreeDbTerm *value_to_insert, + int key_clash_fail, DbTableTree *stack_container); +void db_free_dbterm_tree_common(int compressed, void* obj); +Eterm db_get_dbterm_key_tree_common(DbTable* tb, void* db_term); Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key); TreeDbTerm *db_find_tree_node_common(DbTableCommon*, TreeDbTerm *root, diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c index 096cb8a778..e70a98af1a 100644 --- a/erts/emulator/beam/erl_db_util.c +++ b/erts/emulator/beam/erl_db_util.c @@ -3005,6 +3005,34 @@ void db_free_term(DbTable *tb, void* basep, Uint offset) erts_db_free(ERTS_ALC_T_DB_TERM, tb, basep, size); } +Uint db_term_size(DbTable *tb, void* basep, Uint offset) +{ + DbTerm* db = (DbTerm*) ((byte*)basep + offset); + if (tb->common.compress) { + return db_alloced_size_comp(db); + } + else { + return offset + offsetof(DbTerm,tpl) + db->size*sizeof(Eterm); + } +} + +void db_free_term_no_tab(int compress, void* basep, Uint offset) +{ + DbTerm* db = (DbTerm*) ((byte*)basep + offset); + Uint size; + if (compress) { + db_cleanup_offheap_comp(db); + size = db_alloced_size_comp(db); + } + else { + ErlOffHeap tmp_oh; + tmp_oh.first = db->first_oh; + erts_cleanup_offheap(&tmp_oh); + size = offset + offsetof(DbTerm,tpl) + db->size*sizeof(Eterm); + } + erts_db_free(ERTS_ALC_T_DB_TERM, NULL, basep, size); +} + static ERTS_INLINE Uint align_up(Uint value, Uint pow2) { ASSERT((pow2 & (pow2-1)) == 0); @@ -3013,7 +3041,7 @@ static ERTS_INLINE Uint align_up(Uint value, Uint pow2) /* Compressed size of an uncompressed term */ -static Uint db_size_dbterm_comp(DbTableCommon* tb, Eterm obj) +static Uint db_size_dbterm_comp(int keypos, Eterm obj) { Eterm* tpl = tuple_val(obj); int i; @@ -3022,11 +3050,11 @@ static Uint db_size_dbterm_comp(DbTableCommon* tb, Eterm obj) + sizeof(Uint); /* "alloc_size" */ for (i = arityval(*tpl); i>0; i--) { - if (i != tb->keypos && is_not_immed(tpl[i])) { + if (i != keypos && is_not_immed(tpl[i])) { size += erts_encode_ext_size_ets(tpl[i]); } } - size += size_object(tpl[tb->keypos]) * sizeof(Eterm); + size += size_object(tpl[keypos]) * sizeof(Eterm); return align_up(size, sizeof(Uint)); } @@ -3042,13 +3070,13 @@ static ERTS_INLINE byte* elem2ext(Eterm* tpl, Uint ix) return (byte*)tpl + (tpl[ix] >> _TAG_PRIMARY_SIZE); } -static void* copy_to_comp(DbTableCommon* tb, Eterm obj, DbTerm* dest, +static void* copy_to_comp(int keypos, Eterm obj, DbTerm* dest, Uint alloc_size) { ErlOffHeap tmp_offheap; Eterm* src = tuple_val(obj); Eterm* tpl = dest->tpl; - Eterm key = src[tb->keypos]; + Eterm key = src[keypos]; int arity = arityval(src[0]); union { Eterm* ep; @@ -3062,10 +3090,10 @@ static void* copy_to_comp(DbTableCommon* tb, Eterm obj, DbTerm* dest, tpl[arity + 1] = alloc_size; tmp_offheap.first = NULL; - tpl[tb->keypos] = copy_struct(key, size_object(key), &top.ep, &tmp_offheap); + tpl[keypos] = copy_struct(key, size_object(key), &top.ep, &tmp_offheap); dest->first_oh = tmp_offheap.first; for (i=1; i<=arity; i++) { - if (i != tb->keypos) { + if (i != keypos) { if (is_immed(src[i])) { tpl[i] = src[i]; } @@ -3137,14 +3165,17 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj) } -void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj) +void* db_store_term_comp(DbTableCommon *tb, /* May be NULL */ + int keypos, + DbTerm* old, + Uint offset,Eterm obj) { - Uint new_sz = offset + db_size_dbterm_comp(tb, obj); + Uint new_sz = offset + db_size_dbterm_comp(keypos, obj); byte* basep; DbTerm* newp; byte* top; - ASSERT(tb->compress); + ASSERT(tb == NULL || tb->compress); if (old != 0) { Uint old_sz = db_alloced_size_comp(old); db_cleanup_offheap_comp(old); @@ -3164,7 +3195,7 @@ void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj) } newp->size = size_object(obj); - top = copy_to_comp(tb, obj, newp, new_sz); + top = copy_to_comp(keypos, obj, newp, new_sz); ASSERT(top <= basep + new_sz); (void)top; /* ToDo: Maybe realloc if ((basep+new_sz) - top) > WASTED_SPACE_LIMIT */ @@ -3179,7 +3210,7 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset) DbTerm* newDbTerm; Uint alloc_sz = offset + (tbl->common.compress ? - db_size_dbterm_comp(&tbl->common, make_tuple(handle->dbterm->tpl)) : + db_size_dbterm_comp(tbl->common.keypos, make_tuple(handle->dbterm->tpl)) : sizeof(DbTerm)+sizeof(Eterm)*(handle->new_size-1)); byte* newp = erts_db_alloc(ERTS_ALC_T_DB_TERM, tbl, alloc_sz); byte* oldp = *(handle->bp); @@ -3195,7 +3226,7 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset) /* make a flat copy */ if (tbl->common.compress) { - copy_to_comp(&tbl->common, make_tuple(handle->dbterm->tpl), + copy_to_comp(tbl->common.keypos, make_tuple(handle->dbterm->tpl), newDbTerm, alloc_sz); db_free_tmp_uncompressed(handle->dbterm); } diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h index 341edfcce9..bff1607a5e 100644 --- a/erts/emulator/beam/erl_db_util.h +++ b/erts/emulator/beam/erl_db_util.h @@ -43,18 +43,19 @@ * checks for negative returns and issues BIF_ERRORS based * upon these values. */ -#define DB_ERROR_NONE 0 /* No error */ -#define DB_ERROR_BADITEM -1 /* The item was malformed ie no - tuple or to small*/ -#define DB_ERROR_BADTABLE -2 /* The Table is inconsisitent */ -#define DB_ERROR_SYSRES -3 /* Out of system resources */ -#define DB_ERROR_BADKEY -4 /* Returned if a key that should - exist does not. */ +#define DB_ERROR_NONE_FALSE 1 /* No error am_false reult */ +#define DB_ERROR_NONE 0 /* No error */ +#define DB_ERROR_BADITEM -1 /* The item was malformed ie no + tuple or to small*/ +#define DB_ERROR_BADTABLE -2 /* The Table is inconsisitent */ +#define DB_ERROR_SYSRES -3 /* Out of system resources */ +#define DB_ERROR_BADKEY -4 /* Returned if a key that should + exist does not. */ #define DB_ERROR_BADPARAM -5 /* Returned if a specified slot does not exist (hash table only) or the state parameter in db_match_object is broken.*/ -#define DB_ERROR_UNSPEC -10 /* Unspecified error */ +#define DB_ERROR_UNSPEC -10 /* Unspecified error */ /*#define DEBUG_CLONE*/ @@ -234,14 +235,19 @@ typedef struct db_table_method ** dbterm was not updated. If the handle was of a new object and cret is ** not DB_ERROR_NONE, the object is removed from the table. */ void (*db_finalize_dbterm)(int cret, DbUpdateHandle* handle); - + void* (*db_eterm_to_dbterm)(int compress, int keypos, Eterm obj); + void* (*db_dbterm_list_prepend)(void* list, void* db_term); + void* (*db_dbterm_list_remove_first)(void** list); + int (*db_put_dbterm)(DbTable* tb, /* [in out] */ + void* obj, + int key_clash_fail); /* DB_ERROR_BADKEY if key exists */ + void (*db_free_dbterm)(int compressed, void* obj); + Eterm (*db_get_dbterm_key)(DbTable* tb, void* db_term); int (*db_get_binary_info)(Process*, DbTable* tb, Eterm key, Eterm* ret); - /* Raw first/next same as first/next but also return pseudo deleted keys. Only internal use by ets:info(_,binary) */ int (*db_raw_first)(Process*, DbTable*, Eterm* ret); int (*db_raw_next)(Process*, DbTable*, Eterm key, Eterm* ret); - } DbTableMethod; typedef struct db_fixation { @@ -312,6 +318,12 @@ typedef struct db_table_common { int keypos; /* defaults to 1 */ int compress; + /* For unfinished operations that needs to be helped */ + void (*continuation)(long *reds_ptr, + void** state, + void* extra_context); /* To help yielded process */ + erts_atomic_t continuation_state; + Binary* continuation_res_bin; #ifdef ETS_DBG_FORCE_TRAP erts_atomic_t dbg_force_trap; /* &1 force enabled, &2 trap this call */ #endif @@ -407,6 +419,7 @@ ERTS_GLB_INLINE int db_eq(DbTableCommon* tb, Eterm a, DbTerm* b) #define DB_READ (DB_PROTECTED|DB_PUBLIC) #define DB_WRITE DB_PUBLIC #define DB_INFO (DB_PROTECTED|DB_PUBLIC|DB_PRIVATE) +#define DB_READ_TBL_STRUCT (DB_PROTECTED|DB_PUBLIC|DB_PRIVATE|DB_BUSY) #define ONLY_WRITER(P,T) (((T)->common.status & (DB_PRIVATE|DB_PROTECTED)) \ && (T)->common.owner == (P)->common.id) @@ -424,8 +437,13 @@ void db_initialize_util(void); Eterm db_getkey(int keypos, Eterm obj); void db_cleanup_offheap_comp(DbTerm* p); void db_free_term(DbTable *tb, void* basep, Uint offset); +void db_free_term_no_tab(int compress, void* basep, Uint offset); +Uint db_term_size(DbTable *tb, void* basep, Uint offset); void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj); -void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj); +void* db_store_term_comp(DbTableCommon *tb, /*May be NULL*/ + int keypos, + DbTerm* old, + Uint offset,Eterm obj); Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p, DbTerm* obj, Uint pos, Eterm** hpp, Uint extra); int db_has_map(Eterm obj); diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 3ac6b84122..3d93896c0f 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -2698,7 +2698,9 @@ ERTS_GLB_INLINE void erts_sched_poke(ErtsSchedulerSleepInfo *ssi); void erts_aux_thread_poke(void); ERTS_GLB_INLINE Uint32 erts_sched_local_random_hash_64_to_32_shift(Uint64 key); ERTS_GLB_INLINE Uint32 erts_sched_local_random(Uint additional_seed); - +#ifdef DEBUG +ERTS_GLB_INLINE float erts_sched_local_random_float(Uint additional_seed); +#endif #if ERTS_GLB_INLINE_INCL_FUNC_DEF @@ -2748,6 +2750,20 @@ Uint32 erts_sched_local_random(Uint additional_seed) return erts_sched_local_random_hash_64_to_32_shift(seed); } +#ifdef DEBUG + +/* + * This function returns a random float between 0.0 and 1.0. + */ +ERTS_GLB_INLINE +float erts_sched_local_random_float(Uint additional_seed) +{ + Uint32 rnd = erts_sched_local_random(additional_seed); + return (float)(((double)rnd)/((double)ERTS_UINT32_MAX)); +} + +#endif /* #ifdef DEBUG */ + #endif /* #if ERTS_GLB_INLINE_INCL_FUNC_DEF */ diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 4350e599d4..2cf4707e23 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -261,6 +261,39 @@ __decl_noreturn void __noreturn erl_assert_error(const char* expr, const char *f #endif /* + * ERTS_GCC_DIAG_ON and ERTS_GCC_DIAG_OFF can be used to temporarly + * disable a gcc or clang warning in a file. + * + * Example: + * GCC_DIAG_OFF(unused-function) + * static int test(){ return 0;} + * GCC_DIAG_ON(unused-function) + * + * These macros were orginally authored by Jonathan Wakely and has + * been modified by Patrick Horgan. + * + * Source: http://dbp-consulting.com/tutorials/SuppressingGCCWarnings.html + * + */ +#if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 +#define ERTS_GCC_DIAG_STR(s) #s +#define ERTS_GCC_DIAG_JOINSTR(x,y) ERTS_GCC_DIAG_STR(x ## y) +# define ERTS_GCC_DIAG_DO_PRAGMA(x) _Pragma (#x) +# define ERTS_GCC_DIAG_PRAGMA(x) ERTS_GCC_DIAG_DO_PRAGMA(GCC diagnostic x) +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 +# define ERTS_GCC_DIAG_OFF(x) ERTS_GCC_DIAG_PRAGMA(push) \ + ERTS_GCC_DIAG_PRAGMA(ignored ERTS_GCC_DIAG_JOINSTR(-W,x)) +# define ERTS_GCC_DIAG_ON(x) ERTS_GCC_DIAG_PRAGMA(pop) +# else +# define ERTS_GCC_DIAG_OFF(x) ERTS_GCC_DIAG_PRAGMA(ignored ERTS_GCC_DIAG_JOINSTR(-W,x)) +# define ERTS_GCC_DIAG_ON(x) ERTS_GCC_DIAG_PRAGMA(warning ERTS_GCC_DIAG_JOINSTR(-W,x)) +# endif +#else +# define ERTS_GCC_DIAG_OFF(x) +# define ERTS_GCC_DIAG_ON(x) +#endif + +/* * Compile time assert * (the actual compiler error msg can be a bit confusing) */ diff --git a/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md index a6faf5e833..e68a57a7ab 100644 --- a/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md +++ b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md @@ -51,3 +51,12 @@ the Erlang/OTP system. The documentation of YCF can be found in `"$ERL_TOP"/erts/lib_src/yielding_c_fun/README.md`. A rendered version of YCF documentation can be found [here](https://github.com/erlang/otp/erts/lib_src/yielding_c_fun/README.md). + +Yielding C Fun in the Erlang Run-time System +------------------------------------------- + +YCF is used to implement yielding in the BIFs for the `ets:insert/2` +and `ets:insert_new/2` functions when these two functions get a list +as their second parameter. The implementation of these two functions +is a good starting point if one wants to use YCF to implement +something else inside ERTS. diff --git a/erts/emulator/sys/unix/sys_uds.c b/erts/emulator/sys/unix/sys_uds.c index c9f73622ba..c3094d20d4 100644 --- a/erts/emulator/sys/unix/sys_uds.c +++ b/erts/emulator/sys/unix/sys_uds.c @@ -23,7 +23,7 @@ #endif #if defined(__sun__) && !defined(_XOPEN_SOURCE) -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 600 #endif #include <limits.h> diff --git a/erts/lib_src/yielding_c_fun/GNUmakefile b/erts/lib_src/yielding_c_fun/GNUmakefile index 93a2e89bc1..1a18214c21 100644 --- a/erts/lib_src/yielding_c_fun/GNUmakefile +++ b/erts/lib_src/yielding_c_fun/GNUmakefile @@ -51,27 +51,31 @@ ifdef USE_GC endif ifdef ADD_SAN - CC = clang + V_CC = clang EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=address -fno-omit-frame-pointer USE_GC_STRING = -use_gc endif ifdef MEM_SAN - CC = clang + V_CC = clang EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=memory -fno-omit-frame-pointer endif ifdef UB_SAN - CC = clang + V_CC = clang EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=undefined -fno-omit-frame-pointer endif YCF_SOURCE_DIR = . -CC = cc +ifndef V_CC + V_CC = cc +endif -LD = $(CC) +ifndef V_LD + V_LD = $(CC) +endif CFLAGS = $(EXTRA_C_FLAGS) @@ -138,19 +142,19 @@ test: $(YCF_EXECUTABLE) test_add_san: make clean && \ - make CC=clang ADD_SAN=1 test + make V_CC=clang V_LD=clang ADD_SAN=1 test test_mem_san: make clean && \ - make CC=clang MEM_SAN=1 test + make V_CC=clang V_LD=clang MEM_SAN=1 test test_ub_san: make clean && \ - make CC=clang UB_SAN=1 test + make V_CC=clang V_LD=clang UB_SAN=1 test test_modern_cc: make clean && \ - make CC=clang MODERN_CC=1 test + make V_CC=clang V_LD=clang MODERN_CC=1 test test_sanitizers: make test_add_san && \ @@ -158,9 +162,9 @@ test_sanitizers: make test_ub_san test_gcc_clang_tcc: - make CC=gcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \ - make CC=clang EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \ - make CC=tcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) + make V_CC=gcc V_LD=gcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \ + make V_CC=clang V_LD=clang EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \ + make V_CC=tcc V_LD=tcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) test_32_bit: make clean && \ diff --git a/erts/lib_src/yielding_c_fun/README.md b/erts/lib_src/yielding_c_fun/README.md index d91b3caaf1..f63475e6a8 100644 --- a/erts/lib_src/yielding_c_fun/README.md +++ b/erts/lib_src/yielding_c_fun/README.md @@ -29,7 +29,7 @@ YCF has been created to make it easier to implement yielding Erlang YCF features that are useful when implementing yielding Erlang NIFs and BIFs: - * YSF automatically generates a destroy function for each yieldable + * YCF automatically generates a destroy function for each yieldable function. The destroy function frees resources that are used by a suspended function. The destroy function is useful when a suspended function needs to abort (e.g., when the Erlang process that invoked diff --git a/erts/lib_src/yielding_c_fun/main_target.mk b/erts/lib_src/yielding_c_fun/main_target.mk index f6a6f15616..ba8a12c6ce 100644 --- a/erts/lib_src/yielding_c_fun/main_target.mk +++ b/erts/lib_src/yielding_c_fun/main_target.mk @@ -20,7 +20,7 @@ YCF_CFLAGS = $(filter-out -Wstrict-prototypes -Wdeclaration-after-statement -Wmi YCF_EXECUTABLE = $(YCF_SOURCE_DIR)/bin/yielding_c_fun.bin $(YCF_EXECUTABLE): $(YCF_OBJECTS) - $(LD) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_OBJECTS) -o $@ + $(V_LD) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_OBJECTS) -o $@ $(YCF_SOURCE_DIR)/%.o: $(YCF_SOURCE_DIR)/%.c $(YCF_HEADERS) - $(CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@ + $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@ |