Implementation principle of thread safe try catch exception

Keywords: html5 html css

try{               // Exceptions may be thrown   throw

}catch(){         // Catch exception


}finally{           No matter whether an exception is thrown or caught, it will come here, except return.


}

In c, goto cannot cross functions, while setjmp and longjmp perform this type of jump function

#include <setjmp.h>

int setjmp(jmp_buf env);            The direct call returns 0. If it returns the second parameter of longjmp from the longjmp call

void longjmp(jmp_buf env , int val);

jmp_buf is an array of some form, which stores all the information that can be used to recover the stack state when calling longjmp. env variables are usually defined as global variables because they need to be referenced in another function.

The reason for using the second parameter of longjmp is that there can be multiple longjmps for a setjmp.

#include <setjmp.h>
#include <stdio.h>

jmp_buf env;
int count = 0;
void sub_func(int idx) {
	printf("sub_func --> idx:%d\n", idx);
	longjmp(env, idx);
}
int main(int argc, char *argv[]) {
	int idx = 0;
	count = setjmp(env);
    if (count == 0) {
		printf("count:%d\n", count);
		sub_func(++idx);
	} else if (count == 1) {
		printf("count:%d\n", count);
		sub_func(++idx);
	} else if (count == 2) {
		printf("count:%d\n", count);
		sub_func(++idx);
	} else if (count == 3) {
		printf("count:%d\n", count);
		sub_func(++idx);
	} else {
		printf("other count\n");
	}
	return 0;
}

After long JMP jump, the function stack will be overwritten without stack overflow.

The relationship between setjmp, longjmp and try cart

try   --->  setjmp     This is equivalent to setting this label

throw   ----> longjmp(5)     Jump to setjmp

catch(5)    ------>  Catch what you throw

In a process, threads are almost shared except the thread's own stack and registers. What if the thread wants to maintain a global variable that only belongs to the thread? The thread's private storage solves this problem.

     Let's talk about the specific usage of thread storage.

1. Create a variable of type pthread_key_t.

2. Call pthread_key_create() to create the variable. The function has two parameters. The first parameter is the pthread_key_t variable declared above, and the second parameter is a cleanup function, which is called when the thread releases the thread storage. The function pointer can be set to NULL, so that the system will call the default cleanup function.

3. When the thread needs to store special values, you can call pthread_setspecific(). This function has two parameters, the first is the pthread_key_t variable declared earlier, and the second is the void * variable, so you can store any type of values.

4. If you need to fetch the stored value, call pthread_getspecific(). The parameter of this function is the pthread_key_t variable mentioned above, and this function returns a value of void * type.

pthread_key_t no matter which thread is created, all other threads are visible, that is, phread_key_create() is only required once in a process. It seems to be a global variable, but the global value is only the key value, and the corresponding value values are different for different threads (set through pthread_setspcic() and pthread_getspecific()).

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

No matter which thread calls pthread_key_create(), the created key is accessible to all threads, but each thread can fill different values into the key according to its own needs, which is equivalent to providing a global variable with the same name but different values.

int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)
void * pthread_getspecific(pthread_key_t key)

When writing (pthread_setspecific()), associate the pointer value (not the specified content) with the key, and the corresponding readout function reads out the data associated with the key. The data type is set to void *, so it can point to any type of data.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <pthread.h>


#define PTHREAD_COUNT		2

pthread_key_t key;
typedef void* (*thread_cb)(void*);

struct pair {
	int x;
	int y;
};

void *thread1_proc(void *arg) {

	struct pair p = {1, 2};
	int i = 0;
	
	while (++i < 100) {
		p.x ++;
		p.y ++;
		pthread_setspecific(key, &p);
		
		struct pair *res = (struct pair *)pthread_getspecific(key);
		printf("x: %d, y: %d\n", res->x, res->y);
	}
}

void *thread2_proc(void *arg) {

	int i = 0;

	while (++i < 100) {
		pthread_setspecific(key, &i);

		int *res = (int *)pthread_getspecific(key);
		printf("res: %d\n", *res);
	}
	
}

void *thread3_proc(void *arg) {

	
	int i = 0;
	
	while (++i < 100) {
		
		struct pair *res = (struct pair *)pthread_getspecific(key);
		printf("x: %d, y: %d\n", res->x, res->y);
	}
}


int main(int argc, char *argv[]) {

	pthread_key_create(&key, NULL);

	pthread_t thread_id[PTHREAD_COUNT] = {0};
	thread_cb thread_proc[PTHREAD_COUNT] = {
		thread1_proc,
		thread2_proc
	}; 
	
	int i = 0;
	for (i = 0;i < PTHREAD_COUNT;i ++) {
		pthread_create(&thread_id[i], NULL, thread_proc[i], NULL);
	}
	
	for (i = 0;i < PTHREAD_COUNT;i ++) {
		pthread_join(thread_id[i], NULL);
	}

	pthread_key_delete(key);
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <stdarg.h>

#include <pthread.h>
#include <setjmp.h>



#define ntyThreadData		pthread_key_t
#define ntyThreadDataSet(key, value)	pthread_setspecific((key), (value))
#define ntyThreadDataGet(key)		pthread_getspecific((key))
#define ntyThreadDataCreate(key)	pthread_key_create(&(key), NULL)


#define EXCEPTIN_MESSAGE_LENGTH		512

typedef struct _ntyException {
	const char *name;
} ntyException; 

ntyException SQLException = {"SQLException"};
ntyException TimeoutException = {"TimeoutException"};

ntyThreadData ExceptionStack;


typedef struct _ntyExceptionFrame {
	jmp_buf env;

	int line;
	const char *func;
	const char *file;

	ntyException *exception;
	struct _ntyExceptionFrame *prev;
	
	char message[EXCEPTIN_MESSAGE_LENGTH+1];

} ntyExceptionFrame;

#define ntyExceptionPopStack	\
	ntyThreadDataSet(ExceptionStack, ((ntyExceptionFrame*)ntyThreadDataGet(ExceptionStack))->prev)

#define ReThrow					ntyExceptionThrow(frame.exception, frame.func, frame.file, frame.line, NULL)
#define Throw(e, cause, ...) 	ntyExceptionThrow(&(e), __func__, __FILE__, __LINE__, cause, ##__VA_ARGS__, NULL)


enum {
	ExceptionEntered = 0,
	ExceptionThrown,
	ExceptionHandled,
	ExceptionFinalized
};


#define Try do {							\
			volatile int Exception_flag;	\
			ntyExceptionFrame frame;		\
			frame.message[0] = 0;			\
			frame.prev = (ntyExceptionFrame*)ntyThreadDataGet(ExceptionStack);	\
			ntyThreadDataSet(ExceptionStack, &frame);	\
			Exception_flag = setjmp(frame.env);			\
			if (Exception_flag == ExceptionEntered) {	
			

#define Catch(e) \
				if (Exception_flag == ExceptionEntered) ntyExceptionPopStack; \
			} else if (frame.exception == &(e)) { \
				Exception_flag = ExceptionHandled;


#define Finally \
				if (Exception_flag == ExceptionEntered) ntyExceptionPopStack; \
			} { \
				if (Exception_flag == ExceptionEntered)	\
					Exception_flag = ExceptionFinalized; 

#define EndTry \
				if (Exception_flag == ExceptionEntered) ntyExceptionPopStack; \
			} if (Exception_flag == ExceptionThrown) ReThrow; \
        	} while (0)	


static pthread_once_t once_control = PTHREAD_ONCE_INIT;

static void init_once(void) { 
	ntyThreadDataCreate(ExceptionStack); 
}


void ntyExceptionInit(void) {
	pthread_once(&once_control, init_once);
}


void ntyExceptionThrow(ntyException *excep, const char *func, const char *file, int line, const char *cause, ...) {

	va_list ap;
	ntyExceptionFrame *frame = (ntyExceptionFrame*)ntyThreadDataGet(ExceptionStack);

	if (frame) {

		frame->exception = excep;
		frame->func = func;
		frame->file = file;
		frame->line = line;

		if (cause) {
			va_start(ap, cause);
			vsnprintf(frame->message, EXCEPTIN_MESSAGE_LENGTH, cause, ap);
			va_end(ap);
		}

		ntyExceptionPopStack;

		longjmp(frame->env, ExceptionThrown);
		
	} else if (cause) {

		char message[EXCEPTIN_MESSAGE_LENGTH+1];

		va_start(ap, cause);
		vsnprintf(message, EXCEPTIN_MESSAGE_LENGTH, cause, ap);
		va_end(ap);

		printf("%s: %s\n raised in %s at %s:%d\n", excep->name, message, func ? func : "?", file ? file : "?", line);
		
	} else {

		printf("%s: %p\n raised in %s at %s:%d\n", excep->name, excep, func ? func : "?", file ? file : "?", line);
		
	}

}


/* ** **** ******** **************** debug **************** ******** **** ** */

ntyException A = {"AException"};
ntyException B = {"BException"};
ntyException C = {"CException"};
ntyException D = {"DException"};

void *thread(void *args) {

	pthread_t selfid = pthread_self();

	Try {

		Throw(A, "A");
		
	} Catch (A) {

		printf("catch A : %ld\n", selfid);
		
	} EndTry;

	Try {

		Throw(B, "B");
		
	} Catch (B) {

		printf("catch B : %ld\n", selfid);
		
	} EndTry;

	Try {

		Throw(C, "C");
		
	} Catch (C) {

		printf("catch C : %ld\n", selfid);
		
	} EndTry;

	Try {

		Throw(D, "D");
		
	} Catch (D) {

		printf("catch D : %ld\n", selfid);
		
	} EndTry;

	Try {

		Throw(A, "A Again");
		Throw(B, "B Again");
		Throw(C, "C Again");
		Throw(D, "D Again");

	} Catch (A) {

		printf("catch A again : %ld\n", selfid);
	
	} Catch (B) {

		printf("catch B again : %ld\n", selfid);

	} Catch (C) {

		printf("catch C again : %ld\n", selfid);
		
	} Catch (D) {
	
		printf("catch B again : %ld\n", selfid);
		
	} EndTry;
	
}


#define THREADS		50

int main(void) {

	ntyExceptionInit();

	Throw(D, NULL);

	Throw(C, "null C");

	printf("\n\n=> Test1: Try-Catch\n");

	Try {

		Try {
			Throw(B, "recall B");
		} Catch (B) {
			printf("recall B \n");
		} EndTry;
		
		Throw(A, NULL);

	} Catch(A) {

		printf("\tResult: Ok\n");
		
	} EndTry;

	printf("=> Test1: Ok\n\n");

	printf("=> Test2: Test Thread-safeness\n");
#if 1
	int i = 0;
	pthread_t threads[THREADS];
	
	for (i = 0;i < THREADS;i ++) {
		pthread_create(&threads[i], NULL, thread, NULL);
	}

	for (i = 0;i < THREADS;i ++) {
		pthread_join(threads[i], NULL);
	}
#endif
	printf("=> Test2: Ok\n\n");

} 

Posted by Daguse on Sun, 10 Oct 2021 05:53:19 -0700