Skip to content

Commit 214b3ef

Browse files
committed
Merge pull request #1614 from jgonet/jgonet/ets
Cleanup ETS implementation These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 599ed7b + e48a379 commit 214b3ef

File tree

6 files changed

+349
-530
lines changed

6 files changed

+349
-530
lines changed

src/libAtomVM/ets.c

Lines changed: 56 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "overflow_helpers.h"
2929
#include "term.h"
3030

31+
#define ETS_NO_INDEX SIZE_MAX
32+
#define ETS_ANY_PROCESS -1
33+
3134
#ifndef AVM_NO_SMP
3235
#define SMP_RDLOCK(table) smp_rwlock_rdlock(table->lock)
3336
#define SMP_WRLOCK(table) smp_rwlock_wrlock(table->lock)
@@ -81,51 +84,40 @@ static void ets_add_table(struct Ets *ets, struct EtsTable *ets_table)
8184
synclist_unlock(&ets->ets_tables);
8285
}
8386

84-
static struct EtsTable *ets_get_table_by_ref(struct Ets *ets, uint64_t ref, TableAccessType access_type)
87+
static struct EtsTable *ets_acquire_table(struct Ets *ets, int32_t process_id, term name_or_ref, TableAccessType requested_access_type)
8588
{
86-
struct ListHead *ets_tables_list = synclist_rdlock(&ets->ets_tables);
87-
struct ListHead *item;
88-
struct EtsTable *ret = NULL;
89-
LIST_FOR_EACH (item, ets_tables_list) {
90-
struct EtsTable *table = GET_LIST_ENTRY(item, struct EtsTable, head);
91-
if (table->ref_ticks == ref) {
92-
switch (access_type) {
93-
case TableAccessRead:
94-
SMP_RDLOCK(table);
95-
break;
96-
case TableAccessWrite:
97-
SMP_WRLOCK(table);
98-
break;
99-
default:
100-
break;
101-
}
102-
ret = table;
103-
break;
104-
}
89+
uint64_t ref = 0;
90+
term name = term_invalid_term();
91+
bool is_atom = term_is_atom(name_or_ref);
92+
if (is_atom) {
93+
name = name_or_ref;
94+
} else {
95+
ref = term_to_ref_ticks(name_or_ref);
10596
}
106-
synclist_unlock(&ets->ets_tables);
107-
return ret;
108-
}
10997

110-
static struct EtsTable *ets_get_table_by_name(struct Ets *ets, term name, TableAccessType access_type)
111-
{
98+
struct EtsTable *ret = NULL;
11299
struct ListHead *ets_tables_list = synclist_rdlock(&ets->ets_tables);
113100
struct ListHead *item;
114-
struct EtsTable *ret = NULL;
115101
LIST_FOR_EACH (item, ets_tables_list) {
116102
struct EtsTable *table = GET_LIST_ENTRY(item, struct EtsTable, head);
117-
if (table->is_named && table->name == name) {
118-
switch (access_type) {
119-
case TableAccessRead:
120-
SMP_RDLOCK(table);
121-
break;
122-
case TableAccessWrite:
123-
SMP_WRLOCK(table);
124-
break;
125-
default:
126-
break;
103+
bool found = is_atom ? table->is_named && table->name == name : table->ref_ticks == ref;
104+
if (found) {
105+
bool is_owner = table->owner_process_id == process_id;
106+
bool is_non_private = table->access_type != EtsAccessPrivate;
107+
bool is_public = table->access_type == EtsAccessPublic;
108+
109+
bool can_read = requested_access_type == TableAccessRead && (is_non_private || is_owner);
110+
bool can_write = requested_access_type == TableAccessWrite && (is_public || is_owner);
111+
bool access_none = requested_access_type == TableAccessNone;
112+
if (can_read) {
113+
SMP_RDLOCK(table);
114+
ret = table;
115+
} else if (can_write) {
116+
SMP_WRLOCK(table);
117+
ret = table;
118+
} else if (access_none) {
119+
ret = table;
127120
}
128-
ret = table;
129121
break;
130122
}
131123
}
@@ -147,7 +139,7 @@ void ets_destroy(struct Ets *ets, GlobalContext *global)
147139
EtsErrorCode ets_create_table_maybe_gc(term name, bool is_named, EtsTableType table_type, EtsAccessType access_type, size_t keypos, term *ret, Context *ctx)
148140
{
149141
if (is_named) {
150-
struct EtsTable *ets_table = ets_get_table_by_name(&ctx->global->ets, name, TableAccessNone);
142+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ETS_ANY_PROCESS, name, TableAccessNone);
151143
if (ets_table != NULL) {
152144
return EtsTableNameInUse;
153145
}
@@ -255,10 +247,6 @@ static void ets_delete_all_tables(struct Ets *ets, GlobalContext *global)
255247

256248
static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Context *ctx)
257249
{
258-
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
259-
return EtsPermissionDenied;
260-
}
261-
262250
size_t keypos = ets_table->keypos;
263251

264252
if ((size_t) term_get_tuple_arity(entry) < keypos + 1) {
@@ -270,7 +258,7 @@ static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Con
270258
return EtsAllocationFailure;
271259
}
272260

273-
EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, new_node, EtsHashtableAllowOverwrite, ctx->global);
261+
EtsHashtableStatus res = ets_hashtable_insert(ets_table->hashtable, new_node, EtsHashtableAllowOverwrite, ctx->global);
274262
if (UNLIKELY(res != EtsHashtableOk)) {
275263
return EtsAllocationFailure;
276264
}
@@ -280,10 +268,6 @@ static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Con
280268

281269
static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list, Context *ctx)
282270
{
283-
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
284-
return EtsPermissionDenied;
285-
}
286-
287271
term iter = list;
288272
size_t size = 0;
289273

@@ -309,7 +293,9 @@ static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list,
309293
term tuple = term_get_list_head(list);
310294
nodes[i] = ets_hashtable_new_node(tuple, ets_table->keypos);
311295
if (IS_NULL_PTR(nodes[i])) {
312-
ets_hashtable_free_node_array(nodes, i, ctx->global);
296+
for (size_t it = 0; it < i; ++it) {
297+
ets_hashtable_free_node(nodes[it], ctx->global);
298+
}
313299
free(nodes);
314300
return EtsAllocationFailure;
315301
}
@@ -318,8 +304,7 @@ static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list,
318304
}
319305

320306
for (size_t i = 0; i < size; ++i) {
321-
322-
EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, nodes[i], EtsHashtableAllowOverwrite, ctx->global);
307+
EtsHashtableStatus res = ets_hashtable_insert(ets_table->hashtable, nodes[i], EtsHashtableAllowOverwrite, ctx->global);
323308
assert(res == EtsHashtableOk);
324309
}
325310

@@ -329,9 +314,9 @@ static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list,
329314

330315
EtsErrorCode ets_insert(term name_or_ref, term entry, Context *ctx)
331316
{
332-
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessWrite) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessWrite);
333-
if (ets_table == NULL) {
334-
return EtsTableNotFound;
317+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, name_or_ref, TableAccessWrite);
318+
if (IS_NULL_PTR(ets_table)) {
319+
return EtsBadAccess;
335320
}
336321

337322
EtsErrorCode result;
@@ -350,10 +335,6 @@ EtsErrorCode ets_insert(term name_or_ref, term entry, Context *ctx)
350335

351336
static EtsErrorCode ets_table_lookup_maybe_gc(struct EtsTable *ets_table, term key, term *ret, Context *ctx, int num_roots, term *roots)
352337
{
353-
if (ets_table->access_type == EtsAccessPrivate && ets_table->owner_process_id != ctx->process_id) {
354-
return EtsPermissionDenied;
355-
}
356-
357338
term res = ets_hashtable_lookup(ets_table->hashtable, key, ets_table->keypos, ctx->global);
358339

359340
if (term_is_nil(res)) {
@@ -374,9 +355,9 @@ static EtsErrorCode ets_table_lookup_maybe_gc(struct EtsTable *ets_table, term k
374355

375356
EtsErrorCode ets_lookup_maybe_gc(term name_or_ref, term key, term *ret, Context *ctx)
376357
{
377-
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
378-
if (ets_table == NULL) {
379-
return EtsTableNotFound;
358+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, name_or_ref, TableAccessRead);
359+
if (IS_NULL_PTR(ets_table)) {
360+
return EtsBadAccess;
380361
}
381362

382363
EtsErrorCode result = ets_table_lookup_maybe_gc(ets_table, key, ret, ctx, 0, NULL);
@@ -391,14 +372,9 @@ EtsErrorCode ets_lookup_element_maybe_gc(term name_or_ref, term key, size_t pos,
391372
return EtsBadPosition;
392373
}
393374

394-
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
395-
if (ets_table == NULL) {
396-
return EtsTableNotFound;
397-
}
398-
399-
if (ets_table->access_type == EtsAccessPrivate && ets_table->owner_process_id != ctx->process_id) {
400-
SMP_UNLOCK(ets_table);
401-
return EtsPermissionDenied;
375+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, name_or_ref, TableAccessRead);
376+
if (IS_NULL_PTR(ets_table)) {
377+
return EtsBadAccess;
402378
}
403379

404380
term entry = ets_hashtable_lookup(ets_table->hashtable, key, ets_table->keypos, ctx->global);
@@ -426,29 +402,11 @@ EtsErrorCode ets_lookup_element_maybe_gc(term name_or_ref, term key, size_t pos,
426402
return EtsOk;
427403
}
428404

429-
static EtsErrorCode ets_table_delete(struct EtsTable *ets_table, term key, term *ret, Context *ctx)
430-
{
431-
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
432-
return EtsPermissionDenied;
433-
}
434-
435-
bool _res = ets_hashtable_remove(ets_table->hashtable, key, ets_table->keypos, ctx->global);
436-
UNUSED(_res);
437-
438-
*ret = TRUE_ATOM;
439-
return EtsOk;
440-
}
441-
442405
EtsErrorCode ets_drop_table(term name_or_ref, term *ret, Context *ctx)
443406
{
444-
struct EtsTable *ets_table = term_is_atom(name_or_ref)
445-
? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessWrite)
446-
: ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessWrite);
407+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, name_or_ref, TableAccessWrite);
447408
if (IS_NULL_PTR(ets_table)) {
448-
return EtsTableNotFound;
449-
}
450-
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
451-
return EtsPermissionDenied;
409+
return EtsBadAccess;
452410
}
453411

454412
struct ListHead *ets_tables_list = synclist_wrlock(&ctx->global->ets.ets_tables);
@@ -464,17 +422,17 @@ EtsErrorCode ets_drop_table(term name_or_ref, term *ret, Context *ctx)
464422

465423
EtsErrorCode ets_delete(term name_or_ref, term key, term *ret, Context *ctx)
466424
{
467-
struct EtsTable *ets_table = term_is_atom(name_or_ref)
468-
? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead)
469-
: ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
425+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, name_or_ref, TableAccessWrite);
470426
if (IS_NULL_PTR(ets_table)) {
471-
return EtsTableNotFound;
427+
return EtsBadAccess;
472428
}
473429

474-
EtsErrorCode res = ets_table_delete(ets_table, key, ret, ctx);
475-
430+
bool _found = ets_hashtable_remove(ets_table->hashtable, key, ets_table->keypos, ctx->global);
431+
UNUSED(_found);
476432
SMP_UNLOCK(ets_table);
477-
return res;
433+
434+
*ret = TRUE_ATOM;
435+
return EtsOk;
478436
}
479437

480438
static bool operation_to_tuple4(term operation, size_t default_pos, term *position, term *increment, term *threshold, term *set_value)
@@ -524,11 +482,9 @@ static bool operation_to_tuple4(term operation, size_t default_pos, term *positi
524482

525483
EtsErrorCode ets_update_counter_maybe_gc(term ref, term key, term operation, term default_value, term *ret, Context *ctx)
526484
{
527-
struct EtsTable *ets_table = term_is_atom(ref)
528-
? ets_get_table_by_name(&ctx->global->ets, ref, TableAccessWrite)
529-
: ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(ref), TableAccessWrite);
485+
struct EtsTable *ets_table = ets_acquire_table(&ctx->global->ets, ctx->process_id, ref, TableAccessWrite);
530486
if (IS_NULL_PTR(ets_table)) {
531-
return EtsTableNotFound;
487+
return EtsBadAccess;
532488
}
533489

534490
// do not use an invalid term as a root
@@ -588,7 +544,7 @@ EtsErrorCode ets_update_counter_maybe_gc(term ref, term key, term operation, ter
588544
avm_int_t elem_value;
589545
if (BUILTIN_ADD_OVERFLOW_INT(increment, term_to_int(elem), &elem_value)) {
590546
SMP_UNLOCK(ets_table);
591-
return EtsOverlfow;
547+
return EtsOverflow;
592548
}
593549
if (!term_is_invalid_term(threshold_term) && !term_is_invalid_term(set_value_term)) {
594550
avm_int_t threshold = term_to_int(threshold_term);

src/libAtomVM/ets.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,13 @@ typedef enum EtsAccessType
5151
typedef enum EtsErrorCode
5252
{
5353
EtsOk,
54-
EtsTableNotFound,
54+
EtsBadAccess,
5555
EtsTableNameInUse,
56-
EtsPermissionDenied,
5756
EtsBadEntry,
5857
EtsAllocationFailure,
5958
EtsEntryNotFound,
6059
EtsBadPosition,
61-
EtsOverlfow
60+
EtsOverflow
6261
} EtsErrorCode;
6362
struct Ets
6463
{

src/libAtomVM/ets_hashtable.c

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,12 @@ struct EtsHashTable *ets_hashtable_new()
5353
return htable;
5454
}
5555

56-
static void ets_hashtable_free_node(struct HNode *node, GlobalContext *global)
56+
void ets_hashtable_free_node(struct HNode *node, GlobalContext *global)
5757
{
5858
memory_destroy_heap(&node->heap, global);
5959
free(node);
6060
}
6161

62-
void ets_hashtable_free_node_array(struct HNode **allocated, size_t size, GlobalContext *global)
63-
{
64-
for (size_t i = 0; i < size; ++i) {
65-
ets_hashtable_free_node(allocated[i], global);
66-
}
67-
}
68-
6962
void ets_hashtable_destroy(struct EtsHashTable *hash_table, GlobalContext *global)
7063
{
7164
for (size_t i = 0; i < hash_table->capacity; ++i) {
@@ -96,29 +89,33 @@ static void print_info(struct EtsHashTable *hash_table)
9689

9790
struct HNode *ets_hashtable_new_node(term entry, int keypos)
9891
{
99-
10092
struct HNode *new_node = malloc(sizeof(struct HNode));
10193
if (IS_NULL_PTR(new_node)) {
102-
return NULL;
94+
goto cleanup;
10395
}
10496

105-
size_t size = (size_t) memory_estimate_usage(entry);
106-
if (memory_init_heap(&new_node->heap, size) != MEMORY_GC_OK) {
107-
free(new_node);
108-
return NULL;
97+
size_t size = memory_estimate_usage(entry);
98+
if (UNLIKELY(memory_init_heap(&new_node->heap, size) != MEMORY_GC_OK)) {
99+
goto cleanup;
109100
}
110101

111102
term new_entry = memory_copy_term_tree(&new_node->heap, entry);
103+
assert(term_is_tuple(new_entry));
104+
assert(term_get_tuple_arity(new_entry) >= keypos);
112105
term key = term_get_tuple_element(new_entry, keypos);
113106

114107
new_node->next = NULL;
115108
new_node->key = key;
116109
new_node->entry = new_entry;
117110

118111
return new_node;
112+
113+
cleanup:
114+
free(new_node);
115+
return NULL;
119116
}
120117

121-
EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global)
118+
EtsHashtableStatus ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global)
122119
{
123120
term key = new_node->key;
124121
uint32_t hash = hash_term(key, global);
@@ -133,7 +130,12 @@ EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, stru
133130
struct HNode *node = hash_table->buckets[index];
134131
struct HNode *last_node = NULL;
135132
while (node) {
136-
if (term_compare(key, node->key, TermCompareExact, global) == TermEquals) {
133+
TermCompareResult cmp = term_compare(key, node->key, TermCompareExact, global);
134+
if (UNLIKELY(cmp == TermCompareMemoryAllocFail)) {
135+
return EtsHashtableOutOfMemory;
136+
}
137+
138+
if (cmp == TermEquals) {
137139
if (opts & EtsHashtableAllowOverwrite) {
138140
if (IS_NULL_PTR(last_node)) {
139141
new_node->next = node->next;
@@ -145,8 +147,7 @@ EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, stru
145147
ets_hashtable_free_node(node, global);
146148
return EtsHashtableOk;
147149
} else {
148-
ets_hashtable_free_node(new_node, global);
149-
return EtsHashtableFailure;
150+
return EtsHashtableKeyAlreadyExists;
150151
}
151152
}
152153
last_node = node;
@@ -193,11 +194,8 @@ bool ets_hashtable_remove(struct EtsHashTable *hash_table, term key, size_t keyp
193194
while (node) {
194195
term key_to_compare = term_get_tuple_element(node->entry, keypos);
195196
if (term_compare(key, key_to_compare, TermCompareExact, global) == TermEquals) {
196-
197-
memory_destroy_heap(&node->heap, global);
198197
struct HNode *next_node = node->next;
199-
free(node);
200-
198+
ets_hashtable_free_node(node, global);
201199
if (prev_node != NULL) {
202200
prev_node->next = next_node;
203201
} else {

0 commit comments

Comments
 (0)