dcp-client 5.4.0 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/all +1 -0
- package/build/generate-sandbox-definitions-json +15 -4
- package/build/generate-worktimes-json +41 -0
- package/dist/dcp-client-bundle.js +1 -1
- package/dist/dcp-client-bundle.js.map +1 -1
- package/generated/sandbox-definitions.json +1 -1
- package/generated/worktimes.json +1 -0
- package/lib/get-worktime-info.js +44 -0
- package/libexec/sandbox/bootstrap.js +6 -2
- package/libexec/sandbox/bravojs-env.js +17 -382
- package/libexec/sandbox/calculate-capabilities.js +1 -1
- package/libexec/sandbox/map-basic-worktime.js +53 -0
- package/libexec/sandbox/pyodide-worktime.js +197 -0
- package/libexec/sandbox/worktimes.js +277 -49
- package/npm-hooks/prepublish +22 -0
- package/package.json +19 -19
|
@@ -1 +1 @@
|
|
|
1
|
-
{"browser":["deny-node","kvin/kvin.js","script-load-wrapper","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","
|
|
1
|
+
{"browser":["deny-node","kvin/kvin.js","script-load-wrapper","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","url","polyfills","access-lists","fetch-factory","bravojs-init","bravojs/bravo.js","bravojs-env","worktimes","pyodide-core","pyodide-worktime","map-basic-worktime","calculate-capabilities","bootstrap"],"node":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","url","polyfills","access-lists","fetch-factory","bravojs-init","bravojs/bravo.js","bravojs-env","worktimes","pyodide-core","pyodide-worktime","map-basic-worktime","calculate-capabilities","bootstrap"],"native":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","url","polyfills","access-lists","fetch-factory","bravojs-init","bravojs/bravo.js","bravojs-env","worktimes","pyodide-core","pyodide-worktime","map-basic-worktime","calculate-capabilities","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","url","polyfills","access-lists","fetch-factory","bravojs-init","bravojs/bravo.js","bravojs-env","worktimes","pyodide-core","pyodide-worktime","map-basic-worktime","calculate-capabilities","bootstrap","testing.js"],"testing":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","timer-classes","wrap-event-listeners","event-loop-virtualization","lift-webgl","lift-wasm","lift-webgpu","url","polyfills","access-lists","fetch-factory","bravojs-init","bravojs/bravo.js","bravojs-env","worktimes","pyodide-core","pyodide-worktime","map-basic-worktime","calculate-capabilities","bootstrap","testing.js"],"worktimes":["pyodide-worktime","map-basic-worktime"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"name":"map-basic","version":"1.0.0"},{"name":"pyodide","version":"0.28.0"}]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file generate-worktime-info.js
|
|
3
|
+
* Get the name and version of all worktime files in the libexec directory and export as an array.
|
|
4
|
+
* Worktime files are distinguished from other sandbox scripts by the sandbox-definitions.json worktimes field
|
|
5
|
+
*
|
|
6
|
+
* @author Severn Lortie <severn@distributive.network>
|
|
7
|
+
* @date February 2024
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Require the sandbox scripts and extract the version and name from each worktime. Worktime setup
|
|
12
|
+
* scripts differentiated from normal sandbox scripts by the worktimes field in sandbox-definitions.json
|
|
13
|
+
* Each of these scripts expects self.wrapScriptLoading and protectedStorage to be available, so they
|
|
14
|
+
* are mocked as necessary.
|
|
15
|
+
*
|
|
16
|
+
* @returns {object[]} Worktime info objects ({name: String, version: String}) in descending version order
|
|
17
|
+
*/
|
|
18
|
+
function getWorktimes()
|
|
19
|
+
{
|
|
20
|
+
const semver = require('semver');
|
|
21
|
+
const worktimeScripts = require('../generated/sandbox-definitions.json').worktimes;
|
|
22
|
+
const worktimes = [];
|
|
23
|
+
function wrapScriptLoading(_, fn)
|
|
24
|
+
{
|
|
25
|
+
fn(protectedStorage);
|
|
26
|
+
}
|
|
27
|
+
function registerWorktime(worktime)
|
|
28
|
+
{
|
|
29
|
+
worktimes.push({ name: worktime.name, version: worktime.version })
|
|
30
|
+
}
|
|
31
|
+
const protectedStorage = {};
|
|
32
|
+
protectedStorage.worktimes = {};
|
|
33
|
+
protectedStorage.worktimes.registerWorktime = registerWorktime;
|
|
34
|
+
globalThis.self = {};
|
|
35
|
+
globalThis.self.wrapScriptLoading = wrapScriptLoading;
|
|
36
|
+
for (const script of worktimeScripts)
|
|
37
|
+
require(`../libexec/sandbox/${script}`);
|
|
38
|
+
worktimes.sort((a, b) => semver.compare(b.version, a.version, true));
|
|
39
|
+
delete globalThis.self;
|
|
40
|
+
return worktimes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const worktimes = getWorktimes();
|
|
44
|
+
module.exports.worktimes = worktimes;
|
|
@@ -268,7 +268,11 @@ self.wrapScriptLoading({ scriptName: 'bootstrap', finalScript: true }, function
|
|
|
268
268
|
return;
|
|
269
269
|
// Avoid sending duplicate console message data over the network.
|
|
270
270
|
delete lastConsoleLog.message;
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
try
|
|
272
|
+
{
|
|
273
|
+
postMessage({ request: 'console', payload: lastConsoleLog });
|
|
274
|
+
lastConsoleLog = null;
|
|
275
|
+
}
|
|
276
|
+
catch (e){}
|
|
273
277
|
};
|
|
274
278
|
});
|
|
@@ -11,282 +11,50 @@
|
|
|
11
11
|
|
|
12
12
|
self.wrapScriptLoading({ scriptName: 'bravojs-env' }, function bravojsEvn$$fn(protectedStorage, _ring2PostMessage, wrapPostMessage)
|
|
13
13
|
{
|
|
14
|
-
// This file starts at ring 2, but transitions to ring 3 partway through it.
|
|
15
14
|
const ring2PostMessage = self.postMessage;
|
|
16
|
-
let ring3PostMessage;
|
|
17
|
-
|
|
18
15
|
bravojs.ww = {}
|
|
19
16
|
bravojs.ww.allDeps = []
|
|
20
17
|
bravojs.ww.provideCallbacks = {}
|
|
21
18
|
|
|
22
19
|
//Listens for postMessage from the sandbox
|
|
23
|
-
addEventListener('message', async (event)
|
|
20
|
+
addEventListener('message', async function bravojsEnv$$sandboxPostMessageHandler(event) {
|
|
24
21
|
let message = event
|
|
25
22
|
let indirectEval = eval // eslint-disable-line
|
|
26
|
-
switch (message.request)
|
|
23
|
+
switch (message.request)
|
|
24
|
+
{
|
|
27
25
|
case 'moduleGroup': /* Outside environment is sending us a module group */
|
|
28
26
|
module.declare = bravojs.ww.groupedModuleDeclare
|
|
29
27
|
let packages = Object.keys(message.data)
|
|
30
28
|
|
|
31
|
-
for (let i = 0; i < packages.length; i++)
|
|
29
|
+
for (let i = 0; i < packages.length; i++)
|
|
30
|
+
{
|
|
32
31
|
let fileNames = Object.keys(message.data[packages[i]])
|
|
33
|
-
for (let j = 0; j < fileNames.length; j++)
|
|
32
|
+
for (let j = 0; j < fileNames.length; j++)
|
|
33
|
+
{
|
|
34
34
|
bravojs.ww.moduleId = packages[i] + '/' + fileNames[j]
|
|
35
|
-
|
|
36
|
-
indirectEval(message.data[packages[i]][fileNames[j]], fileNames[j])
|
|
37
|
-
} catch (error) {
|
|
38
|
-
throw error
|
|
39
|
-
}
|
|
35
|
+
indirectEval(message.data[packages[i]][fileNames[j]], fileNames[j])
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
delete module.declare
|
|
44
40
|
|
|
45
|
-
if (bravojs.ww.provideCallbacks.hasOwnProperty(message.id))
|
|
41
|
+
if (bravojs.ww.provideCallbacks.hasOwnProperty(message.id))
|
|
42
|
+
{
|
|
46
43
|
bravojs.ww.provideCallbacks[message.id].callback()
|
|
47
44
|
delete bravojs.ww.provideCallbacks[message.id]
|
|
48
45
|
}
|
|
49
46
|
break
|
|
50
47
|
case 'moduleGroupError': /* Outside environment is sending us a module group error report */
|
|
51
|
-
if (bravojs.ww.provideCallbacks.hasOwnProperty(message.id) && bravojs.ww.provideCallbacks[message.id].onerror)
|
|
52
|
-
bravojs.ww.provideCallbacks[message.id].onerror()
|
|
53
|
-
|
|
54
|
-
console.error('moduleGroupError ', message.stack)
|
|
55
|
-
}
|
|
56
|
-
break
|
|
57
|
-
|
|
58
|
-
/* Sandbox assigned a specific job by supervisor */
|
|
59
|
-
case 'assign': {
|
|
60
|
-
let reportError = function bravojsEnv$$sandboxAssignment$reportError(e) {
|
|
61
|
-
var error = Object.assign({}, e);
|
|
62
|
-
Object.getOwnPropertyNames(e).forEach((prop) => error[prop] = e[prop]);
|
|
63
|
-
if (error.stack)
|
|
64
|
-
error = error.stack.replace(/data:application\/javascript.*?:/g, 'eval:');
|
|
65
|
-
|
|
66
|
-
ring2PostMessage({request: 'reject', error});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
if (typeof module.main !== 'undefined')
|
|
71
|
-
throw new Error('Main module was provided before job assignment');
|
|
72
|
-
|
|
73
|
-
protectedStorage.sandboxConfig = message.sandboxConfig;
|
|
74
|
-
Object.assign(self.work.job.public, message.job.public); /* override locale-specific defaults if specified */
|
|
75
|
-
// Load bravojs' module.main with the work function
|
|
76
|
-
module.declare(message.job.dependencies, async function mainModule(require, exports, module) {
|
|
77
|
-
try {
|
|
78
|
-
if (exports.hasOwnProperty('job'))
|
|
79
|
-
throw new Error("Tried to assign sandbox when it was already assigned"); /* Should be impossible - might happen if throw during assign? */
|
|
80
|
-
exports.job = false; /* placeholder own property */
|
|
81
|
-
|
|
82
|
-
message.job.requirePath.map(p => require.paths.push(p));
|
|
83
|
-
message.job.modulePath.map(p => module.paths.push(p));
|
|
84
|
-
exports.arguments = message.job.arguments;
|
|
85
|
-
exports.worktime = message.job.worktime;
|
|
86
|
-
|
|
87
|
-
switch (message.job.worktime.name)
|
|
88
|
-
{
|
|
89
|
-
case 'map-basic':
|
|
90
|
-
if (message.job.useStrict)
|
|
91
|
-
exports.job = indirectEval(`"use strict"; (${message.job.workFunction})`);
|
|
92
|
-
else
|
|
93
|
-
exports.job = indirectEval(`(${message.job.workFunction})`);
|
|
94
|
-
break;
|
|
95
|
-
case 'pyodide':
|
|
96
|
-
exports.job = await generatePyodideFunction(message.job);
|
|
97
|
-
break;
|
|
98
|
-
default:
|
|
99
|
-
throw new Error(`Unsupported worktime: ${message.job.worktime.name}`);
|
|
100
|
-
}
|
|
101
|
-
} catch(e) {
|
|
102
|
-
reportError(e);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
ring2PostMessage({
|
|
107
|
-
request: 'assigned',
|
|
108
|
-
jobId: message.job.id,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Now that the evaluator is assigned, wrap post message for ring 3
|
|
112
|
-
wrapPostMessage();
|
|
113
|
-
ring3PostMessage = self.postMessage;
|
|
114
|
-
}); /* end of main module */
|
|
115
|
-
} catch (error) {
|
|
116
|
-
reportError(error);
|
|
117
|
-
}
|
|
118
|
-
break /* end of assign */
|
|
119
|
-
}
|
|
120
|
-
/* Supervisor has asked us to execute the work function. message.data is input datum. */
|
|
121
|
-
case 'main':
|
|
122
|
-
{
|
|
123
|
-
try
|
|
124
|
-
{
|
|
125
|
-
await runWorkFunction(message.data);
|
|
126
|
-
}
|
|
127
|
-
catch (error)
|
|
128
|
-
{
|
|
129
|
-
ring3PostMessage({ request: 'sandboxError', error });
|
|
130
|
-
}
|
|
48
|
+
if (bravojs.ww.provideCallbacks.hasOwnProperty(message.id) && bravojs.ww.provideCallbacks[message.id].onerror)
|
|
49
|
+
bravojs.ww.provideCallbacks[message.id].onerror();
|
|
50
|
+
else
|
|
51
|
+
console.error('moduleGroupError ', message.stack);
|
|
131
52
|
break;
|
|
132
|
-
}
|
|
133
53
|
default:
|
|
134
54
|
break;
|
|
135
55
|
}
|
|
136
56
|
})
|
|
137
57
|
|
|
138
|
-
/**
|
|
139
|
-
* Factory function which generates a "map-basic"-like workFunction
|
|
140
|
-
* out of a Pyodide Worktime job (Python code, files, env variables).
|
|
141
|
-
*
|
|
142
|
-
* It takes any "images" passed in the workFunction "arguments" and
|
|
143
|
-
* writes them to the in memory filesystem provided by Emscripten.
|
|
144
|
-
* It adds any environment variables specified in the workFunction
|
|
145
|
-
* "arguments" to the pseudo-"process" for use.
|
|
146
|
-
* It globally imports a dcp module with function "set_slice_handler"
|
|
147
|
-
* which takes a python function as input. The python function passed
|
|
148
|
-
* to that slice handler is invoked by the function which this
|
|
149
|
-
* factory function returns.
|
|
150
|
-
*
|
|
151
|
-
* @param {Object} job The job data associated with the message
|
|
152
|
-
* @returns {Function} function pyodideWorkFn(slice) -> result
|
|
153
|
-
*/
|
|
154
|
-
async function generatePyodideFunction(job)
|
|
155
|
-
{
|
|
156
|
-
var pythonSliceHandler;
|
|
157
|
-
|
|
158
|
-
const pyodide = await pyodideInit();
|
|
159
|
-
const sys = pyodide.pyimport('sys');
|
|
160
|
-
|
|
161
|
-
const findImports = pyodide.runPython('import pyodide; pyodide.code.find_imports');
|
|
162
|
-
const findPythonModuleLoader = pyodide.runPython('import importlib; importlib.util.find_spec');
|
|
163
|
-
|
|
164
|
-
const parsedArguments = parsePyodideArguments(job.arguments);
|
|
165
|
-
|
|
166
|
-
// write images to file and set environment variables
|
|
167
|
-
const prepPyodide = pyodide.runPython(`
|
|
168
|
-
import tarfile, io
|
|
169
|
-
import os, sys
|
|
170
|
-
|
|
171
|
-
def prepPyodide(args):
|
|
172
|
-
for image in args['images']:
|
|
173
|
-
image = bytes(image)
|
|
174
|
-
imageFile = io.BytesIO(image)
|
|
175
|
-
tar = tarfile.open(mode='r', fileobj=imageFile)
|
|
176
|
-
|
|
177
|
-
# Don't overwrite directories which corrupts Pyodide's in memory filesystem
|
|
178
|
-
def safe_extract(tar, path="/"):
|
|
179
|
-
for member in tar.getmembers():
|
|
180
|
-
if member.isdir():
|
|
181
|
-
dir_path = os.path.join(path, member.name)
|
|
182
|
-
if not os.path.exists(dir_path):
|
|
183
|
-
os.makedirs(dir_path)
|
|
184
|
-
else:
|
|
185
|
-
tar.extract(member, path)
|
|
186
|
-
safe_extract(tar)
|
|
187
|
-
|
|
188
|
-
for item, value in args['environmentVariables'].items():
|
|
189
|
-
os.environ[item] = value
|
|
190
|
-
|
|
191
|
-
sys.argv.extend(args['sysArgv'])
|
|
192
|
-
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
prepPyodide`);
|
|
196
|
-
|
|
197
|
-
prepPyodide(pyodide.toPy(parsedArguments));
|
|
198
|
-
|
|
199
|
-
// register the dcp Python module
|
|
200
|
-
if (!sys.modules.get('dcp'))
|
|
201
|
-
{
|
|
202
|
-
const create_proxy = pyodide.runPython('import pyodide;pyodide.ffi.create_proxy');
|
|
203
|
-
|
|
204
|
-
pyodide.registerJsModule('dcp', {
|
|
205
|
-
set_slice_handler: function pyodide$$dcp$$setSliceHandler(func) {
|
|
206
|
-
pythonSliceHandler = create_proxy(func);
|
|
207
|
-
},
|
|
208
|
-
progress,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
pyodide.runPython( 'import dcp' );
|
|
212
|
-
|
|
213
|
-
// attempt to import packages from the package manager (if they're not already loaded)
|
|
214
|
-
const workFunctionPythonImports = findImports(job.workFunction).toJs();
|
|
215
|
-
const packageManagerImports = workFunctionPythonImports.filter(x=>!findPythonModuleLoader(x));
|
|
216
|
-
if (packageManagerImports.length > 0)
|
|
217
|
-
{
|
|
218
|
-
await fetchAndInitPyodidePackages(packageManagerImports);
|
|
219
|
-
await pyodide.loadPackage(packageManagerImports);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return workFunctionWrapper;
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Evaluates the Python WorkFunction string and then executes the slice
|
|
226
|
-
* handler Python function. If no call to `dcp.set_slice_handler` is passed
|
|
227
|
-
* or a non function is passed to it.
|
|
228
|
-
* This function specifically only takes one parameter since Pyodide Slice
|
|
229
|
-
* Handlers only accept one parameter.
|
|
230
|
-
*/
|
|
231
|
-
async function workFunctionWrapper(datum)
|
|
232
|
-
{
|
|
233
|
-
const pyodide = await pyodideInit(); // returns the same promise when called multiple times
|
|
234
|
-
|
|
235
|
-
// load and execute the Python Workfunction, this populates the pythonSliceHandler variable
|
|
236
|
-
await pyodide.runPython(job.workFunction);
|
|
237
|
-
|
|
238
|
-
// failure to specify a slice handler is considered an error
|
|
239
|
-
if (!pythonSliceHandler)
|
|
240
|
-
throw new Error('ENOSLICEHANDLER: Must specify the slice handler using `dcp.set_slice_handler(fn)`');
|
|
241
|
-
|
|
242
|
-
// setting the slice handler to a non function or lambda is not supported
|
|
243
|
-
else if (typeof pythonSliceHandler !== 'function')
|
|
244
|
-
throw new Error('ENOSLICEHANDLER: Slice Handler must be a function');
|
|
245
|
-
|
|
246
|
-
const sliceHandlerResult = await pythonSliceHandler(pyodide.toPy(datum));
|
|
247
|
-
|
|
248
|
-
// if it is a PyProxy, convert its value to JavaScript
|
|
249
|
-
if (sliceHandlerResult?.toJs)
|
|
250
|
-
return sliceHandlerResult.toJs();
|
|
251
|
-
|
|
252
|
-
return sliceHandlerResult;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/*
|
|
256
|
-
* Refer to the "The Pyodide Worktime"."Work Function (JS)"."Arguments"."Commands"
|
|
257
|
-
* part of the DCP Worktimes spec.
|
|
258
|
-
*/
|
|
259
|
-
function parsePyodideArguments(args)
|
|
260
|
-
{
|
|
261
|
-
var index = 1;
|
|
262
|
-
const numArgs = args[0];
|
|
263
|
-
const images = [];
|
|
264
|
-
const environmentVariables = {};
|
|
265
|
-
const sysArgv = args.slice(numArgs);
|
|
266
|
-
|
|
267
|
-
while (index < numArgs)
|
|
268
|
-
{
|
|
269
|
-
switch (args[index])
|
|
270
|
-
{
|
|
271
|
-
case 'gzImage':
|
|
272
|
-
const image = args[index+1];
|
|
273
|
-
images.push(image);
|
|
274
|
-
index+=2;
|
|
275
|
-
break;
|
|
276
|
-
case 'env':
|
|
277
|
-
const env = args[index+1].split(/=(.*)/s);
|
|
278
|
-
index+=2;
|
|
279
|
-
environmentVariables[env[0]] = env[1];
|
|
280
|
-
break;
|
|
281
|
-
default:
|
|
282
|
-
throw new Error(`Invalid argument ${args[index]}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return { sysArgv, images, environmentVariables };
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
58
|
/** A module.declare suitable for running when processing modules arriving as part
|
|
291
59
|
* of a module group or other in-memory cache.
|
|
292
60
|
*/
|
|
@@ -296,7 +64,8 @@ prepPyodide`);
|
|
|
296
64
|
|
|
297
65
|
if (bravojs.debug && bravojs.debug.match(/\bmoduleCache\b/)) { console.log('Loaded ' + dependencies + ' from group') }
|
|
298
66
|
|
|
299
|
-
if (typeof moduleFactory === 'undefined')
|
|
67
|
+
if (typeof moduleFactory === 'undefined')
|
|
68
|
+
{
|
|
300
69
|
moduleFactory = dependencies
|
|
301
70
|
dependencies = []
|
|
302
71
|
}
|
|
@@ -306,9 +75,8 @@ prepPyodide`);
|
|
|
306
75
|
dependencies: dependencies
|
|
307
76
|
}
|
|
308
77
|
|
|
309
|
-
for (i = 0; i < dependencies.length; i++)
|
|
78
|
+
for (i = 0; i < dependencies.length; i++)
|
|
310
79
|
bravojs.ww.allDeps.push(bravojs.makeModuleId(moduleBase, dependencies[i]))
|
|
311
|
-
}
|
|
312
80
|
}
|
|
313
81
|
|
|
314
82
|
/** A module.provide suitable for a web worker, which requests modules via message passing.
|
|
@@ -335,137 +103,4 @@ prepPyodide`);
|
|
|
335
103
|
id,
|
|
336
104
|
});
|
|
337
105
|
};
|
|
338
|
-
|
|
339
|
-
/* Report metrics to sandbox/supervisor */
|
|
340
|
-
function reportTimes (metrics)
|
|
341
|
-
{
|
|
342
|
-
const { total, webGL, webGPU, CPU } = metrics;
|
|
343
|
-
ring3PostMessage({ request: 'measurement', total, webGL, webGPU, CPU });
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/* Report an error from the work function to the supervisor */
|
|
347
|
-
function reportError (error, metrics)
|
|
348
|
-
{
|
|
349
|
-
const err = new Error('initial state');
|
|
350
|
-
|
|
351
|
-
for (let prop of [ 'message', 'name', 'code', 'stack', 'lineNumber', 'columnNumber' ])
|
|
352
|
-
{
|
|
353
|
-
try
|
|
354
|
-
{
|
|
355
|
-
if (typeof error[prop] !== 'undefined')
|
|
356
|
-
err[prop] = error[prop];
|
|
357
|
-
}
|
|
358
|
-
catch(e){};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
reportTimes(metrics); // Report metrics for both 'workReject' and 'workError'.
|
|
362
|
-
ring3PostMessage({ request: 'workError', error: err });
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Report a result from work function and metrics to the supervisor.
|
|
367
|
-
* @param result the value that the work function returned promise resolved to
|
|
368
|
-
*/
|
|
369
|
-
function reportResult (result, metrics)
|
|
370
|
-
{
|
|
371
|
-
try
|
|
372
|
-
{
|
|
373
|
-
reportTimes(metrics);
|
|
374
|
-
ring3PostMessage({ request: 'complete', result });
|
|
375
|
-
}
|
|
376
|
-
catch (error)
|
|
377
|
-
{
|
|
378
|
-
ring3PostMessage({ request: 'sandboxError', error });
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Actual mechanics for running a work function. ** This function will never reject **
|
|
384
|
-
*
|
|
385
|
-
* @param successCallback callback to invoke when the work function has finished running;
|
|
386
|
-
* it receives as its argument the resolved promise returned from
|
|
387
|
-
* the work function
|
|
388
|
-
* @param errorCallback callback to invoke when the work function rejects. It receives
|
|
389
|
-
* as its argument the error that it rejected with.
|
|
390
|
-
* @returns unused promise
|
|
391
|
-
*/
|
|
392
|
-
async function runWorkFunction_inner(datum, wallDuration, successCallback, errorCallback)
|
|
393
|
-
{
|
|
394
|
-
/** @typedef {import("./timer-classes.js").TimeInterval} TimeInterval */
|
|
395
|
-
var rejection = false;
|
|
396
|
-
var result;
|
|
397
|
-
let metrics;
|
|
398
|
-
protectedStorage.emitConsoleMessages = true;
|
|
399
|
-
protectedStorage.flushConsoleBuffer();
|
|
400
|
-
try
|
|
401
|
-
{
|
|
402
|
-
/* module.main.job is the work function; left by assign message */
|
|
403
|
-
result = await module.main.job.apply(null, [datum].concat(module.main.arguments));
|
|
404
|
-
}
|
|
405
|
-
catch (error)
|
|
406
|
-
{
|
|
407
|
-
rejection = error;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
// reset the device states and flush all pending tasks
|
|
412
|
-
protectedStorage.lockTimers(); // lock timers so no new timeouts will be run.
|
|
413
|
-
if (protectedStorage.webGPU)
|
|
414
|
-
protectedStorage.webGPU.lock();
|
|
415
|
-
|
|
416
|
-
// Let microtask queue finish before getting metrics. With all event-loop possibilities locked,
|
|
417
|
-
// only the microtask could trigger new code, so waiting for a setTimeout guarantees everything's done
|
|
418
|
-
await new Promise((r) => protectedStorage.realSetTimeout.call(globalThis, r, 1));
|
|
419
|
-
|
|
420
|
-
// flush any pending console events, especially in the case of a repeating message that hasn't been emitted yet
|
|
421
|
-
// then, disable emission of any more console messages
|
|
422
|
-
try { protectedStorage.dispatchSameConsoleMessage(); } catch(e) {};
|
|
423
|
-
protectedStorage.emitConsoleMessages = false;
|
|
424
|
-
|
|
425
|
-
metrics = await protectedStorage.bigBrother.globalTrackers.getMetrics();
|
|
426
|
-
|
|
427
|
-
await protectedStorage.bigBrother.globalTrackers.reset();
|
|
428
|
-
} catch (e) {
|
|
429
|
-
ring3PostMessage({ request: 'sandboxError', error: e });
|
|
430
|
-
} finally {
|
|
431
|
-
// due to the nature of the micro task queue, await, our `reset()` cancels all the things that could cause new
|
|
432
|
-
// tasks, and we wait for all pending task to finish in `reset()`, we are guaranteed to have an empty task queue
|
|
433
|
-
// now. Hence it's ok to stop the wall clock measurement now
|
|
434
|
-
wallDuration.stop();
|
|
435
|
-
|
|
436
|
-
// safety: wallDuration is always stopped, `length` will not throw
|
|
437
|
-
metrics = { ...metrics, total: wallDuration.length };
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (rejection)
|
|
441
|
-
errorCallback(rejection, metrics);
|
|
442
|
-
else
|
|
443
|
-
successCallback(result, metrics);
|
|
444
|
-
|
|
445
|
-
/* CPU time measurement ends when this function's return value is resolved or rejected */
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Run the work function, returning a promise that resolves once the function has finished
|
|
450
|
-
* executing.
|
|
451
|
-
*
|
|
452
|
-
* @param {datam} an element of the input set
|
|
453
|
-
*/
|
|
454
|
-
async function runWorkFunction(datum)
|
|
455
|
-
{
|
|
456
|
-
// reset the time used for feature detection
|
|
457
|
-
protectedStorage.bigBrother.globalTrackers.resetRecordedTime();
|
|
458
|
-
const wallDuration = new protectedStorage.TimeInterval();
|
|
459
|
-
protectedStorage.bigBrother.globalTrackers.wallDuration = wallDuration;
|
|
460
|
-
|
|
461
|
-
if (protectedStorage.webGPU)
|
|
462
|
-
protectedStorage.webGPU.unlock();
|
|
463
|
-
await protectedStorage.unlockTimers();
|
|
464
|
-
|
|
465
|
-
/* Use setTimeout trampoline to
|
|
466
|
-
* 1. shorten stack
|
|
467
|
-
* 2. initialize the event loop measurement code
|
|
468
|
-
*/
|
|
469
|
-
protectedStorage.setTimeout(() => runWorkFunction_inner(datum, wallDuration, (result, metrics) => reportResult(result, metrics), (rejection, metrics) => reportError(rejection, metrics)));
|
|
470
|
-
}
|
|
471
106
|
}); /* end of fn */
|
|
@@ -90,7 +90,7 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
|
|
|
90
90
|
addEventListener('message', async (event) => {
|
|
91
91
|
try {
|
|
92
92
|
if (event.request === 'describe') {
|
|
93
|
-
const worktimes =
|
|
93
|
+
const worktimes = protectedStorage.worktimes.getWorktimeList();
|
|
94
94
|
const capabilities = await getCapabilities();
|
|
95
95
|
ring2PostMessage({
|
|
96
96
|
capabilities,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file registers the map-basic worktime for DCP. The map-basic worktime is
|
|
3
|
+
* the default worktime used with compute.for, compute.do, etc...
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2018-2024, Distributive, Ltd. All Rights Reserved.
|
|
6
|
+
* @author Severn Lortie <severn@distributive.network>
|
|
7
|
+
*/
|
|
8
|
+
// @ts-nocheck
|
|
9
|
+
self.wrapScriptLoading({ scriptName: 'map-basic-worktime' }, function mapBasicWorktime$$fn(protectedStorage, ring1PostMessage, wrapPostMessage) {
|
|
10
|
+
let workFunction;
|
|
11
|
+
let workFunctionArguments;
|
|
12
|
+
/**
|
|
13
|
+
* Registers the worktime callbacks that allow the worktime controller (worktimes.js) to setup our environment and run the worktime
|
|
14
|
+
*/
|
|
15
|
+
function registerWorktime()
|
|
16
|
+
{
|
|
17
|
+
protectedStorage.worktimes.registerWorktime({
|
|
18
|
+
name: 'map-basic',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
initSandbox,
|
|
21
|
+
processSlice,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Perform setup tasks before processSlice is run
|
|
27
|
+
* @param {object} job The job assigned to this sandbox
|
|
28
|
+
*/
|
|
29
|
+
function initSandbox(job)
|
|
30
|
+
{
|
|
31
|
+
workFunctionArguments = job.arguments;
|
|
32
|
+
workFunctionArguments.unshift(false);
|
|
33
|
+
|
|
34
|
+
const indirectEval = eval;
|
|
35
|
+
if (job.useStrict)
|
|
36
|
+
workFunction = indirectEval(`"use strict"; (${job.workFunction})`);
|
|
37
|
+
else
|
|
38
|
+
workFunction = indirectEval(`(${job.workFunction})`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The processSlice hook function which runs the work function for input slice data
|
|
43
|
+
* @param {*} data The slice data
|
|
44
|
+
* @returns {*} The slice result
|
|
45
|
+
*/
|
|
46
|
+
async function processSlice(data)
|
|
47
|
+
{
|
|
48
|
+
workFunctionArguments[0] = data;
|
|
49
|
+
const result = await workFunction.apply(null, workFunctionArguments);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
registerWorktime();
|
|
53
|
+
});
|