DevNotes #3. Asynchronous Handling of Ctrl-C in MEX

by Pavel Holoborodko on July 2, 2016

There seems to be growing interest in how to detect user interrupt (Ctrl-C) inside compiled MEX library. The main difficulty is that TheMathWorks provides no official API to recognize and handle the situation.

As a workaround, Wotao Yin found undocumented function utIsInterruptPending which can be used to check if Ctrl-C was pressed. The most obvious usage pattern is to include calls to the function in lengthy computational code (loops, etc.) and exit the computations if needed. Collection of various improvements on using utIsInterruptPending can be found in recent Yair Altman’s post.

Unfortunately, despite all these efforts the most important issue was not addressed and was not resolved up to date – asynchronous handling of Ctrl-C.

In order to respond to user interrupt, source code of the MEX module have to be changed to include utIsInterruptPending calls. Every sub-module, every loop of the code needs to be revised. This is not only time-consuming and error-prone process, but also makes pure computational code dependent on MEX API.

Most importantly, it is not possible to do all the modifications if MEX uses third-party libraries with no access to its source code.

The ideal solution would be to avoid any of the changes to computational code in MEX. Here we propose one of the ways to do so.

Internals of utIsInterruptPending

Programmatically the Ctrl-C corresponds to interrupt signal SIGINT which is sent by operating system to application.

By design, it is better to handle the system signals and interrupts (including the SIGINT) in separate processing thread (or asynchronously with other execution paths). MATLAB follows this design pattern and utIsInterruptPending is one of few MEX functions which is thread-safe. So that it can be safely used even in OpenMP parallel sections or other multi-threading environments.

This can be easily verified by examining the machine code of the function (Windows):

It just reads the internal interrupt flag – utInterruptState::AtomicPendingFlags whose state is altered by separate signal handling thread.

Although its code is small, utIsInterruptPending might degrade the performance of a mathematical code significantly. For example, one call adds extra conditional statement into the loop making vectorization/branch prediction more difficult for CPU. More importantly it changes the flow of the program (non-unlined function call) which might lead to cache flushes. These issues are not noticeable in heavy loops but have dramatic effect on fast loops with small code.

Asynchronous handling of Ctrl-C

Our goal is to avoid inserting direct calls to utIsInterruptPending into computational code. The simplest way to achieve this is to run computations in separate worker thread. Then main thread (mexFunction) can do occasional checks on Ctrl-C and terminate worker thread if needed. This is classic design pattern for handling interrupts, GUI events, receive user input, etc.

Code implementing the pattern is below. We use Windows API just for illustration, since it has the cleanest thread API. Absolutely the same technique can be used on GNU Linux and Mac OSX – you will just need to use plain pthreads, std::threads from C++11 or any other threading library.

// Undocumented function utIsInterruptPending requires linking to libut.lib
#ifdef _MSC_VER
    #pragma comment(lib, "libut.lib")
#endif
 
extern "C" bool utIsInterruptPending(void);
 
DWORD WINAPI WorkerThread(LPVOID lpParam) 
{ 
    // Put your computational code here
    // No checks for Ctrl+C are needed
 
    // We just use never-ending loop for simplicity
    do{ }while(true);
 
    return 0; 
} 
 
void mexFunction (int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    HANDLE  hWorkerThread = NULL;
 
    // Run computations in separate thread
    hWorkerThread = CreateThread( 
                    NULL,                   // default security attributes
                    0,                      // use default stack size  
                    WorkerThread,           // thread function name
                    NULL,                   // pass argument(s) to thread function 
                    0,                      // use default creation flags 
                    NULL);                  // returns the thread identifier 
 
    // Wait for worker to finish naturally or interrupt it by user request.
    // Ctrl+C is checked every 500 milliseconds (0.5 sec).
    while(WaitForSingleObject(hWorkerThread, 500) == WAIT_TIMEOUT)
    {
        if(utIsInterruptPending())
        {
            // Terminate thread forcefully if user pressed Ctrl-C
            TerminateThread(hWorkerThread,0);
            CloseHandle(hWorkerThread);
 
            // Show short error message and exit MEX:
            mxShowErrorMessage("Operation was interrupted by Ctrl+C");
        }
    }
 
    CloseHandle(hWorkerThread);
}

Build the MEX (add necessary includes, etc. ) and run it in MATLAB’s command prompt:

>> SimpleCtrlC   % Press Ctrl+C to interrupt never-ending loop
Operation was interrupted by Ctrl+C

We keep the code short and simple for illustrative purposes. Of course, it can be extended in number of ways – probably main thread should pass some arguments to worker (using 4-th argument of CreateThread), handle mxArray inputs, errors, etc.

There are two important facts about this approach:

  • Forceful thread termination is not graceful, so that destructors for objects created in worker thread are not called. Same for heap-allocated memory. Cure is to avoid dynamic memory allocation if possible (e.g. allocate everything in main thread), do not work with files or devices in the worker thread. Keep worker thread for heavy mathematical code and number crunching.

    Nothing disastrous will happen if you do not follow this rule – just remember to restart MATLAB once-a-while to free the memory leaks 😉
  • Avoid calls to MEX functions in worker thread since majority of them are not thread-safe. In general, computation code should be independent from any low-level vendor-specific API, so this rule is probably already implemented in your code.

***
Previous issues of Developer Notes:
#2 – Short, Safe and Informative Error Messages from MEX
#1 – Multiplicative Operations with Complex Infinity

{ 0 comments… add one now }

Leave a Comment

Previous post:

Next post: