Signal Handling   

<<< Error Handling Chapters System Attribute Definitions >>>

Contents

[back to top]


121.1 Introduction

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: If you are using Microsoft Visual Studio on a Windows platform, you need to use non-default settings to allow your application to handle signals from the operating system. We recommend that you consult the Microsoft documentation on this topic.

 

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.

[back to top]


121.2 Parasolid concepts relating to signals

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.

121.2.1 State of the code in execution

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”.

121.2.2 Parasolid functions used for signal handling

The following Parasolid functions are useful when writing signal handlers.

 

Function

Description

PK_SESSION_is_in_kernel_2

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.

  • is_in_kernel determines whether the code has been called from Parasolid
  • is_protected determines whether the code is protected
  • is_subthread determines whether the code has been called from an internal thread

PK_SESSION_abort

This function instructs Parasolid to abort the current session because of a run-time error or user interrupt.

  • For user interrupts, PK_SESSION_abort sets a flag inside the Parasolid kernel to register the abort request and returns to the calling function. If PK_SESSION_abort is called from an application thread outside Parasolid, it will attempt to abort all application threads inside Parasolid, It will only attempt to abort its own thread if called from an application thread inside Parasolid (i.e from within callback or Frustrum code), leaving any other threads inside Parasolid alone.
  • For run-time errors, it does not return to the caller, but jumps straight to the PK function in execution and carries out the abort operation immediately.PK_SESSION_abort will not succeed if called from an application thread outside Parasolid. It will cause its own thread to longjump back inside Parasolid, leaving any other threads alone.

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.

PK_DEBUG_try_error_handler

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.

[back to top]


121.3 Designing and registering signal handlers

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.

121.3.1 Registering signal handlers

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.

 

	// register signal handlers with the operating system
	// user interrupts
	signal( sig_user_interrupt, &myApp::handleUserInterrupt );
	// run-time errors
	signal( sig_run_time_error, &myApp::handleRunTimeError );
	// alarms
	signal( sig_alarm, &myApp::handleAlarm );

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.

121.3.2 Run-time errors

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

121.3.3 User interrupts

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”.

 

Note: The use of timer functions in a user-interrupt handler is entirely optional; it serves to avoid long delays when processing user interrupts, which get converted to run-time errors after a certain length of time. This can cause your application to exit (in situations where it might otherwise remain running) if you do not register an error handler that uses exceptions.

 

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

 

Note: If you wish to be able to interrupt your application at all costs, your signal handler should not check to see if the code is protected even when you do not have an error handler that uses exceptions. Also, it should not cancel the alarm when a PK function cannot be interrupted safely. Interrupts processed in these situations will ultimately cause the application to exit.

[back to top]


121.4 How Parasolid deals with signals

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:

 

Error handler

State of code

Outcome

No

Protected

Parasolid returns an error code to your application.

No

Unprotected

User interrupts are ignored; run-time errors force the application to exit.

Yes (returns)

Protected

An error is passed to your error handler for processing; the error code is then returned to your application.

Yes (returns)

Unprotected

User interrupts are ignored; run-time errors force the application to exit.

Yes (throws an exception)

Any

An error is passed to your error handler for processing; it then throws a suitable exception to your application.

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.

 

Note: If you register an error handler that throws an exception for the error code PK_ERROR_unhandleable_condition, Parasolid can recover from run-time errors and handle user interrupts during unprotected code.

[back to top]


121.5 Summary of signal handling

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

[back to top]


121.6 Re-entrant function calls

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.

 

Note: Outer-level PK functions are always protected, since Parasolid does not call registered functions from unprotected sections of code. Only one level of re-entrance is permitted: an inner-level function cannot call a non-PK function.

 

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.

121.6.1 Run-time errors

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.

121.6.2 User interrupts

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:

 

Error handler

State of code

Outcome

No

Protected

The inner PK function returns an error with code PK_ERROR_aborted to your registered function ( not your master application).

No

Unprotected

Parasolid forces the application to exit.

Yes (returns)

Protected

An error with code PK_ERROR_aborted is passed to your error handler for processing, and is then returned to your registered function ( not your master application).

Yes (returns)

Unprotected

Parasolid forces the application to exit.

Yes (throws an exception)

Any

An error is passed to your error handler, which throws an exception back to your master application.

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.

 

[back to top]

<<< Error Handling Chapters System Attribute Definitions >>>