[std.concurrency] Critical bug

osa osa at aso.osa
Thu Sep 30 10:05:32 PDT 2010


I've struggled with occasional hangs in my code using std.concurrency 
for a while. Initially I thought it was my fault but it seems that 
std.concurrency has a critical bug which makes it completely unusable, 
at least for me. The problem is that in some situations message sent by 
send() is never delivered to receive(). This happens when the thread on 
receive() side has other linked threads (started by spawnLinked) and 
those threads terminate causing LinkTerminated exception. And this 
exception screws receiving thread queue completely. Here is the smallest 
example I could come up with: let's suppose we have two threads, "main" 
and "service", exchanging messages in a simple loop.
The "main" thread sends message A to the "service" and waits for message 
B. The "service" thread waits for message A and sends message B to the 
main thread:

main thread:
   for( ;; ) {
     send( service, A() );
     receive( ( B ){} );
   }

service thread:
   for( ;; ) {
     receive( ( A ){} );
     send( main, B() );
   }

This works like a charm. But if we have another linked thread spawned 
from the service, and that thread terminates causing LinkTerminated 
exception raised by call to receive() in service thread, next receive() 
calls never succeed. Below is the actual test program:
-------
// compile with -version=hang to see the problem
import std.concurrency;
import std.stdio;

struct A { int c; }
struct B {}

void main() {
     auto service = spawn( &service_proc, thisTid );
     int count;
     for( count = 0; count < 200; ++count ) {
         writeln( "main\t: sending A #", count, " to service" );
         send( service, A( count ) );
         writeln( "main\t: waiting for B" );
         receive( ( B ){} );
     }
     writeln( "done: ", count, " iterations" );
}

void service_proc( Tid main_tid ) {
     Tid child;
     for( ;; ) {
         version(hang) if( child == Tid.init ) child = spawnLinked( 
&child_proc );
         try {
             if( child != Tid.init ) send( child, 42 );
             writeln( "service\t: waiting for A" );
             receive( ( A a ) { writeln( "service\t: A #", a.c, " 
received, sending B to main" ); } );
             send( main_tid, B() );
         }
         catch( LinkTerminated e ) {
             assert( e.tid == child );
             writeln( "service\t: link terminated" );
             child = Tid.init;
         }
         catch( OwnerTerminated ) {
             return;
         }
     }
}

void child_proc() {
     for( int i = 0; i < 2; ++i )
         receive( ( int ){} );
}
-------
Without -version=hang, no child thread is started from the service and 
everything works fine, output is like
	main	: sending A #0 to service
	service	: waiting for A
	main	: waiting for B
	service	: A #0 received, sending B to main
	service	: waiting for A
	.......................
	service	: A #199 received, sending B to main
	service	: waiting for A
	done: 200 iterations


If compiled with -version=hang (dmd v2.049, tried both Windows and 
Linux, makes no difference), the service starts a child thread and the 
output is this:
	main	: sending A #0 to service
	main	: waiting for B
	service	: waiting for A
	service	: A #0 received, sending B to main
	service	: waiting for A
	main	: sending A #1 to service
	main	: waiting for B
	service	: A #1 received, sending B to main
	service	: waiting for A
	main	: sending A #2 to service
	main	: waiting for B
	service	: link terminated
	service	: waiting for A
and the program hangs forever.

Sometimes it takes more messages, and sometimes it even works fine, but 
in most cases the last A sent to the service before LinkTerminated 
exception is lost and never received.

I did not file the bug in Bugzilla yet but if anyone confirms that the 
problem is true, I'd file it.


More information about the Digitalmars-d mailing list