Problems w/ condition variables and multi-threadin

BlitzMax Forums/BlitzMax Programming/Problems w/ condition variables and multi-threadin

jtfrench(Posted 2010) [#1]
Hey folks,

I have some strange threading behavior going on here. I only notice this when I use multi-threading in my app.

Pretty much I have a function that checks for new player data with the server. I want to periodically call it so that the game can update over time with other player data. The function that does the checking is called "syncCitizenPositions". I've wrapped it up into a wrapper function for usage with a thread. The logic works by the threaded function starting, hitting a loop, and then waiting on a condition variable to be broadcast from the main thread. Condition variable gets broadcast when the player is actually in gameplay, the syncing occurs, and then the loop repeats, waiting for the next condition to be broadcast.

Only problem is that in reality it doesn't end up working like that. Instead:

a) somehow this function (t_handleRequests) is getting called several times after only a single CreateThread call. I know CreateThread is only getting called once because I log out to a file when it does, and I just get one entry. However, the code inside the function that is passed to CreateThread gets triggered a bunch of times.

For example, here's what the log file looks like after just a few seconds of running the app:

(more explanation of the function producing this output after the jump)

I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap
I think I'll take a nap



b) the syncCitizenPositions() is being executed even WITHOUT the condition variable being broadcast! I've done grep's on the codebase and this is the only place this function is referenced.

Function t_handleRequests:Object(data:Object)
	toLog "Just getting ready for work..."
	While(True)
	LockMutex(theConditionMutex)
	toLog "I think I'll take a nap"
	WaitCondVar(theCondition, theConditionMutex)
	RUNTIME.mGK.syncCitizenPositions();
	UnlockMutex(theConditionMutex)
	Wend
	
	toLog "Processing thread complete."
EndFunction



I'm using BlitzMax 1.41 . I figure I must be doing something fundamentally wrong. Any ideas of what's going on?

Thanks,

Jason

Last edited 2010

Last edited 2010


ima747(Posted 2010) [#2]
what does your condvar braodcast look like? if it's firing syncCitizenPositions() multiple times the assumption would be that theCondition is receiving multiple signals.


jtfrench(Posted 2010) [#3]
I created the condition variable like this:

Global theConditionMutex:TMutex = CreateMutex()


And here's the function I call when ever I want to trigger syncCitizenPositions():

Method wakeupServerThread()
		toLog "Server::wakeupServerThread()";
		saveLog();
		LockMutex(theConditionMutex)
		BroadcastCondVar(theCondition)
		UnlockMutex(theConditionMutex)
		mLastSyncedServer = MilliSecs();
	EndMethod


The toLog/saveLog() functions write out text to a logfile. They use mutexes since I didn't want multiple threads calling toLog()/saveLog() to cause problems.

Any ideas?


jtfrench(Posted 2010) [#4]
I've confirmed the problem doesn't occur if I comment out the call to syncCitizenPositions. That function makes use of curl, and it appears like I'm crashing somewhere in curl. Here's the Mac OS X stack trace:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000000c023cd9f
Crashed Thread:  0

Thread 0 Crashed:
0   client.debug.mt               	0x001d0aef Curl_formclean + 28
1   client.debug.mt               	0x001cbdfd Curl_http_done + 189
2   client.debug.mt               	0x001c1e7a Curl_done + 334
3   client.debug.mt               	0x001bcb07 Curl_perform + 923
4   client.debug.mt               	0x001a3741 1306 + 11
5   client.debug.mt               	0x000dbcdb 61784 + 8
6   client.debug.mt               	0x0000a02d 10583 + 23
7   client.debug.mt               	0x00006afe 9811 + 25
8   client.debug.mt               	0x00002be6 4 + 25
9   client.debug.mt               	0x00002ef1 run + 82
10  client.debug.mt               	0x00003129 -[BlitzMaxAppDelegate applicationDidFinishLaunching:] + 353


And here's what MaxIDE shows me:

Building client
Compiling:client.bmx
Linking:client.debug.mt
Executing:client.debug.mt
client.debug.mt(86971,0xa041d720) malloc: *** error for object 0x8ef790: double free
*** set a breakpoint in malloc_error_break to debug
client.debug.mt(86971,0xa041d720) malloc: *** error for object 0x8e9d30: double free
*** set a breakpoint in malloc_error_break to debug
client.debug.mt(86971,0xa041d720) malloc: *** error for object 0x17514930: double free
*** set a breakpoint in malloc_error_break to debug

Process complete




jtfrench(Posted 2010) [#5]
This sounds like two different problems. 1) I was getting erratic / repeated calling of a function triggered from a condition variable-controlled child thread, and 2) crashing when making curl calls (on what looks like a garbage cleanup related thing in curl)


H&K(Posted 2010) [#6]
While(True)
	LockMutex(theConditionMutex)
	toLog "I think I'll take a nap"
	WaitCondVar(theCondition, theConditionMutex)
	RUNTIME.mGK.syncCitizenPositions();
	UnlockMutex(theConditionMutex)
	Wend


read this again.

When is true true?


ima747(Posted 2010) [#7]
re the curl crashing and garbage collector take a poke at:
http://www.blitzbasic.com/Community/posts.php?topic=91117
http://www.blitzbasic.com/Community/posts.php?topic=91511
http://www.blitzbasic.com/Community/posts.php?topic=92541

I suspect you may be bumping into the threaded garbage collector issues, or curl may not be behaving in a thread safe manner some how (don't use it much myself so I don't know, just a guess).


jtfrench(Posted 2010) [#8]
it definitely looks like curl thread-safety issues. What's interesting is that even calls to curl from the main thread cause the error --- just only when a child thread is created though. If no additional threads are used, then the curl calls appear to behave as normal.

I'll have to look into where I can find more issue on curl & threads


jtfrench(Posted 2010) [#9]
From libcurl-tutorial:

Multi-threading Issues

The first basic rule is that you must never share a libcurl handle (be it easy or multi or whatever) between multiple threads. Only use one handle in one thread at a time.

libcurl is completely thread safe, except for two issues: signals and SSL/TLS handlers. Signals are used timeouting name resolves (during DNS lookup) - when built without c-ares support and not on Windows..

If you are accessing HTTPS or FTPS URLs in a multi-threaded manner, you are then of course using OpenSSL/GnuTLS multi-threaded and those libs have their own requirements on this issue. Basically, you need to provide one or two functions to allow it to function properly. For all details, see this:

OpenSSL

http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION

GnuTLS

http://www.gnu.org/software/gnutls/manual/html_node/Multi_002dthreaded-applications.html

When using multiple threads you should set the CURLOPT_NOSIGNAL option to TRUE for all handles. Everything will or might work fine except that timeouts are not honored during the DNS lookup - which you can work around by building libcurl with c-ares support. c-ares is a library that provides asynchronous name resolves. Unfortunately, c-ares does not yet fully support IPv6. On some platforms, libcurl simply will not function properly multi-threaded unless this option is set.

Also, note that CURLOPT_DNS_USE_GLOBAL_CACHE is not thread-safe.