Skip to content

Commit

Permalink
worker/offscreen prop
Browse files Browse the repository at this point in the history
  • Loading branch information
turbocrime committed Mar 14, 2024
1 parent a0592cd commit 98d64d7
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 42 deletions.
76 changes: 41 additions & 35 deletions apps/extension/src/entry/offscreen-handler.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
import type { JsonValue } from '@bufbuild/protobuf';
import { ConnectError } from '@connectrpc/connect';
import { errorToJson } from '@connectrpc/connect/protocol-connect';
import {
ActionBuildRequest,
ActionBuildResponse,
isActionBuildRequest,
isOffscreenRequest,
} from '@penumbra-zone/types/src/internal-msg/offscreen';

chrome.runtime.onMessage.addListener((req, _sender, respond) => {
if (!isOffscreenRequest(req)) return false;
if (isActionBuildRequest(req.request)) {
if (isOffscreenRequest(req)) {
const { type, request } = req;
void (async () => {
const response = spawnWorker(request);
const res = await response
.then(data => ({ type, data }))
.catch((e: Error) => ({
type,
error: `Offscreen: ${e.message}`,
}));
respond(res);
})();
if (isActionBuildRequest(request)) {
void spawnActionBuildWorker(request)
.then(
data => ({ type, data }),
e => ({
type,
error: errorToJson(ConnectError.from(e), undefined),
}),
)
.then(respond);
return true;
}
}
return true;
return false;
});

const spawnWorker = (req: ActionBuildRequest): Promise<JsonValue> => {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('../wasm-build-action.ts', import.meta.url));
const spawnActionBuildWorker = (req: ActionBuildRequest) => {
const worker = new Worker(new URL('../wasm-build-action.ts', import.meta.url));
return new Promise<ActionBuildResponse>((resolve, reject) => {
worker.addEventListener(
'message',
(e: MessageEvent) => resolve(e.data as ActionBuildResponse),
{ once: true },
);

const onWorkerMessage = (e: MessageEvent) => {
resolve(e.data as JsonValue);
worker.removeEventListener('error', onWorkerError);
worker.terminate();
};
worker.addEventListener(
'error',
({ error, filename, lineno, colno, message }: ErrorEvent) =>
reject(
error instanceof Error
? error
: new Error(`Worker ErrorEvent ${filename}:${lineno}:${colno} ${message}`),
),
{ once: true },
);

const onWorkerError = (ev: ErrorEvent) => {
const { filename, lineno, colno, message } = ev;
reject(
ev.error instanceof Error
? ev.error
: new Error(`Worker ErrorEvent ${filename}:${lineno}:${colno} ${message}`),
);
worker.removeEventListener('message', onWorkerMessage);
worker.terminate();
};
worker.addEventListener(
'messageerror',
(ev: MessageEvent) => reject(ConnectError.from(ev.data ?? ev)),

worker.addEventListener('message', onWorkerMessage, { once: true });
worker.addEventListener('error', onWorkerError, { once: true });
{ once: true },
);

// Send data to web worker
worker.postMessage(req);
});
}).finally(() => worker.terminate());
};
35 changes: 28 additions & 7 deletions packages/router/src/grpc/offscreen-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Action,
ActionPlan,
TransactionPlan,
WitnessData,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';
Expand All @@ -9,6 +10,10 @@ import {
OffscreenMessage,
} from '@penumbra-zone/types/src/internal-msg/offscreen';
import { InternalRequest, InternalResponse } from '@penumbra-zone/types/src/internal-msg/shared';
import { ConnectError } from '@connectrpc/connect';
import { errorToJson } from '@connectrpc/connect/protocol-connect';
import type { Jsonified } from '@penumbra-zone/types/src/jsonified';
import type { JsonValue } from '@bufbuild/protobuf';

const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';

Expand Down Expand Up @@ -44,8 +49,23 @@ const releaseOffscreen = async () => {

const sendOffscreenMessage = async <T extends OffscreenMessage>(
req: InternalRequest<T>,
): Promise<InternalResponse<ActionBuildMessage>> =>
chrome.runtime.sendMessage<InternalRequest<T>, InternalResponse<T>>(req);
): Promise<InternalResponse<T>> =>
chrome.runtime.sendMessage<InternalRequest<T>, InternalResponse<T>>(req).catch(e => ({
type: req.type,
error: errorToJson(ConnectError.from(e), undefined),
}));

type JsonifiedTransactionPlanWithActions = Jsonified<TransactionPlan> & {
actions: Jsonified<ActionPlan>[];
};

const isSuccessfulResponse = <T extends OffscreenMessage>(
r: InternalResponse<T> | null,
): r is InternalResponse<T> & { data: T['response'] } => r != null && 'data' in r;

const isErrorResponse = <T extends OffscreenMessage>(
r: InternalResponse<T> | null,
): r is InternalResponse<T> & { error: JsonValue } => r != null && 'error' in r;

/**
* Build actions in parallel, in an offscreen window where we can run wasm.
Expand All @@ -67,15 +87,16 @@ const buildActions = (
sendOffscreenMessage<ActionBuildMessage>({
type: 'BUILD_ACTION',
request: {
transactionPlan: transactionPlan.toJson(),
witness: witness.toJson(),
transactionPlan: transactionPlan.toJson() as JsonifiedTransactionPlanWithActions,
witness: witness.toJson() as Jsonified<WitnessData>,
fullViewingKey,
actionPlanIndex,
} as ActionBuildRequest,
} satisfies ActionBuildRequest,
}),
]);
if ('error' in buildRes) throw new Error(String(buildRes.error));
return Action.fromJson(buildRes.data);
if (isSuccessfulResponse(buildRes)) return Action.fromJson(buildRes.data);
else if (isErrorResponse(buildRes)) throw ConnectError.from(buildRes.error);
else throw ConnectError.from(buildRes);
});
void Promise.all(buildTasks).finally(() => void releaseOffscreen());
return buildTasks;
Expand Down

0 comments on commit 98d64d7

Please sign in to comment.