WebReflection / coincident

An Atomics based Proxy to simplify, and synchronize, Worker related tasks.
MIT License
203 stars 3 forks source link

How to use coincident with sharedworkers? #43

Open nickchomey opened 3 months ago

nickchomey commented 3 months ago

I would like to be able to use coincident with Shared Workers, rather than normal Web Workers.

I do not know what would be involved in making this work, so do not have any suggestions. Though I do know that your legacy Proxied-Worker library seems to support Shared Workers and uses its port property.

Thanks!

WebReflection commented 2 months ago

Shared Workers can't have access to the window object ... would that be OK? I am thinking about a coincident/shared/main and coincident/shared/worker exports 🤔

nickchomey commented 2 months ago

Unfortunately I don't think I have any useful to say here - your knowledge and judgement far surpass mine. Whatever you think works best is surely best!

WebReflection commented 2 months ago

@nickchomey what I am asking is: do you expect the ability to deal with the window main thread object in there? 'cause AFAIK SharedWorker can be used cross tabs too, not just one, so the complexity around that is exponential.

nickchomey commented 2 months ago

Yes, I believe that the primary use case of Sharedworkers is to create a single worker that is used across tabs. Otherwise normal workers can be used.

Some use cases are for things like

My impression is that we just need to pass messages/data between the tabs and shared worker, but I don't know if the window object would be needed.

(I'm somewhat new to JavaScript so just trying to get my bearings with all of this. But it is quite clear to me that a sharedworker would be the right tool for these sorts of jobs.)

Your proxied-worker library has support for sharedworker - can't you just implement the same mechanism? Or is there something fundamental that I am missing? (perhaps related to whether the sharedworker should have access to the tabs Dom, or even tabs have access to each other's Dom?)

WebReflection commented 2 months ago

so ... coincident/main and coincident/worker allow workers to invoke functions from main in a synchronous way and without blocking, while the main thread can invoke workers functions asynchronously (hence still without blocking the main thread).

enter coincident/window/main and coincident/window/worker ... in this case, for the main thread the contract is the same but the worker can access the page window or globalThis object synchronously, so that a worker can do document.body.textContent = "worker riding the main thread" or even window.location.ref = "else.where" to redirect, which is quite powerful and never existent before coincident project landed on GitHub.

This functionality never existed before means that proxied-worker library never cared about it and never used the primitives I am using in here, most notably: Atomics.

There is even a fallback to a ServiceWorker based on sabayon Atomics and SharedArrayBufer polyfill/orchestration that was never considered in proxied-worker neither as that module is fairly old and limited, or better, it's not based on the same primitives this module is based on ... which are unique in the whole JS ecosystem and mostly misunderstood or underestimated because "too magic to trust" out there (their problem, imho).

As summary, once a SharedWorker implementation lands I believe the discussion should be around these topics:

Last, but not least, the IndexedDB issue you mentioned is not necessarily an issue because, like cookies, or localStorage, IndexedDB is attached to the domain: you bootstrap a db on the same domain/path, you get the same db (last time I've checked).

To share states across tabs you have localStorage too, which also offers storage event to communicate to all same-tabs/domain things get notified when a key changed in there ... and I believe this is the most reactive and desirable pattern you want because it's confined on the main thread but it allows any coincident worker to reach out for window.localStorage.setItem("state", JSON.stringify(state)) so that no matter which worker is doing what, all tabs will react to that change done by maybe the only page that bootstrapped, or requires, a worker.

All I am saying is that it looks like your desire for Shared Worker lacks some background around how other primitives work in the JS world, and hopefully some of these hints would help you forward before I find the time and the will to implement what you are asking as it's not super straight forward, the orchestration across multiple sources is not super trivial neither, and I am not fully sure about what should work and should absolutely be banned from that implementation.

If I could have concrete previous work or uses cases that wouldn't work better with just IndexedDB or localStorage, I might at least try to formulate an opinion around this request.

I hope I have explained myself decently enough here, happy to expand on any topic you like.

nickchomey commented 2 months ago

Thanks, that's helpful! I'll ponder it all as I explore how to implement what I need. Though, I'm likely to use web locks (as mentioned in the previous issue that I closed) so that I can have this functionality on Android chromium (which doesn't yet have shared workers).

In fact, that might be reason enough to not bother with this. Either way, don't feel any pressure from me to work on this soon, or even at all. We're all grateful for everything you do.

WebReflection commented 1 month ago

After refining this module lately to avoid every single possible detail that could interfere with each other when running in a multiple workers or servers scenario (no more shared HEAP, dedicated strong-random channel per each worker) my idea is that a Shared Worker is just an indirection of a shared window ... if multiple workers can interact with the same window object without leaking or interfering with each other, what stops a Shared Worker to provide exactly the same functionalities a window on the main would?

All I am saying is that a Shared Worker is, by definition, a main to target, just like a server, so that in theory you could have a single coincident/window/main on your main thread, a coincident/window/worker on your worker that imports also a coincident/shared/worker so that such Shared Worker can import a coincident/shared/main ... the result is that you have separate modules, and concerns, to synchronously ask operations from both your main tab UI but also from the Shared Worker and it's up to you to use such worker to orchestrate the dance around these two ... would this somehow work for you?

In few words:

does this make sense to you and do you think this would be desired? In all this I am not factoring in the complexity related to the server offer but it's also true that the server current logic is what makes me confident the current proposed approach might work ... as you can see, it's convoluted behind the scene, but it can be easy as cake on the user ... I still need to understand use cases for all this beside a sync update across tabs ... I mean, why do you need multiple tabs to start with? 😅

WebReflection commented 1 month ago

P.S. the more I think about this request, the more I feel like it's just an indirection of what I do already with server export ... server in that case is WebSocket driven and it expects either NodeJS or Bun to deal with, but maybe I can improve that server story to pass a Shared Worker end point instead, and call it a day ... this approach would not make it possible to have both Bun or NodeJS and the Shared Worker in the same equation, which is why I am asking if the bi-import approach per worker could be a solution.

WebReflection commented 1 month ago

let me simplify my question: are you expecting to use just a Shared Worker instead of a Worker from any tab main? 'cause that is a bit of a stretch of what I can do in terms of time to dedicate to move this forward ... I hope you can understand that.

WebReflection commented 1 month ago

and then again ... I see SharedWorker is not available in workes ... now that's a bummer: https://webreflection.github.io/is-it-worker/?SharedWorker

nickchomey commented 1 month ago

Thanks for all of the thought about this!

is a bit of a stretch of what I can do in terms of time to dedicate to move this forward

First, I want to make it clear that you have no obligation to work on this! I opened it more as a placeholder than anything.

Second, as I mentioned, perhaps this isn't worth pursuing until Android Chromium (over 50% of web usage) gets support for sharedworkers. Or, perhaps showing a clear and popular use case for sharedworkers will encourage them to implement it...

Third, i have to admit that my knowledge of the inner workings of workers and other web APIs is limited, so I can't really comment on whether your proposal is appropriate. Dummies like me use libraries from experts like you so that we don't need to become experts ;)

What you've proposed seems good though!

As for the question of using just a sharedworker from each tab rather than a worker, I'm not sure. It seems like a reasonable and normal use case though

And as for you not being able to access a sharedworker from a normal worker, that is surprising. The first paragraph of the mdn doc says that they can be accessed from anywhere. Perhaps, however, the limitation you're seeing is due to how Coincident works?

And as for why would I use multiple tabs? I'm not sure how to answer that to be honest - I can't be the only person who often opens multiple tabs of the same site while browsing. If there's a mechanism in each page that relies on common data (eg web sockets) then it would be prudent - for the sake of browser and backend server cpu and memory resources, as well as bandwidth usage - to have them all share a single, consistent source.

The bun/node/server stuff seems irrelevant to me here - I think this is all client browser-side. But if you see ways to integrate/harmonize that stuff, that would be great.

Is this helpful?

WebReflection commented 1 month ago

Perhaps, however, the limitation you're seeing is due to how Coincident works?

this link simply asks a worker to tell if something is available on its global scope and the answer is no, no coincident involved: https://webreflection.github.io/is-it-worker/?SharedWorker

WebReflection commented 1 month ago

P.S. I don't read Available in Workers in this page anywhere: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker

WebReflection commented 1 month ago

for the sake of browser and backend server cpu and memory resources, as well as bandwidth usage - to have them all share a single, consistent source

well ... no ... a server is the source of truth, usually, a shared worker is not necessarily persistent so it's not really a source of truth, you need extra orchestration to make it one, but then again Service Workers are a better primitive.

Maybe all your pain-points will be solved via a Service Worker instead? 🤔

nickchomey commented 1 month ago

P.S. I don't read Available in Workers in this page anywhere: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker

It is in the very first sentence

The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers

well ... no ... a server is the source of truth, usually, a shared worker is not necessarily persistent so it's not really a source of truth, you need extra orchestration to make it one, but then again Service Workers are a better primitive.

This isn't about persistence. It's about not having (in one popular use case) redundant connections to the backend source of truth - particularly when the backend is pushing updates to the browser.

Also, I and many others do not use node/bun/deno/js in the backend.

Maybe all your pain-points will be solved via a Service Worker instead? 🤔

Service workers have unreliable/murky support for websockets and sse connections. They're ephemeral. It is more appropriate to just initiate a single connection across all tabs.

It is all a very common use case of sharedworkers, and even mentioned in that same mdn doc. And many other places.

WebReflection commented 1 month ago

The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers

that deserves an issue filed to MDN as my test page would beg otherwise ... I've missed that, sorry.

WebReflection commented 1 month ago

It is all a very common use case of sharedworkers, and even mentioned in that same mdn doc. And many other places.

sure, but if I can't create a SharedWorker from a Worker I am afraid there's not much I can move on in here ... I wish we could clarify this aspect though, as it seems misleading from that test page (which does nothing but report what's found in the global context for that entry) so if it's that page doing something weird in the little code it uses, I'd be happy to improve that: https://github.com/WebReflection/is-it-worker/blob/main/worker.js

WebReflection commented 1 month ago

just FYI that (my) page doesn't lie ... https://stackoverflow.com/a/30831231

WebReflection commented 1 month ago

if current state is confirmed (no SharedWorker on Workers ... and I think it is) I need to think upside-down the dance ... or test Atomics would work from a shared worked but at that point I need to orchestrate each main that is connecting to one of those so yeah, we're back to exponential complexity to provide the same ease of API and I don't have this requirement at the moment so my time is really low around this, I am afraid.

nickchomey commented 1 month ago

Ok, perhaps the confusion/conflict is over where to create the SharedWorker from.

You've been focused on creating a SharedWorker from within a normal Worker. I assumed that the SharedWorker would be created in the main thread, just as is normally done for Web Workers. Apologies if I said anything that confused this.

It seems to be possible to communicate between Main<->SharedWorker and Worker<->SharedWorker via

Coincident seemingly exists to avoid using those APIs, instead using SharedArrayBuffers, Atomics etc... So, the question is whether it can use those to communicate Main<->SharedWorker and Worker<->SharedWorker?

Here's an article that doesn't quite answer the question, but may be useful SharedArrayBuffer and Atomics

Do you think it might be possible?

And, again, even if it is possible, there's no obligation whatsoever to actually work on it. I was simply bringing up the idea for consideration at some point.

But I do think there's a lot of legitimate use-cases for something like this

In fact, this last case seems like it would be the most common and, perhaps even, compelling. I imagine that a significant portion of scripts that are run in many duplicate/redundant Workers could actually be run in a single SharedWorker.

WebReflection commented 1 month ago

uhm ... the thing is, you never need more than a Worker per page/tab but you also could:

So, the SharedWorker from main requires both sabayon and coincident changes / updates / features that were never in but maybe I can recycle more code than I think for SharedWorkers and provide a very similar dance I already do with workers ... I need to play around and see but my time to do so is extremely limited.

Let's see ... still MDN should change that "available everywhere" thing because it's extremely confusing to me.