Skip to content

Commit

Permalink
Merge pull request #212 from flatironinstitute/resolve-spurious-unrea…
Browse files Browse the repository at this point in the history
…chable-errors

Resolve spurious 'unreachable code' errors
  • Loading branch information
WardBrian authored Sep 5, 2024
2 parents f735f4c + e3680bc commit e042aff
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 28 deletions.
4 changes: 4 additions & 0 deletions gui/src/app/Scripting/pyodide/pyodideWorker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PyodideInterface, loadPyodide } from "pyodide";
import { isMonacoWorkerNoise } from "@SpUtil/isMonacoWorkerNoise";
import { InterpreterStatus } from "@SpScripting/InterpreterTypes";
import {
MessageFromPyodideWorker,
Expand Down Expand Up @@ -62,6 +63,9 @@ const addImage = (image: any) => {
console.log("pyodide worker loaded");

self.onmessage = async (e) => {
if (isMonacoWorkerNoise(e.data)) {
return;
}
const message = e.data;
await run(message.code, message.spData, message.spRunSettings);
};
Expand Down
67 changes: 53 additions & 14 deletions gui/src/app/StanSampler/StanModelWorker.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
import { isMonacoWorkerNoise } from "@SpUtil/isMonacoWorkerNoise";
import { unreachable } from "@SpUtil/unreachable";
import StanModel from "tinystan";
import StanModel, { PathfinderParams, SamplerParams } from "tinystan";

export enum Requests {
Load = "load",
Sample = "sample",
Pathfinder = "pathfinder",
}

export type StanModelRequestMessage =
| {
purpose: Requests.Load;
url: string;
}
| {
purpose: Requests.Sample;
sampleConfig: Partial<SamplerParams>;
}
| {
purpose: Requests.Pathfinder;
pathfinderConfig: Partial<PathfinderParams>;
};

export enum Replies {
ModelLoaded = "modelLoaded",
StanReturn = "stanReturn",
Progress = "progress",
}

export type StanModelReplyMessage =
| {
purpose: Replies.ModelLoaded;
}
| {
purpose: Replies.StanReturn;
error: string;
}
| {
purpose: Replies.StanReturn;
draws: number[][];
paramNames: string[];
error: null;
}
| {
purpose: Replies.Progress;
report: Progress;
};

export type Progress = {
chain: number;
iteration: number;
Expand All @@ -21,6 +55,8 @@ export type Progress = {
warmup: boolean;
};

const postReply = (message: StanModelReplyMessage) => self.postMessage(message);

const parseProgress = (msg: string): Progress => {
// Examples (note different spacing):
// Chain [1] Iteration: 2000 / 2000 [100%] (Sampling)
Expand All @@ -47,15 +83,17 @@ const progressPrintCallback = (msg: string) => {
return;
}
const report = parseProgress(msg);
self.postMessage({ purpose: Replies.Progress, report });
postReply({ purpose: Replies.Progress, report });
};

let model: StanModel;

self.onmessage = (e) => {
const purpose: Requests = e.data.purpose;
self.onmessage = (e: MessageEvent<StanModelRequestMessage>) => {
if (isMonacoWorkerNoise(e.data)) {
return;
}

switch (purpose) {
switch (e.data.purpose) {
case Requests.Load: {
import(/* @vite-ignore */ e.data.url)
.then((js) => StanModel.load(js.default, progressPrintCallback))
Expand All @@ -65,13 +103,13 @@ self.onmessage = (e) => {
"Web Worker loaded Stan model built from version " +
m.stanVersion(),
);
self.postMessage({ purpose: Replies.ModelLoaded });
postReply({ purpose: Replies.ModelLoaded });
}, console.error);
break;
}
case Requests.Sample: {
if (!model) {
self.postMessage({
postReply({
purpose: Replies.StanReturn,
error: "Model not loaded yet!",
});
Expand All @@ -80,20 +118,20 @@ self.onmessage = (e) => {
try {
const { paramNames, draws } = model.sample(e.data.sampleConfig);
// TODO? use an ArrayBuffer so we can transfer without serialization cost
self.postMessage({
postReply({
purpose: Replies.StanReturn,
draws,
paramNames,
error: null,
});
} catch (e: any) {
self.postMessage({ purpose: Replies.StanReturn, error: e.toString() });
postReply({ purpose: Replies.StanReturn, error: e.toString() });
}
break;
}
case Requests.Pathfinder: {
if (!model) {
self.postMessage({
postReply({
purpose: Replies.StanReturn,
error: "Model not loaded yet!",
});
Expand All @@ -102,18 +140,19 @@ self.onmessage = (e) => {
try {
const { draws, paramNames } = model.pathfinder(e.data.pathfinderConfig);
// TODO? use an ArrayBuffer so we can transfer without serialization cost
self.postMessage({
postReply({
purpose: Replies.StanReturn,
draws,
paramNames,
error: null,
});
} catch (e: any) {
self.postMessage({ purpose: Replies.StanReturn, error: e.toString() });
postReply({ purpose: Replies.StanReturn, error: e.toString() });
}
break;
}
default:
unreachable(purpose);
default: {
unreachable(e.data);
}
}
};
37 changes: 23 additions & 14 deletions gui/src/app/StanSampler/StanSampler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { SamplingOpts } from "@SpCore/ProjectDataModel";
import { Replies, Requests } from "@SpStanSampler/StanModelWorker";
import {
Replies,
Requests,
StanModelReplyMessage,
StanModelRequestMessage,
} from "@SpStanSampler/StanModelWorker";
import StanWorkerUrl from "@SpStanSampler/StanModelWorker?worker&url";
import type { SamplerParams } from "tinystan";
import { type StanRunAction } from "./useStanSampler";
Expand All @@ -19,7 +24,7 @@ type StanSamplerAndCleanup = {
};

class StanSampler {
#worker: Worker | undefined;
#stanWorker: Worker | undefined;
#samplingStartTimeSec: number = 0;

private constructor(
Expand All @@ -36,23 +41,22 @@ class StanSampler {
const sampler = new StanSampler(compiledUrl, update);
const cleanup = () => {
console.log("terminating model worker");
sampler.#worker && sampler.#worker.terminate();
sampler.#worker = undefined;
sampler.#stanWorker && sampler.#stanWorker.terminate();
sampler.#stanWorker = undefined;
};
return { sampler, cleanup };
}

_initialize() {
this.#worker = new Worker(StanWorkerUrl, {
this.#stanWorker = new Worker(StanWorkerUrl, {
name: "tinystan worker",
type: "module",
});

this.update({ type: "clear" });

this.#worker.onmessage = (e) => {
const purpose: Replies = e.data.purpose;
switch (purpose) {
this.#stanWorker.onmessage = (e: MessageEvent<StanModelReplyMessage>) => {
switch (e.data.purpose) {
case Replies.Progress: {
this.update({ type: "progressUpdate", progress: e.data.report });
break;
Expand All @@ -62,7 +66,7 @@ class StanSampler {
break;
}
case Replies.StanReturn: {
if (e.data.error) {
if (e.data.error !== null) {
this.update({
type: "statusUpdate",
status: "failed",
Expand All @@ -79,11 +83,11 @@ class StanSampler {
break;
}
default:
unreachable(purpose);
unreachable(e.data);
}
};
this.update({ type: "statusUpdate", status: "loading" });
this.#worker.postMessage({ purpose: Requests.Load, url: this.compiledUrl });
this.postMessage({ purpose: Requests.Load, url: this.compiledUrl });
}

sample(data: any, samplingOpts: SamplingOpts) {
Expand All @@ -94,16 +98,21 @@ class StanSampler {
seed: samplingOpts.seed !== undefined ? samplingOpts.seed : null,
refresh,
};
if (!this.#worker) throw new Error("model worker is undefined");
if (!this.#stanWorker) throw new Error("model worker is undefined");

this.update({ type: "startSampling", samplingOpts });

this.#samplingStartTimeSec = Date.now() / 1000;
this.#worker.postMessage({ purpose: Requests.Sample, sampleConfig });
this.postMessage({ purpose: Requests.Sample, sampleConfig });
}

private postMessage(message: StanModelRequestMessage) {
if (!this.#stanWorker) throw new Error("model worker is undefined");
this.#stanWorker.postMessage(message);
}

cancel() {
this.#worker?.terminate();
this.#stanWorker?.terminate();
this._initialize();
}
}
Expand Down
8 changes: 8 additions & 0 deletions gui/src/app/Stanc/stancWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
StancWorkerRequests,
} from "@SpStanc/Types";
import rawStancJS from "@SpStanc/stanc.js?raw"; // https://vitejs.dev/guide/assets#importing-asset-as-string
import { isMonacoWorkerNoise } from "@SpUtil/isMonacoWorkerNoise";
import { unreachable } from "@SpUtil/unreachable";

let stanc: undefined | StancFunction;
try {
Expand Down Expand Up @@ -33,6 +35,10 @@ try {
const postReply = (message: StancReplyMessage) => self.postMessage(message);

self.onmessage = (e: MessageEvent<StancRequestMessage>) => {
if (isMonacoWorkerNoise(e.data)) {
return;
}

const { purpose, name, code } = e.data;

if (!stanc) {
Expand Down Expand Up @@ -61,5 +67,7 @@ self.onmessage = (e: MessageEvent<StancRequestMessage>) => {
postReply({ errors, warnings });
break;
}
default:
unreachable(purpose);
}
};
8 changes: 8 additions & 0 deletions gui/src/app/util/isMonacoWorkerNoise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import baseObjectCheck from "./baseObjectCheck";

// monaco-editor has a call to globalThis.postMessage with the '*' target
// so our workers will recieve this message. We ignore it to prevent
// anything weird from happening.
export const isMonacoWorkerNoise = (obj: any): boolean =>
!baseObjectCheck(obj) ||
Object.prototype.hasOwnProperty.call(obj, "vscodeScheduleAsyncWork");

0 comments on commit e042aff

Please sign in to comment.