microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
joaoboechat/test-verbose-gpu

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/npm/qsharp/src/workers/worker.ts

265lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import * as wasm from "../../lib/web/qsc_wasm.js";
5import { QdkDiagnostics } from "../diagnostics.js";
6import { TelemetryEvent, log } from "../log.js";
7import type {
8 CommonEventMessage,
9 EventMessage,
10 IServiceEventMessage,
11 IServiceEventTarget,
12 MethodMap,
13 RequestMessage,
14 ResponseMessage,
15 ServiceMethods,
16 ServiceProtocol,
17} from "./types.js";
18
19type Wasm = typeof wasm;
20
21/**
22 * Creates and initializes a service, setting it up to receive requests.
23 * This function is used in the worker thread. It uses the `WorkerSelf` global,
24 * which is bootstrapped by the platform-specific adapter before this code runs.
25 *
26 * @param serviceProtocol An object that describes the service: its constructor, methods and events
27 * @returns The message handler registered on the worker thread.
28 */
29export function createWorker<
30 TService extends ServiceMethods<TService>,
31 TServiceEventMsg extends IServiceEventMessage,
32>(
33 serviceProtocol: ServiceProtocol<TService, TServiceEventMsg>,
34): (e: MessageEvent) => void {
35 let invokeService: ((req: RequestMessage<TService>) => Promise<void>) | null =
36 null;
37
38 const messageHandler = (e: MessageEvent) => {
39 const data = e.data;
40
41 if (!data.type || typeof data.type !== "string") {
42 log.error(`Unrecognized msg: ${data}`);
43 return;
44 }
45
46 switch (data.type) {
47 case "init":
48 {
49 wasm.initSync({ module: data.wasmModule });
50
51 invokeService = initService<TService, TServiceEventMsg>(
52 WorkerSelf.postMessage.bind(WorkerSelf),
53 serviceProtocol,
54 wasm,
55 data.qscLogLevel,
56 );
57 }
58 break;
59 default:
60 if (!invokeService) {
61 log.error(
62 `Received message before the service was initialized: %o`,
63 data,
64 );
65 } else {
66 invokeService(data);
67 }
68 }
69 };
70
71 WorkerSelf.onMessage(messageHandler);
72 return messageHandler;
73}
74
75/**
76 * Serializes an error, if it is a known type, so that it can be sent between threads.
77 *
78 * By default, browsers can only send certain types of errors between the main thread and a worker.
79 * See: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#error_types
80 *
81 * Serializing our own custom errors allows us to send them between threads.
82 */
83function serializeIfError(err: unknown) {
84 if (err instanceof QdkDiagnostics) {
85 err = { name: err.name, data: err.diagnostics };
86 } else if (err instanceof WebAssembly.RuntimeError) {
87 err = {
88 name: "WebAssembly.RuntimeError",
89 message: err.message,
90 stack: err.stack,
91 };
92 }
93 return err;
94}
95
96/**
97 * Function to wrap a service in a dispatcher. To be used in the worker thread.
98 *
99 * @param service The service to be wrapped
100 * @param methods A map of method names. Should match the list passed into @see createProxyInternal.
101 * @param eventNames The list of event names that the service can emit
102 * @param postMessage A function to post messages back to the main thread
103 * @returns A function that takes a message and invokes the corresponding
104 * method on the service. The caller should then set this method as a message handler.
105 */
106function createDispatcher<
107 TService extends ServiceMethods<TService>,
108 TServiceEventMsg extends IServiceEventMessage,
109>(
110 postMessage: (
111 msg: ResponseMessage<TService> | EventMessage<TServiceEventMsg>,
112 ) => void,
113 service: TService,
114 methods: MethodMap<TService>,
115 eventNames: TServiceEventMsg["type"][],
116): (req: RequestMessage<TService>) => Promise<void> {
117 log.trace("Worker: Constructing WorkerEventHandler");
118
119 function logAndPost(
120 msg: ResponseMessage<TService> | EventMessage<TServiceEventMsg>,
121 ) {
122 log.trace(
123 "Worker: Sending %s message from worker: %o",
124 msg.messageType,
125 msg,
126 );
127 postMessage(msg);
128 }
129
130 const eventTarget =
131 new EventTarget() as IServiceEventTarget<TServiceEventMsg>;
132
133 eventNames.forEach((eventName: TServiceEventMsg["type"]) => {
134 // Subscribe to all known events and forward them as messages to the main thread.
135 eventTarget.addEventListener(eventName, (ev) => {
136 logAndPost({
137 messageType: "event",
138 type: ev.type,
139 detail: ev.detail,
140 });
141 });
142
143 // If there's an addEventListener on the object itself, forward those events as well.
144 if ((service as any).addEventListener) {
145 (service as any).addEventListener(eventName, (ev: any) => {
146 logAndPost({
147 messageType: "event",
148 type: ev.type,
149 detail: ev.detail,
150 });
151 });
152 }
153 });
154
155 return function invokeMethod(req: RequestMessage<TService>) {
156 // Pass the eventTarget to the methods marked as taking progress
157 return service[req.type]
158 .call(
159 service,
160 ...req.args,
161 methods[req.type] === "requestWithProgress" ? eventTarget : undefined,
162 )
163 .then((result: any) =>
164 logAndPost({
165 messageType: "response",
166 type: req.type,
167 result: { success: true, result },
168 }),
169 )
170 .catch((err: any) => {
171 // Serialize the error if it's a known type.
172 err = serializeIfError(err);
173
174 logAndPost({
175 // If this happens then the wasm code likely threw an exception/panicked rather than
176 // completing gracefully and fulfilling the promise. Communicate to the client
177 // that there was an error and it should reject the current request
178 messageType: "response",
179 type: req.type,
180 result: { success: false, error: err },
181 });
182 });
183 };
184}
185
186/**
187 * Creates and initializes the actual service. To be used in the worker thread.
188 *
189 * @param postMessage A function to post messages back to the main thread
190 * @param serviceProtocol An object that describes the service: its constructor, methods and events
191 * @param wasm The wasm module to initialize the service with
192 * @param qscLogLevel The log level to initialize the service with
193 * @returns A function that takes a message and invokes the corresponding
194 * method on the service. The caller should then set this method as a message handler.
195 */
196function initService<
197 TService extends ServiceMethods<TService>,
198 TServiceEventMsg extends IServiceEventMessage,
199>(
200 postMessage: (
201 msg:
202 | ResponseMessage<TService>
203 | EventMessage<TServiceEventMsg>
204 | CommonEventMessage,
205 ) => void,
206 serviceProtocol: ServiceProtocol<TService, TServiceEventMsg>,
207 wasm: Wasm,
208 qscLogLevel?: number,
209): (req: RequestMessage<TService>) => Promise<void> {
210 function postTelemetryMessage(telemetry: TelemetryEvent) {
211 postMessage({
212 messageType: "common-event",
213 type: "telemetry-event",
214 detail: telemetry,
215 });
216 }
217
218 function postLogMessage(level: number, target: string, ...args: any) {
219 if (log.getLogLevel() < level) {
220 return;
221 }
222
223 let data = args;
224 try {
225 // Only structured cloneable objects can be sent in worker messages.
226 // Test if this is the case.
227 structuredClone(args);
228 } catch {
229 // Uncloneable object.
230 // Use String(args) instead of ${args} to handle all possible values
231 // without throwing. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion
232 data = ["unsupported log data " + String(args)];
233 }
234 postMessage({
235 messageType: "common-event",
236 type: "log",
237 detail: { level, target, data },
238 });
239 }
240
241 // Override the global logger
242 log.error = (...args) => postLogMessage(1, "worker", ...args);
243 log.warn = (...args) => postLogMessage(2, "worker", ...args);
244 log.info = (...args) => postLogMessage(3, "worker", ...args);
245 log.debug = (...args) => postLogMessage(4, "worker", ...args);
246 log.trace = (...args) => postLogMessage(5, "worker", ...args);
247
248 if (qscLogLevel !== undefined) {
249 log.setLogLevel(qscLogLevel);
250 }
251
252 // Set up logging and telemetry as soon as possible after instantiating
253 log.onLevelChanged = (level) => wasm.setLogLevel(level);
254 log.setTelemetryCollector(postTelemetryMessage);
255 wasm.initLogging(postLogMessage, log.getLogLevel());
256
257 // Create the actual service and return the dispatcher method
258 const service = new serviceProtocol.class(wasm);
259 return createDispatcher(
260 postMessage,
261 service,
262 serviceProtocol.methods,
263 serviceProtocol.eventNames,
264 );
265}
266