IndexedDb transaction auto-commit behavior in edge cases - javascript

Tx is committed when :
request success callback returns
- that means that multiple requests can be executed within transaction boundaries only when next request is executed from success callback of the previous one
when your task returns to event loop
It means that if no requests are submitted to it, it is not committed until it returns to event loop. These facts pose 2 problematic states :
placing a new IDB request by enqueuing a new task to event loop queue from within the success callback of previous request instead of submitting new request synchronously
in that case the first success callback immediately returns but another IDB request has been scheduled
are all the asynchronous requests executed within the single initial transaction? This is quite essential in case you want to implement result pulling with back-pressure where consumer gives you a feedback in form of a Future that it is ready to consume another response
creating a ReadWrite tx, not placing any requests against it and creating another one before returning to event loop
does creating a new one implicitly commits the previous tx ? If not, serious write lock starvations might occur, because :
If multiple "readwrite" transactions are attempting to access the same
object store (i.e. if they have overlapping scope), the transaction
that was created first must be the transaction which gets access to
the object store first. Due to the requirements in the previous
paragraph, this also means that it is the only transaction which has
access to the object store until the transaction is finished.
The example of enqueuing a new task to event loop queue from within the success callback by recursive request submission with back-pressure :
function recursiveFn(key) {
val req = store.get(key)
req.onsuccess = function() {
observer.onNext(req.result).onsuccess { recursiveFn(nextKey) }
}
}
Observer#onNext // returns Future[Ack] Ack is either Continue or Cancel
Now can onsuccess or onNext do a setTimeout(0) or not to make the whole thing be part of one transaction?
Bonus question :
I think that ReadOnly transactions are exposed to the consumer/user just because it would be hard to detect the end of a batch read if you recursively submit new requests from the success callback of the previous one right? Otherwise I don't see any other reason for them to be exposed to a user, right ?

I'm not sure I understand your question completely but I'll offer an answer on whether you can safely use IDB transaction events to move a state machine.
Yes and no. Yes in theory, no in practice.
I think you understand the transaction lifetime. But to rehash:
The lifetime of a transactions lasts as long as it's referenced: it's "active" so long as it's being referenced, after which it is said to be "finished" and the transaction is committed.
In theory, oncomplete should fire whenever a transaction successfully commits. There's a useful tip in the spec on this that suggests how you could loop:
To determine if a transaction has completed successfully, listen to the transaction’s complete event rather than the IDBObjectStore.add request’s success event, because the transaction may still fail after the success event fires.
To safely use this mechanism be sure to watch for non-success events including onblocked and onabort as well.
Practically speaking, I've found transactions to be flakey when long-lived or done consecutively in batches (as you've noted in another IDB comment). I'm generally not engineering my apps to require tricky behavior because, no matter how the spec says it should behavior, I'm seeing wonky transactions in both Firefox and Chromium (but mostly Blink, interestingly) especially when multiple tabs are open.
I spent many weeks rewriting dash to reuse transactions for supposed performance gains. In the end it could not pass even my basic write tests and I was forced to abandon simultaneous/queued/consecutive transactions and rewrite once again. This time I picked a one-transaction-at-a-time model which is slower but, for me, more reliable (and suggest to avoid my lib and use something like ydn for bulk inserts).
I'm not sure on your application requirements, but in my humble opinion tying in your I/O into your event loop seems like a disastrous idea. If I needed an event loop as what I understand to be the term I would definitely use requestAnimationFrame() and throttle that callback if I needed fewer ticks than one per ~33 milliseconds.

Related

EventSource misses first messages before addEventListener

How to prevent new EventSource from missing first messages before addEventListener called (in pure vanilla JS)?
The problem is race condition between start data receiving and handler subscribing due to non atomic operation create-subscribe.
It would be nice to have a way to create EventSource in closed state or pass subscribers right into constructor.
I can see two workarounds:
to have additional API to notify server that listeners are ready or to request first data after subscribers set
to add some sleep timeout in server before initial data transfer.
They both are ugly enough to not even try to use SSE and go to WebSocket. But it's another question.
I faced this situation trying to create EventSource and subscribe in Chrome Console. So, there was a lag in seconds between creation and adding a listener. Yes, in real life in script the lag will be near milliseconds, but it still exists.
And that was the cause of your problem; the entire reason you experienced it was because you entered each line manually through the console. You cannot expect code entered that way to behave the same as code running sequentially in the browser directly. In "real life" the problem does not exist because the time between statements is less than milliseconds, but more than that, the code is synchronous and is blocking the event loop, so incoming events will not ever be missed in that situation. To have closer to realistic behavior from the console you will need to paste both the creation and listener assignment statements as a single block at the same time. If you do that, you will see that no events are ever missed.

Why are indexedDB requests made before the event handlers are declared?

It feels like this must be a stupid question but I don't understand something fundamental about making a request in indexedDB.
Why are the requests made before the event handlers are defined? For example, the request = objectStore.add(data) is made before the request.onsuccess and request.onerror functions are declared. Is this correct? Is it possible that the request could complete before the event handlers are registered?
I'm comparing it to the creation of an image element, followed by declaring the event handlers for onload and onerror, all before setting the source attribute to the location of a file and attempting to load it. But a request "element" can't be created before a request is made; so, there's nothing to attach the events to until the request has already been made.
Please let me know what I'm missing here. I've been writing and retrieving data from indexedDB without issue, and think I've been coding it correctly; but I want to make sure this is right and will always work.
Thank you.
Duplicate Response
I read that question and answer sometime ago, when I first started reading about indexedDB, and completely forgot about it. Had I found it again before I wrote this question, I likely would not have submitted it and would've just accepted that the code ought to work out whether I understand it or not. Handling error events and transaction aborts is what got me to thinking about the statement order again.
However, after reading the answer again, I don't understand enough to be any further along than just accepting it and hoping it will always work. I'm not trying to be snide. In one sense, it is just confusing for my limited capacity to think of event loops and epochs and that everything sort of happens all at once.
At the end of the epoch (or the start of the next, whatever you think is easier to understand), the underlying JS engine goes back and looks at what is registered to execute, and then executes everything nearly all at once.
There has to be an order of execution or nothing makes sense, asynchronous or not. I understand that the interpreter does not wait for an anysnchronous process to complete before starting to execute the next line of code. But aren't the synchronous statements processed completely in turn in the order they appear in the code and the asynchronous ones started off in the order they appear in the code, such that if an asynchronous process errored quickly, the event could be missed if the event handlers weren't declared in advance? The event handlers are not hoisted like function declarations, are they? This is the part that I still find confusing.
In this article by Jake Archibald on promises, in the introduction he presents an example concerning the loading of images and writes:
Unfortunately, in the example above, it's possible that the events happened before we started listening for them, so we need to work around that using the "complete" property of images.
and
This doesn't catch images that error'd before we got a chance to listen for them; unfortunately the DOM doesn't give us a way to do that. Also, this is loading one image, things get even more complex if we want to know when a set of images have loaded.
That gives the impression that order is important, such that, in the case of images, when possible, the source should be assigned after declaring all the event handlers, in order to not miss hearing events. The important part for me was the fact that an event could take place before the event handler was declared/registered.
I tried to follow the same pattern of making the request after declaring the event handlers in indexedDB, but it doesn't appear possible because there's nothing to attach the events to until the request is made.
Even when all the statements are asynchronous, such as in this example in the MDN Web Docs on Using IndexedDB, some things are still rather confusing. The objectStore.transaction.oncomplete is an interesting statement. We're waiting for the objectStore to be created before attempting to write data to it. (I think that's considered bad practice, writing data in an onupgradeneeded event; so, we don't use that statement.) But what is confusing is why we don't worry about the objectStore being created before creating an index in it. Why isn't the createIndex statement started at the same time the createObjectStore statement started, if everything is processed all at once? If the createObjectStore statement doesn't complete before the createIndex statement begins, shouldn't an event handler be required or it would fail because objectStore wouldn't yet exist?
I know it works because I've been using the same code pattern, but I really don't understand it.
These two items--the potential to miss events and why an event handler isn't needed in this indexedDB example--are what I'd like to better understand. I don't know if this makes my question different or not, but the answer to the duplicate question doesn't answer these for me. Perhaps, I'd have to understand the JS engine better to understand the answer to these questions.
const dbName = "the_name";
var request = indexedDB.open(dbName, 2);
request.onerror = function(event) {
// Handle errors.
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
// Create an objectStore to hold information about our customers. We're
// going to use "ssn" as our key path because it's guaranteed to be
// unique - or at least that's what I was told during the kickoff meeting.
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
// Create an index to search customers by name. We may have duplicates
// so we can't use a unique index.
objectStore.createIndex("name", "name", { unique: false });
// Create an index to search customers by email. We want to ensure that
// no two customers have the same email, so use a unique index.
objectStore.createIndex("email", "email", { unique: true });
// Use transaction oncomplete to make sure the objectStore creation is
// finished before adding data into it.
objectStore.transaction.oncomplete = function(event) {
// Store values in the newly created objectStore.
var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
customerData.forEach(function(customer) {
customerObjectStore.add(customer);
});
};
};
Clarification/Response to Answer/Comments
Thank you for taking the time to respond to my question and for the added explanation.
First, by "before" I simply mean the order in which the statements appear in the script.
I think I follow your analogy and it is a good one. I'm just still not clear on why the employees never submit their work to the secretary until the next day, when it is guaranteed that a secretary will be there to receive it.
It sounds similar to the fact that the javascript interpreter, when it performs its equivalent of compiling the script, hoists the function declarations, such that a function can be invoked in the code before the function declaration has been made.
It would appear that your saying, restated in my simple terms, that the JS engine, at some point before final execution, assigns the event handlers (the secretaries) to be registered in an earlier epoch than the one in which the requests (the employees) that utlimately trigger the events will complete. Therefore, it doesn't matter where in the code the request statements appear relative to the event handlers, that is, as long as they are defined within the same epoch.
The JS engine doesn't know when the request will complete, but only when the event handlers have been registered to start listening and when the request has commenced. As long as the JS engine has a process to order these steps properly independent of the order the statements appear in the code, such that an event cannot be missed, then it's no different to me than the hoisting of function declarations and I don't really have to think much about it any longer to accomplish my tasks.
However, I would still like to better understand what an epoch is, at least in terms of knowing that the statements are made within the same epoch. I don't see any mention of an epoch in the article on "Concurrecny Model and Event Loop" in MDN Web Docs. Would you mind pointing me to any good resources you know of?
Thank you.
Final Notes
I came across these two items through a link, here, on stack overflow. This same question was asked eight years ago and was answered about the same way but with different terminology; that is, instead of epochs, it was that javascript code will "run to completion" or has run-to-completion semantics. This question refers you to this document which can be searched for "run to completion" to read two exchanges about why there is not a race condition in this set up of making a request before registering the event handlers. The older JavaScript book I have by David Flanagan, in discussing the execution of JS "programs," states that, because JS has single-threaded execution, one never has to worry about race conditions; but I don't know if he was referring to this situation exactly.
Thus, this question has been asked and answered multiple times in the past and I guess I'm just another newbie asking an old question as if I was the first to have thought of it, and without enough knowledge of how JS processes.
The article "Concurrency Model and Event Loop", linked above, has a brief "Run-to-completion" section; but I didn't understand its implications until after reading the last document linked above.
I now take it to mean that all the code in a function will run to completion before any other code can begin, which seems to have two interpretations.
One is that the asynchronous request on the database is queued when the statement is reached in the function code but won't actually commence until all the other statements in the function are run, including the event handlers that are declared afterward.
Or, according to that last linked document above, the asynchronous request may run and even complete before the event handlers are registered, but the notification of its completion will remain in the queue and won't execute until after the rest of the statements in the function are run and the event handlers have been registered.
Interpretation 2 appears to be the accurate one but, whichever is the actual case, it all makes adequate sense to me now and explains why the secretary will always be there before the employee submits the work, and why, even if the employee finishes the work in a nanosecond, the employee won't submit the work until the next day when a secretary is guaranteed to be present to receive it. The employee may place the work-complete notification in the queue, but the queue won't sound the notification for a secretary to hear until the next day.
Thanks, Josh, for the additional explanation about what is meant by epochs and how that terminology works out in operation. I accepted your answer and appreciate you taking the time to write it all out.
Now that I seem to understand why the event-handler declarations can be made later in the code than the making of the request, I still don't understand why we can create an object store and then immediately create an index on that object store without having to wait until we know the object store has been successfully created, unless it is synchronous or something else special takes place in a versionchange transaction / onupgradeneeded event. There are no events mentioned in the MDN Web Docs description of createObjectStore and no examples that have any listeners on it; so; I'll just assume it's never necessary.
Thanks again.
Why are the requests made before the event handlers are defined?
It does not matter.
For example, the request = objectStore.add(data) is made before the request.onsuccess and request.onerror functions are declared. Is this correct?
Yes it is correct because again it does not matter.
I would be careful about your use of the word before. Maybe it means something different to me than it does to you. I can't tell. But maybe this is what is tripping you up.
Is it possible that the request could complete before the event handlers are registered?
If you register the event handlers in the same epoch as when you make the request, then no. The request only completes in a later epoch.
Ok, here is my attempt at explaining by example (sorry if this is bad!). Personification is generally a good educational technique, and is less intimidating then using raw technical terms, so let's go with that.
Let's say you are a boss, and have employees. Let's say you ask an employee to do some work for you. Then, you ask that employee to report back to your secretary when they completed the work. Immediately after asking the employee to go do that other work, you carry on doing your own work, without waiting for that employee to finish their work and report back. You are both basically doing work at the same time.
Now, in this situation, what happens if you don't have a secretary at the time you hand the employee a request to do something? Well, no problem. You go and hire another secretary before that employee finishes their work and before that employee even knows who to report back to, which is fine because all the employee knows is they report to your secretary. The employee does not know whether your secretary exists or not at the time of being assigned work, and does not need to know that. The missing secretary did not prevent that employee from getting started, or understanding the work to be done. And by the time that employee completes their work, you have a secretary ready and waiting. Or, you don't, because you don't happen to care to even acknowledge whether the work was actually completed, you just made a command and trusted the employee to do their job, whatever. You really only care about having them report back to your secretary if you need to do some other work that has to wait until after the first project is done, and that is a different concern.
Let's pretend you already had a secretary at the time you assigned the employee the work. What is the difference between this situation of already having a secretary, and the situation where you go and hire one shortly after assigning the work, but before it is done? There is NO difference.
Now, let's try and really address your concern. What you're suggesting is that it seems impossible to reliably go out and hire that secretary before you know whether the employee finished their assignment. This I think is the critical misunderstanding. It is entirely possible to do this. Why is that? I suppose it is not the easiest thing to grasp.
I am going to stretch this metaphor a bit and impose a strange rule. No matter how simple the project you hand off to the employee, even if it is just to run and get you coffee in the morning, they will never ever get back to you the same day. They will always finish their work some later day, at the earliest tomorrow. They might even finish their work within one fleeting nanosecond of you telling them, but they will NEVER get back to you or your secretary right away, they will always be delayed until tomorrow at the earliest.
This means you have all day to go and hire that secretary that did not exist at the time you gave the employee the order. So long as you do it before tomorrow, you're good. That secretary will exist and be working for you by the time the employee responds tomorrow, and will be able to receive the message from the employee.
Edit response to your added comments:
Yep, hoisting is similar in many respects. Things can happen in a different order then written in code. Hoisting is of course synchronous so it is not a perfect similarity, but the out-of-order aspect is still similar.
Epoch is just my own word i use for a single iteration of the event loop. Like in the case of a for loop using i for i from 0 to 2, there are 3 epochs, iteration 0, iteration 1, and iteration 2. i just call them epochs because it is like categories of time.
In a promise case, it might even be a microtask. In a js worker case, it might be thread-like (and workers are the new hotness over the old child-iframe technique). Basically these are all just ways to 'realize' doing more than one thing at a time. Node calls it a tick, and has things like nextTick() that defers code execution until the next tick of its loop. Within a single epoch, things happen in the order they are written (and notably hoisting is all in epoch 0). but some code may be async, and therefore happen across epochs, and therefore may run in a different order than it was written. Code written earlier may happen in a later epoch.
When you make a request, it says, start doing this thing, and get back to me at the earliest in the next epoch. You have up until the end of the current epoch to register handlers for the request.
Some code, like in the case of an image preloader as mentioned in your example, has to take into account that it attaches the listeners too late (images are being preloaded in an alternate timeline and some may already be loaded and in some browsers this means load will not fire), so it wants to check imageElement.complete to catch that case. In other cases of event listener implementations, some dispatcher implementations will fire events to newly added listeners for events that already happened where the new listener was not listening at the time of the event. But that is not a universal characteristic of event listener implementations, just a characteristic of certain implementations.
And in the case of the transaction.oncomplete thing from within onupgradeneeded, that is just not a great example. It is doing stuff it does not need to do.
This is the technical answer to your question:
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
The JS concurrency model is cooperative with "run-to-completion" semantics (no parallel processing of events in the same queue). This means that any async response will be posted as a message to the window event loop, and all the sequential code you see after the request is guaranteed to execute before the async response processing starts.
That said, from usability point of view, the IndexDB API is not affording the intent in the most expressive manner, and coming from other languages with preemptive threading you are excused for being confused :-)

Is it safe to open new IndexedDb transaction from oncomplete callback of the previous one?

The reason why I need it is for composing multiple (add,put) transactions in a flatMap style. Now I'm wondering if I can init a new transaction from an oncomplete callback of the previous one. It seems to be working, but I can never be sure what it does in production. For instance if you do similar thing from request onsuccess callback, it has a special meaning of transaction continuation.
The question might also be, is transaction#oncomplete method invocation a signal that transaction completed OR is it when transaction#oncomplete returns?
I'd have to do some load tests to figure that out myself, In simple test cases it works and it looks the transaction end is transaction#oncomplete method invocation, but if I started to use it heavily, there seemed to be some write lock starvations that made me think about this.
Yes. Starting a second transaction in response to the "complete" event of a previous transaction is both correct and the easiest way to reason about the sequencing.
Per spec (section "steps for committing a transaction"):
[...] Only after the transaction has been successfully written is the "complete" event fired.
As you note, you may see "success" events from individual requests, but the overall transaction may abort (index consistency, quota, etc) so waiting for "complete" is the right thing to do if you need to reason about the success of the first transaction in subsequent ones.

What is the best way to create various IndexedDB objectStores within the same script?

For my IndexedDB I'm pulling various Ajax requests that will provide the data to the objectStores.
Since transactions are asynchronous how should I chain the creation of the objectStores?
I'm thinking in doing it like this:
1- Pull all the Ajax requests in the beginning of the script.
2- Request to open the DB.
3- In the onsuccess handler open the first transaction to create the first objectStore and insert the respective data.
4- Call the oncomplete event on the first transaction to create a second transaction.
5- Do this for all the objectStores that need to be created.
Is this the best way, or should I just write all the transactions inside the onsuccess handler of the indexedDB.open request?
Can I create various objectStores and insert large amounts of data at the same time without causing errors?
You can do everything in one transaction. If you open your transaction, you define the scope of this transaction. The scope can exist out of multiple object stores. By providing an array of all the names of the object stores you want to target.
var transaction = db.transaction(["obj1", "obj2"]);
In my experience transactions tend to close when waiting for an AJAX response so you have to open the transaction in the success handler for the AJAX response.
There is definitely nothing wrong with having multiple, overlapping transactions "running" in parallel, but in practice JavaScript is still single-threaded - you'll find that even the AJAX success handlers run sequentially and ordering is fairly predictable (though not guaranteed), so if you open a transaction in each, and then process the data with a series of puts, what you may actually see is:
openAjax1()
openAjax2()
ajax1Complete()
// in here, open transaction1 and call objectStore1.put() many times
ajax2Complete()
// in here, open transaction2 and call objectStore2.put() many times
put() // from ajax1
put() // from ajax1
put() // from ajax1
... // from ajax1
// transaction1 completes
put() // from ajax2
put() // from ajax2
put() // from ajax2
... // from ajax2
// transaction2 completes
In theory, the only way to actually make things happen in parallel is to use worker threads, but even then most implementations will funnel the work down to a single thread. In practice, the backends are pretty good about optimizing "bulk" writes together so you wouldn't even get much of a performance improvement from using workers.

Are Mutexes needed in javascript?

I have seen this link: Implementing Mutual Exclusion in JavaScript.
On the other hand, I have read that there are no threads in javascript, but what exactly does that mean?
When events occur, where in the code can they interrupt?
And if there are no threads in JS, do I need to use mutexes in JS or not?
Specifically, I am wondering about the effects of using functions called by setTimeout() and XmlHttpRequest's onreadystatechange on globally accessible variables.
Javascript is defined as a reentrant language which means there is no threading exposed to the user, there may be threads in the implementation. Functions like setTimeout() and asynchronous callbacks need to wait for the script engine to sleep before they're able to run.
That means that everything that happens in an event must be finished before the next event will be processed.
That being said, you may need a mutex if your code does something where it expects a value not to change between when the asynchronous event was fired and when the callback was called.
For example if you have a data structure where you click one button and it sends an XmlHttpRequest which calls a callback the changes the data structure in a destructive way, and you have another button that changes the same data structure directly, between when the event was fired and when the call back was executed the user could have clicked and updated the data structure before the callback which could then lose the value.
While you could create a race condition like that it's very easy to prevent that in your code since each function will be atomic. It would be a lot of work and take some odd coding patterns to create the race condition in fact.
The answers to this question are a bit outdated though correct at the time they were given. And still correct if looking at a client-side javascript application that does NOT use webworkers.
Articles on web-workers:
multithreading in javascript using webworkers
Mozilla on webworkers
This clearly shows that javascript via web-workers has multithreading capabilities. As concerning to the question are mutexes needed in javascript? I am unsure of this. But this stackoverflow post seems relevant:
Mutual Exclusion for N Asynchronous Threads
Yes, mutexes can be required in Javascript when accessing resources that are shared between tabs/windows, like localStorage.
For example, if a user has two tabs open, simple code like the following is unsafe:
function appendToList(item) {
var list = localStorage["myKey"];
if (list) {
list += "," + item;
}
else {
list = item;
}
localStorage["myKey"] = list;
}
Between the time that the localStorage item is 'got' and 'set', another tab could have modified the value. It's generally unlikely, but possible - you'd need to judge for yourself the likelihood and risk associated with any contention in your particular circumstances.
See the following articles for a more detail:
Wait, Don't Touch That: Mutual Exclusion Locks & JavaScript - Medium Engineering
JavaScript concurrency and locking the HTML5 localStorage - Benjamin Dumke-von der Eh, Stackoverflow
As #william points out,
you may need a mutex if your code does something where it expects a
value not to change between when the asynchronous event was fired and
when the callback was called.
This can be generalised further - if your code does something where it expects exclusive control of a resource until an asynchronous request resolves, you may need a mutex.
A simple example is where you have a button that fires an ajax call to create a record in the back end. You might need a bit of code to protect you from trigger happy users clicking away and thereby creating multiple records. there are a number of approaches to this problem (e.g. disable the button, enable on ajax success). You could also use a simple lock:
var save_lock = false;
$('#save_button').click(function(){
if(!save_lock){
//lock
save_lock=true;
$.ajax({
success:function()
//unlock
save_lock = false;
}
});
}
}
I'm not sure if that's the best approach and I would be interested to see how others handle mutual exclusion in javascript, but as far as i'm aware that's a simple mutex and it is handy.
JavaScript is single threaded... though Chrome may be a new beast (I think it is also single threaded, but each tab has it's own JavaScript thread... I haven't looked into it in detail, so don't quote me there).
However, one thing you DO need to worry about is how your JavaScript will handle multiple ajax requests coming back in not the same order you send them. So, all you really need to worry about is make sure your ajax calls are handled in a way that they won't step on eachother's feet if the results come back in a different order than you sent them.
This goes for timeouts too...
When JavaScript grows multithreading, then maybe worry about mutexes and the like....
JavaScript, the language, can be as multithreaded as you want, but browser embeddings of the javascript engine only runs one callback (onload, onfocus, <script>, etc...) at a time (per tab, presumably). William's suggestion of using a Mutex for changes between registering and receiving a callback should not be taken too literally because of this, as you wouldn't want to block in the intervening callback since the callback that will unlock it will be blocked behind the current callback! (Wow, English sucks for talking about threading.) In this case, you probably want to do something along the lines of redispatching the current event if a flag is set, either literally or with the likes of setTimeout().
If you are using a different embedding of JS, and that executes multiple threads at once, it can get a bit more dicey, but due to the way JS can use callbacks so easily and locks objects on property access explicit locking is not nearly as necessary. However, I would be surprised if an embedding designed for general code (eg, game scripting) that used multi threading didn't also give some explicit locking primitives as well.
Sorry for the wall of text!
Events are signaled, but JavaScript execution is still single-threaded.
My understanding is that when event is signaled the engine stops what it is executing at the moment to run event handler. After the handler is finished, script execution is resumed. If event handler changed some shared variables then resumed code will see these changes appearing "out of the blue".
If you want to "protect" shared data, simple boolean flag should be sufficient.

Categories