File: gtm_multi_thread.h

package info (click to toggle)
fis-gtm 7.1-006-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 32,908 kB
  • sloc: ansic: 344,906; asm: 5,184; csh: 4,859; sh: 2,000; awk: 294; makefile: 73; sed: 13
file content (241 lines) | stat: -rw-r--r-- 10,407 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/****************************************************************
 *								*
 * Copyright (c) 2015-2023 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#ifndef GTM_MULTI_THREAD_H
#define GTM_MULTI_THREAD_H

#include "gtm_pthread.h"

typedef	void *(*gtm_pthread_fnptr_t)(void *parm);

int	gtm_multi_thread(gtm_pthread_fnptr_t fnptr, int ntasks, int max_threads,
					pthread_t *thr_array, void **ret_array, void *parmarray, int parmElemSize);

GBLREF	boolean_t	multi_thread_in_use;		/* TRUE => threads are in use. FALSE => not in use */
GBLREF	boolean_t	thread_mutex_initialized;	/* TRUE => "thread_mutex" variable is initialized */
GBLREF	pthread_mutex_t	thread_mutex;			/* mutex structure used to ensure serialization amongst threads */
GBLREF	pthread_t	thread_mutex_holder;		/* pid/tid of the thread that has "thread_mutex" currently locked */
GBLREF	pthread_key_t	thread_gtm_putmsg_rname_key;	/* points to region name corresponding to each running thread */
GBLREF	boolean_t	thread_block_sigsent;		/* TRUE => block external signals SIGINT/SIGQUIT/SIGTERM/SIGTSTP/SIGCONT */
GBLREF	int		gtm_mupjnl_parallel;		/* Maximum # of concurrent threads or procs to use in "gtm_multi_thread" */
#ifdef DEBUG
GBLREF	boolean_t	in_nondeferrable_signal_handler;

# define	IN_GENERIC_SIGNAL_HANDLER	1
# define	IN_TIMER_HANDLER		2
#endif
GBLREF	boolean_t	forced_thread_exit;

error_def(ERR_SYSCALL);

/* Some pthread operations don't do well when interrupted by signals, so provide deferred interrupt wrappers for them. */

#define PTHREAD_COND_SIGNAL(COND, RVAL)						\
MBSTART {									\
	intrpt_state_t		prev_intrpt_state;				\
										\
	DEFER_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	(RVAL) = pthread_cond_signal(COND);					\
	ENABLE_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
} MBEND

#define PTHREAD_COND_BROADCAST(COND, RVAL)						\
MBSTART {									\
	intrpt_state_t		prev_intrpt_state;				\
										\
	DEFER_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	(RVAL) = pthread_cond_broadcast(COND);					\
	ENABLE_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
} MBEND

#define PTHREAD_COND_WAIT(COND, MUTEX, RVAL)					\
MBSTART {									\
	GBLREF VSIG_ATOMIC_T	forced_exit;					\
	intrpt_state_t		prev_intrpt_state;				\
										\
	DEFER_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	(RVAL) = pthread_cond_wait(COND, MUTEX);				\
	if (forced_exit)							\
		pthread_mutex_unlock(MUTEX);					\
	ENABLE_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	assert(!forced_exit);							\
} MBEND

#define PTHREAD_COND_TIMEDWAIT(COND, MUTEX, TIMEOUT, RVAL)			\
MBSTART {									\
	GBLREF VSIG_ATOMIC_T	forced_exit;					\
	intrpt_state_t		prev_intrpt_state;				\
										\
	DEFER_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	(RVAL) = pthread_cond_timedwait(COND, MUTEX, TIMEOUT);			\
	if (forced_exit)							\
		pthread_mutex_unlock(MUTEX);					\
	ENABLE_INTERRUPTS(INTRPT_IN_PTHREAD_NB, prev_intrpt_state);		\
	assert(!forced_exit);							\
} MBEND

#ifdef GTM_PTHREAD

/* For use with gtm_multi_thread() */

typedef struct {
	int			ntasks;
	gtm_pthread_fnptr_t	fnptr;
	void			**ret_array;
	void			*parm_array;
	int			parmElemSize;
} thread_parm_t;

int	gtm_multi_thread_helper(thread_parm_t *tparm);

#define	IS_LIBPTHREAD_MUTEX_LOCK_HOLDER 	(pthread_self() == thread_mutex_holder)

#define	PTHREAD_MUTEX_LOCK_IF_NEEDED(WAS_HOLDER)								\
{														\
	int	rc;												\
														\
	if (multi_thread_in_use)										\
	{	/* gtm_malloc/gtm_free is not thread safe. So use locks to serialize */				\
		assert(thread_mutex_initialized);								\
		/* We should never use pthread_* calls inside a signal/timer handler. Assert that */		\
		assert(!in_nondeferrable_signal_handler);							\
		/* Allow for self to already own the lock (due to nested codepaths that need the lock. */	\
		if (!IS_LIBPTHREAD_MUTEX_LOCK_HOLDER)								\
		{												\
			rc = pthread_mutex_lock(&thread_mutex);							\
			if (rc)											\
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_SYSCALL, 5,			\
						RTS_ERROR_LITERAL("pthread_mutex_lock()"), CALLFROM, rc);	\
			thread_mutex_holder = pthread_self();							\
			WAS_HOLDER = FALSE;									\
		} else												\
			WAS_HOLDER = TRUE;									\
	} else													\
	{													\
		assert(!thread_mutex_holder);									\
		WAS_HOLDER = FALSE;										\
	}													\
}

#define	PTHREAD_MUTEX_UNLOCK_IF_NEEDED(WAS_HOLDER)							\
{													\
	int	rc;											\
													\
	if (multi_thread_in_use)									\
	{												\
		assert(thread_mutex_initialized);							\
		/* We should never use pthread_* calls inside a signal/timer handler. Assert that */	\
		assert(!in_nondeferrable_signal_handler);						\
		/* assert self does own the lock */							\
		assert(pthread_self() == thread_mutex_holder);						\
		if (!WAS_HOLDER)									\
		{											\
			thread_mutex_holder = 0;							\
			rc = pthread_mutex_unlock(&thread_mutex);					\
			if (rc)										\
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_SYSCALL, 5,		\
					RTS_ERROR_LITERAL("pthread_mutex_unlock()"), CALLFROM, rc);	\
		}											\
	} else												\
		assert(!thread_mutex_holder);								\
}

/* Below macro identifies if the caller is inside threaded code. A quick check of this is using the global
 * "multi_thread_in_use". If that is FALSE, we are guaranteed not to be in threaded code. If it is TRUE though, it is
 * still possible the caller is the master process (that spawned off the threads) and not the threads themselves.
 * The only way to distinguish between the master and the threads is to get the rname_key. It is NULL for the
 * master and non-NULL for the threads.
 * Note: In the future, if more threads are implemented with no region context, we might be better off checking
 * "gtm_main_thread_id" against "pthread_self()". Right now, the former is not maintained in all cases and is
 * hence not used.
 */
#define	INSIDE_THREADED_CODE(rname) (multi_thread_in_use && (NULL != (rname = pthread_getspecific(thread_gtm_putmsg_rname_key))))

#else

#define	IS_LIBPTHREAD_MUTEX_LOCK_HOLDER 		FALSE
#define	ASSERT_NO_THREAD_USAGE				assert(!multi_thread_in_use && !thread_mutex_holder)
#define	PTHREAD_MUTEX_LOCK_IF_NEEDED(WAS_HOLDER) 	ASSERT_NO_THREAD_USAGE
#define	PTHREAD_MUTEX_UNLOCK_IF_NEEDED(WAS_HOLDER)	ASSERT_NO_THREAD_USAGE
#define	INSIDE_THREADED_CODE(rname)			FALSE

#endif

/* Returns FALSE if threads are in use and we dont own the libpthread mutex lock. Returns TRUE otherwise */
#define	IS_PTHREAD_LOCKED_AND_HOLDER	(!multi_thread_in_use || IS_LIBPTHREAD_MUTEX_LOCK_HOLDER)

/* Below macro is invoked just before we read or write global variables that are also updated inside threaded code.
 * Global variables currently in this list (of those that are updated inside threaded code) are
 *	TREF(util_outbuff_ptr)
 *	TREF(util_outptr)
 * Any time any variable in the above list is read or written, the below macro needs to be added before the reference.
 */
#define	ASSERT_SAFE_TO_UPDATE_THREAD_GBLS	assert(IS_PTHREAD_LOCKED_AND_HOLDER)

/* Note: Below macro can be safely used even in threaded code since this variable only transitions from 0 to 1 inside threaded code.
 * Also, it is okay for other threads to read a stale value of this since they keep checking this at logical points in their
 * execution (using the PTHREAD_EXIT_IF_FORCED_EXIT macro). Reading a stale value will only delay thread exit a little.
 * The 1 to 0 transition happens only in non-threaded code. Assert accordingly.
 */
#define	SET_FORCED_THREAD_EXIT	forced_thread_exit = 1
#define	RESET_FORCED_THREAD_EXIT								\
{												\
	char			*rname;								\
												\
	assert(!INSIDE_THREADED_CODE(rname));							\
	forced_thread_exit = 0;									\
}

/* If a process with multiple threads receives a signal (e.g. SIGTERM) asking it to terminate,
 * the master process sets forced_exit to a non-zero value. But does not initiate exit handling
 * right then. Instead it goes back to what it was doing (most likely "pthread_join" inside
 * "gtm_multi_thread") and continues waiting for the threads to die. The threads are supposed to
 * check for "forced_exit" periodically (when they reach a logical stage in their processing) and
 * exit as soon as possible thereby letting the master process initiate exit handling processing.
 * The below macro helps with that.
 */
#define	PTHREAD_EXIT_IF_FORCED_EXIT				\
{								\
	char	*rname;						\
								\
	GTM_PTHREAD_ONLY(assert(INSIDE_THREADED_CODE(rname)));	\
	NON_GTM_PTHREAD_ONLY(assert(FALSE));			\
	if (forced_thread_exit)					\
		GTM_PTHREAD_EXIT(PTHREAD_CANCELED);		\
}

/* Exit the thread with status "STATUS". But before that release any mutex locks you hold (possible for example
 * if the exiting thread had done a "rts_error" call which would have grabbed the pthread_mutex_t lock but not
 * released it anywhere until thread exit time (which is here). The release is needed to prevent other threads
 * from deadlocking.
 */
#define	GTM_PTHREAD_EXIT(STATUS)										\
{														\
	char	*rname;												\
														\
	GTM_PTHREAD_ONLY(assert(INSIDE_THREADED_CODE(rname));)							\
	NON_GTM_PTHREAD_ONLY(assert(FALSE));									\
	/* If thread is exiting with abnormal status, signal other threads to stop execution			\
	 * at a logical point since the parent is anyways going to return with a non-zero exit status.		\
	 * This is necessary so only ONE thread modifies the condition-handler stack and other global variables	\
	 * as part of error-handling. If multiple threads encounter errors, the first thread will go		\
	 * through the condition handler scheme, the second thread onwards will exit in DRIVECH without		\
	 * going through any condition handler invocations.							\
	 */													\
	if (0 != STATUS)											\
		SET_FORCED_THREAD_EXIT;										\
	if (IS_LIBPTHREAD_MUTEX_LOCK_HOLDER)									\
		PTHREAD_MUTEX_UNLOCK_IF_NEEDED(FALSE);								\
	pthread_exit((void *)(INTPTR_T)STATUS);									\
}

#endif