LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

uninstalling a callback from within the callback

I'm using rs232 library callbacks to read incoming streaming messages of various types on a serial port.

 

Messages look like this:    <Command> <Message Body> <CRG RTN>

 

I use a callback on the rs-232 library to notify on receipt of the command, which is a single ASCII character like 'x'.  (LWRS_RXFLAG) I read the inqueue up to the 'x' using ComRdTerm, throwing everything away (i.e. searching for the message body in the inqueue).  I then do another ComRdTerm for a CRG RTN, thus reading the body of the message I'm after.

 

Originally, I was un-installing the callback as soon as I was in the callback:  i.e., I was trying to prevent callbacks from stacking up due to receipt of subsequent 'x' char in the in queue.  I never saw anything to indicate that I can't do this - uninstall the callback from within the callback itself.

 

While this worked for some commands, it soon became apparent that something wasn't quite right:  the second ComRdTerm was sometimes missing the first several chars of the message body - it varied from one to maybe five or six, and happened about 10% of the time the callback was used  - so looking like a timing issue.

 

I eliminated the un-install of the callback from the beginning of the callback and instead unistalled the calback just before exiting the callback.  This still led to errors of the same kind. 

 

Finally, on entry into the callback , I would immediately zero out the buffer I intended to read into with the ComRdTerm calls.  This caused everything to work solidly.  

 

Interestingly, it is the time it takes to do the zeroing that matters:  I had it zeroing the wrong buffer at one point and the code still worked solidly. 

 

Does anyone understand what type of timing issue might be happening here?  Is it OK to disable a callback from within the callback itself?

What happens to the inqueue if it overflows - does it wrap around?  Drop incoming frames?    If I uninstall a callback, does it then flush any outstanding callbacks on that port that exist at the time of the uninstall?

 

Other info -

 

My inqueue size on OpenComConfig is 4096, and the buffer I do the ComRdTerm's into is 32768 bytes.  Baud rate is 115,200.  No flow control.

 

I also use the callback if the inqueue gets half full, and flush it by reading and discarding 2048 chars out of the inqueue.

 

 

#define INPUT_SIZE      (4096)  

CHAR  szInputBuffer[32767 + 1];  

 

 RS422Error = OpenComConfig (PCP_Comport, NULL, BAUD_RATE, PARITY, DATA_BITS, STOP_BITS, INPUT_SIZE, OUTPUT_SIZE);

 

 

VOID CVICALLBACK MessageCallback (INT portNumber, INT eventMask, LPVOID callbackData) { 

 

 if (eventMask & LWRS_RXFLAG) {  

 

        for (i = 0; i < sizeof (szInputBuffer); i++) szInputBuffer[i] = '\0';  // must be here for this to work!?!?!?!       
        // 'x' is in the input queue.   Read up to the 'x', throw away
       
        iBytesRead = ComRdTerm (portNumber, (&szInputBuffer[0]), INPUT_SIZE, 'x');

        szInputBuffer[0] = 'x';  // Put the 'x' back in the buffer as first char of message    

 

       iBytesRead = ComRdTerm (portNumber, (&(szInputBuffer[1])), INPUT_SIZE, CRG_RTN;

       InstallComCallback (portNumber, DISABLE_COM_CALLBACKS, 0, 0, 0, 0);  

       return;

 

   }

   else if (eventMask == LWRS_RECEIVE) {
       
        // In queue is at least half full, read half the input queue size and discard it, 'x' isn't in the first half. 
        // We can't just flush the inqueue, the 'x' may have arrived after the callback was invoked.  Leave the callback active.
           
        ComRd (portNumber, szInputBuffer, INPUT_SIZE / 2);

            return;

 

  }

 

} // end, callback

Message Edited by menchar on 03-03-2010 04:12 PM
Message Edited by menchar on 03-03-2010 04:14 PM
0 Kudos
Message 1 of 16
(5,164 Views)

Can't speak to all your questions, but I uninstall the callback at the entry and then re-install it at the exit all the time with no problems:

 

static void foo(int portNo, int eventMask, void *callbackData)
{     

    char                 buffer[128];
    int                  cnt;
      
    InstallComCallback (portNo, 0, 0, 0, 0, 0);  //disable callback
    
    cnt = GetInQLen(portNo);  
    
    while(cnt)
    {
        numBytesRead = ComRdTerm(portNo, buffer, 127, '\r');
    
        buffer[numBytesRead] = 0;  

        status = ReturnRS232Err();

 

           <error check & do some processing and such>

        cnt = GetInQLen(portNo);

    }

 

    InstallComCallback(portNo, LWRS_RXFLAG, 127, '\r', foo, 0); 

}

 

My settings are like yours as well.. 115k baud, 4096 serial buffer, no flow

0 Kudos
Message 2 of 16
(5,123 Views)

gtoph -

 

Thanks for the reply - yup, you're doing something similar though I don't re-enable at the end of my callback.

 

I was seeing a difference depending upon where I put the disable callback.

 

It looks to me like I might be seeing a race between reading the first char of the message and starting the read of the message body before the inqueue starts getting overflowed.  Or a race between when the callback is entered and when the first ComRdTerm call is being made.

 

Now I'm curious if I really need to do the first read up to the start of the message.

 

On a  LWRS_RXFLAG  callback, does that guarantee only that the requested character is somewhere in the inqueue, or does it also mean that if I do a read off the inqueue that the first thing I'll get is the requested character?  I'm treating it as if I need to flush the inqueue up to the requested character before looking for a \r 'cause there could be other \r's in the inqueue in front of the requested character.

 

I would jack the inqueue size up to some arbitrarily large number but the NI help indicates that the actual inqueue size is up to the Windows serial driver / OS.  I think I read somewhere that for NT it's always 4096 no matter what you specify.

 

This code originally ran on a older PC just fine, it's running now on a dual-Xeon 2 GHz machine (4 cores total I think).

 

I know NI made some changes to their rs232 library to to accomodate using separate threads to manage the queues.

 

I can't help but wonder if I'm getting hosed by some sort of multi-core multi-thread race condition weirdness in the serial library. 

 

Thanks again for the reply, it's been pretty quiet on this 😉

 

Menchar

 

 

 

 

 

 

0 Kudos
Message 3 of 16
(5,118 Views)

I may understand this a bit better now.

 

I am waiting in another function for the callback to complete.  I am putting the thread to sleep, waking every 250 msec to process events and see if the callback has completed processing the message.  I do this to avoid spin locking / busy waiting, and at the same time keep the GUI somewhat  responsive.

 

BUT the callback won't run at all unless the main thread processes CVI events, even if the windows driver has the character requested in the inqueue and wants to trigger the callback.   I'm assuming that CVI rs232 library uses the native Windows driver's ability to call back on receipt of a particular character.  

 

So, maybe too much time is elapsing between when the requested char appears and when the callback is finally called, and the inqueue gets overwritten with new messages coming in.  I should crank down the interval to 100 msec or so, or maybe set the inqueue size up and see if that helps.  I wonder how quickly the RTE can process events, can it do so at 10Hz?

 

Coud also be a thread scheduling issue since I'm putting the main thread to sleep.  Maybe it occasionally gets pre-empted and doesn't come back awake at the 250 msec interval and it's even longer before the callback gets called.

 

I could create a thread of my own to run continuously and monitor the serial port I suppose but need to think about how to do this.

 

 

 

 

Message Edited by menchar on 03-04-2010 10:18 PM
0 Kudos
Message 4 of 16
(5,108 Views)

I think you might be correct in that the callback wont actually get run until the main thread is allowed to process it.  The way I have my program structured is that the main thread, for the most part, only deals with GUI and other callback kind of stuff...  If a user presses a button, the main thread will send a message to one of it's workers to do the actual work.  This way nothing is tied up like might be the case for you.

 

My messages are relativly small as well.  Most of the time, it's a simple command-response situation so I don't even need the callback.  Only when the attached device is going to spew data is when I use it, and even then the messages (i.e. <data><cr><lf>) are well under 100 byes in length.

 

If I understand your situation correctly, there's a bunch of data comming at you, and you looking for a starting trigger character so you can then read the real message which is terminated by another special character.

 

Since I also think you are right that windows imposes some buffer limit regardless of what you actually tell it, you might be loosing data if you can't get to it fast enough... it would depend on where in the buffer your starting character actually comes in.  Have you thought about using LWRS_RECEIVE instead or in combination?  Say maybe if you haven't received your start character after half your buffer is full, clear it out so there is room for your full message?

 

If you have a function waiting for the callback to complete I would defintally do that in a seperate thread from main.  As a quit test though you could do something like:

 

while(<callback not complete>)
{    
        ProcessSystemEvents(); //do any work
        Sleep(0); //comback to me as soon as you can

}

 

 

Making a thread isn't that difficult either... just put the above code in a function and call a couple of the thread creation functions:

 

    CmtNewThreadPool(2, &g_ThreadPoolHandle);  //creates a new thread pool where you can specify priority and other properties

                  
    CmtScheduleThreadPoolFunctionAdv (g_ThreadPoolHandle,
                                                                 <above funtion name>, NULL,
                                                                 THREAD_PRIORITY_NORMAL,
                                                                 NULL,
                                                                 EVENT_TP_THREAD_FUNCTION_END,
                                                                 NULL,
                                                                 CmtGetCurrentThreadID(), &threadID);
    

 

Or let me know if I'm way off base with what you're trying to accomplish. 🙂

0 Kudos
Message 5 of 16
(5,081 Views)

gtoph -

 

Thanks for the thoughts.

 

We use multi-threading quite a bit, I usually use the Win32 functions rather than the NI functions (CreateThread).   I've just never bothered to set up for a separate thread to run the callback on.  Not a bad idea, I could set the priority high on that thread .

 

I am already using LWRS_RECEIVE  in conjunction with LWRS_RXFLAG.  I read out the first half of the inqueue if I get a callback for LWRS_RECEIVE, and have LWRS_RECEIVE threshold set at half the inqueue size.

 

Sleep (0) gives up the rest of the quantum and lets the scheduler reschedule the thread as it normally would (in competition with all the other threads).

 

Yes, I am trying to pick an individual message out of a stream of periodic messages of various types, all being emitted from the serial port at 115,200 BPS.    So lots and lots of short messages, I'm trying to reach into the stream and perfectly snag exactly one message of a particular type.   A bit of a trick when you think of it.  I had it all working at one point but my suspicion now is that there was always a timing vulnerability that I wasn't seeing until running on a faster machine that offers true concurrency and also with a different CVI version.

 

I see in a thread I was in 3 years ago that NI confirmed that the serial callback can only run when the main thread processes events.   So if I do the math, the inqueue will fill every 350 msec and half-fill every 175msec, so Windows serial driver is trying to make a callback every 175 msec but I'm only processing events every 250 msec.  It could be that when the callback gets called for LWRS_RECEIVE is when it gets into trouuble,  odds are normally that it would get triggered due to LWRS_RXCHAR I think since the messages I'm looking for occur frequently.

 

I can easily instrument the callback to count how many times and when it gets called for LWRS_RECEIVE Vs. LWRS_RXCHAR and get some insight into exactly what's happening.  I can copy out the entire inqueue and save it so I can analyze it.

 

I'll try shortening up the process system events interval to 100 msec (or even continuously as you pointed out) and that may solve it.

 

Thanks for helping me think this through.

 

Menchar

 

 

 

0 Kudos
Message 6 of 16
(5,073 Views)

Sorry about that, should have reread your first post and seen you're already using both flags... oops. 🙂

 

Sounds like you might be on the right track with the timing of the threads though. I don't think sleep(0) will hurt anything... if there's nothing to do, there's nothing to do, so giving up the rest of the current quantum and comming back around as soon as possible isn't going to hurt.

 

Anywho, good luck. 🙂

0 Kudos
Message 7 of 16
(5,068 Views)

No problem, thanks for your help.

 

I've tried googling etc and MSDN to get some hard info on just what the Windows serial driver does as far as inqueue size and overflow semantics, and there's almost nothing out there.   Some good info for Windows CE but it seems Microsoft figures that unless you're running CE you don't care about deterministic behavior on your serial port or how it works.

 

It may be that Windows always uses at least 4096 bytes in the inqueue, but who knows how much bigger you can make it.  I could declare it to be large, then let it fill, then probe the InQLen and see how many chars it says it has buffered.   But dang, couldn't someone just tell me?

 

Menchar

0 Kudos
Message 8 of 16
(5,065 Views)

Found some info on Windows serial driver behavior.

 

The serial driver can use any buffering scheme it wants to, according to MS info on SetupComm Win32 function:

 

The device driver receives the recommended buffer sizes, but is free to use any input and output (I/O) buffering scheme, as long as it provides reasonable performance and data is not lost due to overrun (except under extreme circumstances). For example, the function can succeed even though the driver does not allocate a buffer, as long as some other portion of the system provides equivalent functionality.

 

This is nonsensical, in that "reasonable" isn't defined.  But it may be referring to a serial driver in the abstract, rather than the Windows XP native serial driver used on an rs-232 com port, for example.

 

It looks like the GetCommProperties Win32 function can be used to get a COMMPROP structure which will report the maximum and actual sizes of the in and out queues for the driver.

 

But, as far as I can see, NI doesn't offer a rs232 library call that uses this function, and I can't use the function on a port already opened by the CVI library routines,  since I can't open it and get a handle to it.

 

I'd think that adding a rs-232 library function that retrieves the info in COMMPROP for a serial port the library has open would be a good idea.

 

Menchar

 

 

 

0 Kudos
Message 9 of 16
(5,039 Views)

My bad. 

 

GetSystemComHandle in the CVI rs232 library gives you the system handle to the port so you can use the Win32 api calls.

 

So, I'll try using this function to get the Windows port handle then I'll call GetCommProperties with it to get the COMMPROP structure then I'll look and see what the maximum and the actual buffer sizes are 😉

0 Kudos
Message 10 of 16
(5,035 Views)