Skip to content

make module context-aware #1394

Description

@mac-g

Issue or Feature

I've been experimenting with worker threads in node.js recently and noticed that the canvas module does not appear to be context-aware. In other words, the module will load/instantiate once, but as soon as another thread attempts to require canvas for it's own use, the following error is thrown:

Error: Module did not self-register.
    at Object.Module._extensions..node (internal/modules/cjs/loader.js:840:18)
    at Module.load (internal/modules/cjs/loader.js:666:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:606:12)
    at Function.Module._load (internal/modules/cjs/loader.js:598:3)
    at Module.require (internal/modules/cjs/loader.js:705:19)
    at require (internal/modules/cjs/helpers.js:14:16)
    at Object.<anonymous> (~/node_modules/canvas/lib/bindings.js:3:18)
    at Module._compile (internal/modules/cjs/loader.js:799:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:810:10)
    at Module.load (internal/modules/cjs/loader.js:666:32)

Steps to Reproduce

In main thread...

const {Worker} = require('worker_threads');
var worker1 = new Worker('./child-thread.js'); 
var worker2 = new Worker('./child-thread.js');
// etc.

In child-thread.js...

var Canvas = require('canvas');
var canvas = Canvas.createCanvas(200, 200);
var ctx = canvas.getContext('2d');
// etc.

References

Node.js documentation:
https://nodejs.org/api/addons.html#addons_context_aware_addons
Similar issue:
schroffl/node-lzo#11

Environment

  • node-canvas version 2.4.1
  • Mac OS 10.14.3
  • node version 11.12.1

Activity

  1. LinusU commented on Apr 1, 2019

    @LinusU
    Collaborator

    PRs welcome 👍

  2. jgoralcz commented on Jun 1, 2019

    @jgoralcz

    This would be an incredible addition to node-canvas. Not sure where to fully dive in and modify the code though 🤔

  3. ruiwei commented on Oct 2, 2019

    @ruiwei
  4. anshul-kai commented on Oct 3, 2019

    @anshul-kai

    Nope. Need to fork processes and can't use worker threads.

  5. siddarthareddyt commented on Nov 6, 2019

    @siddarthareddyt

    #1409 doesn't seem to fix this issue. Any plans to provide support for worker_threads?

  6. DePasqualeOrg commented on Jan 31, 2020

    @DePasqualeOrg

    I'm having the same issue. I'd love to be able to use node-canvas in multiple worker threads to improve performance. Anyone want to tackle this? Seems like it should be straightforward to solve:

    https://github.com/nodejs/node/blob/master/doc/api/addons.md#context-aware-addons

  7. mistval commented on Feb 15, 2020

    @mistval
  8. lateiskate commented on Mar 18, 2020

    @lateiskate
  9. anna-rmrf commented on Jul 4, 2020

    @anna-rmrf
  10. mac-g commented on Jul 4, 2020

    @mac-g
    Author

    Looks like I'll have a chance to maybe take a stab at this in a week while I'm on leave. No promises. Has anyone else had a chance to take a look at it? If so, I'd be great to avoid hours of re-discovery.

  11. lostrepo commented on Jul 5, 2020

    @lostrepo

    Maybe this will help.

    I'm total noob in C++
    But I use something like that to use h264 encoder in worker_threads

    #include "v8.h"
    #include <node.h>
    #include <node_buffer.h>
    
    using namespace v8;

    Per NodeJS instance addon data

    class AddonData {
    	public:
    	explicit AddonData(Isolate* isolate):
    	wh(0) {
    		// Ensure this per-addon-instance data is deleted at environment cleanup
    		node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
    	}
    	
    	
    	// Per-addon data
    	int wh;
    	// ... omitted
    	// Node Buffer Object for YUV420 source
    	MaybeLocal<Object> ab;
    	// Node Buffer Object for h264 bitstream
    	MaybeLocal<Object> bsiab;
    	
    	
    	int createEncoder(){
    		int rv	=	WelsCreateSVCEncoder(&encoder_);
    	
    		return encoder_ != NULL ? rv : 1;
    	}
    
    	void clean() {
    		if (encoder_) {
    			encoder_->Uninitialize();
    			WelsDestroySVCEncoder(encoder_);
    		}
    	}
    	
    	
    	static void abDeleteCb(char* data, void* hint) {
    		delete reinterpret_cast<MaybeLocal<Object>*>(hint);
    	}
    	
    	static void noDeleteCb(char* data, void* hint) {
    		// do nothing
    	}
    	
    	static void DeleteInstance(void* data) {
    		AddonData* ndata	=	reinterpret_cast<AddonData*>(data);
    		ndata->clean();
    		
    		delete ndata;
    	}
    };

    Callback that accesses per NodeJS instance addon data

    void encode(const FunctionCallbackInfo<Value>& info) {
    	// Retrieve the per-addon-instance data
    	AddonData* data	=	reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
    	
    	// encode YUV420 from buf
    	data->encode();
    	// construct h264 bitstream from bitstream info
    	data->parseBitstreamInfo();
    	
    	data->bsiab	=	node::Buffer::New(info.GetIsolate(), (char*) data->bsi.data(), (size_t) data->bsiSize, AddonData::abDeleteCb, (void*) &data->bsiab);
    	
    	info.GetReturnValue().Set(data->bsiab.ToLocalChecked());
    }

    Per NodeJS instance module initializer (another method of initialization can be used too but this one was used in another addon that works with worker_threads and I'm total C++ noob)

    extern "C" NODE_MODULE_EXPORT void
    NODE_MODULE_INITIALIZER(Local<Object> exports, Local<Value> module, Local<Context> context) {
    	Isolate* isolate	=	context->GetIsolate();
    	
    	
    	// Create a new instance of `AddonData` for this instance of the addon and
    	// tie its life cycle to that of the Node.js environment
    	AddonData* data	=	new AddonData(isolate);
    	
    	// create encoder instance
    	data->createEncoder();
    	
    	
    	// Wrap the data in a `v8::External` so we can pass it to the method we expose
    	Local<External> external = External::New(isolate, data);
    	
    	
    	// ... omitted
    	
    	exports->Set(
    		context,
    		String::NewFromUtf8(isolate, "encode", NewStringType::kNormal).ToLocalChecked(),
    		FunctionTemplate::New(isolate, encode, external)->GetFunction(context).ToLocalChecked()
    	).FromJust();
    }
  12. maxreisner commented on Jul 23, 2020

    @maxreisner

    I tried getting this to work with no success... If anyone who's more c++ oriented than me (and I'm not) would want some help fixing this, I'd be happy to help!

  13. Mjtlittle commented on Sep 7, 2020

    @Mjtlittle

    I hope i'm not leading anyone astray, as I have no clue how the node API bindings work with creating a context-aware module; but I found a module called 'node-lzo' that has made changes to adapt their library, that I don't see present here.

    schroffl/node-lzo#11

    specifically this replacement...

    NODE_MODULE_INIT(/* exports, module, context */) {
        Init(exports, context);
    }
  14. enricodeleo commented on Sep 20, 2020

    @enricodeleo
  15. 30 remaining items

  16. BowgartField commented on Dec 15, 2023

    @BowgartField

    any updates ?

  17. qbq-ber-alexander-fritsch commented on Jul 4, 2024

    @qbq-ber-alexander-fritsch

    Also just ran into the issue of Module did not self-register, even in 3.0.0-rc2.

    Would love to see this happen, would really make my time a lot easier ;D

  18. deflexable commented on Sep 20, 2024

    @deflexable

    any updates on this?

  19. Zirafnik commented on Oct 12, 2024

    @Zirafnik

    As @anshul-kai pointed out above, the current workaround is to use Node.js child_processes instead of worker_threads.

    From what I've tested, that way works fine.

  20. lvcabral commented on Feb 20, 2025

    @lvcabral

    The proposed workaround is NOT good for all use cases, I need it as a Worker, because my application uses SharedArrayBuffer to communicate to the Worker, there are other issues, that I would have to rewrite several portions of the app. The ideal was really to have Canvas support in Worker, just like the Browser has.

  21. lvcabral commented on Jan 9, 2026

    @lvcabral

    Bump! One year later, any chance this will ever be implemented?

  22. chearon commented on Jan 9, 2026

    @chearon
    Collaborator

    I'm more than halfway through redoing the font stack and prebuilds, and this is on my radar. I don't think we will have any more global/static data after that. It will definitely be done, it just depends on how much free time I have.

  23. lvcabral commented on Jan 10, 2026

    @lvcabral

    @chearon thanks for the feedback!

  24. lvcabral commented on Jun 26, 2026

    @lvcabral

    @chearon just a quick check, any update for us?

  25. chearon commented on Jun 27, 2026

    @chearon
    Collaborator

    Yes, I finished all the hard stuff for v4 (#2476) and it will be out soon! I just tried 4 canvases in parallel via worker threads. I had to tweak a few things, but it worked, with different fonts and SVG rendering in parallel:

    Image Image Image Image

    Development is happening on the next branch. I have to review and test more, then figure out the new way to distribute binaries (#2595) but we're very close.

  26. lvcabral commented on Jun 27, 2026

    @lvcabral

    @chearon I really appreciate the effort! This will unlock the CLI version of my open source Roku simulator to also support Task threads!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions