Skip to content

Commit d5ed9b0

Browse files
committed
feat(bin): run solve fns in workers
this protects imports from one solve module leaking into another it also accounts for solutions that don't gracefully close resources (e.g. `z3-solver` currently used for 2025 Day 10 Part 2)
1 parent f36fcf1 commit d5ed9b0

4 files changed

Lines changed: 68 additions & 50 deletions

File tree

2025/day/10/part/2/solve.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
import { init } from "z3-solver";
2+
import { parseManual } from "../../manuals.ts";
3+
14
export default async function solve(input: string) {
2-
const { href: workerUrl } = new URL("./worker.ts", import.meta.url);
3-
const worker = new Worker(workerUrl, { type: "module" });
4-
try {
5-
const { promise, resolve } = Promise.withResolvers<number>();
6-
worker.onmessage = ({ data }) => resolve(data);
7-
worker.postMessage(input);
8-
return await promise;
9-
} finally {
10-
worker.terminate();
5+
const manual = parseManual(input);
6+
const { Context } = await init();
7+
const { Int, Optimize } = Context("main");
8+
let sum = 0;
9+
for (const { buttons, requirements } of manual) {
10+
const variables = buttons.map((_, i) => Int.const(`b${i}`));
11+
const optimize = new Optimize();
12+
optimize.add(
13+
...requirements.map((requirement, lightIndex) =>
14+
buttons.keys()
15+
.filter((buttonIndex) => buttons[buttonIndex].includes(lightIndex))
16+
.map((buttonIndex) => variables[buttonIndex])
17+
.reduce((a, b) => a.add(b), Int.val(0))
18+
.eq(Int.val(requirement))
19+
),
20+
...variables.map((variable) => variable.ge(0)),
21+
);
22+
const sumExpression = variables.reduce((a, x) => a.add(x), Int.val(0));
23+
optimize.minimize(sumExpression);
24+
if (await optimize.check() !== "sat") throw new Error("No solution found");
25+
sum += +optimize.model().eval(sumExpression);
1126
}
27+
return sum;
1228
}

2025/day/10/part/2/worker.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

bin/aoc.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { bold, gray, red } from "@std/fmt/colors";
88
import prettyMs from "pretty-ms";
99

1010
import { alphanumericalCompareFn } from "@lib/alphanumeric.ts";
11-
import { formatAnswer, getSolveFn } from "@lib/harness.ts";
11+
import { formatAnswer } from "@lib/harness.ts";
12+
import type { InputData, OutputData } from "./worker.ts";
1213

1314
const defaultBaseUrl = "https://adventofcode.com";
1415

@@ -127,15 +128,19 @@ async function solve(
127128
for (const moduleName of new Set(moduleNames)) {
128129
const { year, day } = groupsByModuleName.get(moduleName)!;
129130
const input = await getInput(year, day, session, cachePath);
130-
const solve = await getSolveFn(`.${moduleName}`);
131-
if (solve === undefined) {
132-
console.log(gray(`no default exported function found in ${moduleName}`));
133-
continue;
134-
}
135-
console.log(gray(`running solve from ${moduleName}`));
131+
const { href: workerUrl } = new URL("./worker.ts", import.meta.url);
132+
const worker = new Worker(workerUrl, { type: "module" });
136133
try {
134+
const { promise, resolve, reject } = Promise.withResolvers();
135+
worker.onmessage = ({ data: { key, value } }: MessageEvent<OutputData>) =>
136+
({ answer: resolve, error: reject })[key](value);
137+
worker.onerror = ({ error }) => reject(error);
138+
worker.onmessageerror = (cause) =>
139+
reject(new Error("failed to receive message from worker", { cause }));
140+
console.log(gray(`running solve from ${moduleName}`));
141+
worker.postMessage({ input, moduleName } satisfies InputData);
137142
const start = performance.now();
138-
const answer = await solve(input);
143+
const answer = await promise;
139144
const end = performance.now();
140145
const duration = end - start;
141146
console.log(
@@ -145,6 +150,8 @@ async function solve(
145150
if (answer !== undefined) solved++;
146151
} catch (e) {
147152
console.error(e);
153+
} finally {
154+
worker.terminate();
148155
}
149156
}
150157

bin/worker.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference lib="deno.worker" />
2+
3+
import { getSolveFn } from "@lib/harness.ts";
4+
5+
export interface InputData {
6+
input: string;
7+
moduleName: string;
8+
}
9+
10+
export interface OutputData {
11+
key: "answer" | "error";
12+
value: unknown;
13+
}
14+
15+
self.onmessage = async (
16+
{ data: { input, moduleName } }: MessageEvent<InputData>,
17+
) => {
18+
try {
19+
const solve = await getSolveFn(`.${moduleName}`);
20+
if (solve === undefined) {
21+
throw Error(`no default exported function found in ${moduleName}`);
22+
}
23+
const answer = await solve(input);
24+
self.postMessage({ key: "answer", value: answer } satisfies OutputData);
25+
} catch (error) {
26+
self.postMessage({ key: "error", value: error } satisfies OutputData);
27+
}
28+
};

0 commit comments

Comments
 (0)