Handling Error Codes from Several Libraries in C

Very often I have to use several libraries that handle errors differently or define their own enumerations for errors. This makes it difficult to write functions that can deal with errors from different sources, and then return their own error code. For instance:

int do_foo_and_bar() { int err; if ((err = libfoo_do_something()) < 0) { // return err and indication that it was caused by foo } if ((err = libbar_do_something()) < 0) { // return err and indication that it was caused by bar } // ... return 0; } 

I thought of two possible solutions:

  • Create your own list of error codes and translate these error codes to new ones using functions such as int translate_foo_error(int err) , and I will write my own string representations for each error.
  • Create a struct my_error that contains both the enumeration that identifies the library and the error code. Translation into the string will be delegated to the corresponding function for each library.

This seems like a problem that occurs very often, so I wonder how it is usually handled? It seems that the former is what most libraries do, but the latter works less and plays on tools already provided. This does not help most tutorials just print the message in stderr and exit with any error. I would prefer that each function indicate what went wrong and the caller can decide how to deal with it.

+6
source share
2 answers

Answer: it depends on your code limitations.

collectd prints a standard error, then takes over if it hits a fatal error.

OpenGL will set some general state that you can request. Ignoring this error often leads to undefined behavior.

Collectd has a lot of threading issues, and most errors cannot be fixed or repaired by the program. For example, if a plugin depends on a library, and calling this library fails, the plugin knows more about how to recover this error. Bubbling that a bug up is not useful since core core will never know about the N + 1 plugin

On the other hand, OpenGL applications are usually responsible for any errors that occur and may try to fix the errors. For example, if they try to compile a shader, but may have special ones for a specific vendor or platform.

Depending on the design of your program, consider the following:

  • Will you be wrong so you can make a better decision?
    • Does the caller know about your implementation? Do they have to?
  • Are errors fixed by your application possible?
    • eg. If you cannot open the socket or file, you can try again or fail, and not much more.
  • Are there any restrictions around the global state if you execute the GetLastError() function?

Update:

A transition with a global state parameter may have the following:

 #include <stdlib.h> #include <stdio.h> #include <string.h> #include <math.h> char* last_err = NULL; void set_err(char* error_message) { if(last_err) free(last_err); /* Make a deep copy to be safe. * The error string might be dynamically allocated by an external library. * We can't know for sure where it came from. */ last_err = strdup(error_message); } int can_sqrt(int a) { if(a < 0) { set_err("We can't take the square root of a negative number"); return 0; } return 1; } int main(int argc, char* argv[]) { int i = 1; for(i = 1; i < argc; i++) { int square = atoi(argv[i]); if(can_sqrt(square)) { fprintf(stdout, "the square root of %d is: %.0f\n", square, sqrt(square)); } else { fprintf(stderr, "%s\n", last_err); } } return 0; } 

Running the above program

 $ ./a.out -1 2 -4 0 -6 4 We can't take the square root of a negative number the square root of 2 is: 1 We can't take the square root of a negative number the square root of 0 is: 0 We can't take the square root of a negative number the square root of 4 is: 2 
+2
source

I like to use Thread-Local-Storage (TLS) to store errors found deep inside libraries. They are fast and thread safe. The only real problem is that then the error belongs to the thread calling the function that generates the error. This can be a problem in some streaming models (e.g. anonymous streams in a thread pool). Other threads cannot see the error unless you have a way to propagate the error from one thread to another. However, there are ways to do this, and this way of spreading errors is fast and efficient and makes the code more elegant in the library (I think). The general philosophy is reporting errors, as well as decisions to restore up to the interface. Error recovery can be processed at any level in the call stack (for example, at the middle level in front of the interface), but the general idea is that each function in the library should push responsibility up towards the caller. Each function should take a small responsibility and then transfer the rest of the call to the call chain. For example, each function (possibly most) in the library can log any error in TLS and return a boolean indicating the success of the function / operation for the caller. Then the caller can look at the returned logical value, and if the operation is unsuccessful, either decide to do something (for example, try again), or just abort, clear the stack and return false. If the information you stored in TLS was structured, you could aggregate the error information (as well as any corrective actions taken) over the call chain. Then this process can completely return to the interface level. At any time, the caller can request the latest error and decide to do whatever he likes based on the error indicated. Obviously, your library will need to provide a top-level SetLastError () / GetLastError () function interface. In addition, you will likely have code to enter each interface function (except for SetLastError () / GetLastError (), of course) in the library, which resets the last error state when the interface function is called.

0
source

Source: https://habr.com/ru/post/971900/


All Articles