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