<<< Session Support | Chapters | Glossary >>> |
This chapter tells you about the different types of error that your application might encounter, and how you can handle them. If you have not done so already, you might find it useful to read the introduction to error handling in the frustrum in the Getting Started With Parasolid manual before continuing with this chapter.
Your application is responsible for intercepting and handling error conditions that may arise when calling Parasolid. This may include any of the following types of error:
You must handle the first of these, and, depending on the design of your application, you may need to handle one or both of the others.
The way that you handle any given error depends on both the design of your application, and the type of error being handled.
If you are handling both user interrupts and RTEs, you can use the same signal handler for both types of error, if you wish.
You are highly recommended to implement rollback functionality in your application in order to tidy up a session after severe errors. See Chapter 41, "Partitions and Rollback", for details. The rollback functions are supplied to Parasolid using PK_DELTA_register_callbacks.
The rest of this chapter is structured as follows:
You can find more information about error handling in the following places:
Every PK function returns an error code as the function value to indicate the result of calling that function. If this value is PK_ERROR_no_errors (0) the function has completed successfully and your application can proceed.
If the return value is anything else, then the function has failed to complete. Each numerical value indicates a predictable error, and your application needs to take one of the following courses of action, depending on what that value is, and how severe the error is in this context:
There are two approaches to handling errors from Parasolid.
The strategy you choose probably depends on how errors are handled by the rest of your application. If possible, you are recommended not to mix the two strategies, unless you never longjump in your application code.
Whichever strategy you choose, the same action needs to be taken. This action depends on the severity of the error, as supplied in the error standard form. See Section 59.2.3, "What to do when an error occurs" for more details.
The numerical values and meanings of PK error codes are listed in two formats in the PK Interface Programming Reference Manual (Part 2 - Interface Structures, Tokens & Error Codes):
In order to handle a given error, you must look up the appropriate error code in one of these lists and write your error handling code appropriately. There is no way to extract the meaning of a particular error code programmatically from the PK. You can also find PK error codes listed in the file
parasolid_kernel.h
.
In order to use a registered error handler, you need to do the following:
Once registered, the error handler is automatically invoked if a PK function is about to return an error. The error handler is passed a standard structure (PK_ERROR_sf_t) containing the following data:
The error handler then takes appropriate action on the basis of this information. See Section 59.2.3, "What to do when an error occurs".
The main difference between using a registered error handler and letting your application handle PK errors is that a registered error handler is invoked automatically by Parasolid before a non-zero error code is returned, whereas your code needs to examine error codes explicitly if no error handler is registered.
When you are not using a registered error handler, you need to do the following:
You should use PK_ERROR_ask_last and PK_ERROR_clear_last (see Section 59.2.4) to manage information about PK errors. As with a registered error handler, your code should then take appropriate action on the basis of this information. See Section 59.2.3, "What to do when an error occurs".
Although the general principles are the same, the precise details of what you need to do when a Parasolid error occurs depend on whether you use a registered error handler, and whether you choose to longjump on an error.
There are three error severity levels that your application has to handle:
The error severity is returned in the PK_ERROR_sf_t structure that is passed to the error handler. You can also use PK_ERROR_ask_last to determine the severity of a given error: see Section 59.2.4.
If you decide to examine error codes in your application code, the sequence of events is as follows:
Figure 59-1 Handling a Parasolid error when there is no registered error handler
When no error handler is registered, the return value of every PK function call must be examined, and appropriate action taken if the result is anything other than PK_ERROR_no_errors. Calls to PK functions in your application code can be structured as shown in the pseudo-code below:
If you register an error handler, and decide to return to the failing PK function whenever an error occurs, the sequence of events is as follows.
Figure 59-2 Handling a Parasolid error with a registered error handler that returns to the failing function
Note: You have to longjump when the error code is PK_ERROR_unhandleable_condition. You may also choose to longjump on other error codes if you wish. |
When you register an error handler and then return to the failing PK function, you still need to check the return value of every PK function call. The main difference to the structure of your code between this strategy and using no error handler is that you do not need to explicitly handle every error in your application code. Calls to PK functions in your application code can be structured as shown in the pseudo-code below:
<register error handler> result = PK_function_1 if (result == PK_ERROR_no_errors) result = PK_function_2 if (result == PK_ERROR_no_errors) result = PK_function_3 . . . |
If you return to the failing PK function, then
If you register an error handler, and decide to longjump whenever an error occurs, the sequence of events is as follows.
Figure 59-3 Handling a Parasolid error with a registered error handler always longjumps
The advantage of this method is that it allows you to group calls to PK functions into meaningful clusters in your application. You do not need to explicitly examine the return value of each function in your application. Calls to PK functions in your application code can be structured as shown in the pseudo-code below:
Note: If you longjump out of Parasolid, then you must call PK_SESSION_tidy before the next PK call, either before or after longjumping. For more information, see Section 59.5, "Longjumping". |
This section describes a number of error tracking tools that are particularly useful in the following cases:
Function | Description |
---|---|
This function returns information (using PK_ERROR_sf_t) about the last PK error that was raised. The information returned includes:
A registered error hander receives this information in a PK_ERROR_sf_t. You should use this function to request error information if handling PK errors in your application code. |
|
This function clears the saved information from the last PK error that was raised. |
|
This function raises an appropriate error when passed a PK_ERROR_sf_t standard form. PK_ERROR_raise and PK_ERROR_reraise can be used together to temporarily suppress the standard application error, perform multiple PK calls to encapsulate a piece of functionality, then restore the original handler and (optionally) resignal a PK error to your application. You can use them when you want to perform calls to certain PK functions using your own error handler, while using the registered error handler for calls to other PK functions. |
|
This function repeats the most recent PK error. It should be used in conjunction with PK_ERROR_raise to temporarily replace the registered error handler with your own error handler for certain calls, as described above. |
|
Returns the current application error handler, if there is one. |
As well as handling Parasolid errors, your application can also handle user interrupts and run-time errors (RTEs). Unlike Parasolid errors, this is not mandatory, so you can ignore this section if it is not relevant to your application design.
Note: If you wish to handle either user interrupts or run-time errors, you are strongly advised to register an error handler with Parasolid in order to deal with Parasolid errors. |
You can provide one or more signal handlers to cope with user interrupts, various types of RTEs and, optionally, timers:
If you wish, you can use a single function to handle all these types of signal. Signal handling is specific to both the operating system and application language. Your application must register any signal handlers with the operating system. For applications written in C, please refer to the system documentation for signal-handling functionality in the C run-time library.
For both user interrupt and RTE handling, the basic principle is to use the signal handler to determine what state the kernel is in when the error occurs (using PK_SESSION_is_in_kernel), and then to take appropriate action in Parasolid and your application in order to recover gracefully from the user interrupt or RTE.
In order to determine what action needs to be taken when a user interrupt or RTE occurs, it is important to find out what type of kernel code was executing at the time.
PK functions can be categorised as either lightweight or heavyweight.
Heavyweight functions can be further divided into unprotected and protected sections of code:
Lightweight functions consist of only unprotected sections.
PK_SESSION_is_in_kernel establishes whether the current function is lightweight or heavyweight, and whether the error occurred in a protected or unprotected section of code. This information determines what action Parasolid takes.
A number of functions are available to help handle user interrupts and RTEs:
Signal handlers can call PK_SESSION_is_in_kernel to determine the kernel's state when a user interrupt or RTE occurs. This function returns the following information:
This information is used to determine exactly what action is needed to recover gracefully from the error.
Signal handlers can call PK_SESSION_abort to abort the kernel function that is executing when a user interrupt or RTE occurs. It takes a
reason
argument that describes the type of error for which an abort has been requested:
Value | Description |
---|---|
Returns the name of the PK function currently being called.
Error returns from both user interrupt requests and RTEs are processed as shown in Figure 59-4. This figure illustrates the sequence of events that is common to both UI requests and RTEs. You should also refer to Section 59.3.4, "Handling user interrupts" or Section 59.3.5, "Handling run time errors", as appropriate for the type of error you are handling.
Figure 59-4 Processing the interrupt from a user interrupt request or an RTE
Note: If you handle interrupts, but have not registered an error handler for Parasolid errors, your application may exit if a signal is handled in an unprotected section of code. This is undesirable, especially in the context of a user interrupt. If you cannot register an error handler, an alternative is to detect interrupts in unprotected sections of code (using the initial call to PK_SESSION_is_in_kernel, shown in Figure 59-5 and Figure 59-6), and then ignore them. |
If a user interrupt occurs when execution is in the kernel or the frustrum, and you have provided a signal handler to deal with user interrupts, the error handling process is as shown in Figure 59-5. User interrupt requests are made by calling PK_SESSION_abort with the error token PK_abort_user_interrupt_c.
User interrupts requests are handled by Parasolid at the next safe point in code, rather than immediately. In principle, this could mean that a user interrupt may not be acknowledged for some time, leading to delays and frustration for the user. In order to avoid this, you should set a timer before calling PK_SESSION_abort. If this timer expires before a user interrupt request is handled, then you can force Parasolid to process the request immediately by signalling a run-time error. This is done by calling PK_SESSION_abort with the error token PK_abort_runtime_error_c: see Section 59.3.5, "Handling run time errors".
If you have not provided a signal handler, then any user interrupt requests are processed in whatever manner your application's run-time library support specifies. This could mean, for instance, that user interrupt requests are ignored, or that your application exits.
Figure 59-5 Handling a user interrupt in the Parasolid kernel
If a run-time error occurs when execution is in the kernel, and you have provided a signal handler to deal with RTEs, the error handling process is as shown in Figure 59-6. Requests to handle run-time errors are made by calling PK_SESSION_abort with the error token PK_abort_runtime_error_c. Unlike user interrupts, requests to handle RTEs are processed immediately.
If an RTE occurs when execution is in the frustrum, the procedure is the same as shown in Figure 59-6, except that PK_SESSION_abort is called with the token PK_abort_frustrum_error_c, and the error code that can be returned is PK_ERROR_fru_error.
If no RTE signal handler is provided, then an RTE results in the default behavior of the run-time system. This is likely to exit your application.
Figure 59-6 Handling an RTE in the Parasolid kernel
If the failing function is a recursive (re-entrant) operation called from the GO, then:
Warning: PK_SESSION_is_in_kernel reports the protection status of the top-level function, not the failing re-entrant function, so take care when handling errors in re-entrant Parasolid operations. |
If, when handling an error of any sort, your application longjumps out of Parasolid, it must call PK_SESSION_tidy. Doing this:
You can call PK_SESSION_tidy either before or after longjumping, but, unless you longjumped as a result of a mild error, you must call it before you call the next PK function (often a rollback command).
Many PK functions return information in an array whose space is allocated by Parasolid, together with the length of this array. If the function succeeds, your application should look at the array length to see if anything is returned. If the length is zero, then your application's pointer to the array should be set to NULL. If your application longjumps out of this function as a result of error handling, then the pointer to the array has an undefined value.
The examples in this section demonstrate how to handle a PK error by longjumping, and by returning to the function that has generated the error, respectively.
#include <setjmp.h> #include <stdio.h> #include "parasolid_kernel.h" jmp_buf jmp_label_g; /* saved context to longjump to in application code */ PK_ERROR_code_t longjump_error_handler( PK_ERROR_sf_t * error_sf ) { /* insert application-specific tidying code here */ PK_ERROR_code_t session_tidy_result = PK_SESSION_tidy(); int jmp_code = (session_tidy_result == PK_ERROR_no_errors ? 1 : 2); /* if PK_SESSION_tidy returns an error, something is */ /* seriously corrupted, and the application should */ /* restart Parasolid */ longjmp(jmp_label_g, jmp_code); } /* start of a sequence of possibly complex */ /* Parasolid operations */ PK_MARK_t mark; error = PK_MARK_create(&mark); /* set a mark to rollback to on failure */ PK_ERROR_frustrum_t error_frustrum; error_frustrum.handler_fn = longjump_error_handler; PK_ERROR_register_callbacks(error_frustrum); if ((jmp_result = setjmp(&jmp_label_g)) == 0) { PK_ATTRIB_create_empty(entity, attdef, &attrib); /* ... successful execution continues here, */ /* including other calls of PK functions ... */ } else { /* The error handler will longjump to here if an */ /* error occurs in any of the PK calls made above. */ if (jmp_result == 2) { /* severe error, perhaps longjump again to higher level? */ } else { printf("Rolling back to before <xxx> operation"); PK_MARK_goto(mark); } } |
#include <stdlib.h> #include "parasolid_kernel.h" PK_ERROR_code_t simple_error_handler(PK_ERROR_sf_t * error_sf) { /* There are some classes of errors for which local recovery */ /* is not possible, or at least not simple. These are handled*/ /* here by printing a message for the user and then exiting. */ /* More complex error recovery would in general require a */ /* longjumping handler as described in Section 59.6.1. */ switch (error_sf->code) { case PK_ERROR_aborted: /* user interrupt */ case PK_ERROR_run_time_error: case PK_ERROR_system_error: case PK_ERROR_fatal_error: case PK_ERROR_unhandleable_condition: /* etc. */ print_error_info ("About to exit after error %d\n", (int)error_sf->code); exit(EXIT_FAILURE); default: /* insert application-specific tidying code here */ return error_sf->code; } } /* somewhere in the application initialization code ... */ PK_ERROR_frustrum_t error_frustrum; error_frustrum.handler_fn = simple_error_handler; PK_ERROR_register_callbacks(error_frustrum); /* ... */ PK_ERROR_code_t result = PK_ATTRIB_create_empty(entity, attdef, &attrib); if (result != PK_ERROR_no_errors) { /* Check the error code, and possibly severity (eg by */ /* calling PK_ERROR_ask_last), and take the appropriate */ /* action. Errors which cannot be handled locally have */ /* already been dealt with */ } |
<<< Session Support | Chapters | Glossary >>> |