Skip to content

Commit 1f1d708

Browse files
Anton KirilovNateBrady23
authored andcommitted
H2O: Reuse JSON generators across HTTP requests (#2713)
Previously, whenever a memory allocation for the sake of a JSON generator was performed, the application used the memory pool associated with the HTTP request, so the generator was freed after the response was sent (since the memory pool was subsequently destroyed), and a new one was created for the next request. In this way the memory management overhead for the JSON generator was reduced. However, it has been discovered that some of the memory allocation sizes associated with the JSON generator are above the limit imposed by libh2o, so the latter resorts to calling malloc() directly, which defeats the purpose of using a memory pool in the first place. The solution is to separate the memory management for the JSON generators from the memory pools managed by libh2o, and to reuse generators across HTTP requests. After a response is sent, the state of each generator is fully reset (as if the generator has just been constructed), so that when it is reused, the new JSON response is created from scratch. Reinitializing the generator is much cheaper than freeing it and allocating a new one. Since this approach increases the memory usage of the application, the maximum number of JSON generators that can be reused is configurable. Any generator above that limit will be deallocated immediately as soon as it is no longer used.
1 parent aab7ef6 commit 1f1d708

File tree

9 files changed

+141
-87
lines changed

9 files changed

+141
-87
lines changed

frameworks/C/h2o/setup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ rm -rf "$BUILD_DIR"
7171
echo "Maximum database connections per thread: $DB_CONN"
7272

7373
if "$CLOUD_ENVIRONMENT"; then
74-
run_h2o_app "0-$((NUM_PROC - 1))" "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app"
74+
run_h2o_app 0 "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app"
7575
else
7676
for ((i = 0; i < PHYSICAL_ENVIRONMENT_THREADS; i++)); do
7777
run_h2o_app "$i" "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app" -t1

frameworks/C/h2o/src/main.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
#define USAGE_MESSAGE \
4444
"Usage:\n%s [-a <max connections accepted simultaneously>] [-b <bind address>] " \
4545
"[-c <certificate file>] [-d <database connection string>] [-f fortunes template file path] " \
46-
"[-k <private key file>] [-l <log path>] " \
46+
"[-j <max reused JSON generators>] [-k <private key file>] [-l <log path>] " \
4747
"[-m <max database connections per thread>] [-p <port>] " \
4848
"[-q <max enqueued database queries per thread>] [-r <root directory>] " \
4949
"[-s <HTTPS port>] [-t <thread number>]\n"
@@ -165,10 +165,12 @@ static int initialize_global_data(const config_t *config, global_data_t *global_
165165
static int parse_options(int argc, char *argv[], config_t *config)
166166
{
167167
memset(config, 0, sizeof(*config));
168+
// Need to set the default value here because 0 is a valid input value.
169+
config->max_json_generator = 32;
168170
opterr = 0;
169171

170172
while (1) {
171-
const int opt = getopt(argc, argv, "?a:b:c:d:f:k:l:m:p:q:r:s:t:");
173+
const int opt = getopt(argc, argv, "?a:b:c:d:f:j:k:l:m:p:q:r:s:t:");
172174

173175
if (opt == -1)
174176
break;
@@ -204,6 +206,9 @@ static int parse_options(int argc, char *argv[], config_t *config)
204206
case 'f':
205207
config->template_path = optarg;
206208
break;
209+
case 'j':
210+
PARSE_NUMBER(config->max_json_generator);
211+
break;
207212
case 'k':
208213
config->key = optarg;
209214
break;

frameworks/C/h2o/src/request_handler.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "error.h"
2929
#include "fortune.h"
3030
#include "request_handler.h"
31+
#include "thread.h"
3132
#include "utility.h"
3233
#include "world.h"
3334

@@ -41,21 +42,26 @@ static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req)
4142
{
4243
IGNORE_FUNCTION_PARAMETER(self);
4344

44-
const yajl_gen gen = get_json_generator(&req->pool);
45+
thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
46+
event_loop.h2o_ctx,
47+
req->conn->ctx);
48+
json_generator_t * const gen = get_json_generator(&ctx->json_generator,
49+
&ctx->json_generator_num);
4550

4651
if (gen) {
47-
CHECK_YAJL_STATUS(yajl_gen_map_open, gen);
48-
CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT("message"));
49-
CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT(HELLO_RESPONSE));
50-
CHECK_YAJL_STATUS(yajl_gen_map_close, gen);
52+
CHECK_YAJL_STATUS(yajl_gen_map_open, gen->gen);
53+
CHECK_YAJL_STATUS(yajl_gen_string, gen->gen, YAJL_STRLIT("message"));
54+
CHECK_YAJL_STATUS(yajl_gen_string, gen->gen, YAJL_STRLIT(HELLO_RESPONSE));
55+
CHECK_YAJL_STATUS(yajl_gen_map_close, gen->gen);
5156

5257
// The response is small enough, so that it is simpler to copy it
5358
// instead of doing a delayed deallocation of the JSON generator.
5459
if (!send_json_response(gen, true, req))
5560
return 0;
5661

5762
error_yajl:
58-
yajl_gen_free(gen);
63+
// If there is a problem with the generator, don't reuse it.
64+
free_json_generator(gen, NULL, NULL, 0);
5965
}
6066

6167
send_error(INTERNAL_SERVER_ERROR, REQ_ERROR, req);
@@ -178,20 +184,27 @@ void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req
178184
h2o_send_error_generic(req, status_code, status_code_to_string(status_code), body, 0);
179185
}
180186

181-
int send_json_response(yajl_gen gen, bool free_gen, h2o_req_t *req)
187+
int send_json_response(json_generator_t *gen, bool free_gen, h2o_req_t *req)
182188
{
183189
const unsigned char *buf;
184190
size_t len;
185191
int ret = EXIT_FAILURE;
186192

187-
if (yajl_gen_get_buf(gen, &buf, &len) == yajl_gen_status_ok) {
193+
if (yajl_gen_get_buf(gen->gen, &buf, &len) == yajl_gen_status_ok) {
188194
set_default_response_param(JSON, len, req);
189195
ret = EXIT_SUCCESS;
190196

191197
if (free_gen) {
198+
thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
199+
event_loop.h2o_ctx,
200+
req->conn->ctx);
201+
192202
// h2o_send_inline() makes a copy of its input.
193203
h2o_send_inline(req, (char *) buf, len);
194-
yajl_gen_free(gen);
204+
free_json_generator(gen,
205+
&ctx->json_generator,
206+
&ctx->json_generator_num,
207+
ctx->config->max_json_generator);
195208
}
196209
else {
197210
h2o_generator_t generator;

frameworks/C/h2o/src/request_handler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include <stdbool.h>
2626
#include <yajl/yajl_gen.h>
2727

28+
#include "utility.h"
29+
2830
#define REQ_ERROR "request error\n"
2931

3032
typedef enum {
@@ -47,7 +49,7 @@ const char *get_query_param(const char *query,
4749
size_t param_len);
4850
void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle);
4951
void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req);
50-
int send_json_response(yajl_gen gen, bool free_gen, h2o_req_t *req);
52+
int send_json_response(json_generator_t *gen, bool free_gen, h2o_req_t *req);
5153
void send_service_unavailable_error(const char *body, h2o_req_t *req);
5254
void set_default_response_param(content_type_t content_type,
5355
size_t content_length,

frameworks/C/h2o/src/thread.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "error.h"
3333
#include "event_loop.h"
3434
#include "thread.h"
35+
#include "utility.h"
3536

3637
static void *run_thread(void *arg);
3738

@@ -50,6 +51,16 @@ void free_thread_context(thread_context_t *ctx)
5051
{
5152
free_database_state(ctx->event_loop.h2o_ctx.loop, &ctx->db_state);
5253
free_event_loop(&ctx->event_loop, &ctx->global_thread_data->h2o_receiver);
54+
55+
if (ctx->json_generator)
56+
do {
57+
json_generator_t * const gen = H2O_STRUCT_FROM_MEMBER(json_generator_t,
58+
l,
59+
ctx->json_generator);
60+
61+
ctx->json_generator = gen->l.next;
62+
free_json_generator(gen, NULL, NULL, 0);
63+
} while (ctx->json_generator);
5364
}
5465

5566
global_thread_data_t *initialize_global_thread_data(const config_t *config,

frameworks/C/h2o/src/thread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ struct thread_context_t {
4747
// global_thread_data contains config and global_data as well,
4848
// but keep copies here to avoid some pointer chasing.
4949
global_thread_data_t *global_thread_data;
50+
list_t *json_generator;
51+
size_t json_generator_num;
5052
unsigned random_seed;
5153
pid_t tid;
5254
db_state_t db_state;

frameworks/C/h2o/src/utility.c

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,78 +18,58 @@
1818
*/
1919

2020
#include <assert.h>
21-
#include <h2o.h>
22-
#include <limits.h>
23-
#include <stdalign.h>
2421
#include <stdint.h>
2522
#include <stdlib.h>
26-
#include <string.h>
2723
#include <yajl/yajl_gen.h>
2824

25+
#include "list.h"
2926
#include "utility.h"
3027

31-
#define HEADER_SIZE (MAX(sizeof(intmax_t), sizeof(void(*)(void))))
32-
33-
static void mem_pool_free(void *ctx, void *ptr);
34-
static void *mem_pool_malloc(void *ctx, size_t sz);
35-
static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz);
36-
37-
static void mem_pool_free(void *ctx, void *ptr)
38-
{
39-
// The memory pool will free all allocations in one go.
40-
IGNORE_FUNCTION_PARAMETER(ctx);
41-
IGNORE_FUNCTION_PARAMETER(ptr);
42-
}
43-
44-
static void *mem_pool_malloc(void *ctx, size_t sz)
28+
void free_json_generator(json_generator_t *gen, list_t **pool, size_t *gen_num, size_t max_gen)
4529
{
46-
void *ret = NULL;
47-
48-
if (SIZE_MAX - sz >= HEADER_SIZE) {
49-
size_t * const p = h2o_mem_alloc_pool(ctx, sz + HEADER_SIZE);
50-
51-
*p = sz;
52-
ret = (char *) p + HEADER_SIZE;
53-
// check alignment
54-
assert(!(((uintptr_t) ret) & (alignof(intmax_t) - 1)));
55-
assert(!(((uintptr_t) ret) & (alignof(void(*)(void)) - 1)));
30+
if (gen) {
31+
assert(!pool || gen_num);
32+
33+
if (pool && *gen_num < max_gen) {
34+
yajl_gen_reset(gen->gen, NULL);
35+
yajl_gen_clear(gen->gen);
36+
gen->l.next = *pool;
37+
*pool = &gen->l;
38+
(*gen_num)++;
39+
}
40+
else {
41+
yajl_gen_free(gen->gen);
42+
free(gen);
43+
}
5644
}
57-
58-
return ret;
5945
}
6046

61-
static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz)
47+
json_generator_t *get_json_generator(list_t **pool, size_t *gen_num)
6248
{
63-
void *ret;
49+
json_generator_t *ret;
6450

65-
if (ptr) {
66-
const size_t old_sz = *(const size_t *)((const char *) ptr - HEADER_SIZE);
51+
if (pool && *pool) {
52+
assert(gen_num && *gen_num);
53+
ret = H2O_STRUCT_FROM_MEMBER(json_generator_t, l, *pool);
54+
*pool = ret->l.next;
55+
(*gen_num)--;
56+
}
57+
else {
58+
ret = malloc(sizeof(*ret));
6759

68-
if (sz > old_sz) {
69-
ret = mem_pool_malloc(ctx, sz);
60+
if (ret) {
61+
ret->gen = yajl_gen_alloc(NULL);
7062

71-
if (ret)
72-
memcpy(ret, ptr, old_sz);
63+
if (!ret->gen) {
64+
free(ret);
65+
ret = NULL;
66+
}
7367
}
74-
else
75-
ret = ptr;
7668
}
77-
else
78-
ret = mem_pool_malloc(ctx, sz);
7969

8070
return ret;
8171
}
8272

83-
yajl_gen get_json_generator(h2o_mem_pool_t *pool)
84-
{
85-
const yajl_alloc_funcs mem_pool_alloc_funcs = {mem_pool_malloc,
86-
mem_pool_realloc,
87-
mem_pool_free,
88-
pool};
89-
90-
return yajl_gen_alloc(&mem_pool_alloc_funcs);
91-
}
92-
9373
uint32_t get_random_number(uint32_t max_rand, unsigned int *seed)
9474
{
9575
assert(max_rand <= (uint32_t) RAND_MAX);

frameworks/C/h2o/src/utility.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <yajl/yajl_gen.h>
2929
#include <mustache.h>
3030

31+
#include "list.h"
32+
3133
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
3234
// mainly used to silence compiler warnings about unused function parameters
3335
#define IGNORE_FUNCTION_PARAMETER(p) ((void) (p))
@@ -50,6 +52,7 @@ typedef struct {
5052
const char *template_path;
5153
size_t max_accept;
5254
size_t max_db_conn_num;
55+
size_t max_json_generator;
5356
size_t max_query_num;
5457
size_t thread_num;
5558
uint16_t https_port;
@@ -68,11 +71,13 @@ typedef struct {
6871
h2o_globalconf_t h2o_config;
6972
} global_data_t;
7073

71-
// Call yajl_gen_free() on the result, even though the JSON generator
72-
// uses a memory pool; in this way the code remains correct if the
73-
// underlying memory allocator is changed (e.g. for debugging purposes).
74-
yajl_gen get_json_generator(h2o_mem_pool_t *pool);
74+
typedef struct {
75+
list_t l;
76+
yajl_gen gen;
77+
} json_generator_t;
7578

79+
void free_json_generator(json_generator_t *gen, list_t **pool, size_t *gen_num, size_t max_gen);
80+
json_generator_t *get_json_generator(list_t **pool, size_t *gen_num);
7681
uint32_t get_random_number(uint32_t max_rand, unsigned int *seed);
7782

7883
#endif // UTILITY_H_

0 commit comments

Comments
 (0)