@cavelang/solver-z3 0.35.0 → 0.36.0
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/BENCHMARK.md +56 -3
- package/README.md +267 -9
- package/dist/scripts/benchmark.js +35 -13
- package/dist/scripts/benchmark.js.map +1 -1
- package/dist/scripts/memory-stress.d.ts +2 -0
- package/dist/scripts/memory-stress.d.ts.map +1 -0
- package/dist/scripts/memory-stress.js +74 -0
- package/dist/scripts/memory-stress.js.map +1 -0
- package/dist/src/cleanup.d.ts +8 -0
- package/dist/src/cleanup.d.ts.map +1 -0
- package/dist/src/cleanup.js +65 -0
- package/dist/src/cleanup.js.map +1 -0
- package/dist/src/close-workers.d.ts +12 -0
- package/dist/src/close-workers.d.ts.map +1 -0
- package/dist/src/close-workers.js +27 -0
- package/dist/src/close-workers.js.map +1 -0
- package/dist/src/deadline.d.ts +7 -0
- package/dist/src/deadline.d.ts.map +1 -0
- package/dist/src/deadline.js +46 -0
- package/dist/src/deadline.js.map +1 -0
- package/dist/src/error-message.d.ts +3 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +10 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/runtime.js +137 -72
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/with-release.d.ts +3 -0
- package/dist/src/with-release.d.ts.map +1 -0
- package/dist/src/with-release.js +25 -0
- package/dist/src/with-release.js.map +1 -0
- package/dist/src/workflow-fixture.d.ts.map +1 -1
- package/dist/src/workflow-fixture.js +27 -10
- package/dist/src/workflow-fixture.js.map +1 -1
- package/package.json +4 -3
- package/scripts/benchmark.ts +35 -12
- package/scripts/memory-stress.ts +72 -0
- package/src/cleanup.ts +55 -0
- package/src/close-workers.ts +28 -0
- package/src/deadline.ts +40 -0
- package/src/error-message.ts +5 -0
- package/src/runtime.ts +132 -70
- package/src/with-release.ts +21 -0
- package/src/workflow-fixture.ts +30 -11
package/BENCHMARK.md
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
# Z3 feasibility benchmark
|
|
2
2
|
|
|
3
|
+
## Current macOS measurements
|
|
4
|
+
|
|
5
|
+
Measured on 2026-09-08 on macOS arm64 with `z3-solver` 5.2.0, whose embedded
|
|
6
|
+
backend reports `Z3 5.1.0.0`. Each Node version ran in a separate process,
|
|
7
|
+
sequentially, against the current worktree after the solver input-capture and
|
|
8
|
+
explanation-ownership changes. The fixture uses `Solve.run`; it does not measure
|
|
9
|
+
explanation construction. Each run completed one first optimized check and 25
|
|
10
|
+
warm checks, all returning `optimal`, then closed the runtime and exited.
|
|
11
|
+
|
|
12
|
+
| Measurement | Node 24.16.0 | Node 26.5.0 |
|
|
13
|
+
|---|---:|---:|
|
|
14
|
+
| Cold Wasm initialization | 69 ms | 75 ms |
|
|
15
|
+
| First optimized check | 85.69 ms | 95.06 ms |
|
|
16
|
+
| Warm mean | 5.16 ms | 5.15 ms |
|
|
17
|
+
| Warm p50 | 5 ms | 4.92 ms |
|
|
18
|
+
| Warm p95 | 6.09 ms | 5.97 ms |
|
|
19
|
+
| RSS before initialization | 149,045,248 bytes | 151,977,984 bytes |
|
|
20
|
+
| RSS after 26 checks | 284,917,760 bytes | 248,299,520 bytes |
|
|
21
|
+
| Peak process RSS | 295,780,352 bytes | 256,851,968 bytes |
|
|
22
|
+
|
|
23
|
+
Both runs measured 25 installed dependency files totaling 35,820,846 bytes,
|
|
24
|
+
including 34,938,413 bytes of Wasm. The sum of individually gzip-compressed files
|
|
25
|
+
was 8,066,680 bytes. This measures installed package contents, not an npm tarball.
|
|
26
|
+
|
|
27
|
+
These are single-process observations with 25 warm samples each, not a latency
|
|
28
|
+
budget or a controlled comparison against the earlier implementation. Platform,
|
|
29
|
+
dependency version, cache state and concurrent machine activity can affect the
|
|
30
|
+
results; the Linux measurements below are not a performance baseline for this
|
|
31
|
+
Mac. Use `pnpm --filter @cavelang/solver-z3 benchmark` to reproduce the fixture.
|
|
32
|
+
|
|
33
|
+
## Earlier Linux measurements
|
|
34
|
+
|
|
3
35
|
Measured on 2026-07-15 on Linux x64 with `z3-solver` 4.16.0, both at
|
|
4
|
-
the
|
|
5
|
-
runtime.
|
|
36
|
+
the former minimum Node.js 22.18 runtime and the available Node.js 24.14
|
|
37
|
+
runtime. These figures describe that measured dependency version, not the
|
|
38
|
+
current adapter's performance. The runtime CI matrix now runs portable solver
|
|
39
|
+
and adapter suites plus the large forced-GC cleanup diagnostic on its pinned
|
|
40
|
+
Node 24 and 26 / Linux, macOS, and Windows entries.
|
|
6
41
|
|
|
7
42
|
The fixture optimizes an exact architecture-choice model with a finite enum,
|
|
8
43
|
bounded integer, exact-real cost, conditional constraint, and lexicographic
|
|
@@ -30,6 +65,24 @@ with its published dependencies in a clean consumer, solved a model, called
|
|
|
30
65
|
`close()`, and exited without a worker hang. `scripts/benchmark.ts` reproduces
|
|
31
66
|
the local measurements.
|
|
32
67
|
|
|
68
|
+
Each successful JSON summary identifies the installed `z3-solver` package
|
|
69
|
+
version separately from its embedded backend version, the canonical fixture
|
|
70
|
+
model digest, and the number of verified checks. Keep these fields with saved
|
|
71
|
+
measurements so results can be matched to the dependency and workload used.
|
|
72
|
+
|
|
73
|
+
The current benchmark verifies every answer, not only its status: optimality
|
|
74
|
+
must be proved, the selected architecture must be `monolith`, team size must be
|
|
75
|
+
12, and both assigned monthly cost and the sole objective value must equal the
|
|
76
|
+
exact rational `321/4` (80.25). A wrong answer fails without publishing timing
|
|
77
|
+
statistics. Verification runs after each timed solve interval. The measurements
|
|
78
|
+
above predate this stronger answer assertion and retain their original status-only
|
|
79
|
+
verification scope.
|
|
80
|
+
|
|
81
|
+
The benchmark closes its runtime in a `finally` block after initialization,
|
|
82
|
+
including when the first check or a warm check returns an unexpected status,
|
|
83
|
+
or a solve throws. Failed runs retain their diagnostic and do not emit a
|
|
84
|
+
successful measurement summary.
|
|
85
|
+
|
|
33
86
|
## Decision
|
|
34
87
|
|
|
35
88
|
Accept Z3 as an optional Node.js backend. It produces satisfying assignments,
|
|
@@ -38,7 +91,7 @@ exact decimals round-trip as rationals; actual deadlines return `unknown`;
|
|
|
38
91
|
simultaneous calls are queued; and explicit shutdown terminates workers.
|
|
39
92
|
|
|
40
93
|
Do not add it to `@cavelang/solver`, the CLI, MCP, or website dependency graph
|
|
41
|
-
by default. Cold start, roughly
|
|
94
|
+
by default. Cold start, roughly 35.8 MB installed size in the current measurement, and process-wide
|
|
42
95
|
threaded-Wasm state are material costs. A consumer should opt into
|
|
43
96
|
`@cavelang/solver-z3`, initialize once, reuse it, and close it only during
|
|
44
97
|
process shutdown. Browser shipping remains a separate decision gate.
|
package/README.md
CHANGED
|
@@ -5,6 +5,18 @@ Optional Node.js adapter from CAVE's solver-neutral model to the official
|
|
|
5
5
|
Wasm module is dynamically imported and initialized only when `create()` is
|
|
6
6
|
called.
|
|
7
7
|
|
|
8
|
+
| Task | Start here |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| Run a first model and close its runtime | [Quick start](#quick-start) |
|
|
11
|
+
| Manage shared runtime ownership, failures and limits | [Runtime contract](#runtime-contract) |
|
|
12
|
+
| Understand supported models and proof requirements | [Compilation semantics](#compilation-semantics) |
|
|
13
|
+
| Check the browser support boundary | [Browser delivery decision](#browser-delivery-decision) |
|
|
14
|
+
| Run the named workflow from the CLI | [Named workflow CLI fixture](#named-workflow-cli-fixture) |
|
|
15
|
+
| Investigate timeout and interrupt recovery | [Deadline responsiveness diagnostic](#deadline-responsiveness-diagnostic) |
|
|
16
|
+
| Investigate memory pressure and runtime recovery | [Memory recovery diagnostic](#memory-recovery-diagnostic) |
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
8
20
|
```ts
|
|
9
21
|
import { Model, Solve } from '@cavelang/solver'
|
|
10
22
|
import { create } from '@cavelang/solver-z3'
|
|
@@ -28,12 +40,28 @@ const model: Model.t = {
|
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
const z3 = await create()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
43
|
+
try {
|
|
44
|
+
const result = await Solve.run(z3, model, {
|
|
45
|
+
limits: { timeoutMs: 2_000 }
|
|
46
|
+
})
|
|
47
|
+
console.log(result)
|
|
48
|
+
} finally {
|
|
49
|
+
await z3.close()
|
|
50
|
+
}
|
|
35
51
|
```
|
|
36
52
|
|
|
53
|
+
This example owns its runtime and closes it even when model validation or solving
|
|
54
|
+
throws. In a shared service, keep runtime ownership at the service lifecycle: an
|
|
55
|
+
individual request must not close the runtime used by other requests.
|
|
56
|
+
|
|
57
|
+
Use `Solve.run` or `Solve.runWithExplanation` for application requests. These
|
|
58
|
+
entry points validate options and limits, capture and validate the model, freeze
|
|
59
|
+
the submitted graph, negotiate capabilities and capture the adapter result.
|
|
60
|
+
The runtime's `solve` method is the low-level adapter interface: it queues the
|
|
61
|
+
supplied model for compilation and assumes that preparation has already happened.
|
|
62
|
+
Its targeted timeout and cycle checks do not replace model validation or protect
|
|
63
|
+
a directly supplied mutable model from changes while queued.
|
|
64
|
+
|
|
37
65
|
## Runtime contract
|
|
38
66
|
|
|
39
67
|
- `create()` initializes once and returns the same process runtime until it is
|
|
@@ -41,10 +69,55 @@ await z3.close()
|
|
|
41
69
|
- Solve requests enter an explicit FIFO queue. Z3's TypeScript/Wasm binding is
|
|
42
70
|
not thread-safe, so simultaneous requests never run checks concurrently or
|
|
43
71
|
share solver state accidentally.
|
|
44
|
-
|
|
72
|
+
Feasibility, optimization and unsatisfiable-core checks release their owned
|
|
73
|
+
solver/optimizer only after the operation settles. If both the operation and
|
|
74
|
+
release fail, both errors survive in order, with the operation error as cause;
|
|
75
|
+
neither an unprintable value nor a release error hides the original failure.
|
|
76
|
+
A release failure alone is propagated unchanged. Each release is attempted
|
|
77
|
+
once; this does not guarantee recovery from damaged native state.
|
|
78
|
+
- `close()` waits for queued checks, requests worker termination and awaits
|
|
79
|
+
Node's worker exit acknowledgments before clearing Emscripten's pool. It is
|
|
45
80
|
idempotent. Short-lived commands must call it before exit.
|
|
81
|
+
Closing marks the runtime unavailable to new solves immediately, but existing
|
|
82
|
+
queued work settles before worker termination begins. `close()` does not
|
|
83
|
+
forcibly terminate an active native check; if that check never settles, close
|
|
84
|
+
remains pending as well. The check timeout and shutdown therefore share the
|
|
85
|
+
cooperative-cancellation boundary.
|
|
86
|
+
If worker termination fails, shutdown still attempts every worker in that
|
|
87
|
+
batch and awaits all attempts before rejecting. It retains the pool's message
|
|
88
|
+
handlers; one failure is rethrown unchanged and multiple failures are grouped
|
|
89
|
+
in an `AggregateError` whose message includes each failure diagnostic. A failed runtime shutdown remains failed on subsequent
|
|
90
|
+
`close()` or `create()` calls.
|
|
91
|
+
Pending cleanup messages retain their handlers until worker exit; shutdown
|
|
92
|
+
does not assume they arrive within one timer turn. The pool can contain
|
|
93
|
+
persistent workers, so shutdown does not wait for the running pool to become
|
|
94
|
+
empty on its own.
|
|
95
|
+
Calls to `create()` during shutdown wait for it to finish, then share one
|
|
96
|
+
new runtime. They never receive the closing instance. Repeated calls to the
|
|
97
|
+
old runtime's `close()` cannot clear the replacement.
|
|
46
98
|
- Every check receives Z3's internal timeout plus an independent wall-clock
|
|
47
|
-
interrupt.
|
|
99
|
+
interrupt. After expiry, CAVE repeats the interrupt every 50 ms while the
|
|
100
|
+
check remains pending and stops the timer when the check resolves or rejects.
|
|
101
|
+
An exception from an interrupt request stays within the solve operation instead
|
|
102
|
+
of escaping the timer callback. Cancellation retries continue, retaining the
|
|
103
|
+
first interrupt failure without accumulating repeated errors. The helper waits
|
|
104
|
+
for the native check to settle before reporting failure, so solver/optimizer
|
|
105
|
+
cleanup cannot race pending work. If the check also rejects, its error and the
|
|
106
|
+
first interrupt error are retained together, with the check error as cause.
|
|
107
|
+
Their combined message includes both underlying diagnostics, so the public
|
|
108
|
+
backend-error reason retains them after error conversion. Unprintable thrown
|
|
109
|
+
values use a safe placeholder; later interrupt failures do not replace the first.
|
|
110
|
+
Cancellation is cooperative: the timer does not impose a hard process deadline,
|
|
111
|
+
and initialization, queue wait and model compilation are outside this check timer.
|
|
112
|
+
A live regression times out an 80-variable pigeonhole model, then immediately
|
|
113
|
+
solves a satisfiable Boolean model on the same runtime. It checks the returned
|
|
114
|
+
assignment and preserves the earlier timeout result. This verifies recovery
|
|
115
|
+
for that workload, not termination or recovery from every native stall.
|
|
116
|
+
This adapter accepts `timeoutMs` in 1..2147483647; larger deadlines
|
|
117
|
+
return `unknown` with a `backend-error` before compilation, preventing timer
|
|
118
|
+
overflow from becoming an immediate interrupt. This adapter-specific bound
|
|
119
|
+
does not change other adapters' portable limit contract.
|
|
120
|
+
The portable memory limit is applied through Z3's process-wide
|
|
48
121
|
`memory_max_size` parameter while requests are serialized.
|
|
49
122
|
- Preflight declaration and expression limits remain in `@cavelang/solver`.
|
|
50
123
|
This adapter additionally enforces `maxOutputBytes` on the serialized result.
|
|
@@ -55,17 +128,66 @@ await z3.close()
|
|
|
55
128
|
## Compilation semantics
|
|
56
129
|
|
|
57
130
|
Booleans, bounded integers, exact rationals, conditionals, arithmetic, and
|
|
58
|
-
hard constraints compile directly.
|
|
131
|
+
hard constraints compile directly. A live truth-table regression checks negation,
|
|
132
|
+
conjunction, disjunction, implication, Boolean equality/inequality and conditional
|
|
133
|
+
selection for all four Boolean input pairs. It also checks a conditional whose
|
|
134
|
+
branches mix an exact real half with an integer, preserving the selected value.
|
|
135
|
+
Decimal strings are reduced by
|
|
59
136
|
`@cavelang/solver` and passed to Z3 as `bigint` numerator/denominator pairs,
|
|
60
137
|
never as JavaScript floating point. Finite enums use bounded integer codes and
|
|
61
138
|
are decoded through the domain's canonical lexical order, so declaration
|
|
62
139
|
reordering cannot change an unconstrained assignment.
|
|
140
|
+
Enum literal lookup uses a value-to-code map built once from each sorted domain
|
|
141
|
+
for the compilation call. This removes repeated linear domain scans while
|
|
142
|
+
retaining the sorted array for decoding assignments. The extra maps use space
|
|
143
|
+
proportional to enum members and are rebuilt for each request; domain mutation
|
|
144
|
+
and reordering cannot reuse stale codes. Empty strings, prototype-like names
|
|
145
|
+
and distinct Unicode spellings retain their exact member identities.
|
|
146
|
+
|
|
147
|
+
Expression compilation uses an explicit traversal stack and caches shared
|
|
148
|
+
subexpressions within one model. Models accepted with raised depth limits can
|
|
149
|
+
reach Z3 without recursive JavaScript call-stack exhaustion. Repeated operands
|
|
150
|
+
remain repeated operations, and compiled expressions never cross requests.
|
|
151
|
+
Wide conjunctions and disjunctions compile in groups of at most 1,024 operands,
|
|
152
|
+
preserving every operand through associativity. This avoids JavaScript argument
|
|
153
|
+
limits in both the adapter and the Z3 binding, including for expressions within
|
|
154
|
+
the default node budget.
|
|
155
|
+
Generated variable-domain bounds are also submitted in batches of at most
|
|
156
|
+
1,024 assertions, for both feasibility and optimization. Raising the variable
|
|
157
|
+
limit therefore does not create one unbounded JavaScript argument list. Large
|
|
158
|
+
models may also need an explicit `maxNumericDigits` increase for their aggregate
|
|
159
|
+
bound and literal digits; raising the variable limit does not raise that budget.
|
|
63
160
|
|
|
64
161
|
Named hard constraints use tracked Boolean literals, so an unsatisfiable core
|
|
65
|
-
maps back to portable constraint IDs.
|
|
162
|
+
maps back to portable constraint IDs. After optimization proves infeasibility,
|
|
163
|
+
a separate tracked feasibility check extracts the optional core. That check
|
|
164
|
+
has its own configured timeout; if it returns no infeasibility proof, the
|
|
165
|
+
original result retains its proof with no core. A live regression checks the
|
|
166
|
+
same stable core IDs through both feasibility and optimization. Trackers use fresh Z3 symbols to avoid
|
|
167
|
+
collisions with user variable names; requesting a core cannot add a constraint
|
|
168
|
+
on a user variable through a shared symbol. Objective declaration order is Z3's
|
|
66
169
|
lexicographic order. Explicit weighted soft constraints form the final,
|
|
67
170
|
lowest-priority objective; their weights are never inferred from CAVE belief
|
|
68
|
-
confidence.
|
|
171
|
+
confidence. A live tradeoff regression minimizes `x` and `y` under `x + y >= 10`
|
|
172
|
+
with both variables bounded to 0..10. Reversing objective order switches the
|
|
173
|
+
assignment from `(0, 10)` to `(10, 0)`; a weight-1,000,000 preference for `x = 5`
|
|
174
|
+
cannot override either explicit priority. Returned objective values retain
|
|
175
|
+
declaration order.
|
|
176
|
+
`Solve.run` charges soft weights to the model's aggregate `maxNumericDigits`
|
|
177
|
+
allowance before exact validation and backend invocation. Compilation reduces each weight and passes its
|
|
178
|
+
`numerator/denominator` text directly to Z3; it does not expand repeating decimals
|
|
179
|
+
or convert weights to JavaScript numbers. A real-backend regression swaps two
|
|
180
|
+
nonterminating rational weights that collapse to the same binary64 value and
|
|
181
|
+
verifies that the chosen Boolean follows the stronger exact weight both ways.
|
|
182
|
+
Fraction formatting copies the normalized components; exact reduction and native
|
|
183
|
+
optimization remain separate costs. Numeric preflight is not a wall-clock bound,
|
|
184
|
+
and the check timer does not cover compilation.
|
|
185
|
+
|
|
186
|
+
An `optimal` result requires each explicit objective's lower and upper bounds
|
|
187
|
+
to be the same finite rational, attained by the returned assignment. This check
|
|
188
|
+
uses exact arithmetic for every lexicographic objective. Unbounded objectives,
|
|
189
|
+
unattained strict bounds, or a model that does not attain the proved bound
|
|
190
|
+
return `unknown` with reason `indeterminate`, never a proof of optimality.
|
|
69
191
|
|
|
70
192
|
See [BENCHMARK.md](BENCHMARK.md) for artifact, initialization, solve, memory,
|
|
71
193
|
packaging, and lifecycle measurements. The spike accepts Z3 for optional
|
|
@@ -103,3 +225,139 @@ uses the workflow API, so model validation, capability negotiation, limits,
|
|
|
103
225
|
deterministic tie-breaking, and `unknown` semantics cannot be bypassed. The
|
|
104
226
|
JSON output is a versioned workflow/explanation report. The fixture's relative
|
|
105
227
|
cost formula is deliberately illustrative and does not use belief confidence.
|
|
228
|
+
Argument errors return exit code 2. Runtime startup, workflow execution and
|
|
229
|
+
shutdown failures return code 1 with empty stdout and a diagnostic on stderr.
|
|
230
|
+
If execution and shutdown both fail, the diagnostic retains the execution error
|
|
231
|
+
followed by the shutdown error. A successful report is emitted only after the
|
|
232
|
+
owned runtime closes successfully. Programmatic `runWorkflowFixture` calls
|
|
233
|
+
return the same output fields and close only runtimes created by the fixture;
|
|
234
|
+
caller-supplied adapters remain caller-owned. Exception formatting is guarded:
|
|
235
|
+
values whose message or string conversion throws produce
|
|
236
|
+
`[unprintable thrown value]`, so diagnostic rendering cannot skip owned cleanup
|
|
237
|
+
or replace the error result. Backend-error diagnostics use the same fallback.
|
|
238
|
+
|
|
239
|
+
All four operations accept `--max-explanation-bits <positive integer>` to set
|
|
240
|
+
the local constraint-evaluation size budget (default: 1,000,000). The value is
|
|
241
|
+
validated before loading Z3 and travels in the report's resolved limits.
|
|
242
|
+
For example, `cave-solver-workflow architecture feasibility --max-explanation-bits 2000000`
|
|
243
|
+
allows a larger local evaluation than the default. Exceeding the budget leaves
|
|
244
|
+
the backend outcome intact and marks affected constraints indeterminate; retry
|
|
245
|
+
with a larger value to request a fresh evaluation. This conservative fraction-size
|
|
246
|
+
budget is separate from `--timeout-ms` and backend memory limits.
|
|
247
|
+
All four operations also accept `--max-explanation-work <positive integer>`
|
|
248
|
+
(default: 10,000,000) for cumulative estimated-bit charges within each report.
|
|
249
|
+
It is validated before Z3 starts and recorded with the resolved limits. Exhaustion
|
|
250
|
+
marks affected local evaluations indeterminate while preserving the backend result;
|
|
251
|
+
use a larger allowance for a fresh report requiring more distinct arithmetic.
|
|
252
|
+
This counter does not cover every report phase or guarantee elapsed time.
|
|
253
|
+
The CLI accepts `--timeout-ms` only in Z3's supported range, 1..2147483647.
|
|
254
|
+
Larger values fail as argument errors before adapter startup, rather than
|
|
255
|
+
producing a backend-error workflow report. Both endpoints of the range remain
|
|
256
|
+
valid, and a corrected invocation can run normally.
|
|
257
|
+
|
|
258
|
+
The real-adapter suite also checks sensitivity batch preflight and provenance.
|
|
259
|
+
A later sample that exceeds the generated-model digit budget is rejected before
|
|
260
|
+
any Z3 submission. Raising the limit solves both distinct sample models, reports
|
|
261
|
+
their exact assignments and the observed transition, and retains the authored
|
|
262
|
+
model's digest in the batch, point and explanation reports. Generated sample
|
|
263
|
+
models have their own distinct digests; they do not replace source identity.
|
|
264
|
+
|
|
265
|
+
## Deadline responsiveness diagnostic
|
|
266
|
+
|
|
267
|
+
Run `node scripts/z3-deadline-probe.mjs` from the repository root to exercise
|
|
268
|
+
three fresh processes, each issuing ten 1 ms pigeonhole requests on one runtime
|
|
269
|
+
and following every timeout result with a satisfiable recovery request. The
|
|
270
|
+
parent imposes a 15-second allowance per process, retains partial event output
|
|
271
|
+
and terminates an overrun. This is a diagnostic harness limit, not an adapter
|
|
272
|
+
guarantee; the probe exits unsuccessfully if any process fails or overruns.
|
|
273
|
+
|
|
274
|
+
A Node 26 suite observed a 208.5-second timeout test. The initial bounded probe
|
|
275
|
+
reproduced a stall in one of three processes; the other 25 requests completed in
|
|
276
|
+
118–191 ms. Retrying interrupts while native work remains pending then completed
|
|
277
|
+
all 30 requests and recovery checks on each supported Node major: 118–220 ms on
|
|
278
|
+
Node 24.21.0 and 113–237 ms on Node 26.8.1. These complete-call times include
|
|
279
|
+
preparation. [Partial reproduction events and subsequent results](../../benchmarks/z3-deadline-retry-review.json)
|
|
280
|
+
retain the failure evidence and source hashes. A deterministic test verifies
|
|
281
|
+
repeated cancellation after a check ignores the first interrupt, and timer
|
|
282
|
+
cleanup after settlement. The precise native cause remains unproven; successful
|
|
283
|
+
retries do not establish a hard elapsed-time bound.
|
|
284
|
+
|
|
285
|
+
A later [current-source checkpoint](../../benchmarks/z3-deadline-current-review.json)
|
|
286
|
+
also completes all 30 timeout/recovery pairs per major and closes all three
|
|
287
|
+
runtimes: 112–220 ms per complete call on Node 26.8.1 and 114–226 ms on Node
|
|
288
|
+
24.21.0. The probe now fingerprints every solver and Z3 adapter TypeScript
|
|
289
|
+
source, including the deadline helper and error formatting, plus its own source
|
|
290
|
+
and the lockfile. Earlier reports retain their original narrower fingerprint
|
|
291
|
+
scope; the historical stalled run remains evidence for the unresolved native
|
|
292
|
+
cause. These finite runs do not guarantee recovery from every native stall.
|
|
293
|
+
|
|
294
|
+
## Memory recovery diagnostic
|
|
295
|
+
|
|
296
|
+
Run `pnpm --filter @cavelang/solver-z3 test:memory-stress` to exercise ten
|
|
297
|
+
memory-limit failures followed by optimization of 3,000 bounded variables in
|
|
298
|
+
the same runtime, with forced garbage collection during asynchronous checks.
|
|
299
|
+
The command enables `--expose-gc`, logs environment and phase metadata, and
|
|
300
|
+
verifies each recovery's complete assignment and exact optimum. Assertion
|
|
301
|
+
failures exit unsuccessfully after worker cleanup. This diagnostic is separate
|
|
302
|
+
from the ordinary test suite and does not establish recovery from every fatal
|
|
303
|
+
Wasm error.
|
|
304
|
+
|
|
305
|
+
A [current-source checkpoint](../../benchmarks/z3-memory-current-review.json)
|
|
306
|
+
passes all ten default cycles on Node 26.8.1 and 24.21.0: each 1 MiB memory-limit
|
|
307
|
+
failure is followed by the exact optimum of 3,000 and validation of all 3,000
|
|
308
|
+
assignments in the same runtime. Both processes exit cleanly after runtime
|
|
309
|
+
closure. The record includes phase output and solver/adapter source fingerprints.
|
|
310
|
+
This checkpoint does not rerun `--large` or cover every fatal native error.
|
|
311
|
+
|
|
312
|
+
Add `--large` to run three cycles that insert the original 75,000-variable
|
|
313
|
+
domain-bound contradiction in both feasibility and optimization modes between
|
|
314
|
+
each memory-limit failure and the 3,000-variable recovery probe. The large
|
|
315
|
+
fixture explicitly permits 75,000 variables and 150,002 numeric digits: two
|
|
316
|
+
one-digit bounds per variable, one constraint literal and an optional objective
|
|
317
|
+
literal. These diagnostic settings let the intended backend stress run reach Z3
|
|
318
|
+
without changing production validation defaults. Large phases
|
|
319
|
+
log the complete result, wall time, and Node process memory counters. These
|
|
320
|
+
counters describe process memory, not Z3's internal allocation accounting.
|
|
321
|
+
The diagnostic submits subsequent requests before asserting large-phase
|
|
322
|
+
results, preserving evidence of whether the same runtime remains usable.
|
|
323
|
+
The runtime CI matrix runs the large diagnostic on each configured runtime/OS
|
|
324
|
+
entry. Each large solve has a 120-second budget and each recovery solve has a
|
|
325
|
+
30-second budget; the CI step has a
|
|
326
|
+
20-minute timeout to bound native hangs across all three cycles. Its solve
|
|
327
|
+
budgets total up to 14 minutes, leaving at least six minutes for initialization,
|
|
328
|
+
validation, compilation, forced GC, logging and cleanup. The enclosing
|
|
329
|
+
runtime job allows 40 minutes for that step plus setup, native suites and CLI
|
|
330
|
+
checks; a runtime-policy test keeps at least 15 minutes outside the stress step. The larger
|
|
331
|
+
budget accommodates shared-runner performance: Windows CI exceeded the previous
|
|
332
|
+
30-second feasibility deadline while the following optimization and recovery
|
|
333
|
+
still returned the expected results. A later Windows run reached recovery but
|
|
334
|
+
exceeded its default 10-second budget, so that phase now also has an explicit
|
|
335
|
+
diagnostic budget. This diagnostic checks cleanup correctness;
|
|
336
|
+
it does not impose a 30-second performance requirement. Workload size, forced
|
|
337
|
+
collection frequency and exact result assertions remain unchanged.
|
|
338
|
+
|
|
339
|
+
The adapter defers native reference cleanup while its asynchronous feasibility
|
|
340
|
+
or optimization check runs, draining releases once the native promise settles,
|
|
341
|
+
including on rejection. The binding's garbage-collection finalizers otherwise
|
|
342
|
+
call native reference decrements outside its check mutex, allowing AST deletion
|
|
343
|
+
to overlap the solver thread. The large diagnostic exposed both an incorrect
|
|
344
|
+
recovery optimum (`0` instead of `3000`) and a memory-access crash in that
|
|
345
|
+
cleanup path. The guard belongs to CAVE's private Z3 instance; it changes no
|
|
346
|
+
global JavaScript finalizer behavior and keeps synchronous cleanup immediate.
|
|
347
|
+
Queued releases are attempted in order even when an earlier release throws.
|
|
348
|
+
A lone cleanup failure propagates unchanged; multiple failures are retained in
|
|
349
|
+
an `AggregateError`. If the native check also failed, its error comes first and
|
|
350
|
+
remains the cause, followed by cleanup failures in release order. Unprintable
|
|
351
|
+
thrown values cannot interrupt diagnostic formatting or skip later releases.
|
|
352
|
+
Drained releases are not replayed by a subsequent check. This preserves cleanup
|
|
353
|
+
attempts and error ownership; it does not promise that damaged native state is
|
|
354
|
+
recoverable after a failed release.
|
|
355
|
+
A [post-fix native recovery checkpoint](../../benchmarks/z3-cleanup-recovery-review.json)
|
|
356
|
+
passes three large cycles on each supported Node major under forced garbage
|
|
357
|
+
collection, retaining the 75,000-variable checks and exact recovery optimum.
|
|
358
|
+
It verifies this workload and clean process exit, not arbitrary native failures.
|
|
359
|
+
|
|
360
|
+
Process lifecycle tests launch fixtures using native paths converted with
|
|
361
|
+
`fileURLToPath`, so Windows drive letters and percent-encoded checkout names
|
|
362
|
+
reach Node correctly. These tests exercise worker shutdown and runtime reopening
|
|
363
|
+
in a separate process.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
1
2
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import { gzipSync } from 'node:zlib';
|
|
5
6
|
import { performance } from 'node:perf_hooks';
|
|
6
|
-
import { Model, Solve } from '@cavelang/solver';
|
|
7
|
+
import { Canonical, Model, Solve } from '@cavelang/solver';
|
|
7
8
|
import { create } from '@cavelang/solver-z3';
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
const packageRoot = dirname(require.resolve('z3-solver/package.json'));
|
|
11
|
+
const dependencyVersion = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8')).version;
|
|
10
12
|
const files = async (directory) => {
|
|
11
13
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
12
14
|
return (await Promise.all(entries.map(async (entry) => {
|
|
@@ -49,23 +51,41 @@ const architecture = {
|
|
|
49
51
|
}],
|
|
50
52
|
objectives: [{ id: 'min-monthly-cost', direction: 'minimize', expression: ref('monthly-cost') }]
|
|
51
53
|
};
|
|
54
|
+
const verify = (result, phase) => {
|
|
55
|
+
if (result.status !== 'optimal')
|
|
56
|
+
throw new Error(`${phase} fixture returned ${result.status}`);
|
|
57
|
+
assert.equal(result.optimalityProved, true, `${phase}: optimality must be proved`);
|
|
58
|
+
const cost = { sort: 'real', numerator: '321', denominator: '4' };
|
|
59
|
+
assert.deepEqual(result.assignment, {
|
|
60
|
+
choice: { sort: 'enum', domain: 'architecture', value: 'monolith' },
|
|
61
|
+
'team-size': { sort: 'int', value: '12' },
|
|
62
|
+
'monthly-cost': cost
|
|
63
|
+
}, `${phase}: incorrect benchmark assignment`);
|
|
64
|
+
assert.equal(result.objectives.length, 1, `${phase}: expected one objective`);
|
|
65
|
+
assert.equal(result.objectives[0]?.objectiveId, 'min-monthly-cost');
|
|
66
|
+
assert.deepEqual(result.objectives[0]?.value, cost, `${phase}: incorrect exact optimum`);
|
|
67
|
+
};
|
|
52
68
|
const beforeRssBytes = process.memoryUsage().rss;
|
|
53
69
|
const runtime = await create();
|
|
54
|
-
|
|
55
|
-
const first = await Solve.run(runtime, architecture);
|
|
56
|
-
const firstCheckMs = performance.now() - firstStarted;
|
|
57
|
-
if (first.status !== 'optimal')
|
|
58
|
-
throw new Error(`benchmark fixture returned ${first.status}`);
|
|
70
|
+
let firstCheckMs;
|
|
59
71
|
const warmRuns = 25;
|
|
60
72
|
const warm = [];
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
try {
|
|
74
|
+
const firstStarted = performance.now();
|
|
75
|
+
const first = await Solve.run(runtime, architecture);
|
|
76
|
+
firstCheckMs = performance.now() - firstStarted;
|
|
77
|
+
verify(first, 'benchmark');
|
|
78
|
+
for (let index = 0; index < warmRuns; index += 1) {
|
|
79
|
+
const started = performance.now();
|
|
80
|
+
const result = await Solve.run(runtime, architecture);
|
|
81
|
+
const elapsed = performance.now() - started;
|
|
82
|
+
verify(result, 'warm');
|
|
83
|
+
warm.push(elapsed);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await runtime.close();
|
|
67
88
|
}
|
|
68
|
-
await runtime.close();
|
|
69
89
|
warm.sort((left, right) => left - right);
|
|
70
90
|
const mean = warm.reduce((sum, value) => sum + value, 0) / warm.length;
|
|
71
91
|
console.log(JSON.stringify({
|
|
@@ -73,6 +93,8 @@ console.log(JSON.stringify({
|
|
|
73
93
|
platform: `${process.platform}-${process.arch}`,
|
|
74
94
|
node: process.version,
|
|
75
95
|
backend: runtime.backend,
|
|
96
|
+
dependency: { name: 'z3-solver', version: dependencyVersion },
|
|
97
|
+
fixture: { modelDigest: Canonical.digest(architecture), verifiedChecks: warm.length + 1 },
|
|
76
98
|
artifacts: {
|
|
77
99
|
files: paths.length,
|
|
78
100
|
installedBytes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../../scripts/benchmark.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../../scripts/benchmark.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-stress.d.ts","sourceRoot":"","sources":["../../scripts/memory-stress.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as assert from 'node:assert/strict';
|
|
2
|
+
import { Model, Solve } from '@cavelang/solver';
|
|
3
|
+
import { create } from '@cavelang/solver-z3';
|
|
4
|
+
const gc = globalThis.gc;
|
|
5
|
+
assert.ok(gc, 'run this diagnostic with node --expose-gc');
|
|
6
|
+
assert.ok(process.argv.slice(2).every(argument => argument === '--large') && process.argv.length <= 3, 'usage: memory-stress.ts [--large]');
|
|
7
|
+
const large = process.argv.includes('--large');
|
|
8
|
+
const iterations = large ? 3 : 10;
|
|
9
|
+
const runtime = await create();
|
|
10
|
+
const count = 3_000;
|
|
11
|
+
const model = {
|
|
12
|
+
schema: Model.schema,
|
|
13
|
+
variables: Array.from({ length: count }, (_, index) => ({ id: `x${index}`, sort: 'int', min: 0, max: 1 })),
|
|
14
|
+
constraints: [],
|
|
15
|
+
objectives: [{
|
|
16
|
+
id: 'total', direction: 'maximize',
|
|
17
|
+
expression: { kind: 'add', operands: Array.from({ length: count }, (_, index) => ({ kind: 'variable', id: `x${index}` })) }
|
|
18
|
+
}]
|
|
19
|
+
};
|
|
20
|
+
const small = { schema: Model.schema, variables: [{ id: 'ok', sort: 'bool' }], constraints: [] };
|
|
21
|
+
const timer = setInterval(gc, 1);
|
|
22
|
+
try {
|
|
23
|
+
console.log(JSON.stringify({ node: process.version, platform: process.platform, arch: process.arch, backend: runtime.backend, iterations, variables: count, largeVariables: large ? 75_000 : 0 }));
|
|
24
|
+
for (let iteration = 0; iteration < iterations; iteration++) {
|
|
25
|
+
const limited = await Solve.run(runtime, small, { limits: { maxMemoryBytes: 1024 * 1024 } });
|
|
26
|
+
console.log(JSON.stringify({ iteration, phase: 'limited', result: limited }));
|
|
27
|
+
assert.equal(limited.status, 'unknown');
|
|
28
|
+
if (limited.status === 'unknown') {
|
|
29
|
+
assert.equal(limited.reason.kind, 'resource-limit');
|
|
30
|
+
assert.equal(limited.reason.limit, 'maxMemoryBytes');
|
|
31
|
+
}
|
|
32
|
+
const largeResults = [];
|
|
33
|
+
if (large) {
|
|
34
|
+
const largeCount = 75_000;
|
|
35
|
+
const variables = Array.from({ length: largeCount }, (_, index) => ({ id: `v${index}`, sort: 'int', min: 0, max: 1 }));
|
|
36
|
+
for (const optimize of [false, true]) {
|
|
37
|
+
console.log(JSON.stringify({ iteration, phase: optimize ? 'large-optimization-start' : 'large-feasibility-start' }));
|
|
38
|
+
const started = performance.now();
|
|
39
|
+
const result = await Solve.run(runtime, {
|
|
40
|
+
schema: Model.schema,
|
|
41
|
+
variables,
|
|
42
|
+
constraints: [{ id: 'outside-last-domain', expression: { kind: 'lt', left: { kind: 'variable', id: `v${largeCount - 1}` }, right: { kind: 'literal', sort: 'int', value: '0' } } }],
|
|
43
|
+
...(optimize ? { objectives: [{ id: 'constant', direction: 'minimize', expression: { kind: 'literal', sort: 'int', value: '0' } }] } : {})
|
|
44
|
+
}, { limits: {
|
|
45
|
+
maxVariables: largeCount,
|
|
46
|
+
// Two one-digit bounds per variable, plus the constraint and optional objective literals.
|
|
47
|
+
maxNumericDigits: 2 * largeCount + 2,
|
|
48
|
+
// This diagnostic checks cleanup correctness, not shared-runner latency.
|
|
49
|
+
timeoutMs: 120_000
|
|
50
|
+
} });
|
|
51
|
+
console.log(JSON.stringify({ iteration, phase: optimize ? 'large-optimization' : 'large-feasibility', result, wallMs: performance.now() - started, memory: process.memoryUsage() }));
|
|
52
|
+
// Probe subsequent requests before asserting so a poisoned runtime is visible.
|
|
53
|
+
largeResults.push(result);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
console.log(JSON.stringify({ iteration, phase: 'recovery-start' }));
|
|
57
|
+
const result = await Solve.run(runtime, model, { limits: { maxVariables: count, timeoutMs: 30_000 } });
|
|
58
|
+
console.log(JSON.stringify({ iteration, phase: 'recovered', status: result.status, elapsedMs: result.elapsedMs, ...(result.status === 'unknown' ? { reason: result.reason } : {}), ...(result.status === 'optimal' ? { objectives: result.objectives } : {}) }));
|
|
59
|
+
assert.equal(result.status, 'optimal', JSON.stringify(result));
|
|
60
|
+
if (result.status === 'optimal') {
|
|
61
|
+
assert.deepEqual(result.objectives, [{ objectiveId: 'total', value: { sort: 'int', value: String(count) } }]);
|
|
62
|
+
assert.equal(Object.keys(result.assignment).length, count);
|
|
63
|
+
for (const value of Object.values(result.assignment))
|
|
64
|
+
assert.deepEqual(value, { sort: 'int', value: '1' });
|
|
65
|
+
}
|
|
66
|
+
for (const result of largeResults)
|
|
67
|
+
assert.equal(result.status, 'unsatisfied', JSON.stringify(result));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
clearInterval(timer);
|
|
72
|
+
await runtime.close();
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=memory-stress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-stress.js","sourceRoot":"","sources":["../../scripts/memory-stress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The binding's finalizers call this instance's low-level API directly, outside
|
|
3
|
+
* its async mutex. Native AST deletion must not race a pthread solver check.
|
|
4
|
+
* Guard the two async native operations CAVE uses; normal synchronous releases
|
|
5
|
+
* remain immediate, and queued releases drain before a check returns to CAVE.
|
|
6
|
+
*/
|
|
7
|
+
export declare const guardCleanup: (api: object) => void;
|
|
8
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/cleanup.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,IA8C1C,CAAA"}
|