Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Worker code: Cannot set property self of #<Object> which has only a getter #21054

Closed
DenizUgur opened this issue Jan 10, 2024 · 6 comments
Closed

Comments

@DenizUgur
Copy link

DenizUgur commented Jan 10, 2024

The boilerplate code placed by Emscripten for worker code seems to be creating issues. When the worker is started on Node, it throws the following error and therefore does not load.

TypeError: Cannot set property self of #<Object> which has only a getter
    at Function.assign (<anonymous>)
    at /home/foo/workspace/bar/libs/core/bin/wasm/bar.worker.js:29:10
    ...

I suspect the error comes from this line. However, replacing self with global did not help. I'm not suprised though, probably there is an internal call to it somewhere

self: global,

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.51 (c0c2ca1314672a25699846b4663701bcb6f69cca)
clang version 18.0.0git (https://github.com/llvm/llvm-project f2464ca317bfeeedddb7cbdea3c2c8ec487890bb)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/foo/workspace/_repos/emsdk/upstream/bin

Version of Node:
v20.10.0

Failing command line in full:
N/A

Full link command and output with -v appended:
N/A

@sbc100
Copy link
Collaborator

sbc100 commented Jan 10, 2024

Can you share the full set of link flags you are using? And also maybe the generated bar.worker.js.

If you are using WASM_WORKERS, I think we might not have good support for running those under node yet. Most of the tests for wasm workers are only run in the browser.

@DenizUgur
Copy link
Author

Of course. As far as I can tell, I'm not using WASM_WORKERS. I have a WASM module with a worker and I'm trying to test it using vitest in node environment. I would very much like to continue testing it in browser environment but the browser support is experimental right now (see vitest-dev/vitest#4899).

Without further a do, please see the link commands and bar.worker.js below

-L../../bin/gcc -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXPORT_NAME=bar -sEXPORTED_RUNTIME_METHODS=FS,getValue,setValue,UTF8ToString,lengthBytesUTF8,stringToUTF8 -sEXPORTED_FUNCTIONS=_malloc,_main,PThread -sPTHREAD_POOL_SIZE=1 -sPTHREAD_POOL_SIZE_STRICT=0 -lworkerfs.js -lpthread -lm -sALLOW_TABLE_GROWTH -sWASM_BIGINT -sUSE_PTHREADS -sALLOW_MEMORY_GROWTH
bar.worker.js
/**
 * @license
 * Copyright 2015 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */
// Pthread Web Worker startup routine:
// This is the entry point file that is loaded first by each Web Worker
// that executes pthreads on the Emscripten application.
'use strict';
var Module = {};
// Node.js support
var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string';
if (ENVIRONMENT_IS_NODE) {
// Create as web-worker-like an environment as we can.
var nodeWorkerThreads = require('worker_threads');

var parentPort = nodeWorkerThreads.parentPort;

parentPort.on('message', (data) => onmessage({ data: data }));

var fs = require('fs');
var vm = require('vm');

Object.assign(global, {
	self: global,
	require,
	Module,
	location: {
		href: __filename
	},
	Worker: nodeWorkerThreads.Worker,
	importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}),
	postMessage: (msg) => parentPort.postMessage(msg),
	performance: global.performance || { now: Date.now },
});

}
// Thread-local guard variable for one-time init of the JS state
var initializedJS = false;
function assert(condition, text) {
if (!condition) abort('Assertion failed: ' + text);
}
function threadPrintErr() {
var text = Array.prototype.slice.call(arguments).join(' ');
// See https://github.com/emscripten-core/emscripten/issues/14804
if (ENVIRONMENT_IS_NODE) {
fs.writeSync(2, text + '\n');
return;
}
console.error(text);
}
function threadAlert() {
var text = Array.prototype.slice.call(arguments).join(' ');
postMessage({cmd: 'alert', text, threadId: Module'_pthread_self'});
}
// We don't need out() for now, but may need to add it if we want to use it
// here. Or, if this code all moves into the main JS, that problem will go
// away. (For now, adding it here increases code size for no benefit.)
var out = () => { throw 'out() is not defined in worker.js.'; }
var err = threadPrintErr;
self.alert = threadAlert;
var dbg = threadPrintErr;
Module['instantiateWasm'] = (info, receiveInstance) => {
// Instantiate from the module posted from the main thread.
// We can just use sync instantiation in the worker.
var module = Module['wasmModule'];
// We don't need the module anymore; new threads will be spawned from the main thread.
Module['wasmModule'] = null;
var instance = new WebAssembly.Instance(module, info);
// TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193,
// the above line no longer optimizes out down to the following line.
// When the regression is fixed, we can remove this if/else.
return receiveInstance(instance);
}
// Turn unhandled rejected promises into errors so that the main thread will be
// notified about them.
self.onunhandledrejection = (e) => {
throw e.reason || e;
};
function handleMessage(e) {
try {
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
	// Until we initialize the runtime, queue up any further incoming messages.
	let messageQueue = [];
	self.onmessage = (e) => messageQueue.push(e);

	// And add a callback for when the runtime is initialized.
	self.startWorker = (instance) => {
		Module = instance;
		// Notify the main thread that this thread has loaded.
		postMessage({ 'cmd': 'loaded' });
		// Process any messages that were queued before the thread was ready.
		for (let msg of messageQueue) {
			handleMessage(msg);
		}
		// Restore the real message handler.
		self.onmessage = handleMessage;
	};

		// Module and memory were sent from main thread
		Module['wasmModule'] = e.data.wasmModule;

		// Use `const` here to ensure that the variable is scoped only to
		// that iteration, allowing safe reference from a closure.
		for (const handler of e.data.handlers) {
			Module[handler] = (...args) => {
				postMessage({ cmd: 'callHandler', handler, args: args });
			}
		}

		Module['wasmMemory'] = e.data.wasmMemory;

		Module['buffer'] = Module['wasmMemory'].buffer;

		Module['workerID'] = e.data.workerID;

		Module['ENVIRONMENT_IS_PTHREAD'] = true;

		(e.data.urlOrBlob ? import(e.data.urlOrBlob) : import('./bar.js'))
		.then(exports => exports.default(Module));
	} else if (e.data.cmd === 'run') {
		// Pass the thread address to wasm to store it for fast access.
		Module['__emscripten_thread_init'](e.data.pthread_ptr, /*is_main=*/0, /*is_runtime=*/0, /*can_block=*/1);

		// Await mailbox notifications with `Atomics.waitAsync` so we can start
		// using the fast `Atomics.notify` notification path.
		Module['__emscripten_thread_mailbox_await'](e.data.pthread_ptr);

		assert(e.data.pthread_ptr);
		// Also call inside JS module to set up the stack frame for this pthread in JS module scope
		Module['establishStackSpace']();
		Module['PThread'].receiveObjectTransfer(e.data);
		Module['PThread'].threadInitTLS();

		if (!initializedJS) {
			initializedJS = true;
		}

		try {
			Module['invokeEntryPoint'](e.data.start_routine, e.data.arg);
		} catch(ex) {
			if (ex != 'unwind') {
				// The pthread "crashed".  Do not call `_emscripten_thread_exit` (which
				// would make this thread joinable).  Instead, re-throw the exception
				// and let the top level handler propagate it back to the main thread.
				throw ex;
			}
		}
	} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
		if (Module['_pthread_self']()) {
			Module['__emscripten_thread_exit'](-1);
		}
	} else if (e.data.target === 'setimmediate') {
		// no-op
	} else if (e.data.cmd === 'checkMailbox') {
		if (initializedJS) {
			Module['checkMailbox']();
		}
	} else if (e.data.cmd) {
		// The received message looks like something that should be handled by this message
		// handler, (since there is a e.data.cmd field present), but is not one of the
		// recognized commands:
		err(`worker.js received unknown command ${e.data.cmd}`);
		err(e.data);
	}
} catch(ex) {
	err(`worker.js onmessage() captured an uncaught exception: ${ex}`);
	if (ex?.stack) err(ex.stack);
	Module['__emscripten_thread_crashed']?.();
	throw ex;
}

};
self.onmessage = handleMessage;

@sbc100
Copy link
Collaborator

sbc100 commented Jan 11, 2024

Odd that we have not yet run into this issue testing on any of the verions of node that we test against. By any chance are you trying to load bar.worker.js yourself (rather than have it loaded via the pthread API)?

@DenizUgur
Copy link
Author

Vite bundles all wasm related files as assets and I tell the main js file (bar.js) the URLs of the wasm and worker js. Then main js file calls the worker on its own. So no I don't call the worker directly.

Oddly enough, removing 'use strict' directive didn't help either. Could you give me an example of how you test the wasm workers using node? Maybe there are some configuration issues on my part.

@sbc100
Copy link
Collaborator

sbc100 commented Jan 11, 2024

There are many pthread tests that run under node. For example ./test/runner core0.test_pthread_create will run on of them. The generated output will be in emscripten/out/test

@DenizUgur
Copy link
Author

Ah, I think this is because how @vitest/web-worker sets up Worker. It's not using node's worker_threads by default so that might be the culprit.

@DenizUgur DenizUgur closed this as not planned Won't fix, can't repro, duplicate, stale Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants