Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Synchronize execution of assembly line

68 views
Skip to first unread message

JohnZ

unread,
Jul 13, 2022, 11:07:27 AM7/13/22
to
I'm stumped.

Consider the case of a configuration with three (3) assembly lines. For convenience, let's call them AL1, AL2, and AL3. AL1 and AL2 execute independently from each other but both execute AL3 as needed via the AssemblyLineFC.

I need to insure that one and only one instance of AL3 is running at any one time to insure the executions from AL1 and AL2 aren't interleaved.

I tried adding the following code to the Before Call hook of the AssemblyLineFC uses to call AL3:

var ral = system.getRunningAls('AL3');
for (var I = 0; I < ral.size(); i++) {
var al = ral.get(i);
task.logmsg("Joining AL3");
al.join(10000);
task.logmsg("AL3 complete")
}

While the code executes, it doesn't accomplish my goal. I can see frequent instances of AL1 and AL2 calling AL3 at the same time based on interleaved messages in the log file.

Is there a way to even accomplish this?

Eddie Hartman

unread,
Jul 23, 2022, 3:02:06 PM7/23/22
to
FIrst off, John, are you executing AL3 and waiting for its result? Which means, that AL1 and AL2 rely on feedback from AL3? Or are they sequentially critical?

How about if you try this in your hook instead to prevent multiple launches of AL3:

----
do {
ral = system.getRunningALs("AL3");
task.sleep(500);
} while (ral.length > 0);
----

That seemed to work for me in my tests. Of course, there could be timing situations where both ALs attempt to start the AL at the same time, so you could add similar code to the Prolog - Before Init of AL3, as long as AL1 or AL2 is not waiting for a result:

----
ral = system.getRunningALs(task.getShortName);
if (ral.length > 1) {
task.logmsg("Stopping this AL3 instance because one is already running...");
task.shutdown();
return;
}
----

If you want to get even fancier, then you could use a Java semaphore - this will absolutely prevent multiple instances from running. For example, here is a Resources > Script that you need to include as a Global Prolog for AL1 and AL2:

----
// Here I define a semaphore object - this is Object Orientation the Douglas Crockford way, i.e. Javascript - The Good Parts
var semaphore = {
semaphoreName: "ThereCanBeOnlyOne",
semaphore: null,

// This method is invoked once to establish the single Semaphore instance
// for example, before AL1 or AL2 are started.
prepare: function() {
// permit only one thread to acquire the new Semaphore at a time
this.semaphore = new java.util.concurrent.Semaphore(1);
// now store this as a Java property in the JVM everything is running under
java.lang.System.getProperties().put(this.semaphoreName, this.semaphore)
},

// Boolean method to see if the Semaphore can be acquired
isAcquired: function() {
this.semaphore = this.semaphore || java.lang.System.getProperties().get(this.semaphoreName);
return this.semaphore.tryAcquire();
},

// Releases the semaphore
release: function() {
this.semaphore.release();
}
}
----

So you have to figure out where to call the .prepare() method once and only once when you solution starts up. After that you would have code like this in the Before Call Hook:

----
while (!semaphore.isAcquired()) {
task.sleep(500 + Math.random()*500); // Wait half a 500 msecs, plus a little random time
}
----

Then in the After Call (as long as your AL FC is set to wait for the called AL to complete) you would release the semaphore:

----
semaphore.release();
----

Hope this helps!

/Eddie

Eddie Hartman

unread,
Jul 23, 2022, 3:03:56 PM7/23/22
to
Arg. Looks messy since all the tabs are removed from the post :/

Eddie Hartman

unread,
Jul 24, 2022, 7:29:56 AM7/24/22
to
On Saturday, July 23, 2022 at 9:03:56 PM UTC+2, Eddie Hartman wrote:
> Arg. Looks messy since all the tabs are removed from the post :/

Also, the following line at the top of the .isAcquired method:

this.semaphore = this.semaphore || java.lang.System.getProperties().get(this.semaphoreName);

which sets the local variable to either itself (if it has been previously set), or from Java properties,
should also be at the top of the .release() method as well. I named this object Resources > Scripts > Semaphore
in the Config and simply include it with AssemblyLines that want to use it (do this via AL editor > Options... > AssemblyLine settings > Include Additional Prologs)

/Eddie

P.S. Keep those Integrator questions coming :D

JohnZ

unread,
Jul 27, 2022, 10:53:53 AM7/27/22
to
Eddie,

Thanks for the great suggestion. I've implemented your idea as follows:

// Define a 'class'

function Semaphore(name) {
this.name = name;
this.semaphore = java.lang.System.getProperties().get(this.name) ||
new java.util.concurrent.Semaphore(1);
java.lang.System.getProperties.put(this.name, this.semaphore);

this.isAcquired = function(seconds) {
return this.semaphore.tryAcquire(seconds, java.util.concurrent.TimeUnit.SECONDS);
}

this.release() {
this.semaphore.release();
}
}

// Two ways to use

var mySemaphore = new Semaphore('some-name-here');
if (!mySemaphore.isAcquired(60)) {
task.logmsg("Semaphore acquisition failed after 60 seconds");
task.shutdown();
}

// or

while(!mySemaphore.isAcquired(0)) {
task.sleep(500 + Math.random()*500); // As in your example

Eddie Hartman

unread,
Jul 31, 2022, 10:18:29 AM7/31/22
to
Nice, John. Just remember that when you create a JS class like this, the last line before the closing curly brace should return a reference to *this* object.
----
...
return this;
} // end of class def
----
That way when you instantiate the class, you get a reference to the newly created object.
----
mySem = new Semaphore("SingletonAL", 1); // passing both name and count :)
----
Of course, from my reading this may not be necessary depending on the JS Engine. But it is the Word of Crockford :)

/Eddie

JohnZ

unread,
Aug 3, 2022, 8:38:44 AM8/3/22
to
Good point. Thanks, Eddie.
0 new messages