![]() |
Signal Handling |
<<< Error Handling | Chapters | System Attribute Definitions >>> |
This chapter describes how to handle signals that are caught by the operating system: specifically run-time errors (e.g., access violations, floating-point errors or illegal instructions) and user interrupts. For example, the application might encounter a division-by-zero, or the user may press Ctrl+C while the application is running. You may elect to ignore these if you wish, in which case they will be handled by the operating system. This can lead to the immediate termination of your application, however.
Note: In the case of floating-point errors, you can choose whether a signal should be sent to the operating system; that is, you can decide if they should be signified as errors or ignored. For more information and recommendations for use with Parasolid, please see the
Installation Notes. |
You can handle each type of signal by writing and registering an associated signal handler with the operating system. (It is also common to register a single function to handle both types of signal.) You can also use timer functions to raise an alarm signal, in order to avoid long delays while waiting for user interrupts to be processed.
Note: This chapter does not cover application-defined user interrupts (such as enabling the Escape key), which are caught by the application rather than the operating system. |
Parasolid provides some functions for you to use when writing a signal handler. First and foremost, you need to know whether Parasolid was actually running at the time of the signal. Just as important, however, is knowing the state of the (Parasolid) code that was running when the signal occurred, since any action taken depends on it. Also, you need to make the appropriate abort call to Parasolid, telling it the type of signal that has occurred.
All PK functions can be categorized as either lightweight or heavyweight:
Lightweight functions are highly optimised; therefore they have no protection mechanism to fall back on in the event of an error, since this would slow them down. Heavyweight functions do have some protection from errors, and can be divided into protected and unprotected sections of code:
You can establish whether the code that was running is protected or unprotected by calling PK_SESSION_is_in_kernel_2, as explained in Section 121.2.2, “Parasolid functions used for signal handling”.
The following Parasolid functions are useful when writing signal handlers.
This function determines whether the code currently in execution has been called from a PK function and, if so, whether the code is protected or unprotected. |
|
This function instructs Parasolid to abort the current session because of a run-time error or user interrupt.
Some PK functions cannot be safely interrupted; for details, see the PK_SESSION_abort documentation. If user interrupt occurs in one of these functions, PK_SESSION_abort returns PK_ERROR_cant_be_aborted. |
|
This function is provided to help you develop signal handlers. It allows you to call functions of your own within an executing Parasolid. These functions should generate run-time errors, allowing you to test your signal handlers. |
Warning: PK_SESSION_is_in_kernel_2 and PK_SESSION_abort are the
only PK functions that should be called from a signal handler. |
This section describes handling run-time errors and user interrupts, and also raising alarm signals in conjunction with user interrupts. Having received any of these signals, the operating system immediately switches control to the registered handler for the signal. If the handler subsequently returns, execution will continue in the interrupted routine at the next point after the signal occurred.
Figure 121-1 shows an example of how to register (or re-register) signal handlers in C++. Note that this is not the only possibility; you can also register a single function to handle several types of signal.
Figure 121-1 Example code showing the registration of various signal handlers
Note: Once a given signal occurs, the operating system first restores the default signal-handling mechanism for that signal (i.e., as if no signal handler had been registered). The previously registered signal handler is then called. In order for your registered signal handler to be called for any future instance of that signal, you must
re-register it somewhere in your code. This should be in a place that is certain to be executed; for example, at the beginning of the function itself, as shown in
Figure 121-2,
Figure 121-3 and
Figure 121-4. |
Figure 121-2 shows an example of a signal handler for run-time errors. It first determines whether Parasolid was running when the error occurred; if not, it is the application’s responsibility to handle the signal as it sees fit. If Parasolid was running, the signal handler calls PK_SESSION_abort indicating a run-time error from within a PK function or a registered function called from Parasolid, such as a frustrum function. PK_SESSION_abort does not return to the signal handler, but instead passes control back to the PK function that was running when the error occurred, requesting the immediate termination of that function.
For details of how Parasolid deals with the run-time error once it regains control, see Section 121.4, “How Parasolid deals with signals”.
Note: PK_SESSION_is_in_kernel_2 returns
is_in_kernel
= PK_LOGICAL_true whether Parasolid was running directly (i.e., a PK function was in execution) or indirectly (e.g., a frustrum function called from the PK was in execution). It indicates whether or not your master application is currently calling a PK function. |
void myApp::handleRunTimeError( int sig_run_time_error ) { PK_LOGICAL_t is_in_kernel, is_protected; // first RE-REGISTER the signal handler for run-time errors signal( sig_run_time_error, &myApp::handleRunTimeError ); // determine whether Parasolid was running at the time PK_SESSION_is_in_kernel_2( &is_in_kernel, &is_protected, &is_subthread); if (is_in_kernel) { // tell Parasolid to abort immediately, and say whether the RTE occurred in the PK or the frustrum PK_abort_reason_t reason = ( isFrustrumRunning() ) ? PK_abort_frustrum_error_c : PK_abort_runtime_error_c; // note that this function DOES NOT RETURN PK_SESSION_abort( reason ); } else { // Parasolid was not running -- proceed accordingly ... } } |
Figure 121-2 Example of a signal handler for run-time errors
Figure 121-3 shows an example of a signal handler for user interrupts. It first determines whether Parasolid was running at the time of the interrupt; if so, the signal handler checks to see whether the interrupt took place in a protected section of the code.
void myApp::handleUserInterrupt( int sig_user_interrupt ) { PK_LOGICAL_t is_in_kernel, is_protected; // first RE-REGISTER the signal handler for user interrupts signal( sig_user_interrupt, &myApp::handleUserInterrupt ); // determine whether Parasolid was running at the time PK_SESSION_is_in_kernel_2( &is_in_kernel, &is_protected, &is_subthread); if (is_in_kernel) { // return if code is not protected -- not necessary if you register an error handler that uses exceptions if (!is_protected) return; // set alarm to raise an RTE if PK is slow to abort setTimerForParasolid(); // tell Parasolid to abort at the next safe point PK_abort_reason_t reason = PK_abort_user_interrupt_c; if (PK_SESSION_abort( reason ) != PK_ERROR_no_errors) // function cannot be aborted, so cancel alarm cancelTimerForParasolid(); } else { // Parasolid was not running -- proceed accordingly ... } } |
Figure 121-3 Example of a signal handler for user interrupts
An alarm signal will be raised if too much time elapses before a safe point in the code has been reached. This gets converted to a run-time error, as shown in Figure 121-4, in order to ensure the immediate processing of the signal. Should the interrupt be processed before the timer expires, however, the alarm must be cancelled back in the main application, in order to avoid another (unwanted) signal being raised.
For details of how Parasolid deals with the user interrupt once it regains control, see Section 121.4, “How Parasolid deals with signals”.
void myApp::handleAlarm( int sig_alarm ) { // first RE-REGISTER the signal handler for alarms signal( sig_alarm, &myApp::handleAlarm ); // now raise a run-time error raise( sig_run_time_error ); } |
Figure 121-4 Example of a signal handler for alarms
Once Parasolid has regained control from the signal handler, the handling of a given signal depends on the current state of the code that is being executed.
When Parasolid resumes execution following either a run-time error or a user interrupt, it ascertains the state of the code that was running (i.e., whether it was protected or unprotected), and also checks to see if an error handler has been registered. It then calls the error handler if there is one; if the error handler returns (rather than throwing an exception), or one is not registered, Parasolid returns to your application where possible.
The following table describes the ultimate outcomes of the action taken by Parasolid in the event of a run-time error or user interrupt, based on the example signal handlers shown in Section 121.3, “Designing and registering signal handlers” and depending on the state of the code and the situation regarding the error handler:
When run-time errors occur during the execution of protected code, Parasolid returns the error code PK_ERROR_run_time_error or PK_ERROR_fru_error to your application if the PK function can be interrupted safely see the reference documentation for PK_SESSION_abort, depending on whether the error occurred in a PK function or a frustrum function. If the function cannot be interrupted safely, Parasolid returns PK_ERROR_fatal_error. For user interrupts during the execution of protected code, Parasolid returns PK_ERROR_aborted if the PK function can be interrupted safely; if not, then the function returns to your application as normal.
The diagrams below illustrate the stages involved in processing run-time errors and user interrupts, in sequence starting from the top. The calls from a function are issued in order from left to right.
Following a run-time error, the signal handler calls PK_SESSION_abort with the value PK_abort_runtime_error_c or PK_abort_frustrum_error_c. This function then long-jumps back to the failing PK function, which calls the registered error handler if it exists, with the error code as shown in the respective diagram. If the error handler returns (see Figure 121-6), or there is no error handler (see Figure 121-5), the PK function will return the error to your application if the code is protected, and force the application to exit if the code is unprotected.
Figure 121-5 Flow of control during a run-time error without an error handler
Figure 121-6 Flow of control during a run-time error with an error handler that returns
Figure 121-7 Flow of control during a run-time error with an error handler that throws exceptions
In the case of a user interrupt, the signal handler checks the state of the code in execution. If the code is unprotected, the signal handler should not call PK_SESSION_abort unless there is an error handler that throws exceptions, to avoid forcing the application to exit. If the interrupt is to be processed, the handler can call a timer function to raise an alarm signal after a certain length of time, and then calls PK_SESSION_abort with the argument PK_abort_user_interrupt_c. The error code PK_ERROR_cant_be_aborted is returned if the PK function is one that cannot be interrupted safely (see the reference documentation for PK_SESSION_abort), in which case the handler cancels the alarm.
If PK_SESSION_abort was not called, or PK_ERROR_cant_be_aborted was returned, the signal is ignored and the PK function proceeds as normal. Otherwise the PK function calls the registered error handler if it exists, with the error code as shown in the respective diagram. If the handler returns (see Figure 121-9), or there is no error handler (see Figure 121-8), the PK function returns the error to your application.
Once control has returned to your application, it should take whatever action is necessary following the error, as described in Chapter 120, “Error Handling”. This will almost always require rolling back to a valid state or stopping and restarting the Parasolid session.
Figure 121-8 Flow of control during a user interrupt without an error handler
Figure 121-9 Flow of control during a user interrupt with an error handler that returns
Figure 121-10 Flow of control during a user interrupt with an error handler that throws exceptions
A re-entrant function call takes place when a PK function is called from an application call-back function (that has been registered with Parasolid) that has itself been called from a heavyweight PK function. For example, a call to PK_TOPOL_render_line might lead to a call to the registered function GOSGMT, which in turn might call PK_ENTITY_ask_attribs: the call to PK_ENTITY_ask_attribs is then described as re-entrant. Thus Parasolid is indirectly calling itself, as show in Figure 121-11.
Figure 121-11 Example of a re-entrant function call
Warning: If PK_SESSION_is_in_kernel_2 is called after a re-entrant call to Parasolid, it reports the status of the
outer PK function, which should always be protected. It is therefore
not possible to determine the status of the inner-level PK code. |
When handling signals from Parasolid after a re-entrant function call, the procedures outlined in Section 121.5, “Summary of signal handling” still apply, subject to the alterations described below. Note that, in each case, the signal handler is called from the inner PK function.
For run-time errors, the signal handler calls PK_SESSION_abort, which long-jumps back to the outer PK function; this then calls the error handler (or returns), as for normal (non re-entrant) calls. If the error handler throws exceptions, however, they must be thrown back to a point outside of any PK function calls, and not to registered functions.
For user interrupts, the signal handler returns to the inner PK function (unlike non re-entrant calls). Assuming that this function can be interrupted safely (see the reference documentation for PK_SESSION_abort), the outcome then depends on whether the inner-level PK code is protected or unprotected, and the situation regarding the error handler, as shown in the following table:
If you do not use an error handler that throws exceptions, you must decide how your registered function should process the error information sent from the (protected) PK code. You may want to by-pass the outer-level PK function, and throw an exception back to your application code; if so, you must call PK_SESSION_tidy before making another PK call. Alternatively, you can call PK_SESSION_abort again, from the registered function, in order for the interrupt to filter up to the outer-level of Parasolid code, where it can be processed.
Note that some registered functions have the facility to report PK errors back to the outer-level PK function, and hence back to the master application. For example, several of the GO functions (that are registered with PK_SESSION_register_frustrum) have an
ifail
argument that can be used to send back the error code from the inner PK function. Others, such as B-curve evaluator functions registered via PK_BCURVE_create_by_fitting, do not have this facility.
Warning: If the inner-level PK code is unprotected, your application will exit unless you have an error handler that throws exceptions, even if you use the signal handler model shown in
Figure 121-3. This is because the state of the inner PK function cannot be determined, as
is_protected
is always set to true for re-entrant function calls. |
<<< Error Handling | Chapters | System Attribute Definitions >>> |