vibe-core/source/vibe/core/concurrency.d
2020-03-14 19:47:29 +01:00

1290 lines
38 KiB
D

/**
Functions and structures for dealing with threads and concurrent access.
This module is modeled after std.concurrency, but provides a fiber-aware alternative
to it. All blocking operations will yield the calling fiber instead of blocking it.
Copyright: © 2013-2014 Sönke Ludwig
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
Authors: Sönke Ludwig
*/
module vibe.core.concurrency;
public import std.concurrency;
import core.time;
import std.traits;
import std.typecons;
import std.typetuple;
import std.string;
import vibe.core.task;
private extern (C) pure nothrow void _d_monitorenter(Object h);
private extern (C) pure nothrow void _d_monitorexit(Object h);
/**
Locks the given shared object and returns a ScopedLock for accessing any unshared members.
Using this function will ensure that there are no data races. For this reason, the class
type T is required to contain no unshared or unisolated aliasing.
See_Also: core.concurrency.isWeaklyIsolated
*/
ScopedLock!T lock(T : const(Object))(shared(T) object)
pure nothrow @safe {
return ScopedLock!T(object);
}
/// ditto
void lock(T : const(Object))(shared(T) object, scope void delegate(scope T) nothrow accessor)
nothrow {
auto l = lock(object);
accessor(l.unsafeGet());
}
/// ditto
void lock(T : const(Object))(shared(T) object, scope void delegate(scope T) accessor)
{
auto l = lock(object);
accessor(l.unsafeGet());
}
///
unittest {
import vibe.core.concurrency;
static class Item {
private double m_value;
this(double value) pure { m_value = value; }
@property double value() const pure { return m_value; }
}
static class Manager {
private {
string m_name;
Isolated!(Item) m_ownedItem;
Isolated!(shared(Item)[]) m_items;
}
pure this(string name)
{
m_name = name;
auto itm = makeIsolated!Item(3.5);
m_ownedItem = itm.move;
}
void addItem(shared(Item) item) pure { m_items ~= item; }
double getTotalValue()
const pure {
double sum = 0;
// lock() is required to access shared objects
foreach (itm; m_items.unsafeGet) {
auto l = itm.lock();
sum += l.value;
}
// owned objects can be accessed without locking
sum += m_ownedItem.value;
return sum;
}
}
void test()
{
import std.stdio;
auto man = cast(shared)new Manager("My manager");
{
auto l = man.lock();
l.addItem(new shared(Item)(1.5));
l.addItem(new shared(Item)(0.5));
}
writefln("Total value: %s", man.lock().getTotalValue());
}
}
/**
Proxy structure that keeps the monitor of the given object locked until it
goes out of scope.
Any unshared members of the object are safely accessible during this time. The usual
way to use it is by calling lock.
See_Also: lock
*/
struct ScopedLock(T)
{
static assert(is(T == class), "ScopedLock is only usable with classes.");
// static assert(isWeaklyIsolated!(FieldTypeTuple!T), T.stringof~" contains non-immutable, non-shared references. Accessing it in a multi-threaded environment is not safe.");
private Rebindable!T m_ref;
@disable this(this);
this(shared(T) obj)
pure nothrow @trusted
{
assert(obj !is null, "Attempting to lock null object.");
m_ref = cast(T)obj;
_d_monitorenter(getObject());
assert(getObject().__monitor !is null);
}
~this()
pure nothrow @trusted
{
assert(m_ref !is null);
assert(getObject().__monitor !is null);
_d_monitorexit(getObject());
}
/**
Returns an unshared reference to the locked object.
Note that using this function breaks type safety. Be sure to not escape the reference beyond
the life time of the lock.
*/
@property inout(T) unsafeGet() inout nothrow { return m_ref; }
alias unsafeGet this;
//pragma(msg, "In ScopedLock!("~T.stringof~")");
//pragma(msg, isolatedRefMethods!T());
// mixin(isolatedAggregateMethodsString!T());
private Object getObject()
pure nothrow {
static if( is(Rebindable!T == struct) ) return cast(Unqual!T)m_ref.get();
else return cast(Unqual!T)m_ref;
}
}
/**
Creates a new isolated object.
Isolated objects contain no mutable aliasing outside of their own reference tree. They can thus
be safely converted to immutable and they can be safely passed between threads.
The function returns an instance of Isolated that will allow proxied access to the members of
the object, as well as providing means to convert the object to immutable or to an ordinary
mutable object.
*/
pure Isolated!T makeIsolated(T, ARGS...)(ARGS args)
{
static if (is(T == class)) return Isolated!T(new T(args));
else static if (is(T == struct)) return T(args);
else static if (isPointer!T && is(PointerTarget!T == struct)) {
alias TB = PointerTarget!T;
return Isolated!T(new TB(args));
} else static assert(false, "makeIsolated works only for class and (pointer to) struct types.");
}
///
unittest {
import vibe.core.concurrency;
import vibe.core.core;
static class Item {
double value;
string name;
}
static void modifyItem(Isolated!Item itm)
{
itm.value = 1.3;
// TODO: send back to initiating thread
}
void test()
{
immutable(Item)[] items;
// create immutable item procedurally
auto itm = makeIsolated!Item();
itm.value = 2.4;
itm.name = "Test";
items ~= itm.freeze();
// send isolated item to other thread
auto itm2 = makeIsolated!Item();
runWorkerTask(&modifyItem, itm2.move());
// ...
}
}
unittest {
static class C { this(int x) pure {} }
static struct S { this(int x) pure {} }
alias CI = typeof(makeIsolated!C(0));
alias SI = typeof(makeIsolated!S(0));
alias SPI = typeof(makeIsolated!(S*)(0));
static assert(isStronglyIsolated!CI);
static assert(is(CI == IsolatedRef!C));
static assert(isStronglyIsolated!SI);
static assert(is(SI == S));
static assert(isStronglyIsolated!SPI);
static assert(is(SPI == IsolatedRef!S));
}
/**
Creates a new isolated array.
*/
pure Isolated!(T[]) makeIsolatedArray(T)(size_t size)
{
Isolated!(T[]) ret;
ret.length = size;
return ret.move();
}
///
unittest {
import vibe.core.concurrency;
import vibe.core.core;
static void compute(Tid tid, Isolated!(double[]) array, size_t start_index)
{
foreach( i; 0 .. array.length )
array[i] = (start_index + i) * 0.5;
//send(tid, array.move()); // Isolated!T isn't recognized by std.concurrency
}
void test()
{
import std.stdio;
// compute contents of an array using multiple threads
auto arr = makeIsolatedArray!double(256);
// partition the array (no copying takes place)
size_t[] indices = [64, 128, 192, 256];
Isolated!(double[])[] subarrays = arr.splice(indices);
// start processing in threads
Tid[] tids;
foreach (i, idx; indices)
tids ~= runWorkerTaskH(&compute, thisTid, subarrays[i].move(), idx).tid;
// collect results
auto resultarrays = new Isolated!(double[])[tids.length];
//foreach( i, tid; tids )
// resultarrays[i] = receiveOnly!(Isolated!(double[])).move(); // Isolated!T isn't recognized by std.concurrency
// BUG: the arrays must be sorted here, but since there is no way to tell
// from where something was received, this is difficult here.
// merge results (no copying takes place again)
foreach( i; 1 .. resultarrays.length )
resultarrays[0].merge(resultarrays[i]);
// convert the final result to immutable
auto result = resultarrays[0].freeze();
writefln("Result: %s", result);
}
}
/**
Unsafe facility to assume that an existing reference is unique.
*/
Isolated!T assumeIsolated(T)(T object)
{
return Isolated!T(object);
}
/**
Encapsulates the given type in a way that guarantees memory isolation.
See_Also: makeIsolated, makeIsolatedArray
*/
template Isolated(T)
{
static if( isWeaklyIsolated!T ){
alias Isolated = T;
} else static if( is(T == class) ){
alias Isolated = IsolatedRef!T;
} else static if( isPointer!T ){
alias Isolated = IsolatedRef!(PointerTarget!T);
} else static if( isDynamicArray!T ){
alias Isolated = IsolatedArray!(typeof(T.init[0]));
} else static if( isAssociativeArray!T ){
alias Isolated = IsolatedAssociativeArray!(KeyType!T, ValueType!T);
} else static assert(false, T.stringof~": Unsupported type for Isolated!T - must be class, pointer, array or associative array.");
}
// unit tests fails with DMD 2.064 due to some cyclic import regression
unittest
{
static class CE {}
static struct SE {}
static assert(is(Isolated!CE == IsolatedRef!CE));
static assert(is(Isolated!(SE*) == IsolatedRef!SE));
static assert(is(Isolated!(SE[]) == IsolatedArray!SE));
version(EnablePhobosFails){
// AAs don't work because they are impure
static assert(is(Isolated!(SE[string]) == IsolatedAssociativeArray!(string, SE)));
}
}
/// private
private struct IsolatedRef(T)
{
pure:
static assert(isWeaklyIsolated!(FieldTypeTuple!T), T.stringof ~ " contains non-immutable/non-shared references. Isolation cannot be guaranteed.");
enum __isWeakIsolatedType = true;
static if( isStronglyIsolated!(FieldTypeTuple!T) )
enum __isIsolatedType = true;
alias BaseType = T;
static if( is(T == class) ){
alias Tref = T;
alias Tiref = immutable(T);
} else {
alias Tref = T*;
alias Tiref = immutable(T)*;
}
private Tref m_ref;
//mixin isolatedAggregateMethods!T;
//pragma(msg, isolatedAggregateMethodsString!T());
mixin(isolatedAggregateMethodsString!T());
@disable this(this);
private this(Tref obj)
{
m_ref = obj;
}
this(ref IsolatedRef src)
{
m_ref = src.m_ref;
src.m_ref = null;
}
void opAssign(ref IsolatedRef src)
{
m_ref = src.m_ref;
src.m_ref = null;
}
/**
Returns the raw reference.
Note that using this function breaks type safety. Be sure to not escape the reference.
*/
inout(Tref) unsafeGet() inout { return m_ref; }
/**
Move the contained reference to a new IsolatedRef.
Since IsolatedRef is not copyable, using this function may be necessary when
passing a reference to a function or when returning it. The reference in
this instance will be set to null after the call returns.
*/
IsolatedRef move() { auto r = m_ref; m_ref = null; return IsolatedRef(r); }
/// ditto
void move(ref IsolatedRef target) { target.m_ref = m_ref; m_ref = null; }
/**
Convert the isolated reference to a normal mutable reference.
The reference in this instance will be set to null after the call returns.
*/
Tref extract()
{
auto ret = m_ref;
m_ref = null;
return ret;
}
/**
Converts the isolated reference to immutable.
The reference in this instance will be set to null after the call has returned.
Note that this method is only available for strongly isolated references,
which means references that do not contain shared aliasing.
*/
Tiref freeze()()
{
static assert(isStronglyIsolated!(FieldTypeTuple!T), "freeze() can only be called on strongly isolated values, but "~T.stringof~" contains shared references.");
auto ret = m_ref;
m_ref = null;
return cast(immutable)ret;
}
/**
Performs an up- or down-cast of the reference and moves it to a new IsolatedRef instance.
The reference in this instance will be set to null after the call has returned.
*/
U opCast(U)()
if (isInstanceOf!(IsolatedRef, U) && (is(U.BaseType : BaseType) || is(BaseType : U.BaseType)))
{
auto r = U(cast(U.BaseType)m_ref);
m_ref = null;
return r;
}
/**
Determines if the contained reference is non-null.
This method allows Isolated references to be used in boolean expressions without having to
extract the reference.
*/
U opCast(U)() const if(is(U == bool)) { return m_ref !is null; }
}
/// private
private struct IsolatedArray(T)
{
static assert(isWeaklyIsolated!T, T.stringof ~ " contains non-immutable references. Isolation cannot be guaranteed.");
enum __isWeakIsolatedType = true;
static if( isStronglyIsolated!T )
enum __isIsolatedType = true;
alias BaseType = T[];
private T[] m_array;
mixin isolatedArrayMethods!T;
@disable this(this);
/**
Returns the raw reference.
Note that using this function breaks type safety. Be sure to not escape the reference.
*/
inout(T[]) unsafeGet() inout { return m_array; }
IsolatedArray!T move() pure { auto r = m_array; m_array = null; return IsolatedArray(r); }
void move(ref IsolatedArray target) pure { target.m_array = m_array; m_array = null; }
T[] extract()
pure {
auto arr = m_array;
m_array = null;
return arr;
}
immutable(T)[] freeze()() pure
{
static assert(isStronglyIsolated!T, "Freeze can only be called on strongly isolated values, but "~T.stringof~" contains shared references.");
auto arr = m_array;
m_array = null;
return cast(immutable)arr;
}
/**
Splits the array into individual slices at the given incides.
The indices must be in ascending order. Any items that are larger than
the last given index will remain in this IsolatedArray.
*/
IsolatedArray!T[] splice(in size_t[] indices...) pure
in {
//import std.algorithm : isSorted;
assert(indices.length > 0, "At least one splice index must be given.");
//assert(isSorted(indices), "Indices must be in ascending order.");
assert(indices[$-1] <= m_array.length, "Splice index out of bounds.");
}
body {
auto ret = new IsolatedArray!T[indices.length];
size_t lidx = 0;
foreach( i, sidx; indices ){
ret[i].m_array = m_array[lidx .. sidx];
lidx = sidx;
}
m_array = m_array[lidx .. $];
return ret;
}
void merge(ref IsolatedArray!T array) pure
in {
assert(array.m_array.ptr == m_array.ptr+m_array.length || array.m_array.ptr+array.length == m_array.ptr,
"Argument to merge() must be a neighbouring array partition.");
}
body {
if( array.m_array.ptr == m_array.ptr + m_array.length ){
m_array = m_array.ptr[0 .. m_array.length + array.length];
} else {
m_array = array.m_array.ptr[0 .. m_array.length + array.length];
}
array.m_array.length = 0;
}
}
/// private
private struct IsolatedAssociativeArray(K, V)
{
pure:
static assert(isWeaklyIsolated!K, "Key type has aliasing. Memory isolation cannot be guaranteed.");
static assert(isWeaklyIsolated!V, "Value type has aliasing. Memory isolation cannot be guaranteed.");
enum __isWeakIsolatedType = true;
static if( isStronglyIsolated!K && isStronglyIsolated!V )
enum __isIsolatedType = true;
alias BaseType = V[K];
private {
V[K] m_aa;
}
mixin isolatedAssociativeArrayMethods!(K, V);
/**
Returns the raw reference.
Note that using this function breaks type safety. Be sure to not escape the reference.
*/
inout(V[K]) unsafeGet() inout { return m_aa; }
IsolatedAssociativeArray move() { auto r = m_aa; m_aa = null; return IsolatedAssociativeArray(r); }
void move(ref IsolatedAssociativeArray target) { target.m_aa = m_aa; m_aa = null; }
V[K] extract()
{
auto arr = m_aa;
m_aa = null;
return arr;
}
static if( is(typeof(IsolatedAssociativeArray.__isIsolatedType)) ){
immutable(V)[K] freeze()
{
auto arr = m_aa;
m_aa = null;
return cast(immutable(V)[K])(arr);
}
immutable(V[K]) freeze2()
{
auto arr = m_aa;
m_aa = null;
return cast(immutable(V[K]))(arr);
}
}
}
/** Encapsulates a reference in a way that disallows escaping it or any contained references.
*/
template ScopedRef(T)
{
static if( isAggregateType!T ) alias ScopedRef = ScopedRefAggregate!T;
else static if( isAssociativeArray!T ) alias ScopedRef = ScopedRefAssociativeArray!T;
else static if( isArray!T ) alias ScopedRef = ScopedRefArray!T;
else static if( isBasicType!T ) alias ScopedRef = ScopedRefBasic!T;
else static assert(false, "Unsupported type for ScopedRef: "~T.stringof);
}
/// private
private struct ScopedRefBasic(T)
{
private T* m_ref;
@disable this(this);
this(ref T tref) pure { m_ref = &tref; }
//void opAssign(T value) { *m_ref = value; }
ref T unsafeGet() pure { return *m_ref; }
alias unsafeGet this;
}
/// private
private struct ScopedRefAggregate(T)
{
private T* m_ref;
@disable this(this);
this(ref T tref) pure { m_ref = &tref; }
//void opAssign(T value) { *m_ref = value; }
ref T unsafeGet() pure { return *m_ref; }
static if( is(T == shared) ){
auto lock() pure { return .lock(unsafeGet()); }
} else {
mixin(isolatedAggregateMethodsString!T());
//mixin isolatedAggregateMethods!T;
}
}
/// private
private struct ScopedRefArray(T)
{
alias V = typeof(T.init[0]) ;
private T* m_ref;
private @property ref T m_array() pure { return *m_ref; }
private @property ref const(T) m_array() const pure { return *m_ref; }
mixin isolatedArrayMethods!(V, !is(T == const) && !is(T == immutable));
@disable this(this);
this(ref T tref) pure { m_ref = &tref; }
//void opAssign(T value) { *m_ref = value; }
ref T unsafeGet() pure { return *m_ref; }
}
/// private
private struct ScopedRefAssociativeArray(K, V)
{
alias K = KeyType!T;
alias V = ValueType!T;
private T* m_ref;
private @property ref T m_array() pure { return *m_ref; }
private @property ref const(T) m_array() const pure { return *m_ref; }
mixin isolatedAssociativeArrayMethods!(K, V);
@disable this(this);
this(ref T tref) pure { m_ref = &tref; }
//void opAssign(T value) { *m_ref = value; }
ref T unsafeGet() pure { return *m_ref; }
}
/******************************************************************************/
/* COMMON MIXINS FOR NON-REF-ESCAPING WRAPPER STRUCTS */
/******************************************************************************/
/// private
/*private mixin template(T) isolatedAggregateMethods
{
mixin(isolatedAggregateMethodsString!T());
}*/
/// private
private string isolatedAggregateMethodsString(T)()
{
import vibe.internal.traits;
string ret = generateModuleImports!T();
//pragma(msg, "Type '"~T.stringof~"'");
foreach( mname; __traits(allMembers, T) ){
static if (isPublicMember!(T, mname)) {
static if (isRWPlainField!(T, mname)) {
alias mtype = typeof(__traits(getMember, T, mname)) ;
auto mtypename = fullyQualifiedName!mtype;
//pragma(msg, " field " ~ mname ~ " : " ~ mtype.stringof);
ret ~= "@property ScopedRef!(const("~mtypename~")) "~mname~"() const pure { return ScopedRef!(const("~mtypename~"))(m_ref."~mname~"); }\n";
ret ~= "@property ScopedRef!("~mtypename~") "~mname~"() pure { return ScopedRef!("~mtypename~")(m_ref."~mname~"); }\n";
static if( !is(mtype == const) && !is(mtype == immutable) ){
static if( isWeaklyIsolated!mtype ){
ret ~= "@property void "~mname~"("~mtypename~" value) pure { m_ref."~mname~" = value; }\n";
} else {
ret ~= "@property void "~mname~"(AT)(AT value) pure { static assert(isWeaklyIsolated!AT); m_ref."~mname~" = value.unsafeGet(); }\n";
}
}
} else {
foreach( method; __traits(getOverloads, T, mname) ){
alias ftype = FunctionTypeOf!method;
// only pure functions are allowed (or they could escape references to global variables)
// don't allow non-isolated references to be escaped
if( functionAttributes!ftype & FunctionAttribute.pure_ &&
isWeaklyIsolated!(ReturnType!ftype) )
{
static if( __traits(isStaticFunction, method) ){
//pragma(msg, " static method " ~ mname ~ " : " ~ ftype.stringof);
ret ~= "static "~fullyQualifiedName!(ReturnType!ftype)~" "~mname~"(";
foreach( i, P; ParameterTypeTuple!ftype ){
if( i > 0 ) ret ~= ", ";
ret ~= fullyQualifiedName!P ~ " p"~i.stringof;
}
ret ~= "){ return "~fullyQualifiedName!T~"."~mname~"(";
foreach( i, P; ParameterTypeTuple!ftype ){
if( i > 0 ) ret ~= ", ";
ret ~= "p"~i.stringof;
}
ret ~= "); }\n";
} else if (mname != "__ctor") {
//pragma(msg, " normal method " ~ mname ~ " : " ~ ftype.stringof);
if( is(ftype == const) ) ret ~= "const ";
if( is(ftype == shared) ) ret ~= "shared ";
if( is(ftype == immutable) ) ret ~= "immutable ";
if( functionAttributes!ftype & FunctionAttribute.pure_ ) ret ~= "pure ";
if( functionAttributes!ftype & FunctionAttribute.property ) ret ~= "@property ";
ret ~= fullyQualifiedName!(ReturnType!ftype)~" "~mname~"(";
foreach( i, P; ParameterTypeTuple!ftype ){
if( i > 0 ) ret ~= ", ";
ret ~= fullyQualifiedName!P ~ " p"~i.stringof;
}
ret ~= "){ return m_ref."~mname~"(";
foreach( i, P; ParameterTypeTuple!ftype ){
if( i > 0 ) ret ~= ", ";
ret ~= "p"~i.stringof;
}
ret ~= "); }\n";
}
}
}
}
} //else pragma(msg, " non-public field " ~ mname);
}
return ret;
}
/// private
private mixin template isolatedArrayMethods(T, bool mutableRef = true)
{
@property size_t length() const pure { return m_array.length; }
@property bool empty() const pure { return m_array.length == 0; }
static if( mutableRef ){
@property void length(size_t value) pure { m_array.length = value; }
void opOpAssign(string op = "~")(T item) pure
{
static if( isCopyable!T ) m_array ~= item;
else {
m_array.length++;
m_array[$-1] = item;
}
}
void opOpAssign(string op = "~")(IsolatedArray!T array) pure
{
static if( isCopyable!T ) m_array ~= array.m_array;
else {
size_t start = m_array.length;
m_array.length += array.length;
foreach( i, ref itm; array.m_array )
m_array[start+i] = itm;
}
}
}
ScopedRef!(const(T)) opIndex(size_t idx) const pure { return ScopedRef!(const(T))(m_array[idx]); }
ScopedRef!T opIndex(size_t idx) pure { return ScopedRef!T(m_array[idx]); }
static if( !is(T == const) && !is(T == immutable) )
void opIndexAssign(T value, size_t idx) pure { m_array[idx] = value; }
int opApply(int delegate(ref size_t, ref ScopedRef!T) del)
pure {
foreach( idx, ref v; m_array ){
auto noref = ScopedRef!T(v);
if( auto ret = (cast(int delegate(ref size_t, ref ScopedRef!T) pure)del)(idx, noref) )
return ret;
}
return 0;
}
int opApply(int delegate(ref size_t, ref ScopedRef!(const(T))) del)
const pure {
foreach( idx, ref v; m_array ){
auto noref = ScopedRef!(const(T))(v);
if( auto ret = (cast(int delegate(ref size_t, ref ScopedRef!(const(T))) pure)del)(idx, noref) )
return ret;
}
return 0;
}
int opApply(int delegate(ref ScopedRef!T) del)
pure {
foreach( v; m_array ){
auto noref = ScopedRef!T(v);
if( auto ret = (cast(int delegate(ref ScopedRef!T) pure)del)(noref) )
return ret;
}
return 0;
}
int opApply(int delegate(ref ScopedRef!(const(T))) del)
const pure {
foreach( v; m_array ){
auto noref = ScopedRef!(const(T))(v);
if( auto ret = (cast(int delegate(ref ScopedRef!(const(T))) pure)del)(noref) )
return ret;
}
return 0;
}
}
/// private
private mixin template isolatedAssociativeArrayMethods(K, V, bool mutableRef = true)
{
@property size_t length() const pure { return m_aa.length; }
@property bool empty() const pure { return m_aa.length == 0; }
static if( !is(V == const) && !is(V == immutable) )
void opIndexAssign(V value, K key) pure { m_aa[key] = value; }
inout(V) opIndex(K key) inout pure { return m_aa[key]; }
int opApply(int delegate(ref ScopedRef!K, ref ScopedRef!V) del)
pure {
foreach( ref k, ref v; m_aa )
if( auto ret = (cast(int delegate(ref ScopedRef!K, ref ScopedRef!V) pure)del)(k, v) )
return ret;
return 0;
}
int opApply(int delegate(ref ScopedRef!V) del)
pure {
foreach( ref v; m_aa )
if( auto ret = (cast(int delegate(ref ScopedRef!V) pure)del)(v) )
return ret;
return 0;
}
int opApply(int delegate(ref ScopedRef!(const(K)), ref ScopedRef!(const(V))) del)
const pure {
foreach( ref k, ref v; m_aa )
if( auto ret = (cast(int delegate(ref ScopedRef!(const(K)), ref ScopedRef!(const(V))) pure)del)(k, v) )
return ret;
return 0;
}
int opApply(int delegate(ref ScopedRef!(const(V))) del)
const pure {
foreach( v; m_aa )
if( auto ret = (cast(int delegate(ref ScopedRef!(const(V))) pure)del)(v) )
return ret;
return 0;
}
}
/******************************************************************************/
/* UTILITY FUNCTIONALITY */
/******************************************************************************/
// private
private @property string generateModuleImports(T)()
{
bool[string] visited;
//pragma(msg, "generateModuleImports "~T.stringof);
return generateModuleImportsImpl!T(visited);
}
private @property string generateModuleImportsImpl(T, TYPES...)(ref bool[string] visited)
{
string ret;
//pragma(msg, T);
//pragma(msg, TYPES);
static if( !haveTypeAlready!(T, TYPES) ){
void addModule(string mod){
if( mod !in visited ){
ret ~= "static import "~mod~";\n";
visited[mod] = true;
}
}
static if( isAggregateType!T && !is(typeof(T.__isWeakIsolatedType)) ){ // hack to avoid a recursive template instantiation when Isolated!T is passed to moduleName
addModule(moduleName!T);
foreach( member; __traits(allMembers, T) ){
//static if( isPublicMember!(T, member) ){
static if( !is(typeof(__traits(getMember, T, member))) ){
// ignore sub types
} else static if( !is(FunctionTypeOf!(__traits(getMember, T, member)) == function) ){
alias mtype = typeof(__traits(getMember, T, member)) ;
ret ~= generateModuleImportsImpl!(mtype, T, TYPES)(visited);
} else static if( is(T == class) || is(T == interface) ){
foreach( overload; MemberFunctionsTuple!(T, member) ){
ret ~= generateModuleImportsImpl!(ReturnType!overload, T, TYPES)(visited);
foreach( P; ParameterTypeTuple!overload )
ret ~= generateModuleImportsImpl!(P, T, TYPES)(visited);
}
} // TODO: handle structs!
//}
}
}
else static if( isPointer!T ) ret ~= generateModuleImportsImpl!(PointerTarget!T, T, TYPES)(visited);
else static if( isArray!T ) ret ~= generateModuleImportsImpl!(typeof(T.init[0]), T, TYPES)(visited);
else static if( isAssociativeArray!T ) ret ~= generateModuleImportsImpl!(KeyType!T, T, TYPES)(visited) ~ generateModuleImportsImpl!(ValueType!T, T, TYPES)(visited);
}
return ret;
}
template haveTypeAlready(T, TYPES...)
{
static if( TYPES.length == 0 ) enum haveTypeAlready = false;
else static if( is(T == TYPES[0]) ) enum haveTypeAlready = true;
else alias haveTypeAlready = haveTypeAlready!(T, TYPES[1 ..$]);
}
/******************************************************************************/
/* Additional traits useful for handling isolated data */
/******************************************************************************/
/**
Determines if the given list of types has any non-immutable aliasing outside of their object tree.
The types in particular may only contain plain data, pointers or arrays to immutable data, or references
encapsulated in stdx.typecons.Isolated.
*/
template isStronglyIsolated(T...)
{
static if (T.length == 0) enum bool isStronglyIsolated = true;
else static if (T.length > 1) enum bool isStronglyIsolated = isStronglyIsolated!(T[0 .. $/2]) && isStronglyIsolated!(T[$/2 .. $]);
else {
static if (is(T[0] == immutable)) enum bool isStronglyIsolated = true;
else static if(isInstanceOf!(Rebindable, T[0])) enum bool isStronglyIsolated = isStronglyIsolated!(typeof(T[0].get()));
else static if (is(typeof(T[0].__isIsolatedType))) enum bool isStronglyIsolated = true;
else static if (is(T[0] == class)) enum bool isStronglyIsolated = false;
else static if (is(T[0] == interface)) enum bool isStronglyIsolated = false; // can't know if the implementation is isolated
else static if (is(T[0] == delegate)) enum bool isStronglyIsolated = false; // can't know to what a delegate points
else static if (isDynamicArray!(T[0])) enum bool isStronglyIsolated = is(typeof(T[0].init[0]) == immutable);
else static if (isAssociativeArray!(T[0])) enum bool isStronglyIsolated = false; // TODO: be less strict here
else static if (isSomeFunction!(T[0])) enum bool isStronglyIsolated = true; // functions are immutable
else static if (isPointer!(T[0])) enum bool isStronglyIsolated = is(typeof(*T[0].init) == immutable);
else static if (isAggregateType!(T[0])) enum bool isStronglyIsolated = isStronglyIsolated!(FieldTypeTuple!(T[0]));
else enum bool isStronglyIsolated = true;
}
}
/**
Determines if the given list of types has any non-immutable and unshared aliasing outside of their object tree.
The types in particular may only contain plain data, pointers or arrays to immutable or shared data, or references
encapsulated in stdx.typecons.Isolated. Values that do not have unshared and unisolated aliasing are safe to be passed
between threads.
*/
template isWeaklyIsolated(T...)
{
static if (T.length == 0) enum bool isWeaklyIsolated = true;
else static if (T.length > 1) enum bool isWeaklyIsolated = isWeaklyIsolated!(T[0 .. $/2]) && isWeaklyIsolated!(T[$/2 .. $]);
else {
static if(is(T[0] == immutable)) enum bool isWeaklyIsolated = true;
else static if (is(T[0] == shared)) enum bool isWeaklyIsolated = true;
else static if (is(T[0] == Tid)) enum bool isWeaklyIsolated = true;
else static if (isInstanceOf!(Rebindable, T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(typeof(T[0].get()));
else static if (is(T[0] : Throwable)) enum bool isWeaklyIsolated = true; // WARNING: this is unsafe, but needed for send/receive!
else static if (is(typeof(T[0].__isIsolatedType))) enum bool isWeaklyIsolated = true;
else static if (is(typeof(T[0].__isWeakIsolatedType))) enum bool isWeaklyIsolated = true;
else static if (is(T[0] == class)) enum bool isWeaklyIsolated = false;
else static if (is(T[0] == interface)) enum bool isWeaklyIsolated = false; // can't know if the implementation is isolated
else static if (is(T[0] == delegate)) enum bool isWeaklyIsolated = T[0].stringof.endsWith(" shared"); // can't know to what a delegate points - FIXME: use something better than a string comparison
else static if (isDynamicArray!(T[0])) enum bool isWeaklyIsolated = is(typeof(T[0].init[0]) == immutable);
else static if (isAssociativeArray!(T[0])) enum bool isWeaklyIsolated = false; // TODO: be less strict here
else static if (isSomeFunction!(T[0])) enum bool isWeaklyIsolated = true; // functions are immutable
else static if (isPointer!(T[0])) enum bool isWeaklyIsolated = is(typeof(*T[0].init) == immutable) || is(typeof(*T[0].init) == shared);
else static if (isAggregateType!(T[0])) enum bool isWeaklyIsolated = isWeaklyIsolated!(FieldTypeTuple!(T[0]));
else enum bool isWeaklyIsolated = true;
}
}
unittest {
static class A { int x; string y; }
static struct B {
string a; // strongly isolated
Isolated!A b; // strongly isolated
version(EnablePhobosFails)
Isolated!(Isolated!A[]) c; // strongly isolated
version(EnablePhobosFails)
Isolated!(Isolated!A[string]) c; // AA implementation does not like this
version(EnablePhobosFails)
Isolated!(int[string]) d; // strongly isolated
}
static struct C {
string a; // strongly isolated
shared(A) b; // weakly isolated
Isolated!A c; // strongly isolated
shared(A*) d; // weakly isolated
shared(A[]) e; // weakly isolated
shared(A[string]) f; // weakly isolated
}
static struct D { A a; } // not isolated
static struct E { void delegate() a; } // not isolated
static struct F { void function() a; } // strongly isolated (functions are immutable)
static struct G { void test(); } // strongly isolated
static struct H { A[] a; } // not isolated
static interface I {}
static assert(!isStronglyIsolated!A);
static assert(isStronglyIsolated!(FieldTypeTuple!A));
static assert(isStronglyIsolated!B);
static assert(!isStronglyIsolated!C);
static assert(!isStronglyIsolated!D);
static assert(!isStronglyIsolated!E);
static assert(isStronglyIsolated!F);
static assert(isStronglyIsolated!G);
static assert(!isStronglyIsolated!H);
static assert(!isStronglyIsolated!I);
static assert(!isWeaklyIsolated!A);
static assert(isWeaklyIsolated!(FieldTypeTuple!A));
static assert(isWeaklyIsolated!B);
static assert(isWeaklyIsolated!C);
static assert(!isWeaklyIsolated!D);
static assert(!isWeaklyIsolated!E);
static assert(isWeaklyIsolated!F);
static assert(isWeaklyIsolated!G);
static assert(!isWeaklyIsolated!H);
static assert(!isWeaklyIsolated!I);
}
template isCopyable(T)
{
static if( __traits(compiles, {foreach( t; [T.init]){}}) ) enum isCopyable = true;
else enum isCopyable = false;
}
/******************************************************************************/
/* Future (promise) suppport */
/******************************************************************************/
/**
Represents a values that will be computed asynchronously.
This type uses $(D alias this) to enable transparent access to the result
value.
*/
struct Future(T) {
import vibe.internal.freelistref : FreeListRef;
private {
alias ResultRef = FreeListRef!(shared(Tuple!(T, string)));
ResultRef m_result;
Task m_task;
}
/// Checks if the values was fully computed.
@property bool ready() const @safe { return !m_task.running; }
/** Returns the computed value.
This function waits for the computation to finish, if necessary, and
then returns the final value. In case of an uncaught exception
happening during the computation, the exception will be thrown
instead.
*/
ref T getResult()
@safe {
if (!ready) m_task.join();
assert(ready, "Task still running after join()!?");
if (m_result.get[1].length)
throw new Exception(m_result.get[1]);
// casting away shared is safe, because this is a unique reference
return *() @trusted { return cast(T*)&m_result.get()[0]; } ();
}
alias getResult this;
private void init()
@safe {
m_result = ResultRef();
}
}
/**
Starts an asynchronous computation and returns a future for the result value.
If the supplied callable and arguments are all weakly isolated,
$(D vibe.core.core.runWorkerTask) will be used to perform the computation in
a separate worker thread. Otherwise, $(D vibe.core.core.runTask) will be
used and the result is computed within a separate task within the calling thread.
Params:
callable = A callable value, can be either a function, a delegate, or a
user defined type that defines an $(D opCall).
args = Arguments to pass to the callable.
Returns:
Returns a $(D Future) object that can be used to access the result.
See_also: $(D isWeaklyIsolated)
*/
Future!(ReturnType!CALLABLE) async(CALLABLE, ARGS...)(CALLABLE callable, ARGS args)
if (is(typeof(callable(args)) == ReturnType!CALLABLE))
{
import vibe.core.core;
import vibe.internal.freelistref : FreeListRef;
import std.functional : toDelegate;
alias RET = ReturnType!CALLABLE;
Future!RET ret;
ret.init();
static void compute(Future!RET.ResultRef dst, CALLABLE callable, ARGS args) {
try dst.get[0] = cast(shared(RET))callable(args);
catch (Exception e) dst.get[1] = e.msg.length ? e.msg : "Asynchronous operation failed";
}
static if (isWeaklyIsolated!CALLABLE && isWeaklyIsolated!ARGS) {
ret.m_task = runWorkerTaskH(&compute, ret.m_result, callable, args);
} else {
ret.m_task = runTask(toDelegate(&compute), ret.m_result, callable, args);
}
return ret;
}
///
@safe unittest {
import vibe.core.core;
import vibe.core.log;
void test()
{
static if (__VERSION__ >= 2065) {
auto val = async({
logInfo("Starting to compute value in worker task.");
sleep(500.msecs); // simulate some lengthy computation
logInfo("Finished computing value in worker task.");
return 32;
});
logInfo("Starting computation in main task");
sleep(200.msecs); // simulate some lengthy computation
logInfo("Finished computation in main task. Waiting for async value.");
logInfo("Result: %s", val.getResult());
}
}
}
///
unittest {
int sum(int a, int b)
{
return a + b;
}
static int sum2(int a, int b)
{
return a + b;
}
void test()
{
// Using a delegate will use runTask internally
assert(async(&sum, 2, 3).getResult() == 5);
// Using a static function will use runTaskWorker internally,
// if all arguments are weakly isolated
assert(async(&sum2, 2, 3).getResult() == 5);
}
}
Future!(ReturnType!CALLABLE) asyncWork(CALLABLE, ARGS...)(CALLABLE callable, ARGS args) @safe
if (is(typeof(callable(args)) == ReturnType!CALLABLE) &&
isWeaklyIsolated!CALLABLE && isWeaklyIsolated!ARGS)
{
import vibe.core.core;
import vibe.internal.freelistref : FreeListRef;
import std.functional : toDelegate;
alias RET = ReturnType!CALLABLE;
Future!RET ret;
ret.init();
static void compute(Future!RET.ResultRef dst, CALLABLE callable, ARGS args) {
try *cast(RET*)&dst.get[0] = callable(args);
catch (Exception e) dst.get[1] = e.msg.length ? e.msg : "Asynchronous operation failed";
}
ret.m_task = runWorkerTaskH(&compute, ret.m_result, callable, args);
return ret;
}
/******************************************************************************/
/* std.concurrency compatible interface for message passing */
/******************************************************************************/
enum ConcurrencyPrimitive {
task, // Task run in the caller's thread (`runTask`)
workerTask, // Task run in the worker thread pool (`runWorkerTask`)
thread // Separate thread
}
/** Sets the concurrency primitive to use for `śtd.concurrency.spawn()`.
By default, `spawn()` will start a thread for each call, mimicking the
default behavior of `std.concurrency`.
*/
void setConcurrencyPrimitive(ConcurrencyPrimitive primitive)
{
import core.atomic : atomicStore;
atomicStore(st_concurrencyPrimitive, primitive);
}
void send(ARGS...)(Task task, ARGS args) { std.concurrency.send(task.tid, args); }
void send(ARGS...)(Tid tid, ARGS args) { std.concurrency.send(tid, args); }
void prioritySend(ARGS...)(Task task, ARGS args) { std.concurrency.prioritySend(task.tid, args); }
void prioritySend(ARGS...)(Tid tid, ARGS args) { std.concurrency.prioritySend(tid, args); }
package final class VibedScheduler : Scheduler {
import core.sync.mutex;
import vibe.core.core;
import vibe.core.sync;
override void start(void delegate() op) { op(); }
override void spawn(void delegate() op) {
import core.thread : Thread;
final switch (st_concurrencyPrimitive) with (ConcurrencyPrimitive) {
case task: runTask(op); break;
case workerTask:
static void wrapper(shared(void delegate()) op) {
(cast(void delegate())op)();
}
runWorkerTask(&wrapper, cast(shared)op);
break;
case thread:
auto t = new Thread(op);
t.start();
break;
}
}
override void yield() {}
override @property ref ThreadInfo thisInfo() @trusted { return Task.getThis().tidInfo; }
override TaskCondition newCondition(Mutex m)
{
try {
return new TaskCondition(m);
} catch(Exception e) { assert(false, e.msg); }
}
}
private shared ConcurrencyPrimitive st_concurrencyPrimitive = ConcurrencyPrimitive.thread;