Javascript semaphore / test-and-set / lock?
Asked Answered
M

8

63

Is there such a thing as an atomic test-and-set, semaphore, or lock in Javascript?

I have javascript invoking async background processes via a custom protocol (the background process literally runs in a separate process, unrelated to the browser). I believe I'm running into a race condition; the background process returns between my test and my set, screwing things up on the javascript side. I need a test-and-set operation to make it a real semaphore.

Here's the javascript code that attempts to detect background processes and queue them up:

Call = function () {

var isRunning = true,
    queue = [];

return  {
    // myPublicProperty: "something",

    call: function (method) {
            if (isRunning) {
                console.log("Busy, pushing " + method);
                queue.push(method);
            } else {
                isRunning = true;
                objccall(method);
            }
        },

        done: function() {
            isRunning = false;
            if (queue.length > 0) {
                Call.call(queue.shift());
            }
        }
    };
}();

Call is a singleton that implements the queuing; anybody that wants to invoke an external process does Call.call("something") .

Any ideas?

Mara answered 17/2, 2009 at 0:51 Comment(2)
You might want to specify what your 'process' is more in detailed. Is this server-side javascript?Fleawort
Javascript code is always atomic, so no need to lock or anything. See Why don't we have a concurrency control tool in javascript?.Complemental
P
26

JavaScript has no locking semantics because JS is not a multi threaded language. Multiple threads can only operate concurrently in completely distinct contexts -- eg. HTML5 Worker threads, or in things like multiple instances of JavaScriptCore API's context object (I assume SpiderMonkey has a similar concept). They can't have shared state, so in essence all execution is atomic.

Okay, as you have now provided some of your code i assume you have something akin to:

External Process:
<JSObject>.isRunning = true;
doSomething()
<JSObject>.done()

Or some such (using appropriate APIs). In which case I would expect the JS engine to block if JS is executing in the context of your js object (which is what JavaScriptCore would do), failing that you will probably need to put a manual lock in place around js execution.

What engine are you using to do all of this? I ask because based on your description it sounds like you're setting a flag from a secondary thread from a non-JS language using the C/C++ API provided by that language, and most JS engines assume that any state manipulation made via the API will be occurring on a single thread, typically the same thread that all execution occurs on.

Presentative answered 17/2, 2009 at 1:19 Comment(4)
I added the full code above to make it a bit more clear. The engine is Webkit (safari). From javascript I invoke external processes, and the external process invokes the "done" method when it's done. Basically I need to make sure only one external process runs at a time.Mara
Assuming you're doing something similar to [someObject call] or JSObjectCallAsFunction(context, doneFunction, someObject), you should make sure that you are calling on the main thread, especially if you're using the ObjC JS API provided by WebKit (IIRC it may circumvent some of the locks)Presentative
Have you used the webkit sdk on the iPhone by any chance? I'm using a simple window.location scheme, haven't tried the SDK. Would love to learn more about the SDK.Mara
Nope, i know very little about the iPhone SDK -- I only know JSC's C api :DPresentative
W
4

First of all, while it is true that javaScript is single threaded, it is NOT true that no serialization mechanism is ever required by a javaScript application.

A simple example, is when a submit button should fade out for a set amount of time during which an Ajax request to a server is working. When the asynchronous Ajax request successfully completes then a message should appear where the button used to be.

While it would be nice to be able to cancel the button's fadeout and simply set its style to "display: none", as soon as the Ajax request completes, that is not possible in jQuery. Also, a solution could use Events to synchronize the two simultaneous activities, but that is essentially overkill for a simple problem.

A low-tech solution is to poll a lock and when the fadeout completes it is unlocked but the "server done" message is NOT displayed until the success callback, as set by $.post, executes.

var gl_lock;
var gl_selfID;

function poll_lock(message) {
     if (gl_lock === 0) {
          $('#output').text(message).fadeIn(200);
          window.clearInterval(gl_selfID);
     }
 } // end of poll_lock

 function worker() {

     // no one gets in or out
     gl_lock = 1;

     $.post(..., data,function() { 
           gl_selfID = window.setInterval(poll_lock, 40, data.message);
      }, "json");

     // end of fadeout unlock the semaphore
     $('#submit-button').fadeOut(400, function() { gl_lock = 0; });

  } // end of worker

Finally, I think this is more detailed answer, along the lines previously suggested in this discussion by perrohunter.

Wideopen answered 4/10, 2015 at 0:24 Comment(0)
A
2

Maybe you could implement a basic integer semaphore, just add the variable into the DOM and lock/unlock it and make sure your functions keep checking it, else timeout them =)

If you are using a framework such as Mootools you could try to handle the flow of the app with events such as onComplete and so on.

Androgen answered 17/2, 2009 at 1:16 Comment(0)
W
2

I'm not really sure what the question is asking exactly, but check out my semaphore object here: https://github.com/agamemnus/semaphore.js.

Wassail answered 15/1, 2017 at 2:31 Comment(4)
Yes. There are a few examples of usage in the project html pages.Wassail
That doesn't really look like a semaphore, it looks more like a task scheduler that permits a certain amount of parallelism before queueing tasks. To be a semaphore, you would need to be able to block until the resource became available.Kenric
It does block, but maybe I'm not clear on what that means.Wassail
It doesn't "block", it cedes control to the javascript scheduler, then resumes later. But it looks and acts like a semaphore from the outside. On the inside its "smarter" than a blocking semaphore.Celin
W
0

I have ajax stuff which populates select lists, I needed it to be locked so I did something like this. I think you could probably do it simpler though using deferreds and pipes or something.

var semaphore=[];

function myFunc(arg){
   var dfd;
   $.when(semaphore[table]).done(
      function(){
            dfd=myFuncInner(arg);
      }
      );
return dfd;
}

function myFuncInner(table){
semaphore[arg] = new $.Deferred();
... 
somethingasynchronous({
    semaphore[arg].resolve();
});

return  semaphore[arg];
}
Withdraw answered 8/6, 2014 at 22:33 Comment(0)
D
0

I had the same issue, here is how I solved it. It works fine for two concurrent processes. If you have three processes or more, it is possible that two processes start together.

var isRunning = false;
...
var id = setInterval(function(){ //loop until isRunning becomes false
            if (!isRunning){
                isRunning = true;
                //do something synchronous or use a promise
                isRunning = false;
                clearInterval(id); // stop the loop here
            }
        , 10);

It is better than the while loop because it solves concurrency/async issues for reading/setting isRunning.

Doubt answered 30/10, 2019 at 5:48 Comment(0)
T
0

I used the async-mutex package to solve an issue with Expo SQLite not blocking BEGIN TRANSACTION in iOS. You create the mutex and wrap your critical sections in the runExclusive method

    return this.mutex.runExclusive(async () => {
      try {
        await this.executeSqlAsync('begin immediate transaction');
        const tx = new AsyncSQLTransaction(this);
        const rs = await callback(tx);
        await this.executeSqlAsync('commit');
        return rs;
      } catch (error) {
        await this.executeSqlAsync('rollback');
        throw error;
      }
    });
Ticket answered 8/7, 2021 at 15:53 Comment(0)
P
0

Problem: a function to be called in asynchronous way ( parallely ) but r function to be called in synchronous manner. Similar to this

a->b->
a->.  b->
a->.     b->


const arr = [1, 2, 3, 4, 5];
let S = 1;
const a = (val) => {
  return new Promise((res, _rej) => setTimeout(() => {
    console.log("value is processed by a ", val);
    res(val);
  }, 4000));
};
const r = (val) => {
  setTimeout(() => {
    console.log("value is processed by b ", val);
    S = 1;
  }, 100);
};
const condition1 = () => S==1
async function waitForCondition(condition) {
    while (!condition()) {
        await new Promise(resolve => setTimeout(resolve, 1000)); // wait for 1 second
    }
    S=0;
}
arr.forEach((v) => {
    a(v).then(async () => {
        await waitForCondition(condition1);
        r(v);
    });
})

S represents lock. 1 means lock is released, 0 means it is locked. Now when first value reaches to the waitForCondition function, it would wait until the lock is released and will check it in every second for the lock to be released.The execution thread of first value ( if not lock ) would go to r function and lock the function. The execution thread of second value will wait until lock is released so the execution of second value will stick to waitForCondition statement and will not call r.

Keeping this as the base, you can then try to rebuild your logic.

Perisarc answered 21/4 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.