Error handling: Opinions?
Greetings,
I am in the process of creating an error handling system for a game engine written in C++. But, I am not sure which way to go here. I''m an experienced programmer, and am ware of the basic - intermediate ways of handling errors. I am looking for opinions/experience/ideas on how to create a global error handling system that is readable (to both end-user and developer), flexible and FAST. Here are some ideas I have so far, please comment:
1) Basic usage of try/catch/throw paradigm, where specific error classes handle error output. Big problem: it''s slow. I wouldn''t want to use this in the main loop of the rendering, since minor errors and warnings would defenitely bring Cpu cycles down to a crawl.
2) Returns HRESULTS or something similar. This is good since all DirectX (which is what the engine uses) functions return HRESULTS, but again, it makes code really fat (if this then this.. etc) and harder to follow compared to the clean throw cErrorClass of 1)
3) A combination of both. returns for fast code, class throws for interfaces of the main engine classes for more critical and rarer stuff. Problem, the structure seems to complicated to me. Too much "dilly dallying" and makes the spec of the error system seem more complex than it should be.
These are the only 3 options that I can think right now. Also, I''d like to do something that''ll actually return the name of the function of which the error occured, and show it up the stack, like an Unreal Tournament error, for those who''ve seen it. On that note: Anyone''s seen how Tim Sweeney does his error system? Looks like they call some monitoring function a the beginning and end of each error critical member function, seems very ingenious, only I have no idea on how they did that. Does anyone know what I''m talking about? Any ideas of any other neato exception handling systems?
Please, lemme hear you thoughts...
Aienthiwan
Well, there isn''t much I can add, but I hate to see this thread go to waste .
I too, have been trying to think of a real good error catching scheme. I knew a few people who used try/catch exclusively, but I had also heard there was a large performance penalty, so I did a little testing. Wrote a function that returned a FALSE value, and one that threw a FALSE value, and ran them both 1 million times, here are the results (BTW, this is in DEBUG mode, in RELEASE there was an even bigger gap):
Function throwing value: 5.457423 seconds
Function returning value: 0.024773 seconds
Obviously there is no way I''m using throw/catch for normal error/return codes. What I got setup now is 1 try/catch in my winmain, that will catch any "exceptional" errors, log it, display an error msg box, and quit the application. For all other errors, I have a base error class that converts HRESULTS to text messages and logs the error to a file. I then wrote a macro that will handle everything behind the scenes so I can just say: ERROR(hr); or ERROR("Blah happened"); and it will create an instance of the class and call the appropriate class functions to process the error. For DDraw/D3D specific HRESULT messages I derived a class from my error class, and overrode the HrToErrorMsg function with DDraw/D3D specific errors.
I''m sure there are better ways to be doing something like this.
Anyone else have any good ideas?
I too, have been trying to think of a real good error catching scheme. I knew a few people who used try/catch exclusively, but I had also heard there was a large performance penalty, so I did a little testing. Wrote a function that returned a FALSE value, and one that threw a FALSE value, and ran them both 1 million times, here are the results (BTW, this is in DEBUG mode, in RELEASE there was an even bigger gap):
Function throwing value: 5.457423 seconds
Function returning value: 0.024773 seconds
Obviously there is no way I''m using throw/catch for normal error/return codes. What I got setup now is 1 try/catch in my winmain, that will catch any "exceptional" errors, log it, display an error msg box, and quit the application. For all other errors, I have a base error class that converts HRESULTS to text messages and logs the error to a file. I then wrote a macro that will handle everything behind the scenes so I can just say: ERROR(hr); or ERROR("Blah happened"); and it will create an instance of the class and call the appropriate class functions to process the error. For DDraw/D3D specific HRESULT messages I derived a class from my error class, and overrode the HrToErrorMsg function with DDraw/D3D specific errors.
I''m sure there are better ways to be doing something like this.
Anyone else have any good ideas?
- Houdini
Well, I can add not too much, just few additions AKA my 2 cents.
You could add two constant (?) local string variables declared at the beginning of _each_ your function - one with function name and another with runtime description. I.e. something like:
HRESULT CClass1::Method1()
{
const char* MethodName = "CClass1::Method1";
char* drt="At the beginning";
drt="Calling that dumb function which Bill wrote last week";
hr=BillsFunction();
ERROR(hr);
etc...
}
(don''t blame for this code, I''m not professional examples writer)
And use these variables in the ERROR macro to write down exact place where and what happened. In such case drt variable can handle role of regular comments as well. Of course, these variables can be global, but in case of local variables it''s much more easier to log "stacked" errors.
It doesn''t look too nice but it works, produces nice and clear error logs and even force you to comment your code.
--
Sergey Glukhov.
St.Petersburg, Russia.
You could add two constant (?) local string variables declared at the beginning of _each_ your function - one with function name and another with runtime description. I.e. something like:
HRESULT CClass1::Method1()
{
const char* MethodName = "CClass1::Method1";
char* drt="At the beginning";
drt="Calling that dumb function which Bill wrote last week";
hr=BillsFunction();
ERROR(hr);
etc...
}
(don''t blame for this code, I''m not professional examples writer)
And use these variables in the ERROR macro to write down exact place where and what happened. In such case drt variable can handle role of regular comments as well. Of course, these variables can be global, but in case of local variables it''s much more easier to log "stacked" errors.
It doesn''t look too nice but it works, produces nice and clear error logs and even force you to comment your code.
--
Sergey Glukhov.
St.Petersburg, Russia.
...and if you''ll hide declarations of these variables and tracking of drt in two macroses like STARTMETHOD("CClass1::Method1") and DESCRIBECODE("Call that stupid Bill''s function") code will be looking better and later you even remove all this code tracking by emptying macroses to make code smaller (and a very bit faster).
--
Sergey Glukhov.
St.Petersburg, Russia.
--
Sergey Glukhov.
St.Petersburg, Russia.
Arankhalla''s idea is good, just a few notes:
Generally I use two preprocessor statements in error checking:
1: ERROR_LOGACTIONS
2: ERROR_LOGMINIMAL
1. This is the developer mode that I generally use, it contains absolutely heaps of function checking and logging. However it slows the engine down by about 35% which is just a little too much for my liking. This mode logs everything from return values, such as:
"The function BlahBlah() issued a return value of %d changing MyBlahVar" to stuff like what file/subroutine/line we are currently on.
It is generally useful for finding weird bugs that are using processor time / allocating too much memory etc... like obsolete code.
2. This is the release mode that logs basic stuff like errors that have been returned, and general information about functions that are NOT involved in the game loop. I mean, imagine spitting out:
"CheckForInput() returned %d"
// insert Input functions called here
"CheckForNetwork() returned %d"
// insert Network functions called here
"DoAIStuff() returned %d"
// insert AI Functions called here
"DrawScreen() returned %d"
// insert Pixel drawing functions called here
"FlipScreen() returned %d"
every game loop.
However, in non game loop functions, this sort of logging is fine, because it is not logging everything every screen.
If you get the point of that much weird ranting... congratulations.
Regards,
Nekosion
Generally I use two preprocessor statements in error checking:
1: ERROR_LOGACTIONS
2: ERROR_LOGMINIMAL
1. This is the developer mode that I generally use, it contains absolutely heaps of function checking and logging. However it slows the engine down by about 35% which is just a little too much for my liking. This mode logs everything from return values, such as:
"The function BlahBlah() issued a return value of %d changing MyBlahVar" to stuff like what file/subroutine/line we are currently on.
It is generally useful for finding weird bugs that are using processor time / allocating too much memory etc... like obsolete code.
2. This is the release mode that logs basic stuff like errors that have been returned, and general information about functions that are NOT involved in the game loop. I mean, imagine spitting out:
"CheckForInput() returned %d"
// insert Input functions called here
"CheckForNetwork() returned %d"
// insert Network functions called here
"DoAIStuff() returned %d"
// insert AI Functions called here
"DrawScreen() returned %d"
// insert Pixel drawing functions called here
"FlipScreen() returned %d"
every game loop.
However, in non game loop functions, this sort of logging is fine, because it is not logging everything every screen.
If you get the point of that much weird ranting... congratulations.
Regards,
Nekosion
Regards,Nekosion
That would be good, but it would force you to put in a lot of extra code. You''d be recoding the same description for the same function for every line that calls it.
Another solution might be to create a global array of 10 strings, and keep a global counter. At the beginning of each function, you could call START_FUNCTION("myclass::myfunc"); and it would store that string in the array at the position kept by the global counter. After it is set, the counter would automatically increment 1 (and wrap to 0 once it reaches 9). This would allow you to keep the last 10 (or however many you wish) function calls.
Then, when you get an error, you can traverse the array (starting at the previous counter) and print out the last 10 functions. However, there will be times where you go in 10 functions deep, then return to the 2nd function, and there is an error there, you''ll end up printing that function plus the other 8 which should NOT be printed (just 2 functions should). One way to get past that is to have a counter that keeps track of how "deep" you are in functions. Each time you call START_FUNCTION it increments it, and then you''ll have to put a line END_FUNCTION at the end of each function to decrement the counter. That way you can use that counter to know how many function calls deep you are, so you know how many to print out.
And of course you could keep another array for function descriptions, and set it just like you would set the name. So basically, every function would have this template:
- Houdini
Another solution might be to create a global array of 10 strings, and keep a global counter. At the beginning of each function, you could call START_FUNCTION("myclass::myfunc"); and it would store that string in the array at the position kept by the global counter. After it is set, the counter would automatically increment 1 (and wrap to 0 once it reaches 9). This would allow you to keep the last 10 (or however many you wish) function calls.
Then, when you get an error, you can traverse the array (starting at the previous counter) and print out the last 10 functions. However, there will be times where you go in 10 functions deep, then return to the 2nd function, and there is an error there, you''ll end up printing that function plus the other 8 which should NOT be printed (just 2 functions should). One way to get past that is to have a counter that keeps track of how "deep" you are in functions. Each time you call START_FUNCTION it increments it, and then you''ll have to put a line END_FUNCTION at the end of each function to decrement the counter. That way you can use that counter to know how many function calls deep you are, so you know how many to print out.
And of course you could keep another array for function descriptions, and set it just like you would set the name. So basically, every function would have this template:
BOOL MyClass::MyFunc(){ START_FUNCTION("MyClass::MyFunc()") FUNCTION_DEF("This function does, well, nothing really"); // Insert code here... END_FUNCTION();}
- Houdini
- Houdini
Whoa,
That is great! :-) I posted this topic on multiple newsgroups and didn''t a quarter of good ideas and answers I got here! Woohoo! Well, at least I know where to post from now on.. hehe..
Hold all these thoughts, a lot of them are great. Actually, they''ve inspired a neat algo. Will post it tonight.. (err, Eastern Standard Time)
Regards,
Aienthiwan
That is great! :-) I posted this topic on multiple newsgroups and didn''t a quarter of good ideas and answers I got here! Woohoo! Well, at least I know where to post from now on.. hehe..
Hold all these thoughts, a lot of them are great. Actually, they''ve inspired a neat algo. Will post it tonight.. (err, Eastern Standard Time)
Regards,
Aienthiwan
Just wanted to add a few things. To make the function tracking properly, you should use a linked list (or a dynamic array, stack, queue, etc) to track functions instead of a static array. When a START_FUNCTION macro is called it adds a new record to the end of the list, and when a END_FUNCTION macro is called it just pops the last record off the list. This will of course cause your program to run more slowly (constantly allocating and freeing memory) but for release builds (or better yet, as a command line parameter) you can just set the macros to nothing, as was suggested above.
Also, the END_FUNCTION has 1 major flaw. For functions that return values, you NEED to make sure you call the macro NOT JUST at the end of the function (well, before the return statement) but also before any other return statements that could be scattered throughout the function. Kinda a pain in the ass...
Also, something else I had thought about, but never really tried, that someone might be interested in. That is to overload the << operator on all of your classes to write the contents of its variables to a stream (which could be your logfile stream, if you have one) all nicely formatted. Good for debugging.
What''s cool about it tho, is that if you typed this simple command "logfile << m_pClient;", m_pClient would also call << on all of it''s contained classes, which would do the same, and so on. You would end up with a huge tree of all classes in their current state. If you implemented a simple indentation scheme, you''d actually get a nice formatted tree, easily able to see which gune owns this projectile with the bad coordinates, what players owns that gun, etc.
Of course the bad thing is if you add more variables to the class, you''d have to make sure you also print them out to your << function.
- Houdini
Also, the END_FUNCTION has 1 major flaw. For functions that return values, you NEED to make sure you call the macro NOT JUST at the end of the function (well, before the return statement) but also before any other return statements that could be scattered throughout the function. Kinda a pain in the ass...
Also, something else I had thought about, but never really tried, that someone might be interested in. That is to overload the << operator on all of your classes to write the contents of its variables to a stream (which could be your logfile stream, if you have one) all nicely formatted. Good for debugging.
What''s cool about it tho, is that if you typed this simple command "logfile << m_pClient;", m_pClient would also call << on all of it''s contained classes, which would do the same, and so on. You would end up with a huge tree of all classes in their current state. If you implemented a simple indentation scheme, you''d actually get a nice formatted tree, easily able to see which gune owns this projectile with the bad coordinates, what players owns that gun, etc.
Of course the bad thing is if you add more variables to the class, you''d have to make sure you also print them out to your << function.
- Houdini
- Houdini
Hey, wait a minute!
C++ standard exception handling (try, throw, catch) is not that slow as you people claim!
You see, the basic idea behind exception handling is that you do not pay any performance penalty when there is no exception. In fact, C++ EH might even be faster as the usual method of returning errorcodes when no exceptions occur.
And since exceptions are exceptional , that is often the case.
I can give you some info on the assemblycode behind this:
- There is a stack that is filled with the addresses of where a try statement starts. (A stack is used because try blocks can be nested)
- Every time you enter a try block a pointer is pushed into the stack and every time you leave the try block a pointer is popped
- When an exception is thrown the program jumps to the adress that is at the top of the stack (the closest try block) But first all objects are released using stack unwinding (and that produces the most overhead)
So when you have few try blocks and few exceptions you have blazing fast AND better readible code.
By the way, the Visual Studio 6 compiler does not correctly implement the C++ standard exception system (which might explain your poor test results). I heard the next release actually will. But with M$ you never know.
C++ standard exception handling (try, throw, catch) is not that slow as you people claim!
You see, the basic idea behind exception handling is that you do not pay any performance penalty when there is no exception. In fact, C++ EH might even be faster as the usual method of returning errorcodes when no exceptions occur.
And since exceptions are exceptional , that is often the case.
I can give you some info on the assemblycode behind this:
- There is a stack that is filled with the addresses of where a try statement starts. (A stack is used because try blocks can be nested)
- Every time you enter a try block a pointer is pushed into the stack and every time you leave the try block a pointer is popped
- When an exception is thrown the program jumps to the adress that is at the top of the stack (the closest try block) But first all objects are released using stack unwinding (and that produces the most overhead)
So when you have few try blocks and few exceptions you have blazing fast AND better readible code.
By the way, the Visual Studio 6 compiler does not correctly implement the C++ standard exception system (which might explain your poor test results). I heard the next release actually will. But with M$ you never know.
September 28, 2000 01:57 PM
try/throw/catch should NEVER, NEVER , NEVER be used for normal flow control of a program. The only time I ever throw an exception is if continuing in the current function would crash my program, period. That guideline has kept my code fast and clean (i.e. I''m not checking for return values everywhere).
Exceptions are meant to be used for abnormal conditions. The capability to throw an exception should not slow down your code, but the act of actually throwing one is very slow. If you limit your program to only throw exceptions before it crashes, you should be happy.
What kind of errors are you trying to report here? Do you actually have an error that''s occurring in your game loop every time you execute it? If so, something''s horribly wrong. If not, then what you''re talking about isn''t really an error.
Exceptions are meant to be used for abnormal conditions. The capability to throw an exception should not slow down your code, but the act of actually throwing one is very slow. If you limit your program to only throw exceptions before it crashes, you should be happy.
What kind of errors are you trying to report here? Do you actually have an error that''s occurring in your game loop every time you execute it? If so, something''s horribly wrong. If not, then what you''re talking about isn''t really an error.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement