08-08-2017 12:16 PM
If I place these two call library function nodes in two separate while loops, so that there is a possibility that they execute simultaneously, will this give an error?
Would an error be caused by simultaneous update of buffer[] and *errnoOut?
Also, am I right to believe that changing the names in the call library function block will do nothing, i.e. I would have to change them in the shared object library function definitions instead?
Thanks.
C Functions in Call Library Function Node:
// Recv Data
int myRecv(char buffer[], int *errnoOut) {
int n = recv(5, buffer, 255, 0);
*errnoOut = errno;
return(n);
}
// Send Data
int mySend(char buffer[], int *errnoOut) {
int n = send(5, buffer, strlen(buffer) + 1, 0);
*errnoOut = errno;
return(n);
}
Solved! Go to Solution.
08-09-2017 03:41 AM - edited 08-09-2017 03:50 AM
@JHugh wrote:
If I place these two call library function nodes in two separate while loops, so that there is a possibility that they execute simultaneously, will this give an error?
Would an error be caused by simultaneous update of buffer[] and *errnoOut?
Also, am I right to believe that changing the names in the call library function block will do nothing, i.e. I would have to change them in the shared object library function definitions instead?
Thanks.
C Functions in Call Library Function Node:
// Recv Data
int myRecv(char buffer[], int *errnoOut) {
int n = recv(5, buffer, 255, 0);
*errnoOut = errno;
return(n);
}
// Send Data
int mySend(char buffer[], int *errnoOut) {
int n = send(5, buffer, strlen(buffer) + 1, 0);
*errnoOut = errno;
return(n);
}
Everything in those functions are function local variables or function parameters, e.g. all data that resides on the stack. As such they don't share anything in common on the C side (disregarding the send() and recv() functions, I'm not sure which API that would be that works with a constant first parameter so can not comment on any possible side effects this function calls could have). If you happen to wire the same int32 constant to both errno parameters, you will simply branch the wire and that will cause LabVIEW to create a copy of the int32 to pass to the second function so that you end up having two separate int32 after both functions have returned.
LabVIEW is dataflow and if you branch a wire you usually create a copy of the data in that wire. For some build in functions LabVIEW is smart enough to know that they don't consume/modify certain wires and by scheduling the code such that these functions are called first before any function that is not declared to not stomp on a wire variables, can avoid to create unnecessary copies that it needs to throw away right away again.
For Call Library Nodes it can't assume anything like that on its own. For parameters passed by value it is clear that they will not be modified by the function (they need to be copied onto the stack to be passed to the function anyways). For variables passed by pointer like your errno parameter you can check a checkbox in the parameter configuration that the parameter is not modified by the function. This will allow LabVIEW to do similar optimization (but will never guarantee that this is done even if it might be possible). Obviously if you check this checkbox and your function modifies the pointed at variable anyways, you tricked LabVIEW into potentially creating bad code.
For scalar values this is usually a moot point but marking a long string or array input as being const and therefore not modified by the function can improve performance of that call if the function really doesn't touch this string or array in any other way but by reading from it.
08-09-2017 04:10 AM
1) The naming of the parameters in the function is not relevant. If I remember rightly, just the order and type need to match. Each variable is only valid within that function, and will just take whatever values are on the wires LV passes in and out. As Rolf said, if you branch the wire to the inputs, then even if you are using a pointer, it will be a different pointer for each function as LV will have made a copy of the data when you branched the wire (at least in this case).
2) The functions cannot execute simultaneously unless you specify that they can. The default option in the Call Library Node is "Run in UI thread". If both nodes have this setting, then only one can execute at a time, and the other will wait until it is finished. This is intentional as many DLLs are not safe for simultaneous use. I recommend to leave this setting checked unless you know it is safe to do otherwise. If you choose "run in any thread", then there is the possibility to be running both nodes.
08-09-2017 06:39 AM
Thanks. That makes sense, but now I do not know why my code is not working.
I created a simple TCP/IPv6 program for my LabVIEW RT system running Linux. If I run my code in a linear fashion i.e. Server Send, Client Recv, Client Send, Server Recv in the same loop, my code works perfectly; however, when I try to run my code with parallel loops i.e. one loop does Server Send, Server Recv and the other loop does Client Send, Client Recv, my program hangs up. Why would this happen?
I set both my sockets to non-blocking. I've also triple checked that opening, binding, connecting, and accepting the sockets work. My program makes it into the loops and hangs up after 1 or 2 iterations.
Here are some screenshots of my block diagram and my .so C code.
Parallel Loop Block Diagram (Server top, Client bottom) (They use the same send, recv subVIs):
Relevant C Shared Object Library Functions:
Send SubVI:
Send SubVI Call Library Function Node:
Recv subVI (the other subVI in this subVI just creates a string of zeros to whatever length is in max buffer length):
Recv subVI Call Library Function Node (the input parameters are defined the same as the send subVI):
Please let me know if you see anything that would cause my code to hang up.
Thanks.
08-09-2017 07:07 AM - edited 08-09-2017 07:09 AM
Do you see the orange color of the Call Library Node? You set them all to run in the UI thread. So when one of them is waiting inside the C function for instance on a select() call to wait on incoming data, it blocks the entire UI thread and prevents any of the others to execute, so your send can never start to run to produce data that your receive on the other end waits for.
Change the Run in UI thread checkbox for all functions which can be blocking to Run in any thread and that problem goes away!! Of course it means that none of those functions can access any global data or other resources without very careful checking, although usually even experienced programmers go wrong there regularly.
08-09-2017 07:10 AM
The problem in that case is probably the blocking after all!
I expect your order of execution might go server send, server receive.....(wait forever as the client functions are blocked).
It looks like your code should be thread safe, try using "Run in any thread" for all the call library nodes.
Strangely, if these were on separate applications/machines, it would actually work ok I think.
08-10-2017 07:28 AM
Thank you for the help.
I changed both the send and recv Call Library Function Nodes to 'Run in any thread' and my program kind of works. Instead of only being able to run 1 or 2 loops, my program manages to run 30 to 40 loops before hanging up again. Using the 'highlight execution' button, it appears that my program is hanging up when both loops are trying to run the recv subVI.
By what you said earlier, once I branch the wires, LabVIEW should have separate memory for the recv subVI and its CLFN pointer/non-pointer variables. Did selecting 'Run in any thread' change that logic i.e. now the pointer variables are not separate, but pointing to the same memory location?
Any idea what's going wrong?
Thanks.
08-10-2017 08:09 AM
Yes! LabVIEW uses so called execution system groups. One of them is the UI execution system which has exactly one thread assigned and never more. There are about 5 other execution subsystems (standard, instrument, data-acquisition, other1, other2). For each of these LabVIEW allocates at startup a threadpool of I think currently 16 threads.
Whenever LabVIEW calls a DLL through the Call Library Node the thread on which the call happens is blocked until the function call returns. There is nothing LabVIEW can do about that to avoid this, this is how C functions need to be managed to work properly.
Usually your top level VI runs in the standard execution system and all its subVIs too. You can assign specific execution systems to different VIs, which can help delay such issues further but if you keep calling blocking C functions from LabVIEW you will sooner or later run into the issue that all the preallocated threads are blocking and LabVIEW runs in a dead lock situation since your C functions block unconditionally.
Possible solutions:
1) Using threadconfig.vi somewhere in vi.lib you can configure LabVIEW to startup with more threads for a specific execution system. Then you need to restart LabVIEW. The new configuration is stored in LabVIEW.ini and would need to be copied over to any build executable ini to work the same. Disadvantages: It is still limited since the number of threads is statically defined at startup of LabVIEW or your application and while you could theoretically configure this to allocated 1000 or more threads you are certainly taxing Windows with such a setup.
2) Make your C functions non blocking! This is a lot of extra C programming work and some on LabVIEW. For this you do use something like a select() call to check if there is the desired event available (like incoming data) and if so you return the according data, otherwise you return with a status telling the LabVIEW VI that there was nothing available. In the LabVIEW VI you can now implement some looping where your loop keeps calling the function until it was either successful with data, there was a real IO error on the socket other than that select() did indicate that nothing is available, or a timeout interval that you maintain in the LabVIEW loop has expired. This works since LabVIEW implements on its diagram its own multitasking. Basically inside the loop whenever your C function call indicates that there was no data available AND your timeout hasn't expired you call the LabVIEW Wait (ms) node with a small wait interval of a few milliseconds. This lets LabVIEW execute other code in the same execution system that needs to run and since there was no data available there is nothing lost with suspending your loop for a small amount of time to let other code do its work.
08-10-2017 09:15 AM
I solved it. I was passing the wrong sockfd to fcntl() to set the socket as nonblocking.
Thanks for your help!