summaryrefslogtreecommitdiff
path: root/erts
diff options
context:
space:
mode:
authorKjell Winblad <kjellwinblad@gmail.com>2019-08-15 16:43:49 +0200
committerKjell Winblad <kjellwinblad@gmail.com>2019-12-18 10:00:29 +0100
commitd5d89208521cc0d025ed47c6ea8419ec7aadea48 (patch)
tree56189a7a4680d43574232ab0099c097f421cd641 /erts
parenteb26b2465228324272e4a3df6c517d8c55283939 (diff)
downloaderlang-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/.gitignore1
-rw-r--r--erts/configure.in3
-rw-r--r--erts/emulator/Makefile.in25
-rw-r--r--erts/emulator/beam/erl_alloc.types1
-rw-r--r--erts/emulator/beam/erl_db.c720
-rw-r--r--erts/emulator/beam/erl_db.h19
-rw-r--r--erts/emulator/beam/erl_db_catree.c27
-rw-r--r--erts/emulator/beam/erl_db_hash.c248
-rw-r--r--erts/emulator/beam/erl_db_tree.c204
-rw-r--r--erts/emulator/beam/erl_db_tree_util.h7
-rw-r--r--erts/emulator/beam/erl_db_util.c57
-rw-r--r--erts/emulator/beam/erl_db_util.h42
-rw-r--r--erts/emulator/beam/erl_process.h18
-rw-r--r--erts/emulator/beam/sys.h33
-rw-r--r--erts/emulator/internal_doc/AutomaticYieldingOfCCode.md9
-rw-r--r--erts/emulator/sys/unix/sys_uds.c2
-rw-r--r--erts/lib_src/yielding_c_fun/GNUmakefile28
-rw-r--r--erts/lib_src/yielding_c_fun/README.md2
-rw-r--r--erts/lib_src/yielding_c_fun/main_target.mk4
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 $@