Jump to content
Sign in to follow this  
killzone_kid

callExtension can cripple main process, even if spawned

Recommended Posts

So callExtension waits for the dll to exit. It does not wait for any output from DLL to return. You can send output back immediately upon dll execution, it doesnt matter, call extension will be waiting for the dll thread to run to the end. Minor annoyance, but here comes a bigger one.

If you spawn a thread with callExtention in it and halt the dll waiting for input for example, it cripples ArmA. Why? it is running from a separate parallel thread, the engine should leave it and carry on, but no.

Not sure what to make out of it, but it is pretty poor. Would really like to hear there are improvements to be made.

Share this post


Link to post
Share on other sites

thanks, posted it yet as issue into Arma 3 Feedback tracker?

Share this post


Link to post
Share on other sites
thanks, posted it yet as issue into Arma 3 Feedback tracker?

Wanted first to make sure it is a non intended behaviour.

Could you please provide an example?

I'll make something

Share this post


Link to post
Share on other sites
So callExtension waits for the dll to exit. It does not wait for any output from DLL to return. You can send output back immediately upon dll execution, it doesnt matter, call extension will be waiting for the dll thread to run to the end. Minor annoyance, but here comes a bigger one.

If you spawn a thread with callExtention in it and halt the dll waiting for input for example, it cripples ArmA. Why? it is running from a separate parallel thread, the engine should leave it and carry on, but no.

Not sure what to make out of it, but it is pretty poor. Would really like to hear there are improvements to be made.

... yes, it is because script processing is still single threaded at all (called or spawned) ...

Spawned script code is processed delayed, with lower priority and statement throughput (scheduled, or multiplexed with other spawned scripts), but it is using the SAME CPU THREAD (same core).

So it is always important to return as soon as possible to give the engine the control of the thread back.

If your .dll function really need more time, you could generate an separate worker thread inside your .dll, so that the time and power consuming processing is separated from script processing main thread.

(If you need the result from your function processing immediately, then you are in trouble and all you can do is optimizing and optimizing and ....)

Edited by Fred41

Share this post


Link to post
Share on other sites
I'll make something

Thanks, that'd be great.

I'm mainly interested in this:

So callExtension waits for the dll to exit. It does not wait for any output from DLL to return. You can send output back immediately upon dll execution, it doesnt matter, call extension will be waiting for the dll thread to run to the end.

I'm not sure how you can return input and carry on at the same time.

If you spawn a thread with callExtention in it and halt the dll waiting for input for example, it cripples ArmA. Why? it is running from a separate parallel thread, the engine should leave it and carry on, but no.

This, on the other hand, is rather simple. A thread within the script interpreter is not an actual process thread. The script interpreter runs in one thread and the RVExtension DLL function is blocking, therefore the whole script interpreter will seemingly hang. Yes, a work-around could be introduced in the engine but that would create further complexity and in my opinion, this should be your responsibility rather than BIS'. Create your own thread within the DLL that will run asynchronously and periodically check whether it has finished yet.

EDIT: OK, I see you posted an example in your ticket.

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
   //immediate return
   strncpy_s(output, outputSize, "ok", _TRUNCATE);
   //sleep for 5 sec
   Sleep (5000);
}

That's not a return. A function does not return until the return keyword (or the end of the function scope if none is present).

There is absolutely no way this can ever work. Sure, you could run RVExtension asynchronously and monitor the output but to what end? How can you know that whatever is in output is the desired return value? What if you're filling it by parts. Not to even mention the race condition.

Edited by Deadfast

Share this post


Link to post
Share on other sites

I'm not sure how you can return input and carry on at the same time.

Why not. If I want to dump data into console or database and don't want to wait for it to finish, I should be able to send it and get confirmation it got received and return leaving dll to continue.

Share this post


Link to post
Share on other sites
Why not. If I want to dump data into console or database and don't want to wait for it to finish, I should be able to send it and get confirmation it got received and return leaving dll to continue.

As I said in the edited portion, this is not how C++ works.

Share this post


Link to post
Share on other sites
If I want to dump data into console or database and don't want to wait for it to finish, I should be able to send it and get confirmation it got received and return leaving dll to continue.

... then you need an separate worker thread (see my post above), so that you can return immediate ...

In your example you return PAST the Sleep (5000) :

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
   //immediate return
   strncpy_s(output, outputSize, "ok", _TRUNCATE);
   //sleep for 5 sec
   Sleep (5000);
}

<- here the function returns and the script engine can proceed ....

Share this post


Link to post
Share on other sites

Thanks for the explanation and as you can see I'm a bit of a noob when it comes to C++, been learning it for what a wekk now :) But I have to disagree with this:

this should be your responsibility rather than BIS'.

Every single PC now runs multicore processors yet Arma engine hangs to a single core? Why should it be end user responsibility to manage multithreading especially when all you have is primitive extension support that allows you to send a string and receive a string from dll?

---------- Post added at 16:45 ---------- Previous post was at 16:42 ----------

.

In your example you return PAST the Sleep (5000) :

Thanks fred I got it. What about the one core deal?

Share this post


Link to post
Share on other sites

Thanks fred I got it. What about the one core deal?

.. when i buyed arma 3 alpha, i assumed a complete new script processing engine is waiting for me to be investigated (including java) ...

Let's say, i was a bit "suprised", to discover the same single threaded (scheduled, non scheduled) environment again ... :(

Share this post


Link to post
Share on other sites
.. when i buyed arma 3 alpha, i assumed a complete new script processing engine is waiting for me to be investigated (including java) ...

Let's say, i was a bit "suprised", to discover the same single threaded (scheduled, non scheduled) environment again ... :(

You're not the only one :)

Share this post


Link to post
Share on other sites
But I have to disagree with this:

Every single PC now runs multicore processors yet Arma engine hangs to a single core?

The engine does use multiple threads, the script interpreter doesn't.

At the end of the day, the VM is going to be pretty simple - you got a queue of instructions that are executed one by one in a blocking manner ("blocking" meaning it won't move onto the next one until the current one has returned). This is why the callExtension you demonstrated or nearestObjects with a large search radius will freeze the entire game, no matter how you execute it. What the interpreter calls a thread only influences how the instructions are going to be lined up in the VM queue, nothing else.

Could the VM be rewritten to process the queue in a multithreaded fashion? Sure, it would probably be quite a bit of work though. More importantly, you'd hate the end result.

Race condition, deadlock, mutex and other concurrent programming fun stuff is something that has no place in a scripting language.

Share this post


Link to post
Share on other sites

Is 30000000 large enough radius? I put it to the test and it returned almost immediately with 8451 buildings. Try it yourself, no blocking whatsoever.

_buildings = nearestObjects [player, ["Building"], 30000000];
hint format [
"found: %1 building #1678: %2",
count _buildings,
_buildings select 1678
];

Arma2Net supports async function calls.

Thanks but it took me 3 lines of code to start another thread. It is just that VS 2010 doesn't have thread support but 2012 does.

Share this post


Link to post
Share on other sites
Is 30000000 large enough radius? I put it to the test and it returned almost immediately with 8451 buildings. Try it yourself, no blocking whatsoever.

Stratis has to few buildings, try nearestObjects [player, [], 30000000].

Thanks but it took me 3 lines of code to start another thread. It is just that VS 2010 doesn't have thread support but 2012 does.

C++11 finally adds native concurrency support, previously it had to be done using API of the target OS (WinAPI or POSIX threads).

Share this post


Link to post
Share on other sites

166181 nearest objects, took 3 seconds. I would imagine commands like nearestObjects have to be blocking as you always expect a return. callExtention can be one way call just like generic call command.

Anyway. I can make dll continue to run after extension returned, no problem. Would be great if I could suspend the script thread until it receives return from extension without blocking the whole game. Cannot see how it is possible with just dll tweaking.

Share this post


Link to post
Share on other sites
I would imagine commands like nearestObjects have to be blocking as you always expect a return. callExtention can be one way call just like generic call command.

callExtension also has to wait for the return value, that's how the command was designed.

Cannot see how it is possible with just dll tweaking.

As me and Fred said, you need a worker (the DLL thread) which you can periodically check from SQF to see if it has already finished to get the return value.

Share this post


Link to post
Share on other sites

At the end of the day, the VM is going to be pretty simple - you got a queue of instructions that are executed one by one in a blocking manner ("blocking" meaning it won't move onto the next one until the current one has returned). This is why the callExtension you demonstrated or nearestObjects with a large search radius will freeze the entire game, no matter how you execute it. What the interpreter calls a thread only influences how the instructions are going to be lined up in the VM queue, nothing else.

@Deadfast, thanks for clarification.

I have opened a partly related thread: http://forums.bistudio.com/showthread.php?156542-optimization-for-script-interpreter-possible

Maybe you are the right man to answer my question best :)

Share this post


Link to post
Share on other sites

As me and Fred said, you need a worker (the DLL thread) which you can periodically check from SQF to see if it has already finished to get the return value.

Yes, this is the way. I have been optimising my debug_console extension and not until I added mutex I was able to call extension 100000 times in 0.23 seconds without crashing it. C++ is lots of fun :)

Share this post


Link to post
Share on other sites
Yes, this is the way. I have been optimising my debug_console extension and not until I added mutex I was able to call extension 100000 times in 0.23 seconds without crashing it. C++ is lots of fun :)

... congratulation, your first really self made thread ..., (remembering the nice feeling, when the fresh thread starts to say "... papa ..." :)

Mutex is a relative slow thread synchronization function. If you have fun with optimizing, look for InterlockedIncrement/Decrement/ExChange.

You could use it to safely modifying two array indices (pointing to your array of strings fields, one for reading from and one for writing to array of string).

But 0.23s for 100000 callextensions is already very, very fast :)

Share this post


Link to post
Share on other sites

LOL Fred I felt like this when I made my first extension few weeks ago :) 0.23s is comparable to just plain call function so I'm happy with it. I used deque (push_back, pop_front) for managing the queue. Tried vector but it seem to be inefficient for this method at least according to documentation. list, I literally broke it at least dozen of times. It failed to report proper size LOL. deque seems to be really stable but needed mutex to sync push pop.

Share this post


Link to post
Share on other sites
Stratis has to few buildings, try nearestObjects [player, [], 30000000].

C++11 finally adds native concurrency support, previously it had to be done using API of the target OS (WinAPI or POSIX threads).

I just want to point this out, but the c11 concurrency library is very much a wrapper over WinAPI/POSIX/etc threads. If running on windows 7, std::thread still calls one of the low level winAPI create threads (CreateThread, _beginthread/_beginthreadex, etc). The library merely hides much of the low level stuff from the programmer. However a generic threadpool library into c++ is still under debate, mostly because the committee cant agree upon what the 'correct' way to implement thread pools is.

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×