Skip to content

Commit d119fc9

Browse files
bushidocodesclaude
andauthored
feat: configurable per-module sandbox stack size (#394)
The sandbox execution stack was fixed at WASM_STACK_SIZE (512 KB). The module already carried a stack_size field and module_init already honored a non-zero value (falling back to WASM_STACK_SIZE), but the size was hardcoded to 0, so it was always the default. Expose it as an optional "stack-size" route config field (bytes). It is parsed into route_config, passed through module_alloc/module_init, rounded up to a page, and flows into per-sandbox allocation via the existing wasm_stack_alloc(module->stack_size). Omitting it (or 0) keeps the WASM_STACK_SIZE default, so existing specs are unaffected. Granularity is per-module (keyed by the .so path) because the stack pool is sized per-module; a true per-route size for a shared module would break the pool. The first route to create a module sets its stack size; a later route requesting a different size for an already-created module is warned and the existing size is kept. Preprocess modules use the default. Verified: builds; applied size tracks the config exactly (1 MB -> 1048576, 16 KB -> 16384), omitting the field yields the 524288 default, and an unmodified spec still serves resize requests byte-identically (10/10). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7771c87 commit d119fc9

5 files changed

Lines changed: 30 additions & 9 deletions

File tree

runtime/include/module.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ struct module {
4242
*******************************/
4343

4444
void module_free(struct module *module);
45-
struct module *module_alloc(char *path, enum module_type type);
45+
struct module *module_alloc(char *path, enum module_type type, uint32_t stack_size);
4646

4747
/*************************
4848
* Public Static Inlines *

runtime/include/route_config.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ enum route_config_member
2121
route_config_member_model_beta1,
2222
route_config_member_model_beta2,
2323
route_config_member_http_resp_content_type,
24+
route_config_member_stack_size,
2425
route_config_member_len
2526
};
2627

@@ -36,6 +37,7 @@ struct route_config {
3637
uint32_t model_beta1;
3738
uint32_t model_beta2;
3839
char *http_resp_content_type;
40+
uint32_t stack_size; /* in bytes; 0 means use the runtime default (WASM_STACK_SIZE) */
3941
};
4042

4143
static inline void
@@ -57,6 +59,7 @@ route_config_print(struct route_config *config)
5759
printf("[Route] Admissions Percentile: %hhu\n", config->admissions_percentile);
5860
printf("[Route] Relative Deadline (us): %u\n", config->relative_deadline_us);
5961
printf("[Route] HTTP Response Content Type: %s\n", config->http_resp_content_type);
62+
printf("[Route] Stack Size (bytes, 0=default): %u\n", config->stack_size);
6063
#ifdef EXECUTION_HISTOGRAM
6164
printf("[Route] Path of Preprocessing Module: %s\n", config->path_preprocess);
6265
printf("[Route] Model Bias: %u\n", config->model_bias);

runtime/include/route_config_parse.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
static const char *route_config_json_keys[route_config_member_len] =
1010
{"route", "path", "admissions-percentile", "relative-deadline-us",
1111
"path_preprocess", "model-bias", "model-scale", "model-num-of-param",
12-
"model-beta1", "model-beta2", "http-resp-content-type"};
12+
"model-beta1", "model-beta2", "http-resp-content-type", "stack-size"};
1313

1414
static inline int
1515
route_config_set_key_once(bool *did_set, enum route_config_member member)
@@ -132,6 +132,14 @@ route_config_parse(struct route_config *config, const char *json_buf, jsmntok_t
132132

133133
config->http_resp_content_type = strndup(json_buf + tokens[i].start,
134134
tokens[i].end - tokens[i].start);
135+
} else if (strcmp(key, route_config_json_keys[route_config_member_stack_size]) == 0) {
136+
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
137+
if (route_config_set_key_once(did_set, route_config_member_stack_size) == -1) return -1;
138+
139+
int rc = parse_uint32_t(tokens[i], json_buf,
140+
route_config_json_keys[route_config_member_stack_size],
141+
&config->stack_size);
142+
if (rc < 0) return -1;
135143
} else {
136144
fprintf(stderr, "%s is not a valid key\n", key);
137145
return -1;

runtime/include/tenant_functions.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,22 @@ tenant_alloc(struct tenant_config *config)
103103
struct module *module = module_database_find_by_path(&tenant->module_db, config->routes[i].path);
104104
if (module == NULL) {
105105
/* Ownership of path moves here */
106-
module = module_alloc(config->routes[i].path, APP_MODULE);
106+
module = module_alloc(config->routes[i].path, APP_MODULE, config->routes[i].stack_size);
107107
if (module != NULL) {
108108
module_database_add(&tenant->module_db, module);
109109
config->routes[i].path = NULL;
110110
}
111111
} else {
112+
/* The module (keyed by path) already exists, so its stack size is fixed. The stack pool is
113+
* sized per-module, so a route cannot request a different stack size for a shared module. */
114+
if (config->routes[i].stack_size != 0
115+
&& round_up_to_page(config->routes[i].stack_size) != module->stack_size) {
116+
fprintf(stderr,
117+
"Warning: route %s requested stack-size %u for %s, but that module already "
118+
"exists with stack size %u; keeping the existing size.\n",
119+
config->routes[i].route, config->routes[i].stack_size, config->routes[i].path,
120+
module->stack_size);
121+
}
112122
free(config->routes[i].path);
113123
config->routes[i].path = NULL;
114124
}
@@ -123,7 +133,8 @@ tenant_alloc(struct tenant_config *config)
123133
config->routes[i].path_preprocess);
124134
if (module_proprocess == NULL) {
125135
/* Ownership of path moves here */
126-
module_proprocess = module_alloc(config->routes[i].path_preprocess, PREPROCESS_MODULE);
136+
module_proprocess = module_alloc(config->routes[i].path_preprocess, PREPROCESS_MODULE,
137+
0 /* preprocess modules use the default stack size */);
127138
if (module_proprocess != NULL) {
128139
module_database_add(&tenant->module_db, module_proprocess);
129140
config->routes[i].path_preprocess = NULL;

runtime/src/module.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@
2929
* @returns 0 on success, -1 on error
3030
*/
3131
static inline int
32-
module_init(struct module *module, char *path)
32+
module_init(struct module *module, char *path, uint32_t stack_size)
3333
{
3434
assert(module != NULL);
3535
assert(path != NULL);
3636
assert(strlen(path) > 0);
3737

38-
uint32_t stack_size = 0;
39-
4038
int rc = 0;
4139

4240
atomic_init(&module->reference_count, 0);
@@ -49,6 +47,7 @@ module_init(struct module *module, char *path)
4947

5048
module->path = path;
5149

50+
/* A stack_size of 0 means the config did not specify one, so fall back to the runtime default. */
5251
module->stack_size = ((uint32_t)(round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size)));
5352

5453
module_alloc_table(module);
@@ -107,7 +106,7 @@ module_free(struct module *module)
107106
*/
108107

109108
struct module *
110-
module_alloc(char *path, enum module_type type)
109+
module_alloc(char *path, enum module_type type, uint32_t stack_size)
111110
{
112111
size_t alignment = (size_t)CACHE_PAD;
113112
size_t size_to_alloc = (size_t)round_to_cache_pad(sizeof(struct module));
@@ -123,7 +122,7 @@ module_alloc(char *path, enum module_type type)
123122
memset(module, 0, size_to_alloc);
124123
module->type = type;
125124

126-
int rc = module_init(module, path);
125+
int rc = module_init(module, path, stack_size);
127126
if (rc < 0) goto init_err;
128127

129128
done:

0 commit comments

Comments
 (0)