Skip to content

Commit ea427f3

Browse files
authored
random access set (#361)
move the awslabs/aws-c-common#883 here as private structure since currently the use case is limited, not worth to make it public. Original review and comments can be fount from awslabs/aws-c-common#883. If anyone in the future needs the data structure outside http, he can continue on the PR in common.
1 parent ba08ea3 commit ea427f3

File tree

4 files changed

+470
-0
lines changed

4 files changed

+470
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#ifndef AWS_HTTP_RANDOM_ACCESS_SET_H
2+
#define AWS_HTTP_RANDOM_ACCESS_SET_H
3+
4+
/**
5+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
* SPDX-License-Identifier: Apache-2.0.
7+
*/
8+
9+
#include <aws/common/array_list.h>
10+
#include <aws/common/hash_table.h>
11+
#include <aws/http/http.h>
12+
13+
/* TODO: someday, if you want to use it from other repo, move it to aws-c-common. */
14+
15+
struct aws_random_access_set_impl;
16+
17+
struct aws_random_access_set {
18+
struct aws_random_access_set_impl *impl;
19+
};
20+
21+
AWS_EXTERN_C_BEGIN
22+
23+
/**
24+
* Initialize the set, which support constant time of insert, remove and get random element
25+
* from the data structure.
26+
*
27+
* The underlying hash map will use hash_fn to compute the hash of each element. equals_fn to compute equality of two
28+
* keys.
29+
*
30+
* @param set Pointer of structure to initialize with
31+
* @param allocator Allocator
32+
* @param hash_fn Compute the hash of each element
33+
* @param equals_fn Compute equality of two elements
34+
* @param destroy_element_fn Optional. Called when the element is removed
35+
* @param initial_item_allocation The initial number of item to allocate.
36+
* @return AWS_OP_ERR if any fails to initialize, AWS_OP_SUCCESS on success.
37+
*/
38+
AWS_HTTP_API
39+
int aws_random_access_set_init(
40+
struct aws_random_access_set *set,
41+
struct aws_allocator *allocator,
42+
aws_hash_fn *hash_fn,
43+
aws_hash_callback_eq_fn *equals_fn,
44+
aws_hash_callback_destroy_fn *destroy_element_fn,
45+
size_t initial_item_allocation);
46+
47+
AWS_HTTP_API
48+
void aws_random_access_set_clean_up(struct aws_random_access_set *set);
49+
50+
/**
51+
* Insert the element to the end of the array list. A map from the element to the index of it to the hash table.
52+
*/
53+
AWS_HTTP_API
54+
int aws_random_access_set_add(struct aws_random_access_set *set, const void *element, bool *added);
55+
56+
/**
57+
* Find and remove the element from the table. If the element does not exist, or the table is empty, nothing will
58+
* happen. Switch the element with the end of the arraylist if needed. Remove the end of the arraylist
59+
*/
60+
AWS_HTTP_API
61+
int aws_random_access_set_remove(struct aws_random_access_set *set, const void *element);
62+
63+
/**
64+
* Get the pointer to a random element from the data structure. Fails when the data structure is empty.
65+
*/
66+
AWS_HTTP_API
67+
int aws_random_access_set_random_get_ptr(struct aws_random_access_set *set, void **out);
68+
69+
AWS_HTTP_API
70+
size_t aws_random_access_set_get_size(struct aws_random_access_set *set);
71+
72+
/**
73+
* Check the element exist in the data structure or not.
74+
*/
75+
AWS_HTTP_API
76+
int aws_random_access_set_exist(struct aws_random_access_set *set, const void *element, bool *exist);
77+
78+
AWS_EXTERN_C_END
79+
#endif /* AWS_HTTP_RANDOM_ACCESS_SET_H */

source/random_access_set.c

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
2+
/**
3+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
* SPDX-License-Identifier: Apache-2.0.
5+
*/
6+
7+
#include <aws/common/allocator.h>
8+
#include <aws/common/device_random.h>
9+
#include <aws/http/private/random_access_set.h>
10+
11+
struct aws_random_access_set_impl {
12+
struct aws_allocator *allocator;
13+
struct aws_array_list list; /* Always store the pointer of the element. */
14+
struct aws_hash_table map; /* Map from the element to the index in the array. */
15+
aws_hash_callback_destroy_fn *destroy_element_fn;
16+
};
17+
18+
static void s_impl_destroy(struct aws_random_access_set_impl *impl) {
19+
if (!impl) {
20+
return;
21+
}
22+
aws_array_list_clean_up(&impl->list);
23+
aws_hash_table_clean_up(&impl->map);
24+
aws_mem_release(impl->allocator, impl);
25+
}
26+
27+
static struct aws_random_access_set_impl *s_impl_new(
28+
struct aws_allocator *allocator,
29+
aws_hash_fn *hash_fn,
30+
aws_hash_callback_eq_fn *equals_fn,
31+
aws_hash_callback_destroy_fn *destroy_element_fn,
32+
size_t initial_item_allocation) {
33+
struct aws_random_access_set_impl *impl = aws_mem_calloc(allocator, 1, sizeof(struct aws_random_access_set_impl));
34+
impl->allocator = allocator;
35+
/* Will always store the pointer of the element. */
36+
if (aws_array_list_init_dynamic(&impl->list, allocator, initial_item_allocation, sizeof(void *))) {
37+
s_impl_destroy(impl);
38+
return NULL;
39+
}
40+
41+
if (aws_hash_table_init(
42+
&impl->map, allocator, initial_item_allocation, hash_fn, equals_fn, destroy_element_fn, NULL)) {
43+
s_impl_destroy(impl);
44+
return NULL;
45+
}
46+
impl->destroy_element_fn = destroy_element_fn;
47+
return impl;
48+
}
49+
50+
int aws_random_access_set_init(
51+
struct aws_random_access_set *set,
52+
struct aws_allocator *allocator,
53+
aws_hash_fn *hash_fn,
54+
aws_hash_callback_eq_fn *equals_fn,
55+
aws_hash_callback_destroy_fn *destroy_element_fn,
56+
size_t initial_item_allocation) {
57+
AWS_FATAL_PRECONDITION(set);
58+
AWS_FATAL_PRECONDITION(allocator);
59+
AWS_FATAL_PRECONDITION(hash_fn);
60+
AWS_FATAL_PRECONDITION(equals_fn);
61+
62+
struct aws_random_access_set_impl *impl =
63+
s_impl_new(allocator, hash_fn, equals_fn, destroy_element_fn, initial_item_allocation);
64+
if (!impl) {
65+
return AWS_OP_ERR;
66+
}
67+
set->impl = impl;
68+
return AWS_OP_SUCCESS;
69+
}
70+
71+
void aws_random_access_set_clean_up(struct aws_random_access_set *set) {
72+
if (!set) {
73+
return;
74+
}
75+
s_impl_destroy(set->impl);
76+
}
77+
78+
int aws_random_access_set_add(struct aws_random_access_set *set, const void *element, bool *added) {
79+
AWS_PRECONDITION(set);
80+
AWS_PRECONDITION(element);
81+
AWS_PRECONDITION(added);
82+
bool exist = false;
83+
if (aws_random_access_set_exist(set, element, &exist) || exist) {
84+
*added = false;
85+
return AWS_OP_SUCCESS;
86+
}
87+
/* deep copy the pointer of element to store at the array list */
88+
if (aws_array_list_push_back(&set->impl->list, (void *)&element)) {
89+
goto list_push_error;
90+
}
91+
if (aws_hash_table_put(&set->impl->map, element, (void *)(aws_array_list_length(&set->impl->list) - 1), NULL)) {
92+
goto error;
93+
}
94+
*added = true;
95+
return AWS_OP_SUCCESS;
96+
error:
97+
aws_array_list_pop_back(&set->impl->list);
98+
list_push_error:
99+
*added = false;
100+
return AWS_OP_ERR;
101+
}
102+
103+
int aws_random_access_set_remove(struct aws_random_access_set *set, const void *element) {
104+
AWS_PRECONDITION(set);
105+
AWS_PRECONDITION(element);
106+
size_t current_length = aws_array_list_length(&set->impl->list);
107+
if (current_length == 0) {
108+
/* Nothing to remove */
109+
return AWS_OP_SUCCESS;
110+
}
111+
struct aws_hash_element *find = NULL;
112+
/* find and remove the element from table */
113+
if (aws_hash_table_find(&set->impl->map, element, &find)) {
114+
return AWS_OP_ERR;
115+
}
116+
if (!find) {
117+
/* It's removed already */
118+
return AWS_OP_SUCCESS;
119+
}
120+
121+
size_t index_to_remove = (size_t)find->value;
122+
if (aws_hash_table_remove_element(&set->impl->map, find)) {
123+
return AWS_OP_ERR;
124+
}
125+
/* If assert code failed, we won't be recovered from the failure */
126+
int assert_re = AWS_OP_SUCCESS;
127+
(void)assert_re;
128+
/* Nothing else can fail after here. */
129+
if (index_to_remove != current_length - 1) {
130+
/* It's not the last element, we need to swap it with the end of the list and remove the last element */
131+
void *last_element = NULL;
132+
/* The last element is a pointer of pointer of element. */
133+
assert_re = aws_array_list_get_at_ptr(&set->impl->list, &last_element, current_length - 1);
134+
AWS_ASSERT(assert_re == AWS_OP_SUCCESS);
135+
/* Update the last element index in the table */
136+
struct aws_hash_element *element_to_update = NULL;
137+
assert_re = aws_hash_table_find(&set->impl->map, *(void **)last_element, &element_to_update);
138+
AWS_ASSERT(assert_re == AWS_OP_SUCCESS);
139+
AWS_ASSERT(element_to_update != NULL);
140+
element_to_update->value = (void *)index_to_remove;
141+
/* Swap the last element with the element to remove in the list */
142+
aws_array_list_swap(&set->impl->list, index_to_remove, current_length - 1);
143+
}
144+
/* Remove the current last element from the list */
145+
assert_re = aws_array_list_pop_back(&set->impl->list);
146+
AWS_ASSERT(assert_re == AWS_OP_SUCCESS);
147+
if (set->impl->destroy_element_fn) {
148+
set->impl->destroy_element_fn((void *)element);
149+
}
150+
return AWS_OP_SUCCESS;
151+
}
152+
153+
int aws_random_access_set_random_get_ptr(struct aws_random_access_set *set, void **out) {
154+
AWS_PRECONDITION(set);
155+
AWS_PRECONDITION(out != NULL);
156+
size_t length = aws_array_list_length(&set->impl->list);
157+
if (length == 0) {
158+
return aws_raise_error(AWS_ERROR_LIST_EMPTY);
159+
}
160+
161+
uint64_t random_64_bit_num = 0;
162+
aws_device_random_u64(&random_64_bit_num);
163+
164+
size_t index = (size_t)random_64_bit_num % length;
165+
/* The array list stores the pointer of the element. */
166+
return aws_array_list_get_at(&set->impl->list, (void *)out, index);
167+
}
168+
169+
size_t aws_random_access_set_get_size(struct aws_random_access_set *set) {
170+
return aws_array_list_length(&set->impl->list);
171+
}
172+
173+
int aws_random_access_set_exist(struct aws_random_access_set *set, const void *element, bool *exist) {
174+
AWS_PRECONDITION(set);
175+
AWS_PRECONDITION(element);
176+
AWS_PRECONDITION(exist);
177+
struct aws_hash_element *find = NULL;
178+
int re = aws_hash_table_find(&set->impl->map, element, &find);
179+
*exist = find != NULL;
180+
return re;
181+
}

tests/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,13 @@ add_test_case(test_http_stats_split_across_gather_boundary)
584584
add_test_case(test_http_stats_pipelined)
585585
add_test_case(test_http_stats_multiple_requests_with_gap)
586586

587+
add_test_case(random_access_set_sanitize_test)
588+
add_test_case(random_access_set_insert_test)
589+
add_test_case(random_access_set_get_random_test)
590+
add_test_case(random_access_set_exist_test)
591+
add_test_case(random_access_set_remove_test)
592+
add_test_case(random_access_set_owns_element_test)
593+
587594
set(TEST_BINARY_NAME ${PROJECT_NAME}-tests)
588595

589596
generate_test_driver(${TEST_BINARY_NAME})

0 commit comments

Comments
 (0)