mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 06:00:35 +03:00
1248 lines
32 KiB
D
1248 lines
32 KiB
D
/**
|
|
* This is a low-level messaging API upon which more structured or restrictive
|
|
* APIs may be built. The general idea is that every messageable entity is
|
|
* represented by a common handle type (called a Cid in this implementation),
|
|
* which allows messages to be sent to in-process threads, on-host processes,
|
|
* and foreign-host processes using the same interface. This is an important
|
|
* aspect of scalability because it allows the components of a program to be
|
|
* spread across available resources with few to no changes to the actual
|
|
* implementation.
|
|
*
|
|
* Right now, only in-process threads are supported and referenced by a more
|
|
* specialized handle called a Tid. It is effectively a subclass of Cid, with
|
|
* additional features specific to in-process messaging.
|
|
*
|
|
* Copyright: Copyright Sean Kelly 2009 - 2010.
|
|
* License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
|
* Authors: Sean Kelly
|
|
* Source: $(PHOBOSSRC std/_concurrency.d)
|
|
*/
|
|
/* Copyright Sean Kelly 2009 - 2010.
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*/
|
|
module std.concurrency;
|
|
|
|
|
|
public
|
|
{
|
|
import core.atomic;
|
|
import core.sync.barrier;
|
|
import core.sync.condition;
|
|
import core.sync.mutex;
|
|
import core.sync.rwmutex;
|
|
import core.sync.semaphore;
|
|
import std.variant;
|
|
}
|
|
private
|
|
{
|
|
import core.thread;
|
|
import std.algorithm;
|
|
import std.exception;
|
|
import std.range;
|
|
import std.range;
|
|
import std.traits;
|
|
import std.typecons;
|
|
import std.typetuple;
|
|
|
|
template hasLocalAliasing(T...)
|
|
{
|
|
static if( !T.length )
|
|
enum hasLocalAliasing = false;
|
|
else
|
|
enum hasLocalAliasing = (std.traits.hasLocalAliasing!(T[0]) && !is(T[0] == Tid)) ||
|
|
std.concurrency.hasLocalAliasing!(T[1 .. $]);
|
|
}
|
|
|
|
enum MsgType
|
|
{
|
|
standard,
|
|
priority,
|
|
linkDead,
|
|
}
|
|
|
|
struct Message
|
|
{
|
|
MsgType type;
|
|
Variant data;
|
|
|
|
this(T...)( MsgType t, T vals )
|
|
if( T.length < 1 )
|
|
{
|
|
static assert( false, "messages must contain at least one item" );
|
|
}
|
|
|
|
this(T...)( MsgType t, T vals )
|
|
if( T.length == 1 )
|
|
{
|
|
type = t;
|
|
data = vals[0];
|
|
}
|
|
|
|
this(T...)( MsgType t, T vals )
|
|
if( T.length > 1 )
|
|
{
|
|
type = t;
|
|
data = Tuple!(T)( vals );
|
|
}
|
|
|
|
auto convertsTo(T...)()
|
|
{
|
|
static if( T.length == 1 )
|
|
return is( T[0] == Variant ) ||
|
|
data.convertsTo!(T);
|
|
else
|
|
return data.convertsTo!(Tuple!(T));
|
|
}
|
|
|
|
auto get(T...)()
|
|
{
|
|
static if( T.length == 1 )
|
|
{
|
|
static if( is( T[0] == Variant ) )
|
|
return data;
|
|
else
|
|
return data.get!(T);
|
|
}
|
|
else
|
|
{
|
|
return data.get!(Tuple!(T));
|
|
}
|
|
}
|
|
|
|
auto map(Op)( Op op )
|
|
{
|
|
alias ParameterTypeTuple!(Op) Args;
|
|
|
|
static if( Args.length == 1 )
|
|
{
|
|
static if( is( Args[0] == Variant ) )
|
|
return op( data );
|
|
else
|
|
return op( data.get!(Args) );
|
|
}
|
|
else
|
|
{
|
|
return op( data.get!(Tuple!(Args)).expand );
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkops(T...)( T ops )
|
|
{
|
|
foreach( i, t1; T )
|
|
{
|
|
static assert( is( t1 == function ) || is( t1 == delegate ) );
|
|
alias ParameterTypeTuple!(t1) a1;
|
|
alias ReturnType!(t1) r1;
|
|
|
|
static if( i < T.length - 1 && is( r1 == void ) )
|
|
{
|
|
static assert( a1.length != 1 || !is( a1[0] == Variant ),
|
|
"function with arguments " ~ a1.stringof ~
|
|
" occludes successive function" );
|
|
|
|
foreach( t2; T[i+1 .. $] )
|
|
{
|
|
static assert( is( t2 == function ) || is( t2 == delegate ) );
|
|
alias ParameterTypeTuple!(t2) a2;
|
|
|
|
static assert( !is( a1 == a2 ),
|
|
"function with arguments " ~ a1.stringof ~
|
|
" occludes successive function" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MessageBox mbox;
|
|
bool[Tid] links;
|
|
Tid owner;
|
|
}
|
|
|
|
|
|
static this()
|
|
{
|
|
// NOTE: thisTid will construct a new MessageBox if one doesn't exist,
|
|
// which should only be true of the main thread and threads created
|
|
// via core.thread instead of spawn.
|
|
}
|
|
|
|
|
|
static ~this()
|
|
{
|
|
if( mbox !is null )
|
|
{
|
|
mbox.close();
|
|
auto me = thisTid;
|
|
foreach( tid; links.keys )
|
|
_send( MsgType.linkDead, tid, me );
|
|
if( owner != Tid.init )
|
|
_send( MsgType.linkDead, owner, me );
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Exceptions
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class MessageMismatch : Exception
|
|
{
|
|
this( string msg = "Unexpected message type" )
|
|
{
|
|
super( msg );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OwnerTerminated : Exception
|
|
{
|
|
this( Tid t, string msg = "Owner terminated" )
|
|
{
|
|
super( msg );
|
|
tid = t;
|
|
}
|
|
|
|
Tid tid;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class LinkTerminated : Exception
|
|
{
|
|
this( Tid t, string msg = "Link terminated" )
|
|
{
|
|
super( msg );
|
|
tid = t;
|
|
}
|
|
|
|
Tid tid;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class PriorityMessageException : Exception
|
|
{
|
|
this( Variant vals )
|
|
{
|
|
super( "Priority message" );
|
|
message = vals;
|
|
}
|
|
|
|
Variant message;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class MailboxFull : Exception
|
|
{
|
|
this( Tid t, string msg = "Mailbox full" )
|
|
{
|
|
super( msg );
|
|
tid = t;
|
|
}
|
|
|
|
Tid tid;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Thread ID
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* An opaque type used to represent a logical local process.
|
|
*/
|
|
struct Tid
|
|
{
|
|
void send(T...)( T vals )
|
|
{
|
|
static assert( !hasLocalAliasing!(T),
|
|
"Aliases to mutable thread-local data not allowed." );
|
|
_send( this, vals );
|
|
}
|
|
|
|
|
|
private:
|
|
this( MessageBox m )
|
|
{
|
|
mbox = m;
|
|
}
|
|
|
|
|
|
MessageBox mbox;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the caller's Tid.
|
|
*/
|
|
@property Tid thisTid()
|
|
{
|
|
if( mbox )
|
|
return Tid( mbox );
|
|
mbox = new MessageBox;
|
|
return Tid( mbox );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Thread Creation
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Executes the supplied function in a new context represented by Tid. The
|
|
* calling context is designated as the owner of the new context. When the
|
|
* owner context terminated an OwnerTerminated message will be sent to the
|
|
* new context, causing an OwnerTerminated exception to be thrown on
|
|
* receive().
|
|
*
|
|
* Params:
|
|
* fn = The function to execute.
|
|
* args = Arguments to the function.
|
|
*
|
|
* Returns:
|
|
* A Tid representing the new context.
|
|
*/
|
|
Tid spawn(T...)( void function(T) fn, T args )
|
|
{
|
|
static assert( !hasLocalAliasing!(T),
|
|
"Aliases to mutable thread-local data not allowed." );
|
|
return _spawn( false, fn, args );
|
|
}
|
|
|
|
|
|
/**
|
|
* Executes the supplied function in a new context represented by Tid. This
|
|
* new context is linked to the calling context so that if either it or the
|
|
* calling context terminates a LinkTerminated message will be sent to the
|
|
* other, causing a LinkTerminated exception to be thrown on receive(). The
|
|
* owner relationship from spawn() is preserved as well, so if the link
|
|
* between threads is broken, owner termination will still result in an
|
|
* OwnerTerminated exception to be thrown on receive().
|
|
*
|
|
* Params:
|
|
* fn = The function to execute.
|
|
* args = Arguments to the function.
|
|
*
|
|
* Returns:
|
|
* A Tid representing the new context.
|
|
*/
|
|
Tid spawnLinked(T...)( void function(T) fn, T args )
|
|
{
|
|
static assert( !hasLocalAliasing!(T),
|
|
"Aliases to mutable thread-local data not allowed." );
|
|
return _spawn( true, fn, args );
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
private Tid _spawn(T...)( bool linked, void function(T) fn, T args )
|
|
{
|
|
// TODO: MessageList and &exec should be shared.
|
|
auto spawnTid = Tid( new MessageBox );
|
|
auto ownerTid = thisTid;
|
|
|
|
void exec()
|
|
{
|
|
mbox = spawnTid.mbox;
|
|
owner = ownerTid;
|
|
fn( args );
|
|
}
|
|
|
|
// TODO: MessageList and &exec should be shared.
|
|
auto t = new Thread( &exec ); t.start();
|
|
links[spawnTid] = linked;
|
|
return spawnTid;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Sending and Receiving Messages
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Sends the supplied value to the context represented by tid.
|
|
*/
|
|
void send(T...)( Tid tid, T vals )
|
|
{
|
|
static assert( !hasLocalAliasing!(T),
|
|
"Aliases to mutable thread-local data not allowed." );
|
|
_send( tid, vals );
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void prioritySend(T...)( Tid tid, T vals )
|
|
{
|
|
static assert( !hasLocalAliasing!(T),
|
|
"Aliases to mutable thread-local data not allowed." );
|
|
_send( MsgType.priority, tid, vals );
|
|
}
|
|
|
|
|
|
/*
|
|
* ditto
|
|
*/
|
|
private void _send(T...)( Tid tid, T vals )
|
|
{
|
|
_send( MsgType.standard, tid, vals );
|
|
}
|
|
|
|
|
|
/*
|
|
* Implementation of send. This allows parameter checking to be different for
|
|
* both Tid.send() and .send().
|
|
*/
|
|
private void _send(T...)( MsgType type, Tid tid, T vals )
|
|
{
|
|
tid.mbox.put( Message( type, vals ) );
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void receive(T...)( T ops )
|
|
{
|
|
checkops( ops );
|
|
mbox.get( ops );
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
assert( __traits( compiles,
|
|
{
|
|
receive( (Variant x) {} );
|
|
receive( (int x) {}, (Variant x) {} );
|
|
} ) );
|
|
|
|
assert( !__traits( compiles,
|
|
{
|
|
receive( (Variant x) {}, (int x) {} );
|
|
} ) );
|
|
|
|
assert( !__traits( compiles,
|
|
{
|
|
receive( (int x) {}, (int x) {} );
|
|
} ) );
|
|
}
|
|
|
|
|
|
private template receiveOnlyRet(T...)
|
|
{
|
|
static if( T.length == 1 )
|
|
alias T[0] receiveOnlyRet;
|
|
else
|
|
alias Tuple!(T) receiveOnlyRet;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
receiveOnlyRet!(T) receiveOnly(T...)()
|
|
{
|
|
Tuple!(T) ret;
|
|
|
|
mbox.get( ( T val )
|
|
{
|
|
static if( T.length )
|
|
ret.field = val;
|
|
},
|
|
( LinkTerminated e )
|
|
{
|
|
throw e;
|
|
},
|
|
( OwnerTerminated e )
|
|
{
|
|
throw e;
|
|
},
|
|
( Variant val )
|
|
{
|
|
throw new MessageMismatch;
|
|
} );
|
|
static if( T.length == 1 )
|
|
return ret[0];
|
|
else
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
bool receiveTimeout(T...)( long ms, T ops )
|
|
{
|
|
checkops( ops );
|
|
static enum long TICKS_PER_MILLI = 10_000;
|
|
return mbox.get( ms * TICKS_PER_MILLI, ops );
|
|
}
|
|
|
|
/++ ditto +/
|
|
bool receiveTimeout(T...)( Duration duration, T ops )
|
|
{
|
|
return receiveTimeout(duration.total!"msecs"(), ops);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
assert( __traits( compiles,
|
|
{
|
|
receiveTimeout( 0, (Variant x) {} );
|
|
receiveTimeout( 0, (int x) {}, (Variant x) {} );
|
|
} ) );
|
|
|
|
assert( !__traits( compiles,
|
|
{
|
|
receiveTimeout( 0, (Variant x) {}, (int x) {} );
|
|
} ) );
|
|
|
|
assert( !__traits( compiles,
|
|
{
|
|
receiveTimeout( 0, (int x) {}, (int x) {} );
|
|
} ) );
|
|
|
|
assert( __traits( compiles,
|
|
{
|
|
receiveTimeout( dur!"msecs"(10), (int x) {}, (Variant x) {} );
|
|
} ) );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// MessageBox Limits
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* These behaviors may be specified when a mailbox is full.
|
|
*/
|
|
enum OnCrowding
|
|
{
|
|
block, /// Wait until room is available.
|
|
throwException, /// Throw a MailboxFull exception.
|
|
ignore /// Abort the send and return.
|
|
}
|
|
|
|
|
|
private
|
|
{
|
|
bool onCrowdingBlock( Tid tid )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
bool onCrowdingThrow( Tid tid )
|
|
{
|
|
throw new MailboxFull( tid );
|
|
}
|
|
|
|
|
|
bool onCrowdingIgnore( Tid tid )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets a limit on the maximum number of user messages allowed in the mailbox.
|
|
* If this limit is reached, the caller attempting to add a new message will
|
|
* execute the behavior specified by doThis. If messages is zero, the mailbox
|
|
* is unbounded.
|
|
*
|
|
* Params:
|
|
* tid = The Tid of the thread for which this limit should be set.
|
|
* messages = The maximum number of messages or zero if no limit.
|
|
* doThis = The behavior executed when a message is sent to a full
|
|
* mailbox.
|
|
*/
|
|
void setMaxMailboxSize( Tid tid, size_t messages, OnCrowding doThis )
|
|
{
|
|
final switch( doThis )
|
|
{
|
|
case OnCrowding.block:
|
|
return tid.mbox.setMaxMsgs( messages, &onCrowdingBlock );
|
|
case OnCrowding.throwException:
|
|
return tid.mbox.setMaxMsgs( messages, &onCrowdingThrow );
|
|
case OnCrowding.ignore:
|
|
return tid.mbox.setMaxMsgs( messages, &onCrowdingIgnore );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets a limit on the maximum number of user messages allowed in the mailbox.
|
|
* If this limit is reached, the caller attempting to add a new message will
|
|
* execute onCrowdingDoThis. If messages is zero, the mailbox is unbounded.
|
|
*
|
|
* Params:
|
|
* tid = The Tid of the thread for which this limit should be set.
|
|
* messages = The maximum number of messages or zero if no limit.
|
|
* onCrowdingDoThis = The routine called when a message is sent to a full
|
|
* mailbox.
|
|
*/
|
|
void setMaxMailboxSize( Tid tid, size_t messages, bool function(Tid) onCrowdingDoThis )
|
|
{
|
|
tid.mbox.setMaxMsgs( messages, onCrowdingDoThis );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// MessageBox Implementation
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
private
|
|
{
|
|
/*
|
|
* A MessageBox is a message queue for one thread. Other threads may send
|
|
* messages to this owner by calling put(), and the owner receives them by
|
|
* calling get(). The put() call is therefore effectively shared and the
|
|
* get() call is effectively local. setMaxMsgs may be used by any thread
|
|
* to limit the size of the message queue.
|
|
*/
|
|
class MessageBox
|
|
{
|
|
this()
|
|
{
|
|
m_lock = new Mutex;
|
|
m_putMsg = new Condition( m_lock );
|
|
m_notFull = new Condition( m_lock );
|
|
m_closed = false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Sets a limit on the maximum number of user messages allowed in the
|
|
* mailbox. If this limit is reached, the caller attempting to add
|
|
* a new message will execute call. If num is zero, there is no limit
|
|
* on the message queue.
|
|
*
|
|
* Params:
|
|
* num = The maximum size of the queue or zero if the queue is
|
|
* unbounded.
|
|
* call = The routine to call when the queue is full.
|
|
*/
|
|
final void setMaxMsgs( size_t num, bool function(Tid) call )
|
|
{
|
|
synchronized( m_lock )
|
|
{
|
|
m_maxMsgs = num;
|
|
m_onMaxMsgs = call;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* If maxMsgs is not set, the message is added to the queue and the
|
|
* owner is notified. If the queue is full, the message will still be
|
|
* accepted if it is a control message, otherwise onCrowdingDoThis is
|
|
* called. If the routine returns true, this call will block until
|
|
* the owner has made space available in the queue. If it returns
|
|
* false, this call will abort.
|
|
*
|
|
* Params:
|
|
* msg = The message to put in the queue.
|
|
*
|
|
* Throws:
|
|
* An exception if the queue is full and onCrowdingDoThis throws.
|
|
*/
|
|
final void put( ref Message msg )
|
|
{
|
|
synchronized( m_lock )
|
|
{
|
|
// TODO: Generate an error here if m_closed is true, or maybe
|
|
// put a message in the caller's queue?
|
|
if( !m_closed )
|
|
{
|
|
while( true )
|
|
{
|
|
if( isPriorityMsg( msg ) )
|
|
{
|
|
m_sharedPty.put( msg );
|
|
m_putMsg.notify();
|
|
return;
|
|
}
|
|
if( !mboxFull() || isControlMsg( msg ) )
|
|
{
|
|
m_sharedBox.put( msg );
|
|
m_putMsg.notify();
|
|
return;
|
|
}
|
|
if( m_onMaxMsgs !is null && !m_onMaxMsgs( thisTid ) )
|
|
{
|
|
return;
|
|
}
|
|
m_putQueue++;
|
|
m_notFull.wait();
|
|
m_putQueue--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Matches ops against each message in turn until a match is found.
|
|
*
|
|
* Params:
|
|
* ops = The operations to match. Each may return a bool to indicate
|
|
* whether a message with a matching type is truly a match.
|
|
*
|
|
* Returns:
|
|
* true if a message was retrieved and false if not (such as if a
|
|
* timeout occurred).
|
|
*
|
|
* Throws:
|
|
* LinkTerminated if a linked thread terminated, or OwnerTerminated
|
|
* if the owner thread terminates and no existing messages match the
|
|
* supplied ops.
|
|
*/
|
|
final bool get(T...)( T vals )
|
|
{
|
|
static assert( T.length );
|
|
|
|
static if( isImplicitlyConvertible!(T[0], long) )
|
|
{
|
|
alias TypeTuple!(T[1 .. $]) Ops;
|
|
alias vals[1 .. $] ops;
|
|
assert( vals[0] >= 0 );
|
|
enum timedWait = true;
|
|
long period = vals[0];
|
|
}
|
|
else
|
|
{
|
|
alias TypeTuple!(T) Ops;
|
|
alias vals[0 .. $] ops;
|
|
enum timedWait = false;
|
|
}
|
|
|
|
bool onStandardMsg( ref Message msg )
|
|
{
|
|
foreach( i, t; Ops )
|
|
{
|
|
alias ParameterTypeTuple!(t) Args;
|
|
auto op = ops[i];
|
|
|
|
if( msg.convertsTo!(Args) )
|
|
{
|
|
static if( is( ReturnType!(t) == bool ) )
|
|
{
|
|
return msg.map( op );
|
|
}
|
|
else
|
|
{
|
|
msg.map( op );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool onLinkDeadMsg( ref Message msg )
|
|
{
|
|
assert( msg.convertsTo!(Tid) );
|
|
auto tid = msg.get!(Tid);
|
|
|
|
if( bool* depends = (tid in links) )
|
|
{
|
|
links.remove( tid );
|
|
// Give the owner relationship precedence.
|
|
if( *depends && tid != owner )
|
|
{
|
|
auto e = new LinkTerminated( tid );
|
|
if( onStandardMsg( Message( MsgType.standard, e ) ) )
|
|
return true;
|
|
throw e;
|
|
}
|
|
}
|
|
if( tid == owner )
|
|
{
|
|
owner = Tid.init;
|
|
auto e = new OwnerTerminated( tid );
|
|
if( onStandardMsg( Message( MsgType.standard, e ) ) )
|
|
return true;
|
|
throw e;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool onControlMsg( ref Message msg )
|
|
{
|
|
switch( msg.type )
|
|
{
|
|
case MsgType.linkDead:
|
|
return onLinkDeadMsg( msg );
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool scan( ref ListT list )
|
|
{
|
|
for( auto range = list[]; !range.empty; )
|
|
{
|
|
// Only the message handler will throw, so if this occurs
|
|
// we can be certain that the message was handled.
|
|
scope(failure) list.removeAt( range );
|
|
|
|
if( isControlMsg( range.front ) )
|
|
{
|
|
if( onControlMsg( range.front ) )
|
|
{
|
|
// Although the linkDead message is a control message,
|
|
// it can be handled by the user. Since the linkDead
|
|
// message throws if not handled, if we get here then
|
|
// it has been handled and we can return from receive.
|
|
// This is a weird special case that will have to be
|
|
// handled in a more general way if more are added.
|
|
if( !isLinkDeadMsg( range.front ) )
|
|
{
|
|
list.removeAt( range );
|
|
continue;
|
|
}
|
|
list.removeAt( range );
|
|
return true;
|
|
}
|
|
range.popFront();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( onStandardMsg( range.front ) )
|
|
{
|
|
list.removeAt( range );
|
|
return true;
|
|
}
|
|
range.popFront();
|
|
continue;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool pty( ref ListT list )
|
|
{
|
|
if( !list.empty )
|
|
{
|
|
auto range = list[];
|
|
|
|
if( onStandardMsg( range.front ) )
|
|
{
|
|
list.removeAt( range );
|
|
return true;
|
|
}
|
|
if( range.front.convertsTo!(Throwable) )
|
|
throw range.front.get!(Throwable);
|
|
else if( range.front.convertsTo!(shared(Throwable)) )
|
|
throw range.front.get!(shared(Throwable));
|
|
else throw new PriorityMessageException( range.front.data );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
while( true )
|
|
{
|
|
ListT arrived;
|
|
|
|
if( pty( m_localPty ) ||
|
|
scan( m_localBox ) )
|
|
{
|
|
return true;
|
|
}
|
|
synchronized( m_lock )
|
|
{
|
|
updateMsgCount();
|
|
while( m_sharedPty.empty && m_sharedBox.empty )
|
|
{
|
|
// NOTE: We're notifying all waiters here instead of just
|
|
// a few because the onCrowding behavior may have
|
|
// changed and we don't want to block sender threads
|
|
// unnecessarily if the new behavior is not to block.
|
|
// This will admittedly result in spurious wakeups
|
|
// in other situations, but what can you do?
|
|
if( m_putQueue && !mboxFull() )
|
|
m_notFull.notifyAll();
|
|
static if( timedWait )
|
|
{
|
|
if( !m_putMsg.wait( period ) )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_putMsg.wait();
|
|
}
|
|
}
|
|
m_localPty.put( m_sharedPty );
|
|
arrived.put( m_sharedBox );
|
|
}
|
|
if( m_localPty.empty )
|
|
{
|
|
scope(exit) m_localBox.put( arrived );
|
|
if( scan( arrived ) )
|
|
return true;
|
|
else continue;
|
|
}
|
|
m_localBox.put( arrived );
|
|
pty( m_localPty );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Called on thread termination. This routine processes any remaining
|
|
* control messages, clears out message queues, and sets a flag to
|
|
* reject any future messages.
|
|
*/
|
|
final void close()
|
|
{
|
|
void onLinkDeadMsg( ref Message msg )
|
|
{
|
|
assert( msg.convertsTo!(Tid) );
|
|
auto tid = msg.get!(Tid);
|
|
|
|
links.remove( tid );
|
|
if( tid == owner )
|
|
owner = Tid.init;
|
|
}
|
|
|
|
void sweep( ref ListT list )
|
|
{
|
|
for( auto range = list[]; !range.empty; range.popFront() )
|
|
{
|
|
if( range.front.type == MsgType.linkDead )
|
|
onLinkDeadMsg( range.front );
|
|
}
|
|
}
|
|
|
|
ListT arrived;
|
|
|
|
sweep( m_localBox );
|
|
synchronized( m_lock )
|
|
{
|
|
arrived.put( m_sharedBox );
|
|
m_closed = true;
|
|
}
|
|
m_localBox.clear();
|
|
sweep( arrived );
|
|
}
|
|
|
|
|
|
private:
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Routines involving shared data, m_lock must be held.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
bool mboxFull()
|
|
{
|
|
return m_maxMsgs &&
|
|
m_maxMsgs <= m_localMsgs + m_sharedBox.length;
|
|
}
|
|
|
|
|
|
void updateMsgCount()
|
|
{
|
|
m_localMsgs = m_localBox.length;
|
|
}
|
|
|
|
|
|
private:
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Routines involving local data only, no lock needed.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
pure final bool isControlMsg( ref Message msg )
|
|
{
|
|
return msg.type != MsgType.standard &&
|
|
msg.type != MsgType.priority;
|
|
}
|
|
|
|
|
|
pure final bool isPriorityMsg( ref Message msg )
|
|
{
|
|
return msg.type == MsgType.priority;
|
|
}
|
|
|
|
|
|
pure final bool isLinkDeadMsg( ref Message msg )
|
|
{
|
|
return msg.type == MsgType.linkDead;
|
|
}
|
|
|
|
|
|
private:
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Type declarations.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
alias bool function(Tid) OnMaxFn;
|
|
alias List!(Message) ListT;
|
|
|
|
private:
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Local data, no lock needed.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ListT m_localBox;
|
|
ListT m_localPty;
|
|
|
|
|
|
private:
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Shared data, m_lock must be held on access.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Mutex m_lock;
|
|
Condition m_putMsg;
|
|
Condition m_notFull;
|
|
size_t m_putQueue;
|
|
ListT m_sharedBox;
|
|
ListT m_sharedPty;
|
|
OnMaxFn m_onMaxMsgs;
|
|
size_t m_localMsgs;
|
|
size_t m_maxMsgs;
|
|
bool m_closed;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
struct List(T)
|
|
{
|
|
struct Range
|
|
{
|
|
@property bool empty() const
|
|
{
|
|
return !m_prev.next;
|
|
}
|
|
|
|
@property ref T front()
|
|
{
|
|
enforce( m_prev.next );
|
|
return m_prev.next.val;
|
|
}
|
|
|
|
@property void front( T val )
|
|
{
|
|
enforce( m_prev.next );
|
|
m_prev.next.val = val;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
enforce( m_prev.next );
|
|
m_prev = m_prev.next;
|
|
}
|
|
|
|
//T moveFront()
|
|
//{
|
|
// enforce( m_prev.next );
|
|
// return move( m_prev.next.val );
|
|
//}
|
|
|
|
private this( Node* p )
|
|
{
|
|
m_prev = p;
|
|
}
|
|
|
|
private Node* m_prev;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void put( T val )
|
|
{
|
|
put( new Node( val ) );
|
|
m_count++;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void put( ref List!(T) rhs )
|
|
{
|
|
if( !rhs.empty )
|
|
{
|
|
put( rhs.m_first );
|
|
while( m_last.next !is null )
|
|
{
|
|
m_last = m_last.next;
|
|
m_count++;
|
|
}
|
|
rhs.m_first = null;
|
|
rhs.m_last = null;
|
|
rhs.m_count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
Range opSlice()
|
|
{
|
|
return Range( cast(Node*) &m_first );
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void removeAt( Range r )
|
|
{
|
|
Node* n = r.m_prev;
|
|
enforce( n && n.next );
|
|
|
|
if( m_last is m_first )
|
|
m_last = null;
|
|
else if( m_last is n.next )
|
|
m_last = n;
|
|
Node* todelete = n.next;
|
|
n.next = n.next.next;
|
|
//delete todelete;
|
|
m_count--;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
@property size_t length()
|
|
{
|
|
return m_count;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void clear()
|
|
{
|
|
m_first = m_last = null;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool empty()
|
|
{
|
|
return m_first is null;
|
|
}
|
|
|
|
|
|
private:
|
|
struct Node
|
|
{
|
|
Node* next;
|
|
T val;
|
|
|
|
this( T v )
|
|
{
|
|
val = v;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void put( Node* n )
|
|
{
|
|
if( !empty )
|
|
{
|
|
m_last.next = n;
|
|
m_last = n;
|
|
return;
|
|
}
|
|
m_first = n;
|
|
m_last = n;
|
|
}
|
|
|
|
|
|
Node* m_first;
|
|
Node* m_last;
|
|
size_t m_count;
|
|
}
|
|
}
|
|
|
|
|
|
version( unittest )
|
|
{
|
|
import std.stdio;
|
|
|
|
void testfn( Tid tid )
|
|
{
|
|
receive( (float val) { assert(0); },
|
|
(int val, int val2)
|
|
{
|
|
assert( val == 42 && val2 == 86 );
|
|
} );
|
|
receive( (Tuple!(int, int) val)
|
|
{
|
|
assert( val[0] == 42 &&
|
|
val[1] == 86 );
|
|
} );
|
|
receive( (Variant val) {} );
|
|
receive( (string val)
|
|
{
|
|
if( "the quick brown fox" != val )
|
|
return false;
|
|
return true;
|
|
},
|
|
(string val)
|
|
{
|
|
assert( false );
|
|
} );
|
|
prioritySend( tid, "done" );
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
auto tid = spawn( &testfn, thisTid );
|
|
|
|
send( tid, 42, 86 );
|
|
send( tid, tuple(42, 86) );
|
|
send( tid, "hello", "there" );
|
|
send( tid, "the quick brown fox" );
|
|
receive( (string val) { assert(val == "done"); } );
|
|
}
|
|
}
|