Rewrote error & log functions to be more general, use longjmp to get out of

trouble and into predictable cleanup functions (one for each of serve,
client & control contexts).  We use 'fatal' to mean 'kill the thread' and
'error' to mean 'don't kill the thread', assuming some recovery action,
except I don't use error anywhere yet.
This commit is contained in:
Matthew Bloch
2012-06-09 02:25:12 +01:00
parent 8691533d88
commit b546539ab8
12 changed files with 259 additions and 174 deletions

View File

@@ -3,36 +3,106 @@
#include <stdio.h>
#include <pthread.h>
void error_init();
void error(int consult_errno, int fatal, int close_socket, pthread_mutex_t* unlock, const char* format, ...);
#include <string.h>
#include <errno.h>
void* xrealloc(void* ptr, size_t size);
void* xmalloc(size_t size);
void set_debug(int value);
typedef void (cleanup_handler)(void* /* context */, int /* is fatal? */);
/* set from 0 - 5 depending on what level of verbosity you want */
extern int log_level;
/* set up the error globals */
void error_init();
/* error_set_handler must be a macro not a function due to setjmp stack rules */
#include <setjmp.h>
struct error_handler_context {
jmp_buf jmp;
cleanup_handler* handler;
void* data;
};
#define DECLARE_ERROR_CONTEXT(name) \
struct error_handler_context *name = (struct error_handler_context*) \
pthread_getspecific(cleanup_handler_key)
/* clean up with the given function & data when error_handler() is invoked,
* non-fatal errors will also return here (if that's dangerous, use fatal()
* instead of error()).
*
* error handlers are thread-local, so you need to call this when starting a
* new thread.
*/
extern pthread_key_t cleanup_handler_key;
#define error_set_handler(cleanfn, cleandata) \
{ \
DECLARE_ERROR_CONTEXT(old); \
struct error_handler_context *context = \
xmalloc(sizeof(struct error_handler_context)); \
context->handler = (cleanfn); \
context->data = (cleandata); \
\
switch (setjmp(context->jmp)) \
{ \
case 0: /* setup time */ \
if (old) \
free(old); \
pthread_setspecific(cleanup_handler_key, context); \
break; \
case 1: /* fatal error, terminate thread */ \
context->handler(context->data, 1); \
pthread_exit((void*) 1); \
abort(); \
case 2: /* non-fatal error, return to context of error handler setup */ \
context->handler(context->data, 0); \
default: \
abort(); \
} \
}
/* invoke the error handler - longjmps away, don't use directly */
void error_handler(int fatal);
/* mylog a line at the given level (0 being most verbose) */
void mylog(int line_level, const char* format, ...);
#ifdef DEBUG
void debug(const char*msg, ...);
# define debug(msg, ...) mylog(0, "%s:%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__)
#else
/* no-op */
# define debug( msg, ...)
# define debug(msg, ...) /* no-op */
#endif
#define CLIENT_ERROR(msg, ...) \
error(0, 0, client->socket, &client->serve->l_io, msg, ##__VA_ARGS__)
#define CLIENT_ERROR_ON_FAILURE(test, msg, ...) \
if (test < 0) { error(1, 0, client->socket, &client->serve->l_io, msg, ##__VA_ARGS__); }
/* informational message, not expected to be compiled out */
#define info(msg, ...) mylog(1, "%s:%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__)
#define SERVER_ERROR(msg, ...) \
error(0, 1, 0, NULL, msg, ##__VA_ARGS__)
#define SERVER_ERROR_ON_FAILURE(test, msg, ...) \
if (test < 0) { error(1, 1, 0, NULL, msg, ##__VA_ARGS__); }
/* messages that might indicate a problem */
#define warn(msg, ...) mylog(2, "%s:%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__)
/* mylog a message and invoke the error handler to recover */
#define error(msg, ...) { \
mylog(3, "*** %s:%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__); \
error_handler(0); \
}
#define NULLCHECK(x) \
do { if ( NULL == (x) ) { SERVER_ERROR( "Null " #x "." ); } } while(0)
/* mylog a message and invoke the error handler to kill the current thread */
#define fatal(msg, ...) { \
mylog(4, "*** %s:%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__); \
error_handler(1); \
}
#define ERROR_IF_NULL(value, msg, ...) if (NULL == value) error(msg " (errno=%d, %s)", ##__VA_ARGS__, errno, strerror(errno))
#define ERROR_IF_NEGATIVE(value, msg, ...) if (value < 0) error(msg, ##__VA_ARGS__)
#define ERROR_IF_ZERO(value, msg, ...) if (0 == value) error(msg, ##__VA_ARGS__)
#define FATAL_IF_NULL(value, msg, ...) if (NULL == value) fatal(msg, ##__VA_ARGS__)
#define FATAL_IF_NEGATIVE(value, msg, ...) if (value < 0) fatal(msg " (errno=%d, %s)", ##__VA_ARGS__, errno, strerror(errno))
#define FATAL_IF_ZERO(value, msg, ...) if (0 == value) fatal(msg, ##__VA_ARGS__)
#define NULLCHECK(value) FATAL_IF_NULL(value, "BUG: " #value " is null")
#endif