mirror of https://github.com/grpc/grpc.git
[chttp2] Eliminate grpc_chttp2_stream_map (#33503)
No need for a bespoke type anymore... and a step along the path to C++ification. --------- Co-authored-by: ctiller <ctiller@users.noreply.github.com>pull/33610/head
parent
c0889a4f23
commit
c5bb43ab61
24 changed files with 52 additions and 656 deletions
@ -1,177 +0,0 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/stream_map.h" |
||||
|
||||
#include <stdlib.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map* map, |
||||
size_t initial_capacity) { |
||||
GPR_DEBUG_ASSERT(initial_capacity > 1); |
||||
map->keys = |
||||
static_cast<uint32_t*>(gpr_malloc(sizeof(uint32_t) * initial_capacity)); |
||||
map->values = |
||||
static_cast<void**>(gpr_malloc(sizeof(void*) * initial_capacity)); |
||||
map->count = 0; |
||||
map->free = 0; |
||||
map->capacity = initial_capacity; |
||||
} |
||||
|
||||
void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map* map) { |
||||
gpr_free(map->keys); |
||||
gpr_free(map->values); |
||||
} |
||||
|
||||
static size_t compact(uint32_t* keys, void** values, size_t count) { |
||||
size_t i, out; |
||||
|
||||
for (i = 0, out = 0; i < count; i++) { |
||||
if (values[i]) { |
||||
keys[out] = keys[i]; |
||||
values[out] = values[i]; |
||||
out++; |
||||
} |
||||
} |
||||
|
||||
return out; |
||||
} |
||||
|
||||
void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map* map, uint32_t key, |
||||
void* value) { |
||||
size_t count = map->count; |
||||
size_t capacity = map->capacity; |
||||
uint32_t* keys = map->keys; |
||||
void** values = map->values; |
||||
|
||||
// The first assertion ensures that the table is monotonically increasing.
|
||||
GPR_ASSERT(count == 0 || keys[count - 1] < key); |
||||
GPR_DEBUG_ASSERT(value); |
||||
// Asserting that the key is not already in the map can be a debug assertion.
|
||||
// Why: we're already checking that the map elements are monotonically
|
||||
// increasing. If we re-add a key, i.e. if the key is already present, then
|
||||
// either it is the most recently added key in the map (in which case the
|
||||
// first assertion fails due to key == last_key) or there is a more recently
|
||||
// added (larger) key at the end of the map: in which case the first assertion
|
||||
// still fails due to key < last_key.
|
||||
GPR_DEBUG_ASSERT(grpc_chttp2_stream_map_find(map, key) == nullptr); |
||||
|
||||
if (count == capacity) { |
||||
if (map->free > capacity / 4) { |
||||
count = compact(keys, values, count); |
||||
map->free = 0; |
||||
} else { |
||||
// resize when less than 25% of the table is free, because compaction
|
||||
// won't help much
|
||||
map->capacity = capacity = 2 * capacity; |
||||
map->keys = keys = static_cast<uint32_t*>( |
||||
gpr_realloc(keys, capacity * sizeof(uint32_t))); |
||||
map->values = values = |
||||
static_cast<void**>(gpr_realloc(values, capacity * sizeof(void*))); |
||||
} |
||||
} |
||||
|
||||
keys[count] = key; |
||||
values[count] = value; |
||||
map->count = count + 1; |
||||
} |
||||
|
||||
template <bool strict_find> |
||||
static void** find(grpc_chttp2_stream_map* map, uint32_t key) { |
||||
size_t min_idx = 0; |
||||
size_t max_idx = map->count; |
||||
size_t mid_idx; |
||||
uint32_t* keys = map->keys; |
||||
void** values = map->values; |
||||
uint32_t mid_key; |
||||
|
||||
GPR_DEBUG_ASSERT(!strict_find || max_idx > 0); |
||||
if (!strict_find && max_idx == 0) return nullptr; |
||||
|
||||
while (min_idx < max_idx) { |
||||
// find the midpoint, avoiding overflow
|
||||
mid_idx = min_idx + ((max_idx - min_idx) / 2); |
||||
mid_key = keys[mid_idx]; |
||||
|
||||
if (mid_key < key) { |
||||
min_idx = mid_idx + 1; |
||||
} else if (mid_key > key) { |
||||
max_idx = mid_idx; |
||||
} else // mid_key == key
|
||||
{ |
||||
return &values[mid_idx]; |
||||
} |
||||
} |
||||
|
||||
GPR_DEBUG_ASSERT(!strict_find); |
||||
return nullptr; |
||||
} |
||||
|
||||
void* grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map* map, uint32_t key) { |
||||
void** pvalue = find<true>(map, key); |
||||
GPR_DEBUG_ASSERT(pvalue != nullptr); |
||||
void* out = *pvalue; |
||||
GPR_DEBUG_ASSERT(out != nullptr); |
||||
*pvalue = nullptr; |
||||
map->free++; |
||||
// recognize complete emptyness and ensure we can skip
|
||||
// defragmentation later
|
||||
if (map->free == map->count) { |
||||
map->free = map->count = 0; |
||||
} |
||||
GPR_DEBUG_ASSERT(grpc_chttp2_stream_map_find(map, key) == nullptr); |
||||
return out; |
||||
} |
||||
|
||||
void* grpc_chttp2_stream_map_find(grpc_chttp2_stream_map* map, uint32_t key) { |
||||
void** pvalue = find<false>(map, key); |
||||
return pvalue != nullptr ? *pvalue : nullptr; |
||||
} |
||||
|
||||
size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map* map) { |
||||
return map->count - map->free; |
||||
} |
||||
|
||||
void* grpc_chttp2_stream_map_rand(grpc_chttp2_stream_map* map) { |
||||
if (map->count == map->free) { |
||||
return nullptr; |
||||
} |
||||
if (map->free != 0) { |
||||
map->count = compact(map->keys, map->values, map->count); |
||||
map->free = 0; |
||||
GPR_ASSERT(map->count > 0); |
||||
} |
||||
return map->values[(static_cast<size_t>(rand())) % map->count]; |
||||
} |
||||
|
||||
void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map* map, |
||||
void (*f)(void* user_data, uint32_t key, |
||||
void* value), |
||||
void* user_data) { |
||||
size_t i; |
||||
|
||||
for (i = 0; i < map->count; i++) { |
||||
if (map->values[i]) { |
||||
f(user_data, map->keys[i], map->values[i]); |
||||
} |
||||
} |
||||
} |
@ -1,68 +0,0 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_STREAM_MAP_H |
||||
#define GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_STREAM_MAP_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
// Data structure to map a uint32_t to a data object (represented by a void*)
|
||||
|
||||
// Represented as a sorted array of keys, and a corresponding array of values.
|
||||
// Lookups are performed with binary search.
|
||||
// Adds are restricted to strictly higher keys than previously seen (this is
|
||||
// guaranteed by http2).
|
||||
struct grpc_chttp2_stream_map { |
||||
uint32_t* keys; |
||||
void** values; |
||||
size_t count; |
||||
size_t free; |
||||
size_t capacity; |
||||
}; |
||||
void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map* map, |
||||
size_t initial_capacity); |
||||
void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map* map); |
||||
|
||||
// Add a new key: given http2 semantics, new keys must always be greater than
|
||||
// existing keys - this is asserted
|
||||
void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map* map, uint32_t key, |
||||
void* value); |
||||
|
||||
// Delete an existing key - returns the previous value of the key if it existed,
|
||||
// or NULL otherwise
|
||||
void* grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map* map, uint32_t key); |
||||
|
||||
// Return an existing key, or NULL if it does not exist
|
||||
void* grpc_chttp2_stream_map_find(grpc_chttp2_stream_map* map, uint32_t key); |
||||
|
||||
// Return a random entry
|
||||
void* grpc_chttp2_stream_map_rand(grpc_chttp2_stream_map* map); |
||||
|
||||
// How many (populated) entries are in the stream map?
|
||||
size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map* map); |
||||
|
||||
// Callback on each stream
|
||||
void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map* map, |
||||
void (*f)(void* user_data, uint32_t key, |
||||
void* value), |
||||
void* user_data); |
||||
|
||||
#endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_STREAM_MAP_H
|
@ -1,196 +0,0 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/stream_map.h" |
||||
|
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "test/core/util/test_config.h" |
||||
|
||||
#define LOG_TEST(x) gpr_log(GPR_INFO, "%s", x) |
||||
|
||||
// test creation & destruction
|
||||
static void test_no_op(void) { |
||||
grpc_chttp2_stream_map map; |
||||
|
||||
LOG_TEST("test_no_op"); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 8); |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
// test lookup on an empty map
|
||||
static void test_empty_find(void) { |
||||
grpc_chttp2_stream_map map; |
||||
|
||||
LOG_TEST("test_empty_find"); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 8); |
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(&map, 39128)); |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
// test add & lookup
|
||||
static void test_basic_add_find(uint32_t n) { |
||||
grpc_chttp2_stream_map map; |
||||
uint32_t i; |
||||
size_t got; |
||||
|
||||
LOG_TEST("test_basic_add_find"); |
||||
gpr_log(GPR_INFO, "n = %d", n); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 8); |
||||
ASSERT_EQ(0, grpc_chttp2_stream_map_size(&map)); |
||||
for (i = 1; i <= n; i++) { |
||||
grpc_chttp2_stream_map_add(&map, i, reinterpret_cast<void*>(i)); |
||||
} |
||||
ASSERT_EQ(n, grpc_chttp2_stream_map_size(&map)); |
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(&map, 0)); |
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(&map, n + 1)); |
||||
for (i = 1; i <= n; i++) { |
||||
got = reinterpret_cast<uintptr_t>(grpc_chttp2_stream_map_find(&map, i)); |
||||
ASSERT_EQ(i, got); |
||||
} |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
// verify that for_each gets the right values during test_delete_evens_XXX
|
||||
static void verify_for_each(void* user_data, uint32_t stream_id, void* ptr) { |
||||
uint32_t* for_each_check = static_cast<uint32_t*>(user_data); |
||||
ASSERT_TRUE(ptr); |
||||
ASSERT_EQ(*for_each_check, stream_id); |
||||
*for_each_check += 2; |
||||
} |
||||
|
||||
static void check_delete_evens(grpc_chttp2_stream_map* map, uint32_t n) { |
||||
uint32_t for_each_check = 1; |
||||
uint32_t i; |
||||
size_t got; |
||||
|
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(map, 0)); |
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(map, n + 1)); |
||||
for (i = 1; i <= n; i++) { |
||||
if (i & 1) { |
||||
got = reinterpret_cast<uintptr_t>(grpc_chttp2_stream_map_find(map, i)); |
||||
ASSERT_EQ(i, got); |
||||
} else { |
||||
ASSERT_EQ(nullptr, grpc_chttp2_stream_map_find(map, i)); |
||||
} |
||||
} |
||||
|
||||
grpc_chttp2_stream_map_for_each(map, verify_for_each, &for_each_check); |
||||
if (n & 1) { |
||||
ASSERT_EQ(for_each_check, n + 2); |
||||
} else { |
||||
ASSERT_EQ(for_each_check, n + 1); |
||||
} |
||||
} |
||||
|
||||
// add a bunch of keys, delete the even ones, and make sure the map is
|
||||
// consistent
|
||||
static void test_delete_evens_sweep(uint32_t n) { |
||||
grpc_chttp2_stream_map map; |
||||
uint32_t i; |
||||
|
||||
LOG_TEST("test_delete_evens_sweep"); |
||||
gpr_log(GPR_INFO, "n = %d", n); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 8); |
||||
for (i = 1; i <= n; i++) { |
||||
grpc_chttp2_stream_map_add(&map, i, reinterpret_cast<void*>(i)); |
||||
} |
||||
for (i = 1; i <= n; i++) { |
||||
if ((i & 1) == 0) { |
||||
ASSERT_EQ((void*)(uintptr_t)i, grpc_chttp2_stream_map_delete(&map, i)); |
||||
} |
||||
} |
||||
check_delete_evens(&map, n); |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
// add a bunch of keys, delete the even ones immediately, and make sure the map
|
||||
// is consistent
|
||||
static void test_delete_evens_incremental(uint32_t n) { |
||||
grpc_chttp2_stream_map map; |
||||
uint32_t i; |
||||
|
||||
LOG_TEST("test_delete_evens_incremental"); |
||||
gpr_log(GPR_INFO, "n = %d", n); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 8); |
||||
for (i = 1; i <= n; i++) { |
||||
grpc_chttp2_stream_map_add(&map, i, reinterpret_cast<void*>(i)); |
||||
if ((i & 1) == 0) { |
||||
grpc_chttp2_stream_map_delete(&map, i); |
||||
} |
||||
} |
||||
check_delete_evens(&map, n); |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
// add a bunch of keys, delete old ones after some time, ensure the
|
||||
// backing array does not grow
|
||||
static void test_periodic_compaction(uint32_t n) { |
||||
grpc_chttp2_stream_map map; |
||||
uint32_t i; |
||||
uint32_t del; |
||||
|
||||
LOG_TEST("test_periodic_compaction"); |
||||
gpr_log(GPR_INFO, "n = %d", n); |
||||
|
||||
grpc_chttp2_stream_map_init(&map, 16); |
||||
ASSERT_EQ(map.capacity, 16); |
||||
for (i = 1; i <= n; i++) { |
||||
grpc_chttp2_stream_map_add(&map, i, reinterpret_cast<void*>(i)); |
||||
if (i > 8) { |
||||
del = i - 8; |
||||
ASSERT_EQ((void*)(uintptr_t)del, |
||||
grpc_chttp2_stream_map_delete(&map, del)); |
||||
} |
||||
} |
||||
ASSERT_EQ(map.capacity, 16); |
||||
grpc_chttp2_stream_map_destroy(&map); |
||||
} |
||||
|
||||
TEST(StreamMapTest, MainTest) { |
||||
uint32_t n = 1; |
||||
uint32_t prev = 1; |
||||
uint32_t tmp; |
||||
|
||||
test_no_op(); |
||||
test_empty_find(); |
||||
|
||||
while (n < 100000) { |
||||
test_basic_add_find(n); |
||||
test_delete_evens_sweep(n); |
||||
test_delete_evens_incremental(n); |
||||
test_periodic_compaction(n); |
||||
|
||||
tmp = n; |
||||
n += prev; |
||||
prev = tmp; |
||||
} |
||||
} |
||||
|
||||
int main(int argc, char** argv) { |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue