castrum 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +25 -42
- package/Cargo.toml +21 -4
- package/README.md +8 -6
- package/castrum.darwin-arm64.node +0 -0
- package/castrum.darwin-x64.node +0 -0
- package/castrum.linux-arm64-gnu.node +0 -0
- package/castrum.linux-arm64-musl.node +0 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/castrum.linux-x64-musl.node +0 -0
- package/castrum.linux-x64-v3-gnu.node +0 -0
- package/castrum.win32-x64-msvc.node +0 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +4432 -3641
- package/docs/API.md +1 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CASE_STUDY.md +4 -2
- package/docs/ENVIRONMENT.md +9 -2
- package/docs/FFI_BUN_GUIDE.md +154 -9
- package/docs/HOW_TO_ADD_AN_OP.md +10 -0
- package/docs/REPO_MAP.md +16 -5
- package/docs/RND-CONCURRENCY.md +772 -0
- package/docs/bun-builtins-decision-matrix.md +4 -1
- package/index.ts +5 -0
- package/package.json +172 -163
- package/rust/crypto/pbkdf2.rs +9 -0
- package/rust/ffi/mod.rs +1 -0
- package/rust/ffi/task.rs +172 -0
- package/rust/http/form.rs +6 -2
- package/rust/http/query_parser.rs +53 -22
- package/rust/ingress/native_route.rs +131 -140
- package/rust/ingress/pipeline.rs +3 -3
- package/rust/ingress/tests.rs +5 -4
- package/rust/json/json_ser.rs +13 -11
- package/rust/lib.rs +1 -0
- package/rust/task/completion.rs +119 -0
- package/rust/task/mod.rs +65 -0
- package/rust/task/ops.rs +503 -0
- package/rust/task/runtime.rs +220 -0
- package/rust/task/tests.rs +231 -0
- package/rust/util/bytes.rs +238 -51
- package/src/ingress/health.ts +1 -2
- package/src/native/ffi/build/codecs.ts +29 -7
- package/src/native/ffi/build/compress.ts +8 -0
- package/src/native/ffi/build/instances.ts +7 -2
- package/src/native/ffi/build/metrics.ts +23 -0
- package/src/native/ffi/build/task.ts +79 -0
- package/src/native/ffi/build/util.ts +18 -0
- package/src/native/ffi/build.ts +2 -0
- package/src/native/ffi/selftest.ts +3 -1
- package/src/native/ffi/types.ts +40 -0
- package/src/native/ffi.ts +73 -14
- package/src/native/loader.ts +53 -3
- package/src/rust-ffi/scalar/http.ts +54 -16
- package/src/rust-ffi/text.ts +25 -3
- package/src/shared/bytes.ts +19 -0
- package/src/task/index.ts +24 -0
- package/src/task/op.ts +171 -0
- package/src/task/runtime.ts +568 -0
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
## Methodology
|
|
9
9
|
|
|
10
|
-
- **Runtime**: Bun **1.4.0** (the
|
|
10
|
+
- **Runtime**: Bun **1.4.0** (the measurement anchor for the rows below; the
|
|
11
|
+
local toolchain has since moved to **1.4.2**, and these TIMING rows have not
|
|
12
|
+
been re-measured — only re-verified for parity, which
|
|
13
|
+
`test/unit/contract/delegation.test.ts` does on every run).
|
|
11
14
|
- **Addon**: castrum **release** build (`bun run build`, baseline CPU — the
|
|
12
15
|
shipped profile).
|
|
13
16
|
- **Driver**: `bun run check` (CPU benchmark → `bench/results/cpu/latest.json`).
|
package/index.ts
CHANGED
|
@@ -20,6 +20,11 @@ export * from './src/integration'
|
|
|
20
20
|
// dispatch single items, packed batches, and load() coalescing. See README.
|
|
21
21
|
export * from './src/loader'
|
|
22
22
|
export * from './src/rust-ffi'
|
|
23
|
+
// Off-thread native task runtime ("castrum Tasks"): submit a CPU-bound native
|
|
24
|
+
// op to the Rust pool and `await` it without ever blocking the JS thread.
|
|
25
|
+
// Bun-only offload today; Node runs the op synchronously. See
|
|
26
|
+
// docs/RND-CONCURRENCY.md.
|
|
27
|
+
export * from './src/task'
|
|
23
28
|
export type { OpDecision, OpImpl } from './src/selection'
|
|
24
29
|
// Native-vs-JS selection (OWNED HERE, not by consumers): `opImpl(op)` is the
|
|
25
30
|
// benchmark-driven recommendation for which operations should use the Rust
|
package/package.json
CHANGED
|
@@ -1,168 +1,177 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
2
|
+
"name": "castrum",
|
|
3
|
+
"version": "0.9.6",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Enterprise-grade Bun + Rust runtime: Rust-accelerated FFI primitives, a production-grade HTTP ingress pipeline, validation, hashing, and JSON processing with benchmark-grade performance.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"bun",
|
|
13
|
+
"rust",
|
|
14
|
+
"napi",
|
|
15
|
+
"ffi",
|
|
16
|
+
"ingress",
|
|
17
|
+
"http",
|
|
18
|
+
"validation",
|
|
19
|
+
"hashing",
|
|
20
|
+
"json",
|
|
21
|
+
"performance",
|
|
22
|
+
"benchmark"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Axylabs/castrum.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/Axylabs/castrum#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/Axylabs/castrum/issues"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"bun": "./index.ts",
|
|
39
|
+
"node": "./dist/index.js",
|
|
40
|
+
"default": "./dist/index.js"
|
|
27
41
|
},
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"bench:http:spike": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts spike",
|
|
102
|
-
"bench:http:soak": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts soak",
|
|
103
|
-
"bench:http:storm": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts storm",
|
|
104
|
-
"bench:http:boundary": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts boundary",
|
|
105
|
-
"bench:http:elysia": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' elysia",
|
|
106
|
-
"bench:http:ingress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' ingress",
|
|
107
|
-
"bench:http:bun-only": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' bun",
|
|
108
|
-
"bench:http:all-heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts 1",
|
|
109
|
-
"bench:http:ac": "bun bench/autocannon-stress.mjs",
|
|
110
|
-
"bench:http:stress:ac": "SCENARIO=03-stress bun bench/autocannon-stress.mjs",
|
|
111
|
-
"bench:startup": "bun run bench/startup.ts",
|
|
112
|
-
"bench:load": "bun run bench/http/load-phase.ts",
|
|
113
|
-
"bench:ffi": "bun run bench/ffi/ffi-all.ts",
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"index.ts",
|
|
46
|
+
"dist",
|
|
47
|
+
"src",
|
|
48
|
+
"docs",
|
|
49
|
+
"README.md",
|
|
50
|
+
"*.node",
|
|
51
|
+
"scripts/postinstall.mjs",
|
|
52
|
+
"rust",
|
|
53
|
+
"Cargo.toml",
|
|
54
|
+
"Cargo.lock",
|
|
55
|
+
"build.rs",
|
|
56
|
+
"rust-toolchain.toml",
|
|
57
|
+
"!src/bench",
|
|
58
|
+
"!src/baseline",
|
|
59
|
+
"!src/data"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
63
|
+
"build": "napi build --release --platform",
|
|
64
|
+
"build:perf": "bash scripts/build-perf.sh",
|
|
65
|
+
"build:v3": "bash scripts/build-v3.sh",
|
|
66
|
+
"build:all": "bun run build && bun run build:v3",
|
|
67
|
+
"build:js": "bun run build:js:bundle && bun run build:js:types",
|
|
68
|
+
"build:js:bundle": "bun build ./index.ts --target=node --format=esm --outdir=dist",
|
|
69
|
+
"build:js:types": "dts-bundle-generator -o dist/index.d.ts index.ts",
|
|
70
|
+
"bench": "bun run build && bun bench.ts",
|
|
71
|
+
"check": "bun bench.ts",
|
|
72
|
+
"test": "bun test test/unit test/property test/compat",
|
|
73
|
+
"test:all": "bun test && bun run typecheck && bun run typecheck:test && bun run lint:ci && bun run check:clean && cargo test --lib",
|
|
74
|
+
"test:coverage": "node scripts/check-coverage.mjs",
|
|
75
|
+
"setup": "bun install && bun run build",
|
|
76
|
+
"test:node": "node --test test/integration/node-smoke.test.mjs test/integration/node-enterprise.test.mjs",
|
|
77
|
+
"verify:install": "node scripts/verify-install.mjs",
|
|
78
|
+
"build:debug": "napi build --platform",
|
|
79
|
+
"lint": "bunx biome check .",
|
|
80
|
+
"lint:fix": "bunx biome check --write .",
|
|
81
|
+
"lint:ci": "bunx biome check . --formatter-enabled=false --assist-enabled=false --error-on-warnings",
|
|
82
|
+
"format": "bunx biome format --write .",
|
|
83
|
+
"typecheck": "bunx tsc --noEmit",
|
|
84
|
+
"typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
|
|
85
|
+
"test:rust": "cargo test",
|
|
86
|
+
"deny": "cargo deny check",
|
|
87
|
+
"check:version": "bun scripts/check-version.mjs",
|
|
88
|
+
"check:jsdoc": "bun scripts/check-jsdoc.ts",
|
|
89
|
+
"check:selection": "bun scripts/select-native.ts --check",
|
|
90
|
+
"check:clean": "bun scripts/check-clean.ts",
|
|
91
|
+
"gen:ai-map": "bun scripts/gen-ai-map.ts",
|
|
92
|
+
"audit": "bun audit",
|
|
93
|
+
"release": "bun scripts/release.ts",
|
|
94
|
+
"release:dry": "bun scripts/release.ts --dry-run",
|
|
95
|
+
"release:manual": "bun scripts/release.ts --publish",
|
|
96
|
+
"prepublishOnly": "bun run build:js && node scripts/prepublish.mjs",
|
|
97
|
+
"bench:http": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts",
|
|
98
|
+
"bench:http:smoke": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts smoke",
|
|
99
|
+
"bench:http:stress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts stress",
|
|
100
|
+
"bench:http:heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts heavy-json",
|
|
101
|
+
"bench:http:crud": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts crud",
|
|
102
|
+
"bench:http:spike": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts spike",
|
|
103
|
+
"bench:http:soak": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts soak",
|
|
104
|
+
"bench:http:storm": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts storm",
|
|
105
|
+
"bench:http:boundary": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts boundary",
|
|
106
|
+
"bench:http:elysia": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' elysia",
|
|
107
|
+
"bench:http:ingress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' ingress",
|
|
108
|
+
"bench:http:bun-only": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' bun",
|
|
109
|
+
"bench:http:all-heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts 1",
|
|
110
|
+
"bench:http:ac": "bun bench/autocannon-stress.mjs",
|
|
111
|
+
"bench:http:stress:ac": "SCENARIO=03-stress bun bench/autocannon-stress.mjs",
|
|
112
|
+
"bench:startup": "bun run bench/startup.ts",
|
|
113
|
+
"bench:load": "bun run bench/http/load-phase.ts",
|
|
114
|
+
"bench:ffi": "bun run bench/ffi/ffi-all.ts",
|
|
114
115
|
"bench:newops": "bun bench/cost/rust-vs-js-new-ops.ts",
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
116
|
+
"bench:ffi:load": "bun run bench/ffi/ffi-load.ts",
|
|
117
|
+
"bench:ffi:public": "bun run bench/ffi/ffi-public.ts",
|
|
118
|
+
"bench:ffi:workers": "bun run bench/ffi/ffi-workers.ts",
|
|
119
|
+
"bench:margin": "bun run bench/ffi/ffi-margin.ts",
|
|
120
|
+
"bench:router": "bun run bench/cost/router-cost.ts",
|
|
121
|
+
"bench:task": "bun run bench/cost/task-offload.ts",
|
|
122
|
+
"bench:ingress-cost": "bun run bench/cost/ingress-cost.ts",
|
|
123
|
+
"bench:ingress-cost:post": "bun run bench/cost/ingress-cost-post.ts",
|
|
124
|
+
"verify:native:batch": "bun run scripts/verify-native-batch.ts"
|
|
125
|
+
},
|
|
126
|
+
"dependencies": {},
|
|
127
|
+
"peerDependencies": {
|
|
128
|
+
"@types/node": "^26.1.2"
|
|
129
|
+
},
|
|
130
|
+
"peerDependenciesMeta": {
|
|
131
|
+
"@types/node": {
|
|
132
|
+
"optional": true
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"devDependencies": {
|
|
136
|
+
"@elysia/cors": "^1.4.2",
|
|
137
|
+
"@napi-rs/cli": "^3.8.6",
|
|
138
|
+
"@types/bun": "^1.3.14",
|
|
139
|
+
"@types/mime-types": "^3.0.1",
|
|
140
|
+
"autocannon": "^8.0.0",
|
|
141
|
+
"@types/validator": "^13.15.10",
|
|
142
|
+
"ajv": "^8.20.0",
|
|
143
|
+
"crc-32": "^1.2.2",
|
|
144
|
+
"dts-bundle-generator": "^9.5.1",
|
|
145
|
+
"elysia": "^1.4.29",
|
|
146
|
+
"fast-json-patch": "^3.1.1",
|
|
147
|
+
"mime-types": "^3.0.2",
|
|
148
|
+
"typescript": "7.0.2",
|
|
149
|
+
"validator": "^13.15.35"
|
|
150
|
+
},
|
|
151
|
+
"overrides": {
|
|
152
|
+
"typescript": "7.0.2",
|
|
153
|
+
"dts-bundle-generator": {
|
|
154
|
+
"typescript": "5.9.3"
|
|
151
155
|
},
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
"node": ">=20.3"
|
|
156
|
+
"@napi-rs/cli": {
|
|
157
|
+
"typescript": "6.0.3"
|
|
155
158
|
},
|
|
156
|
-
"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
"hyperid": "^4.0.0"
|
|
160
|
+
},
|
|
161
|
+
"engines": {
|
|
162
|
+
"bun": ">=1.1.0",
|
|
163
|
+
"node": ">=20.3"
|
|
164
|
+
},
|
|
165
|
+
"napi": {
|
|
166
|
+
"binaryName": "castrum",
|
|
167
|
+
"targets": [
|
|
168
|
+
"aarch64-apple-darwin",
|
|
169
|
+
"aarch64-unknown-linux-gnu",
|
|
170
|
+
"aarch64-unknown-linux-musl",
|
|
171
|
+
"x86_64-apple-darwin",
|
|
172
|
+
"x86_64-pc-windows-msvc",
|
|
173
|
+
"x86_64-unknown-linux-gnu",
|
|
174
|
+
"x86_64-unknown-linux-musl"
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
package/rust/crypto/pbkdf2.rs
CHANGED
|
@@ -17,6 +17,15 @@ pub const PBKDF2_MIN_LEN: u32 = 1;
|
|
|
17
17
|
/// Cap derived-key length so a hostile caller can't request a huge allocation.
|
|
18
18
|
pub const PBKDF2_MAX_LEN: u32 = 1024 * 1024;
|
|
19
19
|
|
|
20
|
+
/// Pure core (no napi types) for [`pbkdf2_sha256`] and the off-thread task
|
|
21
|
+
/// runtime: derive `dk_len` bytes, clamping the length to the documented range.
|
|
22
|
+
pub fn pbkdf2_sha256_into(password: &[u8], salt: &[u8], rounds: u32, dk_len: u32) -> Vec<u8> {
|
|
23
|
+
let dk_len = dk_len.clamp(PBKDF2_MIN_LEN, PBKDF2_MAX_LEN) as usize;
|
|
24
|
+
let mut out = vec![0u8; dk_len];
|
|
25
|
+
pbkdf2_hmac::<Sha256>(password, salt, rounds.max(1), &mut out);
|
|
26
|
+
out
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
/// Derive a PBKDF2-HMAC-SHA256 key: `out = PBKDF2(password, salt, rounds, dkLen)`.
|
|
21
30
|
#[napi]
|
|
22
31
|
pub fn pbkdf2_sha256(
|
package/rust/ffi/mod.rs
CHANGED
package/rust/ffi/task.rs
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// rust/ffi/task.rs — C-ABI exports for the off-thread task runtime.
|
|
2
|
+
//
|
|
3
|
+
// Eight symbols, all thin: the pool/ring/ops live in `rust/task/`. `submit`
|
|
4
|
+
// COPIES the packed args onto the worker (the JS caller's pooled buffer is
|
|
5
|
+
// released as soon as submit returns), and `drain` follows the repo-wide
|
|
6
|
+
// needed-size convention so JS grows once and retries once.
|
|
7
|
+
|
|
8
|
+
use std::slice;
|
|
9
|
+
|
|
10
|
+
use super::util::panic_guard;
|
|
11
|
+
use crate::task;
|
|
12
|
+
|
|
13
|
+
/// Start the pool with `threads` workers (0 → runtime default).
|
|
14
|
+
/// Returns `0` started, `1` already running, `2` internal error.
|
|
15
|
+
#[no_mangle]
|
|
16
|
+
pub extern "C" fn castrum_task_init(threads: u32) -> u32 {
|
|
17
|
+
panic_guard(|| task::init(Some(threads)), 2)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Submit `op` with `args_len` packed arg bytes, completing under `task_id`.
|
|
21
|
+
/// Returns `1` accepted, `0` rejected (null args with a non-zero length).
|
|
22
|
+
///
|
|
23
|
+
/// # Safety
|
|
24
|
+
/// `args` must be valid for reads of `args_len` bytes.
|
|
25
|
+
#[no_mangle]
|
|
26
|
+
pub unsafe extern "C" fn castrum_task_submit(
|
|
27
|
+
op: u32,
|
|
28
|
+
args: *const u8,
|
|
29
|
+
args_len: usize,
|
|
30
|
+
task_id: usize,
|
|
31
|
+
) -> u32 {
|
|
32
|
+
if args.is_null() && args_len != 0 {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
let owned: Vec<u8> = if args_len == 0 {
|
|
36
|
+
Vec::new()
|
|
37
|
+
} else {
|
|
38
|
+
slice::from_raw_parts(args, args_len).to_vec()
|
|
39
|
+
};
|
|
40
|
+
u32::from(task::submit_op(op, owned, task_id as u64))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Drain finished tasks into `out` (packed `[u32 count][entry…]`).
|
|
44
|
+
/// Returns bytes written, `0` when nothing is pending, or the EXACT required
|
|
45
|
+
/// size when `out` is too small (nothing is consumed in that case).
|
|
46
|
+
///
|
|
47
|
+
/// # Safety
|
|
48
|
+
/// `out` must be valid for writes of `out_cap` bytes.
|
|
49
|
+
#[no_mangle]
|
|
50
|
+
pub unsafe extern "C" fn castrum_task_drain(out: *mut u8, out_cap: usize) -> usize {
|
|
51
|
+
if out.is_null() {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
panic_guard(|| task::drain(slice::from_raw_parts_mut(out, out_cap)), 0)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Completions waiting to be drained.
|
|
58
|
+
#[no_mangle]
|
|
59
|
+
pub extern "C" fn castrum_task_pending() -> u32 {
|
|
60
|
+
task::pending()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Submit an op that writes its result DIRECTLY into `out` (zero-copy output):
|
|
64
|
+
/// the completion body is the 8-byte LE written length, not the data, so JS
|
|
65
|
+
/// resolves a view of the buffer it already owns. A too-small `out` completes
|
|
66
|
+
/// with status 3 (TOO_SMALL) and the exact required size as the body; status 4
|
|
67
|
+
/// (UNSUPPORTED) means the op has no `_into` form and the caller should fall
|
|
68
|
+
/// back to `castrum_task_submit`.
|
|
69
|
+
///
|
|
70
|
+
/// Returns `1` accepted, `0` rejected.
|
|
71
|
+
///
|
|
72
|
+
/// # Safety
|
|
73
|
+
/// `args` must be valid for reads of `args_len` bytes. `out` must be valid for
|
|
74
|
+
/// writes of `out_cap` bytes and must stay alive (and untouched by JS) until the
|
|
75
|
+
/// completion for `task_id` is drained.
|
|
76
|
+
#[no_mangle]
|
|
77
|
+
pub unsafe extern "C" fn castrum_task_submit_out(
|
|
78
|
+
op: u32,
|
|
79
|
+
args: *const u8,
|
|
80
|
+
args_len: usize,
|
|
81
|
+
task_id: usize,
|
|
82
|
+
out: *mut u8,
|
|
83
|
+
out_cap: usize,
|
|
84
|
+
) -> u32 {
|
|
85
|
+
if (args.is_null() && args_len != 0) || out.is_null() || out_cap == 0 {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
let owned: Vec<u8> = if args_len == 0 {
|
|
89
|
+
Vec::new()
|
|
90
|
+
} else {
|
|
91
|
+
slice::from_raw_parts(args, args_len).to_vec()
|
|
92
|
+
};
|
|
93
|
+
u32::from(task::submit_op_into(
|
|
94
|
+
op,
|
|
95
|
+
owned,
|
|
96
|
+
task_id as u64,
|
|
97
|
+
out as usize,
|
|
98
|
+
out_cap,
|
|
99
|
+
))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Mark `task_id` cancelled. Returns `1` when newly marked, `0` otherwise.
|
|
103
|
+
#[no_mangle]
|
|
104
|
+
pub extern "C" fn castrum_task_cancel(task_id: usize) -> u32 {
|
|
105
|
+
u32::from(task::mark_cancelled(task_id as u64))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Submit an op whose PAYLOAD is read IN PLACE from the caller's buffer
|
|
109
|
+
/// (zero-copy INPUT). Only the `hdr_len` header bytes are copied, so a large
|
|
110
|
+
/// argument never crosses the boundary — without this, offloading a big
|
|
111
|
+
/// compression costs a full-size copy on the JS thread and measured WORSE than
|
|
112
|
+
/// the synchronous built-in (docs/RND-CONCURRENCY.md §7b).
|
|
113
|
+
///
|
|
114
|
+
/// Returns `1` accepted, `0` rejected.
|
|
115
|
+
///
|
|
116
|
+
/// # Safety
|
|
117
|
+
/// `hdr` must be valid for reads of `hdr_len` bytes. `data` must be valid for
|
|
118
|
+
/// reads of `data_len` bytes and must stay alive (and unmodified by JS) until
|
|
119
|
+
/// the completion for `task_id` is drained.
|
|
120
|
+
#[no_mangle]
|
|
121
|
+
pub unsafe extern "C" fn castrum_task_submit_slice(
|
|
122
|
+
op: u32,
|
|
123
|
+
hdr: *const u8,
|
|
124
|
+
hdr_len: usize,
|
|
125
|
+
data: *const u8,
|
|
126
|
+
data_len: usize,
|
|
127
|
+
task_id: usize,
|
|
128
|
+
) -> u32 {
|
|
129
|
+
if (hdr.is_null() && hdr_len != 0) || (data.is_null() && data_len != 0) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
// The header is small, so copying it keeps the caller free to reuse a
|
|
133
|
+
// scratch buffer; the payload is never copied.
|
|
134
|
+
let owned = if hdr_len == 0 {
|
|
135
|
+
Vec::new()
|
|
136
|
+
} else {
|
|
137
|
+
slice::from_raw_parts(hdr, hdr_len).to_vec()
|
|
138
|
+
};
|
|
139
|
+
u32::from(task::submit_slice_op(
|
|
140
|
+
op,
|
|
141
|
+
owned,
|
|
142
|
+
data as usize,
|
|
143
|
+
data_len,
|
|
144
|
+
task_id as u64,
|
|
145
|
+
))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Register the thread-safe doorbell trampoline address (`0` disables it).
|
|
149
|
+
/// Returns `1` on success.
|
|
150
|
+
///
|
|
151
|
+
/// # Safety
|
|
152
|
+
/// `cb` must be a live `void()` C trampoline that is safe to call from any
|
|
153
|
+
/// thread (a `bun:ffi` `JSCallback({ threadsafe: true }).ptr`).
|
|
154
|
+
#[no_mangle]
|
|
155
|
+
pub extern "C" fn castrum_task_set_doorbell(cb: *const std::os::raw::c_void) -> u32 {
|
|
156
|
+
task::set_doorbell(cb as usize);
|
|
157
|
+
1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Stop the pool and drop pending completions. Returns `0`.
|
|
161
|
+
#[no_mangle]
|
|
162
|
+
pub extern "C" fn castrum_task_shutdown() -> u32 {
|
|
163
|
+
task::shutdown();
|
|
164
|
+
task::completion::clear();
|
|
165
|
+
0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Configured pool worker count (0 before the pool starts).
|
|
169
|
+
#[no_mangle]
|
|
170
|
+
pub extern "C" fn castrum_task_threads() -> u32 {
|
|
171
|
+
task::threads()
|
|
172
|
+
}
|
package/rust/http/form.rs
CHANGED
|
@@ -108,8 +108,12 @@ mod tests {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
#[test]
|
|
111
|
-
fn
|
|
112
|
-
|
|
111
|
+
fn form_parse_malformed_percent_falls_back_to_raw() {
|
|
112
|
+
let packed = form_parse_packed_vec(b"a=%ZZ").unwrap();
|
|
113
|
+
assert_eq!(
|
|
114
|
+
crate::test_support::decode_packed_pairs(&packed),
|
|
115
|
+
vec![(b"a".to_vec(), b"%ZZ".to_vec())]
|
|
116
|
+
);
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
#[test]
|