blob: ceb77df34d324101431700e996fcf458ec366082 [file] [log] [blame]
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08001/*
2 *
3 * Copyright 2014, Google Inc.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
34#include "src/core/transport/metadata.h"
35
36#include <stddef.h>
37#include <string.h>
38
39#include <grpc/support/alloc.h>
40#include <grpc/support/log.h>
41#include "src/core/support/murmur_hash.h"
42#include <grpc/support/time.h>
43
44#define INITIAL_STRTAB_CAPACITY 4
45#define INITIAL_MDTAB_CAPACITY 4
46
47#define KV_HASH(key, value) ((key)->hash ^ (value)->hash)
48
49typedef struct internal_string {
50 /* must be byte compatible with grpc_mdstr */
51 gpr_slice slice;
52 gpr_uint32 hash;
53
54 /* private only data */
55 gpr_uint32 refs;
56 gpr_slice_refcount refcount;
57
58 grpc_mdctx *context;
59
60 struct internal_string *bucket_next;
61} internal_string;
62
63typedef struct internal_metadata {
64 /* must be byte compatible with grpc_mdelem */
65 internal_string *key;
66 internal_string *value;
67
68 /* private only data */
69 void *user_data;
70 void (*destroy_user_data)(void *user_data);
71
72 gpr_uint32 refs;
73 grpc_mdctx *context;
74 struct internal_metadata *bucket_next;
75} internal_metadata;
76
77struct grpc_mdctx {
78 gpr_uint32 hash_seed;
79 int orphaned;
80
81 gpr_mu mu;
82
83 internal_string **strtab;
84 size_t strtab_count;
85 size_t strtab_capacity;
86
87 internal_metadata **mdtab;
88 size_t mdtab_count;
89 size_t mdtab_free;
90 size_t mdtab_capacity;
91};
92
93static void internal_string_ref(internal_string *s);
94static void internal_string_unref(internal_string *s);
95static void discard_metadata(grpc_mdctx *ctx);
96static void gc_mdtab(grpc_mdctx *ctx);
97static void metadata_context_destroy(grpc_mdctx *ctx);
98
99static void lock(grpc_mdctx *ctx) { gpr_mu_lock(&ctx->mu); }
100
101static void unlock(grpc_mdctx *ctx) {
102 /* If the context has been orphaned we'd like to delete it soon. We check
103 conditions in unlock as it signals the end of mutations on a context.
104
105 We need to ensure all grpc_mdelem and grpc_mdstr elements have been deleted
106 first. This is equivalent to saying that both tables have zero counts,
107 which is equivalent to saying that strtab_count is zero (as mdelem's MUST
108 reference an mdstr for their key and value slots).
109
110 To encourage that to happen, we start discarding zero reference count
111 mdelems on every unlock (instead of the usual 'I'm too loaded' trigger
112 case), since otherwise we can be stuck waiting for a garbage collection
113 that will never happen. */
114 if (ctx->orphaned) {
115 /* uncomment if you're having trouble diagnosing an mdelem leak to make
116 things clearer (slows down destruction a lot, however) */
117 /* gc_mdtab(ctx); */
118 if (ctx->mdtab_count && ctx->mdtab_count == ctx->mdtab_free) {
119 discard_metadata(ctx);
120 }
121 if (ctx->strtab_count == 0) {
122 gpr_mu_unlock(&ctx->mu);
123 metadata_context_destroy(ctx);
124 return;
125 }
126 }
127 gpr_mu_unlock(&ctx->mu);
128}
129
130static void ref_md(internal_metadata *md) {
131 if (0 == md->refs++) {
132 md->context->mdtab_free--;
133 }
134}
135
136grpc_mdctx *grpc_mdctx_create_with_seed(gpr_uint32 seed) {
137 grpc_mdctx *ctx = gpr_malloc(sizeof(grpc_mdctx));
138
139 ctx->orphaned = 0;
140 ctx->hash_seed = seed;
141 gpr_mu_init(&ctx->mu);
142 ctx->strtab = gpr_malloc(sizeof(internal_string *) * INITIAL_STRTAB_CAPACITY);
143 memset(ctx->strtab, 0, sizeof(grpc_mdstr *) * INITIAL_STRTAB_CAPACITY);
144 ctx->strtab_count = 0;
145 ctx->strtab_capacity = INITIAL_STRTAB_CAPACITY;
146 ctx->mdtab = gpr_malloc(sizeof(internal_metadata *) * INITIAL_MDTAB_CAPACITY);
147 memset(ctx->mdtab, 0, sizeof(grpc_mdelem *) * INITIAL_MDTAB_CAPACITY);
148 ctx->mdtab_count = 0;
149 ctx->mdtab_capacity = INITIAL_MDTAB_CAPACITY;
150 ctx->mdtab_free = 0;
151
152 return ctx;
153}
154
155grpc_mdctx *grpc_mdctx_create() {
156 /* This seed is used to prevent remote connections from controlling hash table
157 * collisions. It needs to be somewhat unpredictable to a remote connection.
158 */
159 return grpc_mdctx_create_with_seed(gpr_now().tv_nsec);
160}
161
162static void discard_metadata(grpc_mdctx *ctx) {
163 size_t i;
164 internal_metadata *next, *cur;
165
166 for (i = 0; i < ctx->mdtab_capacity; i++) {
167 cur = ctx->mdtab[i];
168 while (cur) {
169 GPR_ASSERT(cur->refs == 0);
170 next = cur->bucket_next;
171 internal_string_unref(cur->key);
172 internal_string_unref(cur->value);
173 if (cur->user_data) {
174 cur->destroy_user_data(cur->user_data);
175 }
176 gpr_free(cur);
177 cur = next;
178 ctx->mdtab_free--;
179 ctx->mdtab_count--;
180 }
181 ctx->mdtab[i] = NULL;
182 }
183}
184
185static void metadata_context_destroy(grpc_mdctx *ctx) {
186 gpr_mu_lock(&ctx->mu);
187 GPR_ASSERT(ctx->strtab_count == 0);
188 GPR_ASSERT(ctx->mdtab_count == 0);
189 GPR_ASSERT(ctx->mdtab_free == 0);
190 gpr_free(ctx->strtab);
191 gpr_free(ctx->mdtab);
192 gpr_mu_unlock(&ctx->mu);
193 gpr_mu_destroy(&ctx->mu);
194 gpr_free(ctx);
195}
196
197void grpc_mdctx_orphan(grpc_mdctx *ctx) {
198 lock(ctx);
199 GPR_ASSERT(!ctx->orphaned);
200 ctx->orphaned = 1;
201 unlock(ctx);
202}
203
204static void grow_strtab(grpc_mdctx *ctx) {
205 size_t capacity = ctx->strtab_capacity * 2;
206 size_t i;
207 internal_string **strtab = gpr_malloc(sizeof(internal_string *) * capacity);
208 internal_string *s, *next;
209 memset(strtab, 0, sizeof(internal_string *) * capacity);
210
211 for (i = 0; i < ctx->strtab_capacity; i++) {
212 for (s = ctx->strtab[i]; s; s = next) {
213 next = s->bucket_next;
214 s->bucket_next = strtab[s->hash % capacity];
215 strtab[s->hash % capacity] = s;
216 }
217 }
218
219 gpr_free(ctx->strtab);
220 ctx->strtab = strtab;
221 ctx->strtab_capacity = capacity;
222}
223
224static void internal_destroy_string(internal_string *is) {
225 internal_string **prev_next;
226 internal_string *cur;
227 grpc_mdctx *ctx = is->context;
228 for (prev_next = &ctx->strtab[is->hash % ctx->strtab_capacity],
229 cur = *prev_next;
230 cur != is; prev_next = &cur->bucket_next, cur = cur->bucket_next)
231 ;
232 *prev_next = cur->bucket_next;
233 ctx->strtab_count--;
234 gpr_free(is);
235}
236
237static void internal_string_ref(internal_string *s) { ++s->refs; }
238
239static void internal_string_unref(internal_string *s) {
240 GPR_ASSERT(s->refs > 0);
241 if (0 == --s->refs) {
242 internal_destroy_string(s);
243 }
244}
245
246static void slice_ref(void *p) {
247 internal_string *is =
248 (internal_string *)((char *)p - offsetof(internal_string, refcount));
249 grpc_mdctx *ctx = is->context;
250 lock(ctx);
251 internal_string_ref(is);
252 unlock(ctx);
253}
254
255static void slice_unref(void *p) {
256 internal_string *is =
257 (internal_string *)((char *)p - offsetof(internal_string, refcount));
258 grpc_mdctx *ctx = is->context;
259 lock(ctx);
260 internal_string_unref(is);
261 unlock(ctx);
262}
263
264grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str) {
265 return grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)str, strlen(str));
266}
267
268grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice) {
269 grpc_mdstr *result = grpc_mdstr_from_buffer(ctx, GPR_SLICE_START_PTR(slice),
270 GPR_SLICE_LENGTH(slice));
271 gpr_slice_unref(slice);
272 return result;
273}
274
275grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *buf,
276 size_t length) {
277 gpr_uint32 hash = gpr_murmur_hash3(buf, length, ctx->hash_seed);
278 internal_string *s;
279
280 lock(ctx);
281
282 /* search for an existing string */
283 for (s = ctx->strtab[hash % ctx->strtab_capacity]; s; s = s->bucket_next) {
284 if (s->hash == hash && GPR_SLICE_LENGTH(s->slice) == length &&
285 0 == memcmp(buf, GPR_SLICE_START_PTR(s->slice), length)) {
286 internal_string_ref(s);
287 unlock(ctx);
288 return (grpc_mdstr *)s;
289 }
290 }
291
292 /* not found: create a new string */
293 if (length + 1 < GPR_SLICE_INLINED_SIZE) {
294 /* string data goes directly into the slice */
295 s = gpr_malloc(sizeof(internal_string));
296 s->refs = 1;
297 s->slice.refcount = NULL;
298 memcpy(s->slice.data.inlined.bytes, buf, length);
299 s->slice.data.inlined.bytes[length] = 0;
300 s->slice.data.inlined.length = length;
301 } else {
302 /* string data goes after the internal_string header, and we +1 for null
303 terminator */
304 s = gpr_malloc(sizeof(internal_string) + length + 1);
305 s->refs = 1;
306 s->refcount.ref = slice_ref;
307 s->refcount.unref = slice_unref;
308 s->slice.refcount = &s->refcount;
309 s->slice.data.refcounted.bytes = (gpr_uint8 *)(s + 1);
310 s->slice.data.refcounted.length = length;
311 memcpy(s->slice.data.refcounted.bytes, buf, length);
312 /* add a null terminator for cheap c string conversion when desired */
313 s->slice.data.refcounted.bytes[length] = 0;
314 }
315 s->hash = hash;
316 s->context = ctx;
317 s->bucket_next = ctx->strtab[hash % ctx->strtab_capacity];
318 ctx->strtab[hash % ctx->strtab_capacity] = s;
319
320 ctx->strtab_count++;
321
322 if (ctx->strtab_count > ctx->strtab_capacity * 2) {
323 grow_strtab(ctx);
324 }
325
326 unlock(ctx);
327
328 return (grpc_mdstr *)s;
329}
330
331static void gc_mdtab(grpc_mdctx *ctx) {
332 size_t i;
333 internal_metadata **prev_next;
334 internal_metadata *md, *next;
335
336 for (i = 0; i < ctx->mdtab_capacity; i++) {
337 prev_next = &ctx->mdtab[i];
338 for (md = ctx->mdtab[i]; md; md = next) {
339 next = md->bucket_next;
340 if (md->refs == 0) {
341 internal_string_unref(md->key);
342 internal_string_unref(md->value);
343 if (md->user_data) {
344 md->destroy_user_data(md->user_data);
345 }
346 gpr_free(md);
347 *prev_next = next;
348 ctx->mdtab_free--;
349 ctx->mdtab_count--;
350 } else {
351 prev_next = &md->bucket_next;
352 }
353 }
354 }
355
356 GPR_ASSERT(ctx->mdtab_free == 0);
357}
358
359static void grow_mdtab(grpc_mdctx *ctx) {
360 size_t capacity = ctx->mdtab_capacity * 2;
361 size_t i;
362 internal_metadata **mdtab =
363 gpr_malloc(sizeof(internal_metadata *) * capacity);
364 internal_metadata *md, *next;
365 gpr_uint32 hash;
366 memset(mdtab, 0, sizeof(internal_metadata *) * capacity);
367
368 for (i = 0; i < ctx->mdtab_capacity; i++) {
369 for (md = ctx->mdtab[i]; md; md = next) {
370 hash = KV_HASH(md->key, md->value);
371 next = md->bucket_next;
372 md->bucket_next = mdtab[hash % capacity];
373 mdtab[hash % capacity] = md;
374 }
375 }
376
377 gpr_free(ctx->mdtab);
378 ctx->mdtab = mdtab;
379 ctx->mdtab_capacity = capacity;
380}
381
382static void rehash_mdtab(grpc_mdctx *ctx) {
383 if (ctx->mdtab_free > ctx->mdtab_capacity / 4) {
384 gc_mdtab(ctx);
385 } else {
386 grow_mdtab(ctx);
387 }
388}
389
390grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx,
391 grpc_mdstr *mkey,
392 grpc_mdstr *mvalue) {
393 internal_string *key = (internal_string *)mkey;
394 internal_string *value = (internal_string *)mvalue;
395 gpr_uint32 hash = KV_HASH(mkey, mvalue);
396 internal_metadata *md;
397
398 GPR_ASSERT(key->context == ctx);
399 GPR_ASSERT(value->context == ctx);
400
401 lock(ctx);
402
403 /* search for an existing pair */
404 for (md = ctx->mdtab[hash % ctx->mdtab_capacity]; md; md = md->bucket_next) {
405 if (md->key == key && md->value == value) {
406 ref_md(md);
407 internal_string_unref(key);
408 internal_string_unref(value);
409 unlock(ctx);
410 return (grpc_mdelem *)md;
411 }
412 }
413
414 /* not found: create a new pair */
415 md = gpr_malloc(sizeof(internal_metadata));
416 md->refs = 1;
417 md->context = ctx;
418 md->key = key;
419 md->value = value;
420 md->user_data = NULL;
421 md->destroy_user_data = NULL;
422 md->bucket_next = ctx->mdtab[hash % ctx->mdtab_capacity];
423 ctx->mdtab[hash % ctx->mdtab_capacity] = md;
424 ctx->mdtab_count++;
425
426 if (ctx->mdtab_count > ctx->mdtab_capacity * 2) {
427 rehash_mdtab(ctx);
428 }
429
430 unlock(ctx);
431
432 return (grpc_mdelem *)md;
433}
434
435grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key,
436 const char *value) {
437 return grpc_mdelem_from_metadata_strings(ctx,
438 grpc_mdstr_from_string(ctx, key),
439 grpc_mdstr_from_string(ctx, value));
440}
441
442grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key,
443 gpr_slice value) {
444 return grpc_mdelem_from_metadata_strings(ctx, grpc_mdstr_from_slice(ctx, key),
445 grpc_mdstr_from_slice(ctx, value));
446}
447
448grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx,
449 const char *key,
450 const gpr_uint8 *value,
451 size_t value_length) {
452 return grpc_mdelem_from_metadata_strings(
453 ctx, grpc_mdstr_from_string(ctx, key),
454 grpc_mdstr_from_buffer(ctx, value, value_length));
455}
456
457grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *gmd) {
458 internal_metadata *md = (internal_metadata *)gmd;
459 grpc_mdctx *ctx = md->context;
460 lock(ctx);
461 ref_md(md);
462 unlock(ctx);
463 return gmd;
464}
465
466void grpc_mdelem_unref(grpc_mdelem *gmd) {
467 internal_metadata *md = (internal_metadata *)gmd;
468 grpc_mdctx *ctx = md->context;
469 lock(ctx);
470 GPR_ASSERT(md->refs);
471 if (0 == --md->refs) {
472 ctx->mdtab_free++;
473 }
474 unlock(ctx);
475}
476
477const char *grpc_mdstr_as_c_string(grpc_mdstr *s) {
478 return (const char *)GPR_SLICE_START_PTR(s->slice);
479}
480
481grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *gs) {
482 internal_string *s = (internal_string *)gs;
483 grpc_mdctx *ctx = s->context;
484 lock(ctx);
485 internal_string_ref(s);
486 unlock(ctx);
487 return gs;
488}
489
490void grpc_mdstr_unref(grpc_mdstr *gs) {
491 internal_string *s = (internal_string *)gs;
492 grpc_mdctx *ctx = s->context;
493 lock(ctx);
494 internal_string_unref(s);
495 unlock(ctx);
496}
497
498size_t grpc_mdctx_get_mdtab_capacity_test_only(grpc_mdctx *ctx) {
499 return ctx->mdtab_capacity;
500}
501
502size_t grpc_mdctx_get_mdtab_count_test_only(grpc_mdctx *ctx) {
503 return ctx->mdtab_count;
504}
505
506size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *ctx) {
507 return ctx->mdtab_free;
508}
509
510void *grpc_mdelem_get_user_data(grpc_mdelem *md,
511 void (*if_destroy_func)(void *)) {
512 internal_metadata *im = (internal_metadata *)md;
513 return im->destroy_user_data == if_destroy_func ? im->user_data : NULL;
514}
515
516void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *),
517 void *user_data) {
518 internal_metadata *im = (internal_metadata *)md;
519 GPR_ASSERT((user_data == NULL) == (destroy_func == NULL));
520 if (im->destroy_user_data) {
521 im->destroy_user_data(im->user_data);
522 }
523 im->destroy_user_data = destroy_func;
524 im->user_data = user_data;
525}