[Bug] v8.1.12 Error on invoking extension function, NoBlockingWorkAllowedException

version 8.1.12

Im getting an error on my component, when i invoke an extension function in the java ComponentModelDelegate.

com.inductiveautomation.perspective.gateway.threading.BlockingWork$NoBlockingWorkAllowedException: No blocking work allowed on thread: perspective-queue-41

Though i am quite sure it worked and i have not changed anything around this. Did something in igntion change around this?

@PGriffith (calling you since you seem to have been answering my questions to the java side a lot :D)
So looking in the previous version of the javadocs i see no mention of BlockingWork before 8.1.12 :frowning:

Yes, we became more strict about Perspective’s threading model.
You need to submit your extension function invocation to the work queue.
One way to do it is the new PerspectivePollingFuture class; for the alarm status table we create one in the model delegate in the constructor:

image

Oke what do i do with the querypoll once i created one in the constructor?

Ahh the fetchAlarmStatusFuture points to somethign about the Blockingoworker...
andthe this::receaiveAlarmStatus points to a function with invoke...

phew this got quite a bit more complicated xD

Is the defaultrate how often it checks the extension function exists or what do i make of that?
Cuz normaly i only invoke the extensionfunction on a specific handleEvent. i dont really want to poll to this function, just execute it one time when the clientside send a specific message. which really wont happen often

Heh i cant build it doesnt find the packages blocking worker or polling future.
do i have to update the base packages of ignition in order to build? thats odd i dont think ive ever done that xD

Yes, since we added that class in 8.1.12 you’ll have to bump your SDK dependency version(s) to that. You’ll also want to bump the minimum required version of your module to at least that SDK version, so that someone can’t attempt to install your module on an older gateway (which would invariably fail, due to the missing classes).

You might be able to get away with a simpler strategy - post a Runnable (probably a lambda) to the common queue, e.g.:
queue.submitOrRun(() -> fireEvent(ResponseEvent.DATA, asJsonObject("fetchUrl", fetchId)));
If you invoke your extension function on queue, you shouldn’t get that error message.

Disclaimer: I’m mostly just reading the code and aware of some background here, this is not my area of expertise.

Hm i need to use runOrSubmit to get the return value, but even doing so im still getting the blocking error :frowning: i notice the error is now on a much lower queue (1 or 2) compared to last time (40+) though that might be unrelated or just because the server hasnt run that long yet xd
I havent updated the sdk yet tho or included anything about the blocking

Edit: i bumped up the sdk.version in my gradle.build (i think)

It started downloading a bunch of thiings but then i got this error
Do i need to do something else here? the snapshot of gradle? where can i find what path i need?

Edit3:
So i tried updating sdk again and noticed it continued downloading where it left of…
it gave an similar error with some other numbers but the same file.
then i did it again and it downloaded it fully and now it builds xd
weird xd

Phew finally got it working again!

I created a runnable with a getValue function
where i set BlockingWork.setBLockingWarkAllowed(true)

(im doing it twice idk which one is needed but it works)


Then initialize the runnable,
start it in a thread.
wait for it to finish running (join)
and get the value from the runnable

.join() is a blocking operation, so I think you are just cheating. I don’t know enough about the queuing rules to say if that is safe. I would expect you to put the assignment to openDefault in the runnable. Possibly needing synchronization, though.

yes xD
I never wanted/needed this to be in a seperate thread,
but with the new update (8.1.12+) it seems extensionfunction.invoke automatically gets created in a new one, because so far igntion only used it for polling functions, but i do not use it for that purpose.

Alright it seems this is causing some unexpected problems afterall…
for some reason i can not access any params or props inside my extension function.
When i call one it seems to get stuck infinitly, i can see the script running in diagnosics running scripts.
The server gets completly stuck even after canceling the script, forcing me to restart, yet no logs or errors are shown.

even something as simple as a print, gets stuck on the view param line.
image

Ive tested this in the alarm component and there it does work. Seems im blocking the async props call somehow and it keeps waiting for it to respond?
I doubt this has anything to do with me “joining” the thread tho…
And probably more with how i create the runnable thread itself with the blockingwork…

@PGriffith @Kevin.Herron
any idea how i could do this differently?
Do i maybe have to use wrapThreadFactory?BlockingTaskQueue? if so how?

Solving these sorts of problems usually means rethinking the movement of data through your logic, so that the problem item is already “present” when requested. And you deliver it without blocking. If stale, the caller gets the stale value.

Your comment that it probably isn’t the “joining” seems outrageously unlikely to me.

I mean i could move along this one prop through the data flow... but who knows next weeks its a different prop...
The fact that is works normally for the alarms status means the self.view.props should be accesable in extension functions somehow.

Maybe it is? i do not have all that much experience with threads in java (or in general).
i thought it just waits for it finish what its doing, should not also wait for any async (prop) calls inside its thread aswell?

The PerspectivePollingFuture mentioned above automatically handles the subscription to the properties.
The Supplier<CompletableFuture<V>> fetch is a computed function that returns a CompletableFuture (think Promise) containing your result type V. Then BiConsumer<V, Throwable> consumer will be invoked when your future completes, with either your result type V or some exception that was thrown during the fetch.
That BiConsumer will be called on the Perspective executor pool that allows ‘blocking’ work automatically, so you’re allowed to invoke whatever extension functions you want.

2 Likes

Hm i did not see that function subscribed to the props. I guess ill have to do something similar.

or else i suppose ill have to dig a bit deeper into the CompletableFuture and BiConsumer to make something else that works without having to poll.

So ive been digging into the polling future and found an easier way to call the invoke.

I was almost like you said here:

but instead of running it on the queue, i run it on the executor.
This by passes all the blocking and threading problems :smiley:
component.getSession().getPerspectiveContext().getExecutorService().submit(()-> ...invoke(...)).get()

However im still getting frozen on calling session props. I looked at the pollingfuture and it did not seem to provide the props to executor service or something. OR maybe i have missed it, im looking a bit more into it and will update this post if i find something

Edit1:

Alright so insteadof print(self.view.params.key) i did print(self.view.params.get('key'))
and im getting the print of the prop after the event and the event is returning nothing

So it seems like Future.get() is not waiting for the params.get('key') but waiting infinity on the params.key?
So i guess this also was happening before in the thread i made with the joining...

I really dont understand this...

How would i wait for it to finish?
or do i just put all my code inside the future? Guess ill try that next

Oke so putting everything inside the future submit works…
I really dont understand it though, why were the get() and join() not waiting correctly?

 component.getSession().getPerspectiveContext().getExecutorService().submit(() -> {
            responsePayload.add("data", data);
            responsePayload.addProperty("invoked", extensionfunction.get().invoke(String.class, "Extension Default", data));
            fireEvent("invoking", responsePayload);
          });

There are many situations, and this appears to be one, where you CANNOT WAIT. The original thread does not permit blocking. Any kind of wait is BLOCKING. I’m glad you stumbled on a solution (putting the balance of the code in the executor), but it is frustrating that you can’t accept “Don’t wait/block/sleep” as the answer.

(You could also have put the balance of the code in a .whenComplete lambda on CompletableFuture.)

Yes, you made me shout. ):

1 Like

Sorry i was very frustrated too :frowning: I would have expected some kind of feedback erros or logs about what was happening, but it just got stuck.

There just had to be a way to know when it was ready to do something after it... And i didnt find anything other then "get()" in the java.util.concurrent.future class, which said it waited...

But seems this is proabably the thing i was looking for...