File 4351-Add-persistent_term-put_new.patch of Package erlang

From d5ea4b67bdce4bdd63d506f1a8500c5ee28ef029 Mon Sep 17 00:00:00 2001
From: Benjamin Philip <benjamin.philip495@gmail.com>
Date: Mon, 7 Apr 2025 15:07:09 +0530
Subject: [PATCH] Add persistent_term:put_new

This PR adds the persistent_term:put_new function to conditionally add a
key-value pair only if the key is new.

Closes #9681.
---
 erts/emulator/beam/atom.names                |   1 +
 erts/emulator/beam/bif.tab                   |   2 +-
 erts/emulator/beam/erl_bif_persistent.c      | 347 ++++++++++---------
 erts/emulator/test/persistent_term_SUITE.erl |  41 ++-
 erts/preloaded/src/persistent_term.erl       |  19 +-
 5 files changed, 246 insertions(+), 164 deletions(-)

diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index d3296846b1..53ed97cf63 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -601,6 +601,7 @@ atom protected
 atom protection
 atom ptab_list_continue
 atom public
+atom put_common_trap
 atom queue_size
 atom raw
 atom re
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 95fcb0589f..610961993f 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -834,3 +833,4 @@ bif erl_debugger:peek_xreg/3
 # New in 29.
 #
 ubif erlang:is_integer/3
+bif persistent_term:put_new/2
diff --git a/erts/emulator/beam/erl_bif_persistent.c b/erts/emulator/beam/erl_bif_persistent.c
index 7ff7c53d8a..6f95e1f657 100644
--- a/erts/emulator/beam/erl_bif_persistent.c
+++ b/erts/emulator/beam/erl_bif_persistent.c
@@ -113,11 +113,11 @@ typedef struct {
 } ErtsPersistentTermCpyTableCtx;
 
 typedef enum {
-    PUT2_TRAP_LOCATION_NEW_KEY
-} ErtsPersistentTermPut2TrapLocation;
+    PUT_COMMON_TRAP_LOCATION_NEW_KEY,
+} ErtsPersistentTermPutCommonTrapLocation;
 
 typedef struct {
-    ErtsPersistentTermPut2TrapLocation trap_location;
+    ErtsPersistentTermPutCommonTrapLocation trap_location;
     Eterm key;
     Eterm term;
     Uint entry_index;
@@ -125,7 +125,7 @@ typedef struct {
     Eterm heap[3];
     Eterm tuple;
     ErtsPersistentTermCpyTableCtx cpy_ctx;
-} ErtsPersistentTermPut2Context;
+} ErtsPersistentTermPutCommonContext;
 
 typedef enum {
     ERASE1_TRAP_LOCATION_TMP_COPY,
@@ -148,6 +148,7 @@ typedef struct {
  * Declarations of local functions.
  */
 
+static Eterm put_common(Process* process, Eterm key, Eterm term, Eterm new);
 static HashTable* create_initial_table(void);
 static Uint lookup(HashTable* hash_table, Eterm key, Eterm *bucket);
 static int is_erasable(HashTable* hash_table, Uint idx);
@@ -172,6 +173,8 @@ static int cleanup_trap_data(Binary *bp);
  * Traps
  */
 
+static Export persistent_term_put_common_export;
+static BIF_RETTYPE persistent_term_put_common_trap(BIF_ALIST_3);
 static Export persistent_term_get_all_export;
 static BIF_RETTYPE persistent_term_get_all_trap(BIF_ALIST_2);
 static Export persistent_term_info_export;
@@ -253,6 +256,10 @@ void erts_init_bif_persistent_term(void)
      * Initialize export entry for traps
      */
 
+
+    erts_init_trap_export(&persistent_term_put_common_export,
+			  am_persistent_term, am_put_common_trap, 3,
+			  &persistent_term_put_common_trap);
     erts_init_trap_export(&persistent_term_get_all_export,
 			  am_persistent_term, am_get_all_trap, 2,
 			  &persistent_term_get_all_trap);
@@ -262,10 +269,10 @@ void erts_init_bif_persistent_term(void)
 }
 
 /*
- * Macro used for trapping in persistent_term_put_2 and
- * persistent_term_erase_1
+ * Macro used for trapping in persistent_term_put_2,
+ * persistent_term_put_new_2 and persistent_term_erase_1
  */
-#define TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, TRAP_CODE) \
+#define TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, TRAP_CODE, BIF_PROCESS) \
     do {                                                                \
         ctx->cpy_ctx = (ErtsPersistentTermCpyTableCtx){                 \
             .old_table = OLD_TABLE,                                     \
@@ -279,15 +286,15 @@ void erts_init_bif_persistent_term(void)
         iterations_until_trap -= ctx->cpy_ctx.total_iterations_done;    \
         if (TABLE_DEST == NULL) {                                       \
             ctx->trap_location = LOC_NAME;                              \
-            erts_set_gc_state(BIF_P, 0);                                \
-            BUMP_ALL_REDS(BIF_P);                                       \
+            erts_set_gc_state(BIF_PROCESS, 0);                          \
+            BUMP_ALL_REDS(BIF_PROCESS);                                 \
             TRAP_CODE;                                                  \
         }                                                               \
     } while (0)
 
-static int persistent_term_put_2_ctx_bin_dtor(Binary *context_bin)
+static int persistent_term_put_common_bin_dtor(Binary *context_bin)
 {
-    ErtsPersistentTermPut2Context* ctx = ERTS_MAGIC_BIN_DATA(context_bin);
+    ErtsPersistentTermPutCommonContext* ctx = ERTS_MAGIC_BIN_DATA(context_bin);
     if (ctx->cpy_ctx.new_table != NULL) {
         erts_free(ERTS_ALC_T_PERSISTENT_TERM, ctx->cpy_ctx.new_table);
         release_update_permission(0);
@@ -295,9 +302,9 @@ static int persistent_term_put_2_ctx_bin_dtor(Binary *context_bin)
     return 1;
 }
 /*
- * A linear congruential generator that is used in the debug emulator
- * to trap after a random number of iterations in
- * persistent_term_put_2 and persistent_term_erase_1.
+ * A linear congruential generator that is used in the debug emulator to trap
+ * after a random number of iterations in persistent_term_put_common and
+ * persistent_term_erase_1.
  *
  * https://en.wikipedia.org/wiki/Linear_congruential_generator
  */
@@ -306,140 +313,12 @@ static int persistent_term_put_2_ctx_bin_dtor(Binary *context_bin)
 
 BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
 {
-    static const Uint ITERATIONS_PER_RED = 32;
-    ErtsPersistentTermPut2Context* ctx;
-    Eterm state_mref = THE_NON_VALUE;
-    Eterm old_bucket;
-    long iterations_until_trap;
-    long max_iterations;
-#define PUT_TRAP_CODE                                                   \
-    BIF_TRAP2(BIF_TRAP_EXPORT(BIF_persistent_term_put_2), BIF_P, state_mref, BIF_ARG_2)
-#define TRAPPING_COPY_TABLE_PUT(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME) \
-    TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, PUT_TRAP_CODE)
-
-#ifdef DEBUG
-        (void)ITERATIONS_PER_RED;
-        iterations_until_trap = max_iterations =
-            GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(BIF_P) + (Uint)&ctx);
-#else
-        iterations_until_trap = max_iterations =
-            ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P);
-#endif
-    if (is_internal_magic_ref(BIF_ARG_1) &&
-        (ERTS_MAGIC_BIN_DESTRUCTOR(erts_magic_ref2bin(BIF_ARG_1)) ==
-         persistent_term_put_2_ctx_bin_dtor)) {
-        /* Restore state after a trap */
-        Binary* state_bin;
-        state_mref = BIF_ARG_1;
-        state_bin = erts_magic_ref2bin(state_mref);
-        ctx = ERTS_MAGIC_BIN_DATA(state_bin);
-        ASSERT(BIF_P->flags & F_DISABLE_GC);
-        erts_set_gc_state(BIF_P, 1);
-        ASSERT(ctx->trap_location == PUT2_TRAP_LOCATION_NEW_KEY);
-        goto L_PUT2_TRAP_LOCATION_NEW_KEY;
-    } else {
-        /* Save state in magic bin in case trapping is necessary */
-        Eterm* hp;
-        Binary* state_bin = erts_create_magic_binary(sizeof(ErtsPersistentTermPut2Context),
-                                                     persistent_term_put_2_ctx_bin_dtor);
-        hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE);
-        state_mref = erts_mk_magic_ref(&hp, &MSO(BIF_P), state_bin);
-        ctx = ERTS_MAGIC_BIN_DATA(state_bin);
-        /*
-         * IMPORTANT: The following field is used to detect if
-         * persistent_term_put_2_ctx_bin_dtor needs to free memory
-         */
-        ctx->cpy_ctx.new_table = NULL;
-    }
-
-
-    if (!try_seize_update_permission(BIF_P)) {
-	ERTS_BIF_YIELD2(BIF_TRAP_EXPORT(BIF_persistent_term_put_2),
-                        BIF_P, BIF_ARG_1, BIF_ARG_2);
-    }
-    ctx->hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
-
-    ctx->key = BIF_ARG_1;
-    ctx->term = BIF_ARG_2;
-
-    ctx->entry_index = lookup(ctx->hash_table, ctx->key, &old_bucket);
-
-    ctx->heap[0] = make_arityval(2);
-    ctx->heap[1] = ctx->key;
-    ctx->heap[2] = ctx->term;
-    ctx->tuple = make_tuple(ctx->heap);
-
-    if (is_nil(old_bucket)) {
-        if (MUST_GROW(ctx->hash_table)) {
-            Uint new_size = ctx->hash_table->allocated * 2;
-            TRAPPING_COPY_TABLE_PUT(ctx->hash_table,
-                                    ctx->hash_table,
-                                    new_size,
-                                    ERTS_PERSISTENT_TERM_CPY_NO_REHASH,
-                                    PUT2_TRAP_LOCATION_NEW_KEY);
-            ctx->entry_index = lookup(ctx->hash_table,
-                                      ctx->key,
-                                      &old_bucket);
-        }
-        ctx->hash_table->num_entries++;
-    } else {
-        Eterm old_term;
-
-        ASSERT(is_tuple_arity(old_bucket, 2));
-        old_term = boxed_val(old_bucket)[2];
-
-        if (EQ(ctx->term, old_term)) {
-            /* Same value. No need to update anything. */
-            release_update_permission(0);
-            BIF_RET(am_ok);
-        }
-    }
-
-    {
-        Uint term_size;
-        Uint lit_area_size;
-        ErlOffHeap code_off_heap;
-        ErtsLiteralArea* literal_area;
-        erts_shcopy_t info;
-        Eterm* ptr;
-        /*
-         * Preserve internal sharing in the term by using the
-         * sharing-preserving functions. However, literals must
-         * be copied in case the module holding them are unloaded.
-         */
-        INITIALIZE_SHCOPY(info);
-        info.copy_literals = 1;
-        term_size = copy_shared_calculate(ctx->tuple, &info);
-        ERTS_INIT_OFF_HEAP(&code_off_heap);
-        lit_area_size = ERTS_LITERAL_AREA_ALLOC_SIZE(term_size);
-        literal_area = erts_alloc(ERTS_ALC_T_LITERAL, lit_area_size);
-        ptr = &literal_area->start[0];
-        literal_area->end = ptr + term_size;
-        ctx->tuple = copy_shared_perform(ctx->tuple, term_size, &info, &ptr, &code_off_heap);
-        ASSERT(tuple_val(ctx->tuple) == literal_area->start);
-        literal_area->off_heap = code_off_heap.first;
-        DESTROY_SHCOPY(info);
-        erts_set_literal_tag(&ctx->tuple, literal_area->start, term_size);
-
-        if (ctx->hash_table == (HashTable *) erts_atomic_read_nob(&the_hash_table)) {
-            /* Schedule fast update in active hash table */
-            fast_update_index = ctx->entry_index;
-            fast_update_term = ctx->tuple;
-        }
-        else {
-            /* Do update in copied table */
-            set_bucket(ctx->hash_table, ctx->entry_index, ctx->tuple);
-        }
+    return put_common(BIF_P, BIF_ARG_1, BIF_ARG_2, am_false);
+}
 
-        /*
-         * Now wait thread progress before making update visible to guarantee
-         * consistent view of table&term without memory barrier in every get/1.
-         */
-        erts_schedule_thr_prgr_later_op(table_updater, ctx->hash_table, &thr_prog_op);
-        suspend_updater(BIF_P);
-    }
-    BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
-    ERTS_BIF_YIELD_RETURN(BIF_P, am_ok);
+BIF_RETTYPE persistent_term_put_new_2(BIF_ALIST_2)
+{
+    return put_common(BIF_P, BIF_ARG_1, BIF_ARG_2, am_true);
 }
 
 BIF_RETTYPE persistent_term_get_0(BIF_ALIST_0)
@@ -543,17 +422,17 @@ BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1)
     long iterations_until_trap;
     long max_iterations;
 #ifdef DEBUG
-        (void)ITERATIONS_PER_RED;
-        iterations_until_trap = max_iterations =
-            GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(BIF_P) + (Uint)&ctx);
+    (void)ITERATIONS_PER_RED;
+    iterations_until_trap = max_iterations =
+        GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(BIF_P) + (Uint)&ctx);
 #else
-        iterations_until_trap = max_iterations =
-            ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P);
+    iterations_until_trap = max_iterations =
+        ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P);
 #endif
 #define ERASE_TRAP_CODE                                                 \
         BIF_TRAP1(BIF_TRAP_EXPORT(BIF_persistent_term_erase_1), BIF_P, state_mref);
 #define TRAPPING_COPY_TABLE_ERASE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME) \
-        TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME, ERASE_TRAP_CODE)
+        TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME, ERASE_TRAP_CODE, BIF_P)    
     if (is_internal_magic_ref(BIF_ARG_1) &&
         (ERTS_MAGIC_BIN_DESTRUCTOR(erts_magic_ref2bin(BIF_ARG_1)) ==
          persistent_term_erase_1_ctx_bin_dtor)) {
@@ -586,7 +465,7 @@ BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1)
         ctx->tmp_table = NULL;
     }
     if (!try_seize_update_permission(BIF_P)) {
-	ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_persistent_term_erase_1),
+        ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_persistent_term_erase_1),
                         BIF_P, BIF_ARG_1);
     }
 
@@ -750,6 +629,160 @@ erts_init_persistent_dumping(void)
  * Local functions.
  */
 
+/*
+ * put and put_new helpers.
+ */
+
+static BIF_RETTYPE
+persistent_term_put_common_trap(BIF_ALIST_3)
+{
+    return put_common(BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
+}
+
+static Eterm put_common(Process* c_p, Eterm key, Eterm term, Eterm new)
+{
+    static const Uint ITERATIONS_PER_RED = 32;
+    ErtsPersistentTermPutCommonContext* ctx;
+    Eterm state_mref = THE_NON_VALUE;
+    Eterm old_bucket;
+    long iterations_until_trap;
+    long max_iterations;
+#define PUT_COMMON_TRAP_CODE                                            \
+    BIF_TRAP3(&persistent_term_put_common_export, c_p, state_mref, term, new)
+
+#define TRAPPING_COPY_TABLE_PUT_COMMON(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME) \
+    TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, PUT_COMMON_TRAP_CODE, c_p)
+
+#ifdef DEBUG
+    (void)ITERATIONS_PER_RED;
+    iterations_until_trap = max_iterations =
+        GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(c_p) + (Uint)&ctx);
+#else
+    iterations_until_trap = max_iterations =
+        ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(c_p);
+#endif
+    if (is_internal_magic_ref(key) &&
+        (ERTS_MAGIC_BIN_DESTRUCTOR(erts_magic_ref2bin(key)) ==
+         persistent_term_put_common_bin_dtor)) {
+        /* Restore state after a trap */
+        Binary* state_bin;
+        state_mref = key;
+        state_bin = erts_magic_ref2bin(state_mref);
+        ctx = ERTS_MAGIC_BIN_DATA(state_bin);
+        ASSERT(c_p->flags & F_DISABLE_GC);
+        erts_set_gc_state(c_p, 1);
+        ASSERT(ctx->trap_location == PUT_COMMON_TRAP_LOCATION_NEW_KEY);
+        goto L_PUT_COMMON_TRAP_LOCATION_NEW_KEY;
+    } else {
+        /* Save state in magic bin in case trapping is necessary */
+        Eterm* hp;
+        Binary* state_bin = erts_create_magic_binary(sizeof(ErtsPersistentTermPutCommonContext),
+                                                     persistent_term_put_common_bin_dtor);
+        hp = HAlloc(c_p, ERTS_MAGIC_REF_THING_SIZE);
+        state_mref = erts_mk_magic_ref(&hp, &MSO(c_p), state_bin);
+        ctx = ERTS_MAGIC_BIN_DATA(state_bin);
+        /*
+         * IMPORTANT: The following field is used to detect if
+         * persistent_term_put_common_ctx_bin_dtor needs to free memory
+         */
+        ctx->cpy_ctx.new_table = NULL;
+    }
+
+
+    if (!try_seize_update_permission(c_p)) {
+        ERTS_BIF_YIELD3(&persistent_term_put_common_export, c_p, key, term, new);
+    }
+    ctx->hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+
+    ctx->key = key;
+    ctx->term = term;
+
+    ctx->entry_index = lookup(ctx->hash_table, ctx->key, &old_bucket);
+
+    ctx->heap[0] = make_arityval(2);
+    ctx->heap[1] = ctx->key;
+    ctx->heap[2] = ctx->term;
+    ctx->tuple = make_tuple(ctx->heap);
+
+    if (is_nil(old_bucket)) {
+        if (MUST_GROW(ctx->hash_table)) {
+            Uint new_size = ctx->hash_table->allocated * 2;
+            TRAPPING_COPY_TABLE_PUT_COMMON(ctx->hash_table,
+                                           ctx->hash_table,
+                                           new_size,
+                                           ERTS_PERSISTENT_TERM_CPY_NO_REHASH,
+                                           PUT_COMMON_TRAP_LOCATION_NEW_KEY);
+            ctx->entry_index = lookup(ctx->hash_table,
+                                      ctx->key,
+                                      &old_bucket);
+        }
+        ctx->hash_table->num_entries++;
+    } else {
+        Eterm old_term;
+
+        ASSERT(is_tuple_arity(old_bucket, 2));
+        old_term = boxed_val(old_bucket)[2];
+
+        if (EQ(ctx->term, old_term)) {
+            /* Same value. No need to update anything. */
+            release_update_permission(0);
+            BIF_RET(am_ok);
+        } else if (new == am_true) {
+            /* Different value. Raise badarg. */
+            release_update_permission(0);
+            BIF_ERROR(c_p, BADARG);
+        }
+    }
+
+    {
+        Uint term_size;
+        Uint lit_area_size;
+        ErlOffHeap code_off_heap;
+        ErtsLiteralArea* literal_area;
+        erts_shcopy_t info;
+        Eterm* ptr;
+        /*
+         * Preserve internal sharing in the term by using the
+         * sharing-preserving functions. However, literals must
+         * be copied in case the module holding them are unloaded.
+         */
+        INITIALIZE_SHCOPY(info);
+        info.copy_literals = 1;
+        term_size = copy_shared_calculate(ctx->tuple, &info);
+        ERTS_INIT_OFF_HEAP(&code_off_heap);
+        lit_area_size = ERTS_LITERAL_AREA_ALLOC_SIZE(term_size);
+        literal_area = erts_alloc(ERTS_ALC_T_LITERAL, lit_area_size);
+        ptr = &literal_area->start[0];
+        literal_area->end = ptr + term_size;
+        ctx->tuple = copy_shared_perform(ctx->tuple, term_size, &info, &ptr, &code_off_heap);
+        ASSERT(tuple_val(ctx->tuple) == literal_area->start);
+        literal_area->off_heap = code_off_heap.first;
+        DESTROY_SHCOPY(info);
+        erts_set_literal_tag(&ctx->tuple, literal_area->start, term_size);
+
+        if (ctx->hash_table == (HashTable *) erts_atomic_read_nob(&the_hash_table)) {
+            /* Schedule fast update in active hash table */
+            fast_update_index = ctx->entry_index;
+            fast_update_term = ctx->tuple;
+        }
+        else {
+            /* Do update in copied table */
+            set_bucket(ctx->hash_table, ctx->entry_index, ctx->tuple);
+        }
+
+        /*
+         * Now wait thread progress before making update visible to guarantee
+         * consistent view of table&term without memory barrier in every get/1.
+         */
+        erts_schedule_thr_prgr_later_op(table_updater, ctx->hash_table,
+                                        &thr_prog_op);
+        suspend_updater(c_p);
+    }
+
+    BUMP_REDS(c_p, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
+    ERTS_BIF_YIELD_RETURN(c_p, am_ok);
+}
+
 static HashTable*
 create_initial_table(void)
 {
@@ -817,7 +850,7 @@ do_get_all(Process* c_p, TrapData* trap_data, Eterm res)
     max_iter = ERTS_BIF_REDS_LEFT(c_p);
 #endif
     remaining = trap_data->remaining < max_iter ?
-        trap_data->remaining : max_iter;
+                trap_data->remaining : max_iter;
     trap_data->remaining -= remaining;
 
     copy_data = (struct copy_term *) erts_alloc(ERTS_ALC_T_TMP,
@@ -917,7 +950,7 @@ do_info(Process* c_p, TrapData* trap_data)
             ErtsLiteralArea* area = term_to_area(bucket);
 
             trap_data->memory += sizeof(ErtsLiteralArea) +
-                sizeof(Eterm) * (area->end - area->start - 1);
+                                 sizeof(Eterm) * (area->end - area->start - 1);
 
             remaining--;
         }
@@ -936,7 +969,7 @@ do_info(Process* c_p, TrapData* trap_data)
         Uint hsz = MAP_SZ(2);
 
         memory = sizeof(HashTable) + (trap_data->table->allocated-1) *
-            sizeof(Eterm) + trap_data->memory;
+                                     sizeof(Eterm) + trap_data->memory;
         (void) erts_bld_uint(NULL, &hsz, hash_table->num_entries);
         (void) erts_bld_uint(NULL, &hsz, memory);
         hp = HAlloc(c_p, hsz);
diff --git a/erts/emulator/test/persistent_term_SUITE.erl b/erts/emulator/test/persistent_term_SUITE.erl
index 37fc17e0a5..019a429f6f 100644
--- a/erts/emulator/test/persistent_term_SUITE.erl
+++ b/erts/emulator/test/persistent_term_SUITE.erl
@@ -32,6 +32,7 @@
          off_heap_values/1,keys/1,collisions/1,
          init_restart/1, put_erase_trapping/1,
          killed_while_trapping_put/1,
+         killed_while_trapping_put_new/1,
          killed_while_trapping_erase/1,
          error_info/1,
 	 whole_message/1,
@@ -91,10 +92,18 @@ basic(_Config) ->
     seq(3, Seq, Chk),                                %Same values.
     _ = [begin
              Key = {?MODULE,{key,I}},
+
              true = persistent_term:erase(Key),
              false = persistent_term:erase(Key),
+
              {'EXIT',{badarg,_}} = (catch persistent_term:get(Key)),
-             {not_present,Key} = persistent_term:get(Key, {not_present,Key})
+             {not_present,Key} = persistent_term:get(Key, {not_present,Key}),
+
+             ok = persistent_term:put_new(Key, {value, I}),
+             ok = persistent_term:put_new(Key, {value, I}),
+             {'EXIT',{badarg,_}} = (catch persistent_term:put_new(Key, {new_value, I})),
+             {value, I} = persistent_term:get(Key),
+             true = persistent_term:erase(Key)
          end || I <- Seq],
     [] = [P || {{?MODULE,_},_}=P <- pget(Chk)],
     chk(Chk).
@@ -937,6 +946,21 @@ killed_while_trapping_put(_Config) ->
       10),
     ok.
 
+killed_while_trapping_put_new(_Config) ->
+    repeat(
+      fun() ->
+              NrOfPutsInChild = 10000,
+              Pid =
+                  spawn(fun() ->
+                                do_put_news(NrOfPutsInChild, my_value)
+                        end),
+              timer:sleep(1),
+              erlang:exit(Pid, kill),
+              do_erases(NrOfPutsInChild)
+      end,
+      10),
+    ok.
+
 killed_while_trapping_erase(_Config) ->
     repeat(
       fun() ->
@@ -960,14 +984,20 @@ put_erase_trapping(_Config) ->
     do_erases(NrOfItems),
     ok.
 
-do_puts(0, _) -> ok;
 do_puts(NrOfPuts, ValuePrefix) ->
+    do_puts_iter(NrOfPuts, ValuePrefix, put).
+
+do_put_news(NrOfPuts, ValuePrefix) ->
+    do_puts_iter(NrOfPuts, ValuePrefix, put_new).
+
+do_puts_iter(0, _, _) -> ok;
+do_puts_iter(NrOfPuts, ValuePrefix, PutFun) ->
     Key = {?MODULE, NrOfPuts},
     Value = {ValuePrefix, NrOfPuts},
     erts_debug:set_internal_state(reds_left, rand:uniform(250)),
-    persistent_term:put(Key, Value),
+    erlang:apply(persistent_term, PutFun, [Key, Value]),
     Value = persistent_term:get(Key),
-    do_puts(NrOfPuts - 1, ValuePrefix).
+    do_puts_iter(NrOfPuts - 1, ValuePrefix, PutFun).
 
 do_erases(0) -> ok;
 do_erases(NrOfErases) ->
@@ -987,7 +1017,8 @@ error_info(_Config) ->
     L = [{erase, [{?MODULE,my_key}], [no_fail]},
          {get, [{?MODULE,certainly_not_existing}]},
          {get, [{?MODULE,certainly_not_existing}, default], [no_fail]},
-         {put, 2}                               %Can't fail.
+         {put, 2},                               %Can't fail.
+         {put_new, [{?MODULE,new_key}, default], [no_fail]}
         ],
     do_error_info(L).
 
diff --git a/erts/preloaded/src/persistent_term.erl b/erts/preloaded/src/persistent_term.erl
index 2e5576d61a..bf0f2aebc0 100644
--- a/erts/preloaded/src/persistent_term.erl
+++ b/erts/preloaded/src/persistent_term.erl
@@ -139,7 +139,7 @@ tables are stored as a single persistent term:
 %%
 -module(persistent_term).
 
--export([erase/1,get/0,get/1,get/2,info/0,put/2]).
+-export([erase/1,get/0,get/1,get/2,info/0,put/2, put_new/2]).
 
 -type key() :: term().
 -type value() :: term().
@@ -250,3 +250,9 @@ GC has been initiated when [`put/2`](`put/2`) returns. See
       Value :: value().
 put(_Key, _Value) ->
     erlang:nif_error(undef).
+
+-spec put_new(Key, Value) -> 'ok' when
+      Key :: key(),
+      Value :: value().
+put_new(_Key, _Value) ->
+    erlang:nif_error(undef).
-- 
2.51.0

openSUSE Build Service is sponsored by