Update all imports to .js for esm support, use threads.js
threads.js has better support for modules - no need to give a project-relative path to the worker file, which complicated the build. Add rudimentary thread pooling w/ execution timeout.
This commit is contained in:
parent
e54f4c42f1
commit
24d8ab6763
@ -4,7 +4,7 @@ import {
|
||||
RouterContext,
|
||||
graphql,
|
||||
makeExecutableSchema,
|
||||
} from "./mods";
|
||||
} from "./mods.js";
|
||||
|
||||
export interface ResolversProps {
|
||||
Query?: any;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { mergeTypeDefs, mergeResolvers } from "../mods";
|
||||
import { mergeTypeDefs, mergeResolvers } from "../mods.js";
|
||||
import * as sudoku from "./sudoku.js";
|
||||
|
||||
const modules = [require("./sudoku")];
|
||||
const modules = [sudoku];
|
||||
|
||||
export const typeDefs = mergeTypeDefs(modules.map((mod) => mod.typeDefs));
|
||||
export const resolvers = mergeResolvers(modules.map((mod) => mod.resolvers));
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { gql } from "../mods";
|
||||
import { generate, GenerateArguments } from "../sudoku/index";
|
||||
import { gql } from "../mods.js";
|
||||
import { generate, GenerateArguments } from "../sudoku/index.js";
|
||||
|
||||
export const typeDefs = gql`
|
||||
"""
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Application, bodyParser } from "./mods";
|
||||
import { applyGraphQL } from "./graphql";
|
||||
import { typeDefs, resolvers } from "./graphql/index";
|
||||
import { Application, bodyParser } from "./mods.js";
|
||||
import { applyGraphQL } from "./graphql.js";
|
||||
import { typeDefs, resolvers } from "./graphql/index.js";
|
||||
import stoppable from "stoppable";
|
||||
|
||||
import cors from "@koa/cors";
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { StaticPool, isTimeoutError } from "node-worker-threads-pool";
|
||||
import { spawn, Thread, Worker } from "threads";
|
||||
|
||||
import WORKERS from "physical-cpu-count";
|
||||
import { prettyPrint } from "./util";
|
||||
import { prettyPrint } from "./util.js";
|
||||
|
||||
const TIMEOUT = 20000;
|
||||
|
||||
export type Cell = number;
|
||||
@ -19,46 +20,64 @@ export type Sudoku = {
|
||||
cells: Cell[];
|
||||
};
|
||||
|
||||
const pool = new StaticPool<GenerateArguments, Cell[]>({
|
||||
size: WORKERS,
|
||||
task: "./src/sudoku/worker.js",
|
||||
});
|
||||
function getWorker() {
|
||||
return spawn(new Worker("./worker"));
|
||||
}
|
||||
|
||||
const available: any = [];
|
||||
|
||||
function initialize() {
|
||||
console.log(`Starting ${WORKERS} worker threads`);
|
||||
for (let n = 0; n < WORKERS; n++) {
|
||||
getWorker().then((worker) => available.push(worker));
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
|
||||
/**
|
||||
* Awaits a promise with a timeout.
|
||||
*
|
||||
* @param promise the promise to await
|
||||
* @param ms the timeout in milliseconds
|
||||
* @param cb a callback to call when the timeout is reached. The promise is
|
||||
* rejected with whatever gets returned here.
|
||||
*/
|
||||
function withTimeout<T>(promise: Promise<T>, ms: number, cb: () => any) {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
timeout = setTimeout(() => {
|
||||
reject(cb());
|
||||
}, ms);
|
||||
promise.then(resolve).catch(reject);
|
||||
}).finally(() => clearTimeout(timeout!));
|
||||
}
|
||||
|
||||
let activeWorkers = 0;
|
||||
export async function generate(
|
||||
regionWidth: number,
|
||||
regionHeight: number,
|
||||
clues: number
|
||||
): Promise<Sudoku> {
|
||||
if (activeWorkers >= WORKERS) {
|
||||
throw new Error("No workers available. Please try again in a moment.");
|
||||
const proxy = available.pop();
|
||||
if (!proxy) {
|
||||
throw new Error("No workers available right now. Please try again.");
|
||||
}
|
||||
|
||||
try {
|
||||
activeWorkers++;
|
||||
const puzzle = await pool.exec(
|
||||
{
|
||||
regionWidth,
|
||||
regionHeight,
|
||||
clues,
|
||||
},
|
||||
TIMEOUT
|
||||
);
|
||||
|
||||
prettyPrint(regionWidth, regionHeight, puzzle);
|
||||
|
||||
return {
|
||||
regionWidth,
|
||||
regionHeight,
|
||||
size: (regionWidth * regionHeight) ** 2,
|
||||
cells: puzzle,
|
||||
};
|
||||
} catch (err) {
|
||||
if (isTimeoutError(err)) {
|
||||
throw new Error("Timed out. Try increasing the number of clues.");
|
||||
const puzzle = await withTimeout<number[]>(
|
||||
proxy.generate(regionWidth, regionHeight, clues),
|
||||
TIMEOUT,
|
||||
() => {
|
||||
Thread.terminate(proxy);
|
||||
getWorker().then((worker) => available.push(worker));
|
||||
return new Error("Timed out. Try reducing the number of clues.");
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
activeWorkers--;
|
||||
}
|
||||
);
|
||||
|
||||
available.push(proxy);
|
||||
prettyPrint(regionWidth, regionHeight, puzzle);
|
||||
return {
|
||||
regionWidth,
|
||||
regionHeight,
|
||||
size: (regionWidth * regionHeight) ** 2,
|
||||
cells: puzzle,
|
||||
};
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
addNodeToColumn,
|
||||
maskRow,
|
||||
unmaskRow,
|
||||
} from "./dlx";
|
||||
import { shuffle, range } from "./util";
|
||||
import { Cell } from "./index";
|
||||
} from "./dlx.js";
|
||||
import { shuffle, range } from "./util.js";
|
||||
import { Cell } from "./index.js";
|
||||
|
||||
type NodeMeta = {
|
||||
index: number;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Cell } from "./index";
|
||||
import { Cell } from "./index.js";
|
||||
|
||||
export function randInt(lower: number, upper: number) {
|
||||
return Math.floor(Math.random() * (upper - lower)) + lower;
|
||||
|
@ -1,15 +1,13 @@
|
||||
const { SudokuMath } = require("./math");
|
||||
const { parentPort } = require("worker_threads");
|
||||
import { SudokuMath } from "./math.js";
|
||||
import { expose } from "threads/worker";
|
||||
|
||||
const maths = {};
|
||||
|
||||
parentPort.on("message", ({ regionWidth, regionHeight, clues }) => {
|
||||
const math =
|
||||
maths[`${regionWidth}:${regionHeight}`] ||
|
||||
(maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
|
||||
regionWidth,
|
||||
regionHeight
|
||||
));
|
||||
const puzzle = math.generate(clues);
|
||||
parentPort.postMessage(puzzle);
|
||||
expose({
|
||||
generate(regionWidth, regionHeight, clues) {
|
||||
const key = `${regionWidth}:${regionHeight}`;
|
||||
const math =
|
||||
maths[key] ?? (maths[key] = new SudokuMath(regionWidth, regionHeight));
|
||||
return math.generate(clues);
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user