Some folks implementing a JS engine were wondering if realms running on different threads could share GCd objects. I was looking for spec text that asserts that this isn't possible, and I couldn't find any.
There is, of course, the concept of an "agent", but nowhere does it say that GCd objects may not be shared across agents. All I can find is some non-normative text in the HTML specification.
It's kind of fine in this spec given that I don't see any provided mechanism to share objects between realms so this problem is moot, however the spec does not forbid implementations from implementing any-sort of cross-realm object sharing mechanism (like the one present in HTML with contentWindow), and thus does not constrain cross-realm object sharing to always be same-agent.
We should probably have some normative text forbidding implementations from allowing cross-agent object sharing, as well as some notes about this scattered throughout the spec?
We should probably have some normative text forbidding implementations from allowing cross-agent object sharing
Hm. What's the motivation for putting this constraint on implementations?
Hm. What's the motivation for putting this constraint on implementations?
Because the constraint that “there is at most one execution context per agent that is actually executing code” is agent-level, if such sharing were possible, I think it would imply the possibility of data races that ES is likely interested in prohibiting — not only for ordinary objects, but wouldn’t all the guarantees of the Atomics API be out the window?
Hm. What's the motivation for putting this constraint on implementations?
This entire specification is written based on the assumption that this does not happen. A simple way to see this would be that there are no locks _anywhere_ except in SharedArrayBuffer, which means that you can cause data races if two threads run related algorithms concurrently on the same non-SAB object.
A concrete example are the (non-Shared) ArrayBuffer algorithms: the "detached buffer" check ensures you're never operating on a null buffer, but there is no synchronization or locking involved, which means that one thread could call DetachArrayBuffer while another is running GetValueFromBuffer and perform a null pointer dereference.
locks only occuring in atomic operations doesn't mean they mustn't occur elsewhere.
I think examples can also be found in many bread and butter operations. Say an invariant exists that something not be mutated after an object’s [[Extensible]] has somewhere been observed to be false, as below. One could never be certain the invariant isn’t being violated — it could always change and be observed to change elsewhere between (local) observation and its effect, sorta Zeno’s paradox stylee. It seems apparent that the algorithms are written with the assumption that agents don’t share objects.

locks only occuring in atomic operations doesn't mean they mustn't occur elsewhere.
That's not what I'm saying, I'm saying that the absence of locks in this specification renders it broken when used in a multithreaded way not explicitly forbidden by the specification.
This is a specification, if there are supposed to be locks in the other algorithms they should be specified. If there are not supposed to be locks in the other algorithms then the one-object-one-agent relationship needs to be specified to obviate the need of locks. I don't see how you can have it both ways.
An implementation can do literally whatever it wants as long as the running code is still observably correct. It can solve pi between every line of code, compose a song whenever it evaluates the numeric literal 42, or use read/write barriers to prevent races when sharing data.
Of course it can. However, with the absence of locks _in the specification_ the specification enables an implementation to be written that behaves like a very broken version of javascript. That ... seems like a bad idea.
Reading between the lines of your rather brief comments, it seems like an unstated assertion you are making here is that it is totally okay for implementations to share objects between agents (or, between agents that live on different threads), but in that case it is up to them to make sure that the algorithms still work correctly. Is my reading accurate?
This seems like an okay position for a specification to take. I'm not really fond of implementation-specific behavior that enables implementations to exhibit practically-broken-but-spec-compliant behavior, I'd prefer if there was specification work to avoid it.
However, if _that_ is indeed the position of this specification, there should _at least_ be some non normative text about this, and _ideally_ text using SHOULD of the form "implementations SHOULD not share objects across agents (that live on different threads?)" followed by a clarifying note.
Whatever the desired behavior may be, an actual implementation of JS (https://github.com/boa-dev/boa) was not aware of the complexities around this and off-hand was wondering about using a multithreaded GC to enable cross-thread object sharing, which as demonstrated earlier will cause very broken behavior unless the implementation does a bunch of work not mentioned in the spec. They were not aware of these complexities, nor was I able to find spec text talking about this. This is an indication that _some_ spec change should happen, be it normative changes, or non-normative notes.
I was talking about this to @mystor and she pointed out that that the problem is not really cross-agent sharing, it's cross-_thread_ sharing. I was focusing on agents because they are the closest concept in this spec to something like a shared execution thread, and furthermore the HTML spec basically explicitly defines them as such.
However, it is totally okay for objects to be shared across agents that are on the same thread, and apparently this happens in Firefox where content objects are accessible from the browser-chrome realms/agent.
If we do indeed want to take the path of forbidding cross-thread object sharing as was my intention in the original issue, one path forward that allows same-thread cross-agent sharing is to explicitly specify a concept of an event loop or thread or whatever, and allow multiple agents to live in it but forbid objects from escaping it.
I'll bring this up at the editor call.
If I understand correctly, your concern is that an object might change out from under you while you are executing code, and nothing explicitly prohibits this. (SharedArrayBuffer can already do this, although that manifests as the value of the internal slot changing, which is maybe different.) I think it would be reasonable to restrict this, though it would of course be a normative change.
I don't think we'd want to phrase this in terms of threads, though - from the perspective of running code, a value changing because it was shared with another thread is not particularly different from the implementation deciding it wanted to randomly flip some bits, which is no more or less forbidden than sharing objects across threads. (Though it would make sense to have threads as an example for illustration.)
So I'd rather say something more along the lines of "no value can change while the execution context stack is not empty except where explicitly changed by a step in this specification or when such a step invokes implementation-defined code), with the exception of the [[ArrayBufferData]] slot of objects created by AllocateSharedArrayBuffer and any host-defined internal slots". Does that get at the restriction you care about?
@bakkot Yep, you've characterized my concern accurately.
I think your proposed verbiage would suffice.
Though I would ideally like non-normative notes on that text and/or on the section for agents that explains the implications of this. "Multiple execution contexts cannot operate on the same value" or "if the implementor decides to allow agents to share objects, agents using different threads should not be able to share objects in a way that breaks the \". Or something, I'm not sure what.
Thanks for filing this issue, @Manishearth . I agree that this property is a core part of JavaScript and deserves to be specified. I've searched in vain for text about this in the past. It would be particularly useful to have something official to point to here, when discussing JavaScript's semantics, rather than ending up relying on informal or host-specific documentation. Making a requirement in spec text wouldn't shut us off from proposals to loosen it, such as JSC's https://webkit.org/blog/7846/concurrent-javascript-it-can-work/ , it would just mean that enabling multithreading would be done in a separate proposal which would answer questions like the one @bathos raised.
Most helpful comment
I'll bring this up at the editor call.
If I understand correctly, your concern is that an object might change out from under you while you are executing code, and nothing explicitly prohibits this. (SharedArrayBuffer can already do this, although that manifests as the value of the internal slot changing, which is maybe different.) I think it would be reasonable to restrict this, though it would of course be a normative change.
I don't think we'd want to phrase this in terms of threads, though - from the perspective of running code, a value changing because it was shared with another thread is not particularly different from the implementation deciding it wanted to randomly flip some bits, which is no more or less forbidden than sharing objects across threads. (Though it would make sense to have threads as an example for illustration.)
So I'd rather say something more along the lines of "no value can change while the execution context stack is not empty except where explicitly changed by a step in this specification or when such a step invokes implementation-defined code), with the exception of the [[ArrayBufferData]] slot of objects created by AllocateSharedArrayBuffer and any host-defined internal slots". Does that get at the restriction you care about?