You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by jf...@apache.org on 2012/03/22 22:49:13 UTC

svn commit: r1304085 [4/10] - in /thrift/trunk: ./ aclocal/ compiler/cpp/ compiler/cpp/src/generate/ lib/ lib/d/ lib/d/src/ lib/d/src/thrift/ lib/d/src/thrift/async/ lib/d/src/thrift/codegen/ lib/d/src/thrift/internal/ lib/d/src/thrift/internal/test/ l...

Added: thrift/trunk/lib/d/src/thrift/codegen/processor.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/codegen/processor.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/codegen/processor.d (added)
+++ thrift/trunk/lib/d/src/thrift/codegen/processor.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,497 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.codegen.processor;
+
+import std.algorithm : find;
+import std.array : empty, front;
+import std.conv : to;
+import std.traits : ParameterTypeTuple, ReturnType, Unqual;
+import std.typetuple : allSatisfy, TypeTuple;
+import std.variant : Variant;
+import thrift.base;
+import thrift.codegen.base;
+import thrift.internal.codegen;
+import thrift.internal.ctfe;
+import thrift.protocol.base;
+import thrift.protocol.processor;
+
+/**
+ * Service processor for Interface, which implements TProcessor by
+ * synchronously forwarding requests for the service methods to a handler
+ * implementing Interface.
+ *
+ * The generated class implements TProcessor and additionally allows a
+ * TProcessorEventHandler to be specified via the public eventHandler property.
+ * The constructor takes a single argument of type Interface, which is the
+ * handler to forward the requests to:
+ * ---
+ * this(Interface iface);
+ * TProcessorEventHandler eventHandler;
+ * ---
+ *
+ * If Interface is derived from another service BaseInterface, this class is
+ * also derived from TServiceProcessor!BaseInterface.
+ *
+ * The optional Protocols template tuple parameter can be used to specify
+ * one or more TProtocol implementations to specifically generate code for. If
+ * the actual types of the protocols passed to process() at runtime match one
+ * of the items from the list, the optimized code paths are taken, otherwise,
+ * a generic TProtocol version is used as fallback. For cases where the input
+ * and output protocols differ, TProtocolPair!(InputProtocol, OutputProtocol)
+ * can be used in the Protocols list:
+ * ---
+ * interface FooService { void foo(); }
+ * class FooImpl { override void foo {} }
+ *
+ * // Provides fast path if TBinaryProtocol!TBufferedTransport is used for
+ * // both input and output:
+ * alias TServiceProcessor!(FooService, TBinaryProtocol!TBufferedTransport)
+ *   BinaryProcessor;
+ *
+ * auto proc = new BinaryProcessor(new FooImpl());
+ *
+ * // Low overhead.
+ * proc.process(tBinaryProtocol(tBufferTransport(someSocket)));
+ *
+ * // Not in the specialization list – higher overhead.
+ * proc.process(tBinaryProtocol(tFramedTransport(someSocket)));
+ *
+ * // Same as above, but optimized for the Compact protocol backed by a
+ * // TPipedTransport for input and a TBufferedTransport for output.
+ * alias TServiceProcessor!(FooService, TProtocolPair!(
+ *   TCompactProtocol!TPipedTransport, TCompactProtocol!TBufferedTransport)
+ * ) MixedProcessor;
+ * ---
+ */
+template TServiceProcessor(Interface, Protocols...) if (
+  isService!Interface && allSatisfy!(isTProtocolOrPair, Protocols)
+) {
+  mixin({
+    static if (is(Interface BaseInterfaces == super) && BaseInterfaces.length > 0) {
+      static assert(BaseInterfaces.length == 1,
+        "Services cannot be derived from more than one parent.");
+
+      string code = "class TServiceProcessor : " ~
+        "TServiceProcessor!(BaseService!Interface) {\n";
+      code ~= "private Interface iface_;\n";
+
+      string constructorCode = "this(Interface iface) {\n";
+      constructorCode ~= "super(iface);\n";
+      constructorCode ~= "iface_ = iface;\n";
+    } else {
+      string code = "class TServiceProcessor : TProcessor {";
+      code ~= q{
+        override bool process(TProtocol iprot, TProtocol oprot,
+          Variant context = Variant()
+        ) {
+          auto msg = iprot.readMessageBegin();
+
+          void writeException(TApplicationException e) {
+            oprot.writeMessageBegin(TMessage(msg.name, TMessageType.EXCEPTION,
+              msg.seqid));
+            e.write(oprot);
+            oprot.writeMessageEnd();
+            oprot.transport.writeEnd();
+            oprot.transport.flush();
+          }
+
+          if (msg.type != TMessageType.CALL && msg.type != TMessageType.ONEWAY) {
+            skip(iprot, TType.STRUCT);
+            iprot.readMessageEnd();
+            iprot.transport.readEnd();
+
+            writeException(new TApplicationException(
+              TApplicationException.Type.INVALID_MESSAGE_TYPE));
+            return false;
+          }
+
+          auto dg = msg.name in processMap_;
+          if (!dg) {
+            skip(iprot, TType.STRUCT);
+            iprot.readMessageEnd();
+            iprot.transport.readEnd();
+
+            writeException(new TApplicationException("Invalid method name: '" ~
+              msg.name ~ "'.", TApplicationException.Type.INVALID_MESSAGE_TYPE));
+
+            return false;
+          }
+
+          (*dg)(msg.seqid, iprot, oprot, context);
+          return true;
+        }
+
+        TProcessorEventHandler eventHandler;
+
+        alias void delegate(int, TProtocol, TProtocol, Variant) ProcessFunc;
+        protected ProcessFunc[string] processMap_;
+        private Interface iface_;
+      };
+
+      string constructorCode = "this(Interface iface) {\n";
+      constructorCode ~= "iface_ = iface;\n";
+    }
+
+    // Generate the handling code for each method, consisting of the dispatch
+    // function, registering it in the constructor, and the actual templated
+    // handler function.
+    foreach (methodName;
+      FilterMethodNames!(Interface, __traits(derivedMembers, Interface))
+    ) {
+      // Register the processing function in the constructor.
+      immutable procFuncName = "process_" ~ methodName;
+      immutable dispatchFuncName = procFuncName ~ "_protocolDispatch";
+      constructorCode ~= "processMap_[`" ~ methodName ~ "`] = &" ~
+        dispatchFuncName ~ ";\n";
+
+      bool methodMetaFound;
+      TMethodMeta methodMeta;
+      static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
+        enum meta = find!`a.name == b`(Interface.methodMeta, methodName);
+        if (!meta.empty) {
+          methodMetaFound = true;
+          methodMeta = meta.front;
+        }
+      }
+
+      // The dispatch function to call the specialized handler functions. We
+      // test the protocols if they can be converted to one of the passed
+      // protocol types, and if not, fall back to the generic TProtocol
+      // version of the processing function.
+      code ~= "void " ~ dispatchFuncName ~
+        "(int seqid, TProtocol iprot, TProtocol oprot, Variant context) {\n";
+      code ~= "foreach (Protocol; TypeTuple!(Protocols, TProtocol)) {\n";
+      code ~= q{
+        static if (is(Protocol _ : TProtocolPair!(I, O), I, O)) {
+          alias I IProt;
+          alias O OProt;
+        } else {
+          alias Protocol IProt;
+          alias Protocol OProt;
+        }
+        auto castedIProt = cast(IProt)iprot;
+        auto castedOProt = cast(OProt)oprot;
+      };
+      code ~= "if (castedIProt && castedOProt) {\n";
+      code ~= procFuncName ~
+        "!(IProt, OProt)(seqid, castedIProt, castedOProt, context);\n";
+      code ~= "return;\n";
+      code ~= "}\n";
+      code ~= "}\n";
+      code ~= "throw new TException(`Internal error: Null iprot/oprot " ~
+        "passed to processor protocol dispatch function.`);\n";
+      code ~= "}\n";
+
+      // The actual handler function, templated on the input and output
+      // protocol types.
+      code ~= "void " ~ procFuncName ~ "(IProt, OProt)(int seqid, IProt " ~
+        "iprot, OProt oprot, Variant connectionContext) " ~
+        "if (isTProtocol!IProt && isTProtocol!OProt) {\n";
+      code ~= "TArgsStruct!(Interface, `" ~ methodName ~ "`) args;\n";
+
+      // Store the (qualified) method name in a manifest constant to avoid
+      // having to litter the code below with lots of string manipulation.
+      code ~= "enum methodName = `" ~ methodName ~ "`;\n";
+
+      code ~= q{
+        enum qName = Interface.stringof ~ "." ~ methodName;
+
+        Variant callContext;
+        if (eventHandler) {
+          callContext = eventHandler.createContext(qName, connectionContext);
+        }
+
+        scope (exit) {
+          if (eventHandler) {
+            eventHandler.deleteContext(callContext, qName);
+          }
+        }
+
+        if (eventHandler) eventHandler.preRead(callContext, qName);
+
+        args.read(iprot);
+        iprot.readMessageEnd();
+        iprot.transport.readEnd();
+
+        if (eventHandler) eventHandler.postRead(callContext, qName);
+      };
+
+      code ~= "TResultStruct!(Interface, `" ~ methodName ~ "`) result;\n";
+      code ~= "try {\n";
+
+      // Generate the parameter list to pass to the called iface function.
+      string[] paramList;
+      foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
+        string paramName;
+        if (methodMetaFound && i < methodMeta.params.length) {
+          paramName = methodMeta.params[i].name;
+        } else {
+          paramName = "param" ~ to!string(i + 1);
+        }
+        paramList ~= "args." ~ paramName;
+      }
+
+      immutable call = "iface_." ~ methodName ~ "(" ~ ctfeJoin(paramList) ~ ")";
+      if (is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
+        code ~= call ~ ";\n";
+      } else {
+        code ~= "result.set!`success`(" ~ call ~ ");\n";
+      }
+
+      // If this is not a oneway method, generate the recieving code.
+      if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) {
+        if (methodMetaFound) {
+          foreach (e; methodMeta.exceptions) {
+            code ~= "} catch (Interface." ~ e.type ~ " " ~ e.name ~ ") {\n";
+            code ~= "result.set!`" ~ e.name ~ "`(" ~ e.name ~ ");\n";
+          }
+        }
+        code ~= "}\n";
+
+        code ~= q{
+          catch (Exception e) {
+            if (eventHandler) {
+              eventHandler.handlerError(callContext, qName, e);
+            }
+
+            auto x = new TApplicationException(to!string(e));
+            oprot.writeMessageBegin(
+              TMessage(methodName, TMessageType.EXCEPTION, seqid));
+            x.write(oprot);
+            oprot.writeMessageEnd();
+            oprot.transport.writeEnd();
+            oprot.transport.flush();
+            return;
+          }
+
+          if (eventHandler) eventHandler.preWrite(callContext, qName);
+
+          oprot.writeMessageBegin(TMessage(methodName,
+            TMessageType.REPLY, seqid));
+          result.write(oprot);
+          oprot.writeMessageEnd();
+          oprot.transport.writeEnd();
+          oprot.transport.flush();
+
+          if (eventHandler) eventHandler.postWrite(callContext, qName);
+        };
+      } else {
+        // For oneway methods, we obviously cannot notify the client of any
+        // exceptions, just call the event handler if one is set.
+        code ~= "}\n";
+        code ~= q{
+          catch (Exception e) {
+            if (eventHandler) {
+              eventHandler.handlerError(callContext, qName, e);
+            }
+            return;
+          }
+
+          if (eventHandler) eventHandler.onewayComplete(callContext, qName);
+        };
+      }
+      code ~= "}\n";
+
+    }
+
+    code ~= constructorCode ~ "}\n";
+    code ~= "}\n";
+
+    return code;
+  }());
+}
+
+/**
+ * A struct representing the arguments of a Thrift method call.
+ *
+ * There should usually be no reason to use this directly without the help of
+ * TServiceProcessor, but it is documented publicly to help debugging in case
+ * of CTFE errors.
+ *
+ * Consider this example:
+ * ---
+ * interface Foo {
+ *   int bar(string a, bool b);
+ *
+ *   enum methodMeta = [
+ *     TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)])
+ *   ];
+ * }
+ *
+ * alias TArgsStruct!(Foo, "bar") FooBarArgs;
+ * ---
+ *
+ * The definition of FooBarArgs is equivalent to:
+ * ---
+ * struct FooBarArgs {
+ *   string a;
+ *   bool b;
+ *
+ *   mixin TStructHelpers!([TFieldMeta("a", 1, TReq.OPT_IN_REQ_OUT),
+ *     TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT)]);
+ * }
+ * ---
+ *
+ * If the TVerboseCodegen version is defined, a warning message is issued at
+ * compilation if no TMethodMeta for Interface.methodName is found.
+ */
+template TArgsStruct(Interface, string methodName) {
+  static assert(is(typeof(mixin("Interface." ~ methodName))),
+    "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
+  mixin({
+    bool methodMetaFound;
+    TMethodMeta methodMeta;
+    static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
+      auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
+      if (!meta.empty) {
+        methodMetaFound = true;
+        methodMeta = meta.front;
+      }
+    }
+
+    string memberCode;
+    string[] fieldMetaCodes;
+    foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
+      // If we have no meta information, just use param1, param2, etc. as
+      // field names, it shouldn't really matter anyway. 1-based »indexing«
+      // is used to match the common scheme in the Thrift world.
+      string memberId;
+      string memberName;
+      if (methodMetaFound && i < methodMeta.params.length) {
+        memberId = to!string(methodMeta.params[i].id);
+        memberName = methodMeta.params[i].name;
+      } else {
+        memberId = to!string(i + 1);
+        memberName = "param" ~ to!string(i + 1);
+      }
+
+      // Unqual!() is needed to generate mutable fields for ref const()
+      // struct parameters.
+      memberCode ~= "Unqual!(ParameterTypeTuple!(Interface." ~ methodName ~
+        ")[" ~ to!string(i) ~ "])" ~ memberName ~ ";\n";
+
+      fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~
+        ", TReq.OPT_IN_REQ_OUT)";
+    }
+
+    string code = "struct TArgsStruct {\n";
+    code ~= memberCode;
+    version (TVerboseCodegen) {
+      if (!methodMetaFound &&
+        ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
+      {
+        code ~= "pragma(msg, `[thrift.codegen.processor.TArgsStruct] Warning: No " ~
+          "meta information for method '" ~ methodName ~ "' in service '" ~
+          Interface.stringof ~ "' found.`);\n";
+      }
+    }
+    immutable fieldMetaCode =
+      fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
+    code ~= "mixin TStructHelpers!(" ~ fieldMetaCode  ~ ");\n";
+    code ~= "}\n";
+    return code;
+  }());
+}
+
+/**
+ * A struct representing the result of a Thrift method call.
+ *
+ * It contains a field called "success" for the return value of the function
+ * (with id 0), and additional fields for the exceptions declared for the
+ * method, if any.
+ *
+ * There should usually be no reason to use this directly without the help of
+ * TServiceProcessor, but it is documented publicly to help debugging in case
+ * of CTFE errors.
+ *
+ * Consider the following example:
+ * ---
+ * interface Foo {
+ *   int bar(string a);
+ *
+ *   alias .FooException FooException;
+ *
+ *   enum methodMeta = [
+ *     TMethodMeta("bar",
+ *       [TParamMeta("a", 1)],
+ *       [TExceptionMeta("fooe", 1, "FooException")]
+ *     )
+ *   ];
+ * }
+ * alias TResultStruct!(Foo, "bar") FooBarResult;
+ * ---
+ *
+ * The definition of FooBarResult is equivalent to:
+ * ---
+ * struct FooBarResult {
+ *   int success;
+ *   FooException fooe;
+ *
+ *   mixin(TStructHelpers!([TFieldMeta("success", 0, TReq.OPTIONAL),
+ *     TFieldMeta("fooe", 1, TReq.OPTIONAL)]));
+ * }
+ * ---
+ *
+ * If the TVerboseCodegen version is defined, a warning message is issued at
+ * compilation if no TMethodMeta for Interface.methodName is found.
+ */
+template TResultStruct(Interface, string methodName) {
+  static assert(is(typeof(mixin("Interface." ~ methodName))),
+    "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
+
+  mixin({
+    string code = "struct TResultStruct {\n";
+
+    string[] fieldMetaCodes;
+
+    static if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
+      code ~= "ReturnType!(Interface." ~ methodName ~ ") success;\n";
+      fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)";
+    }
+
+    bool methodMetaFound;
+    static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
+      auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
+      if (!meta.empty) {
+        foreach (e; meta.front.exceptions) {
+          code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n";
+          fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~
+            ", TReq.OPTIONAL)";
+        }
+        methodMetaFound = true;
+      }
+    }
+
+    version (TVerboseCodegen) {
+      if (!methodMetaFound &&
+        ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
+      {
+        code ~= "pragma(msg, `[thrift.codegen.processor.TResultStruct] Warning: No " ~
+          "meta information for method '" ~ methodName ~ "' in service '" ~
+          Interface.stringof ~ "' found.`);\n";
+      }
+    }
+
+    immutable fieldMetaCode =
+      fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
+    code ~= "mixin TStructHelpers!(" ~ fieldMetaCode  ~ ");\n";
+    code ~= "}\n";
+    return code;
+  }());
+}

Added: thrift/trunk/lib/d/src/thrift/index.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/index.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/index.d (added)
+++ thrift/trunk/lib/d/src/thrift/index.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,33 @@
+Ddoc
+
+<h2>Package overview</h2>
+
+<dl>
+  <dt>$(D_CODE thrift.async)</dt>
+  <dd>Support infrastructure for handling client-side asynchronous operations using non-blocking I/O and coroutines.</dd>
+
+  <dt>$(D_CODE thrift.codegen)</dt>
+  <dd>
+    <p>Templates used for generating Thrift clients/processors from regular D struct and interface definitions.</p>
+    <p><strong>Note:</strong> Several artifacts in these modules have options for specifying the exact protocol types used. In this case, the amount of virtual calls can be greatly reduced and as a result, the code also can be optimized better. If performance is not a concern or the actual protocol type is not known at compile time, these parameters can just be left at their defaults.
+    </p>
+  </dd>
+
+  <dt>$(D_CODE thrift.internal)</dt>
+  <dd>Internal helper modules used by the Thrift library. This package is not part of the public API, and no stability guarantees are given whatsoever.</dd>
+
+  <dt>$(D_CODE thrift.protocol)</dt>
+  <dd>The Thrift protocol implemtations which specify how to pass messages over a TTransport.</dd>
+
+  <dt>$(D_CODE thrift.server)</dt>
+  <dd>Generic Thrift server implementations handling clients over a TTransport interface and forwarding requests to a TProcessor (which is in turn usually provided by thrift.codegen).</dd>
+
+  <dt>$(D_CODE thrift.transport)</dt>
+  <dd>The TTransport data source/sink interface used in the Thrift library and its imiplementations.</dd>
+
+  <dt>$(D_CODE thrift.util)</dt>
+  <dd>General-purpose utility modules not specific to Thrift, part of the public API.</dd>
+</dl>
+
+Macros:
+  TITLE = Thrift D Software Library

Added: thrift/trunk/lib/d/src/thrift/internal/algorithm.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/algorithm.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/algorithm.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/algorithm.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,55 @@
+/**
+ * Contains a modified version of std.algorithm.remove that doesn't take an
+ * alias parameter to avoid DMD @@BUG6395@@.
+ */
+module thrift.internal.algorithm;
+
+import std.algorithm : move;
+import std.exception;
+import std.functional;
+import std.range;
+import std.traits;
+
+enum SwapStrategy
+{
+    unstable,
+    semistable,
+    stable,
+}
+
+Range removeEqual(SwapStrategy s = SwapStrategy.stable, Range, E)(Range range, E e)
+if (isBidirectionalRange!Range)
+{
+    auto result = range;
+    static if (s != SwapStrategy.stable)
+    {
+        for (;!range.empty;)
+        {
+            if (range.front !is e)
+            {
+                range.popFront;
+                continue;
+            }
+            move(range.back, range.front);
+            range.popBack;
+            result.popBack;
+        }
+    }
+    else
+    {
+        auto tgt = range;
+        for (; !range.empty; range.popFront)
+        {
+            if (range.front is e)
+            {
+                // yank this guy
+                result.popBack;
+                continue;
+            }
+            // keep this guy
+            move(range.front, tgt.front);
+            tgt.popFront;
+        }
+    }
+    return result;
+}

Added: thrift/trunk/lib/d/src/thrift/internal/codegen.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/codegen.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/codegen.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/codegen.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+module thrift.internal.codegen;
+
+import std.traits : InterfacesTuple, isSomeFunction, isSomeString;
+import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple;
+import thrift.codegen.base;
+
+/**
+ * Removes all type qualifiers from T.
+ *
+ * In contrast to std.traits.Unqual, FullyUnqual also removes qualifiers from
+ * array elements (e.g. immutable(byte[]) -> byte[], not immutable(byte)[]),
+ * excluding strings (string isn't reduced to char[]).
+ */
+template FullyUnqual(T) {
+  static if (is(T _ == const(U), U)) {
+    alias FullyUnqual!U FullyUnqual;
+  } else static if (is(T _ == immutable(U), U)) {
+    alias FullyUnqual!U FullyUnqual;
+  } else static if (is(T _ == shared(U), U)) {
+    alias FullyUnqual!U FullyUnqual;
+  } else static if (is(T _ == U[], U) && !isSomeString!T) {
+    alias FullyUnqual!(U)[] FullyUnqual;
+  } else static if (is(T _ == V[K], K, V)) {
+    alias FullyUnqual!(V)[FullyUnqual!K] FullyUnqual;
+  } else {
+    alias T FullyUnqual;
+  }
+}
+
+/**
+ * true if null can be assigned to the passed type, false if not.
+ */
+template isNullable(T) {
+  enum isNullable = __traits(compiles, { T t = null; });
+}
+
+template isStruct(T) {
+  enum isStruct = is(T == struct);
+}
+
+template isException(T) {
+ enum isException = is(T : Exception);
+}
+
+template isEnum(T) {
+  enum isEnum = is(T == enum);
+}
+
+/**
+ * Aliases itself to T.name.
+ */
+template GetMember(T, string name) {
+  mixin("alias T." ~ name ~ " GetMember;");
+}
+
+/**
+ * Aliases itself to typeof(symbol).
+ */
+template TypeOf(alias symbol) {
+  alias typeof(symbol) TypeOf;
+}
+
+/**
+ * Aliases itself to the type of the T member called name.
+ */
+alias Compose!(TypeOf, GetMember) MemberType;
+
+/**
+ * Returns the field metadata array for T if any, or an empty array otherwise.
+ */
+template getFieldMeta(T) if (isStruct!T || isException!T) {
+  static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
+    enum getFieldMeta = T.fieldMeta;
+  } else {
+    enum TFieldMeta[] getFieldMeta = [];
+  }
+}
+
+/**
+ * Merges the field metadata array for D with the passed array.
+ */
+template mergeFieldMeta(T, alias fieldMetaData = cast(TFieldMeta[])null) {
+  // Note: We don't use getFieldMeta here to avoid bug if it is instantiated
+  // from TIsSetFlags, see comment there.
+  static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
+    enum mergeFieldMeta = T.fieldMeta ~ fieldMetaData;
+  } else {
+    enum TFieldMeta[] mergeFieldMeta = fieldMetaData;
+  }
+}
+
+/**
+ * Returns the field requirement level for T.name.
+ */
+template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
+  enum memberReq = memberReqImpl!(T, name, fieldMetaData).result;
+}
+
+private {
+  import std.algorithm : find;
+  // DMD @@BUG@@: Missing import leads to failing build without error
+  // message in unittest/debug/thrift/codegen/async_client.
+  import std.array : empty, front;
+
+  template memberReqImpl(T, string name, alias fieldMetaData) {
+    enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
+    static if (meta.empty || meta.front.req == TReq.AUTO) {
+      static if (isNullable!(MemberType!(T, name))) {
+        enum result = TReq.OPTIONAL;
+      } else {
+        enum result = TReq.REQUIRED;
+      }
+    } else {
+      enum result = meta.front.req;
+    }
+  }
+}
+
+
+template notIgnored(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
+  enum notIgnored = memberReq!(T, name, fieldMetaData) != TReq.IGNORE;
+}
+
+/**
+ * Returns the method metadata array for T if any, or an empty array otherwise.
+ */
+template getMethodMeta(T) if (isService!T) {
+  static if (is(typeof(T.methodMeta) == TMethodMeta[])) {
+    enum getMethodMeta = T.methodMeta;
+  } else {
+    enum TMethodMeta[] getMethodMeta = [];
+  }
+}
+
+
+/**
+ * true if T.name is a member variable. Exceptions include methods, static
+ * members, artifacts like package aliases, …
+ */
+template isValueMember(T, string name) {
+  static if (!is(MemberType!(T, name))) {
+    enum isValueMember = false;
+  } else static if (
+    is(MemberType!(T, name) == void) ||
+    isSomeFunction!(MemberType!(T, name)) ||
+    __traits(compiles, { return mixin("T." ~ name); }())
+  ) {
+    enum isValueMember = false;
+  } else {
+    enum isValueMember = true;
+  }
+}
+
+/**
+ * Returns a tuple containing the names of the fields of T, not including
+ * inherited fields. If a member is marked as TReq.IGNORE, it is not included
+ * as well.
+ */
+template FieldNames(T, alias fieldMetaData = cast(TFieldMeta[])null) {
+  alias StaticFilter!(
+    All!(
+      PApply!(isValueMember, T),
+      PApply!(notIgnored, T, PApplySkip, fieldMetaData)
+    ),
+    __traits(derivedMembers, T)
+  ) FieldNames;
+}
+
+template derivedMembers(T) {
+  alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers;
+}
+
+template AllMemberMethodNames(T) if (isService!T) {
+  alias NoDuplicates!(
+    FilterMethodNames!(
+      T,
+      staticMap!(
+        derivedMembers,
+        TypeTuple!(T, InterfacesTuple!T)
+      )
+    )
+  ) AllMemberMethodNames;
+}
+
+private template FilterMethodNames(T, MemberNames...) {
+  alias StaticFilter!(
+    CompilesAndTrue!(
+      Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
+    ),
+    MemberNames
+  ) FilterMethodNames;
+}
+
+/**
+ * Returns a type tuple containing only the elements of T for which the
+ * eponymous template predicate pred is true.
+ *
+ * Example:
+ * ---
+ * alias StaticFilter!(isIntegral, int, string, long, float[]) Filtered;
+ * static assert(is(Filtered == TypeTuple!(int, long)));
+ * ---
+ */
+template StaticFilter(alias pred, T...) {
+  static if (T.length == 0) {
+    alias TypeTuple!() StaticFilter;
+  } else static if (pred!(T[0])) {
+    alias TypeTuple!(T[0], StaticFilter!(pred, T[1 .. $])) StaticFilter;
+  } else {
+    alias StaticFilter!(pred, T[1 .. $]) StaticFilter;
+  }
+}
+
+/**
+ * Binds the first n arguments of a template to a particular value (where n is
+ * the number of arguments passed to PApply).
+ *
+ * The passed arguments are always applied starting from the left. However,
+ * the special PApplySkip marker template can be used to indicate that an
+ * argument should be skipped, so that e.g. the first and third argument
+ * to a template can be fixed, but the second and remaining arguments would
+ * still be left undefined.
+ *
+ * Skipping a number of parameters, but not providing enough arguments to
+ * assign all of them during instantiation of the resulting template is an
+ * error.
+ *
+ * Example:
+ * ---
+ * struct Foo(T, U, V) {}
+ * alias PApply!(Foo, int, long) PartialFoo;
+ * static assert(is(PartialFoo!float == Foo!(int, long, float)));
+ *
+ * alias PApply!(Test, int, PApplySkip, float) SkippedTest;
+ * static assert(is(SkippedTest!long == Test!(int, long, float)));
+ * ---
+ */
+template PApply(alias Target, T...) {
+  template PApply(U...) {
+    alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply;
+  }
+}
+
+/// Ditto.
+template PApplySkip() {}
+
+private template PApplyMergeArgs(alias Preset, Args...) {
+  static if (Preset.length == 0) {
+    alias Args Result;
+  } else {
+    enum nextSkip = staticIndexOf!(PApplySkip, Preset.Tuple);
+    static if (nextSkip == -1) {
+      alias TypeTuple!(Preset.Tuple, Args) Result;
+    } else static if (Args.length == 0) {
+      // Have to use a static if clause instead of putting the condition
+      // directly into the assert to avoid DMD trying to access Args[0]
+      // nevertheless below.
+      static assert(false,
+        "PArgsSkip encountered, but no argument left to bind.");
+    } else {
+      alias TypeTuple!(
+        Preset.Tuple[0 .. nextSkip],
+        Args[0],
+        PApplyMergeArgs!(
+          ConfinedTuple!(Preset.Tuple[nextSkip + 1 .. $]),
+          Args[1 .. $]
+        ).Result
+      ) Result;
+    }
+  }
+}
+
+unittest {
+  struct Test(T, U, V) {}
+  alias PApply!(Test, int, long) PartialTest;
+  static assert(is(PartialTest!float == Test!(int, long, float)));
+
+  alias PApply!(Test, int, PApplySkip, float) SkippedTest;
+  static assert(is(SkippedTest!long == Test!(int, long, float)));
+
+  alias PApply!(Test, int, PApplySkip, PApplySkip) TwoSkipped;
+  static assert(!__traits(compiles, TwoSkipped!long));
+}
+
+
+/**
+ * Composes a number of templates. The result is a template equivalent to
+ * all the passed templates evaluated from right to left, akin to the
+ * mathematical function composition notation: Instantiating Compose!(A, B, C)
+ * is the same as instantiating A!(B!(C!(…))).
+ *
+ * This is especially useful for creating a template to use with staticMap/
+ * StaticFilter, as demonstrated below.
+ *
+ * Example:
+ * ---
+ * template AllMethodNames(T) {
+ *   alias StaticFilter!(
+ *     CompilesAndTrue!(
+ *       Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
+ *     ),
+ *     __traits(allMembers, T)
+ *   ) AllMethodNames;
+ * }
+ *
+ * pragma(msg, AllMethodNames!Object);
+ * ---
+ */
+template Compose(T...) {
+  static if (T.length == 0) {
+    template Compose(U...) {
+      alias U Compose;
+    }
+  } else {
+    template Compose(U...) {
+      alias Instantiate!(T[0], Instantiate!(.Compose!(T[1 .. $]), U)) Compose;
+    }
+  }
+}
+
+/**
+ * Instantiates the given template with the given list of parameters.
+ *
+ * Used to work around syntactic limiations of D with regard to instantiating
+ * a template from a type tuple (e.g. T[0]!(...) is not valid) or a template
+ * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed).
+ */
+template Instantiate(alias Template, Params...) {
+  alias Template!Params Instantiate;
+}
+
+/**
+ * Combines several template predicates using logical AND, i.e. instantiating
+ * All!(a, b, c) with parameters P for some templates a, b, c is equivalent to
+ * a!P && b!P && c!P.
+ *
+ * The templates are evaluated from left to right, aborting evaluation in a
+ * shurt-cut manner if a false result is encountered, in which case the latter
+ * instantiations do not need to compile.
+ */
+template All(T...) {
+  static if (T.length == 0) {
+    template All(U...) {
+      enum All = true;
+    }
+  } else {
+    template All(U...) {
+      static if (Instantiate!(T[0], U)) {
+        alias Instantiate!(.All!(T[1 .. $]), U) All;
+      } else {
+        enum All = false;
+      }
+    }
+  }
+}
+
+/**
+ * Combines several template predicates using logical OR, i.e. instantiating
+ * Any!(a, b, c) with parameters P for some templates a, b, c is equivalent to
+ * a!P || b!P || c!P.
+ *
+ * The templates are evaluated from left to right, aborting evaluation in a
+ * shurt-cut manner if a true result is encountered, in which case the latter
+ * instantiations do not need to compile.
+ */
+template Any(T...) {
+  static if (T.length == 0) {
+    template Any(U...) {
+      enum Any = false;
+    }
+  } else {
+    template Any(U...) {
+      static if (Instantiate!(T[0], U)) {
+        enum Any = true;
+      } else {
+        alias Instantiate!(.Any!(T[1 .. $]), U) Any;
+      }
+    }
+  }
+}
+
+template ConfinedTuple(T...) {
+  alias T Tuple;
+  enum length = T.length;
+}
+
+/*
+ * foreach (Item; Items) {
+ *   List = Operator!(Item, List);
+ * }
+ * where Items is a ConfinedTuple and List is a type tuple.
+ */
+template ForAllWithList(alias Items, alias Operator, List...) if (
+  is(typeof(Items.length) : size_t)
+){
+  static if (Items.length == 0) {
+    alias List ForAllWithList;
+  } else {
+    alias .ForAllWithList!(
+      ConfinedTuple!(Items.Tuple[1 .. $]),
+      Operator,
+      Operator!(Items.Tuple[0], List)
+    ) ForAllWithList;
+  }
+}
+
+/**
+ * Wraps the passed template predicate so it returns true if it compiles and
+ * evaluates to true, false it it doesn't compile or evaluates to false.
+ */
+template CompilesAndTrue(alias T) {
+  template CompilesAndTrue(U...) {
+    static if (is(typeof(T!U) : bool)) {
+      enum bool CompilesAndTrue = T!U;
+    } else {
+      enum bool CompilesAndTrue = false;
+    }
+  }
+}

Added: thrift/trunk/lib/d/src/thrift/internal/ctfe.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/ctfe.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/ctfe.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/ctfe.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+module thrift.internal.ctfe;
+
+import std.conv : to;
+import std.traits;
+
+/*
+ * Simple eager join() for strings, std.algorithm.join isn't CTFEable yet.
+ */
+string ctfeJoin(string[] strings, string separator = ", ") {
+  string result;
+  if (strings.length > 0) {
+    result ~= strings[0];
+    foreach (s; strings[1..$]) {
+      result ~= separator ~ s;
+    }
+  }
+  return result;
+}
+
+/*
+ * A very primitive to!string() implementation for floating point numbers that
+ * is evaluatable at compile time.
+ *
+ * There is a wealth of problems associated with the algorithm used (e.g. 5.0
+ * prints as 4.999…, incorrect rounding, etc.), but a better alternative should
+ * be included with the D standard library instead of implementing it here.
+ */
+string ctfeToString(T)(T val) if (isFloatingPoint!T) {
+  if (val is T.nan) return "nan";
+  if (val is T.infinity) return "inf";
+  if (val is -T.infinity) return "-inf";
+  if (val is 0.0) return "0";
+  if (val is -0.0) return "-0";
+
+  auto b = val;
+
+  string result;
+  if (b < 0) {
+    result ~= '-';
+    b *= -1;
+  }
+
+  short magnitude;
+  while (b >= 10) {
+    ++magnitude;
+    b /= 10;
+  }
+  while (b < 1) {
+    --magnitude;
+    b *= 10;
+  }
+
+  foreach (i; 0 .. T.dig) {
+    if (i == 1) result ~= '.';
+
+    auto first = cast(ubyte)b;
+    result ~= to!string(first);
+
+    b -= first;
+    import std.math;
+    if (b < pow(10.0, i - T.dig)) break;
+    b *= 10;
+  }
+
+  if (magnitude != 0) result ~= "e" ~ to!string(magnitude);
+  return result;
+}
+
+unittest {
+  static assert(ctfeToString(double.infinity) == "inf");
+  static assert(ctfeToString(-double.infinity) == "-inf");
+  static assert(ctfeToString(double.nan) == "nan");
+  static assert(ctfeToString(0.0) == "0");
+  static assert(ctfeToString(-0.0) == "-0");
+  static assert(ctfeToString(3.1415) == "3.1415");
+  static assert(ctfeToString(2e-200) == "2e-200");
+}

Added: thrift/trunk/lib/d/src/thrift/internal/endian.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/endian.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/endian.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/endian.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Simple helpers for handling typical byte order-related issues.
+ */
+module thrift.internal.endian;
+
+import core.bitop : bswap;
+import std.traits : isIntegral;
+
+union IntBuf(T) {
+  ubyte[T.sizeof] bytes;
+  T value;
+}
+
+T byteSwap(T)(T t) pure nothrow @trusted if (isIntegral!T) {
+  static if (T.sizeof == 2) {
+    return cast(T)((t & 0xff) << 8) | cast(T)((t & 0xff00) >> 8);
+  } else static if (T.sizeof == 4) {
+    return cast(T)bswap(cast(uint)t);
+  } else static if (T.sizeof == 8) {
+    return cast(T)byteSwap(cast(uint)(t & 0xffffffff)) << 32 |
+      cast(T)bswap(cast(uint)(t >> 32));
+  } else static assert(false, "Type of size " ~ to!string(T.sizeof) ~ " not supported.");
+}
+
+T doNothing(T)(T val) { return val; }
+
+version (BigEndian) {
+  alias doNothing hostToNet;
+  alias doNothing netToHost;
+  alias byteSwap hostToLe;
+  alias byteSwap leToHost;
+} else {
+  alias byteSwap hostToNet;
+  alias byteSwap netToHost;
+  alias doNothing hostToLe;
+  alias doNothing leToHost;
+}
+
+unittest {
+  import std.exception;
+
+  IntBuf!short s;
+  s.bytes = [1, 2];
+  s.value = byteSwap(s.value);
+  enforce(s.bytes == [2, 1]);
+
+  IntBuf!int i;
+  i.bytes = [1, 2, 3, 4];
+  i.value = byteSwap(i.value);
+  enforce(i.bytes == [4, 3, 2, 1]);
+
+  IntBuf!long l;
+  l.bytes = [1, 2, 3, 4, 5, 6, 7, 8];
+  l.value = byteSwap(l.value);
+  enforce(l.bytes == [8, 7, 6, 5, 4, 3, 2, 1]);
+}

Added: thrift/trunk/lib/d/src/thrift/internal/resource_pool.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/resource_pool.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/resource_pool.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/resource_pool.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.internal.resource_pool;
+
+import core.time : Duration, dur, TickDuration;
+import std.algorithm : minPos, reduce, remove;
+import std.array : array, empty;
+import std.exception : enforce;
+import std.conv : to;
+import std.random : randomCover, rndGen;
+import std.range : zip;
+import thrift.internal.algorithm : removeEqual;
+
+/**
+ * A pool of resources, which can be iterated over, and where resources that
+ * have failed too often can be temporarily disabled.
+ *
+ * This class is oblivious to the actual resource type managed.
+ */
+final class TResourcePool(Resource) {
+  /**
+   * Constructs a new instance.
+   *
+   * Params:
+   *   resources = The initial members of the pool.
+   */
+  this(Resource[] resources) {
+    resources_ = resources;
+  }
+
+  /**
+   * Adds a resource to the pool.
+   */
+  void add(Resource resource) {
+    resources_ ~= resource;
+  }
+
+  /**
+   * Removes a resource from the pool.
+   *
+   * Returns: Whether the resource could be found in the pool.
+   */
+  bool remove(Resource resource) {
+    auto oldLength = resources_.length;
+    resources_ = removeEqual(resources_, resource);
+    return resources_.length < oldLength;
+  }
+
+  /**
+   * Returns an »enriched« input range to iterate over the pool members.
+   */
+  struct Range {
+    /**
+     * Whether the range is empty.
+     *
+     * This is the case if all members of the pool have been popped (or skipped
+     * because they were disabled) and TResourcePool.cycle is false, or there
+     * is no element to return in cycle mode because all have been temporarily
+     * disabled.
+     */
+    bool empty() @property {
+      // If no resources are in the pool, the range will never become non-empty.
+      if (resources_.empty) return true;
+
+      // If we already got the next resource in the cache, it doesn't matter
+      // whether there are more.
+      if (cached_) return false;
+
+      size_t examineCount;
+      if (parent_.cycle) {
+        // We want to check all the resources, but not iterate more than once
+        // to avoid spinning in a loop if nothing is available.
+        examineCount = resources_.length;
+      } else {
+        // When not in cycle mode, we just iterate the list exactly once. If all
+        // items have been consumed, the interval below is empty.
+        examineCount = resources_.length - nextIndex_;
+      }
+
+      foreach (i; 0 .. examineCount) {
+        auto r = resources_[(nextIndex_ + i) % resources_.length];
+        auto fi = r in parent_.faultInfos_;
+
+        if (fi && fi.resetTime != fi.resetTime.init) {
+          // The argument to < needs to be an lvalue…
+          auto currentTick = TickDuration.currSystemTick;
+          if (fi.resetTime < currentTick) {
+            // The timeout expired, remove the resource from the list and go
+            // ahead trying it.
+            parent_.faultInfos_.remove(r);
+          } else {
+            // The timeout didn't expire yet, try the next resource.
+            continue;
+          }
+        }
+
+        cache_ = r;
+        cached_ = true;
+        nextIndex_ = nextIndex_ + i + 1;
+        return false;
+      }
+
+      // If we get here, all resources are currently inactive or the non-cycle
+      // pool has been exhausted, so there is nothing we can do.
+      nextIndex_ = nextIndex_ + examineCount;
+      return true;
+    }
+
+    /**
+     * Returns the first resource in the range.
+     */
+    Resource front() @property {
+      enforce(!empty);
+      return cache_;
+    }
+
+    /**
+     * Removes the first resource from the range.
+     *
+     * Usually, this is combined with a call to TResourcePool.recordSuccess()
+     * or recordFault().
+     */
+    void popFront() {
+      enforce(!empty);
+      cached_ = false;
+    }
+
+    /**
+     * Returns whether the range will become non-empty at some point in the
+     * future, and provides additional information when this will happen and
+     * what will be the next resource.
+     *
+     * Makes only sense to call on empty ranges.
+     *
+     * Params:
+     *   next = The next resource that will become available.
+     *   waitTime = The duration until that resource will become available.
+     */
+    bool willBecomeNonempty(out Resource next, out Duration waitTime) {
+      // If no resources are in the pool, the range will never become non-empty.
+      if (resources_.empty) return true;
+
+      // If cycle mode is not enabled, a range never becomes non-empty after
+      // being empty once, because all the elements have already been
+      // used/skipped in order to become empty.
+      if (!parent_.cycle) return false;
+
+      auto fi = parent_.faultInfos_;
+      auto nextPair = minPos!"a[1].resetTime < b[1].resetTime"(
+        zip(fi.keys, fi.values)
+      ).front;
+
+      next = nextPair[0];
+      waitTime = to!Duration(nextPair[1].resetTime - TickDuration.currSystemTick);
+
+      return true;
+    }
+
+  private:
+    this(TResourcePool parent, Resource[] resources) {
+      parent_ = parent;
+      resources_ = resources;
+    }
+
+    TResourcePool parent_;
+
+    /// All available resources. We keep a copy of it as to not get confused
+    /// when resources are added to/removed from the parent pool.
+    Resource[] resources_;
+
+    /// After we have determined the next element in empty(), we store it here.
+    Resource cache_;
+
+    /// Whether there is currently something in the cache.
+    bool cached_;
+
+    /// The index to start searching from at the next call to empty().
+    size_t nextIndex_;
+  }
+
+  /// Ditto
+  Range opSlice() {
+    auto res = resources_;
+    if (permute) {
+      res = array(randomCover(res, rndGen));
+    }
+    return Range(this, res);
+  }
+
+  /**
+   * Records a success for an operation on the given resource, cancelling a
+   * fault streak, if any.
+   */
+  void recordSuccess(Resource resource) {
+    if (resource in faultInfos_) {
+      faultInfos_.remove(resource);
+    }
+  }
+
+  /**
+   * Records a fault for the given resource.
+   *
+   * If a resource fails consecutively for more than faultDisableCount times,
+   * it is temporarily disabled (no longer considered) until
+   * faultDisableDuration has passed.
+   */
+  void recordFault(Resource resource) {
+    auto fi = resource in faultInfos_;
+
+    if (!fi) {
+      faultInfos_[resource] = FaultInfo();
+      fi = resource in faultInfos_;
+    }
+
+    ++fi.count;
+    if (fi.count >= faultDisableCount) {
+      // If the resource has hit the fault count limit, disable it for
+      // specified duration.
+      fi.resetTime = TickDuration.currSystemTick +
+        TickDuration.from!"hnsecs"(faultDisableDuration.total!"hnsecs");
+    }
+  }
+
+  /**
+   * Whether to randomly permute the order of the resources in the pool when
+   * taking a range using opSlice().
+   *
+   * This can be used e.g. as a simple form of load balancing.
+   */
+  bool permute = true;
+
+  /**
+   * Whether to keep iterating over the pool members after all have been
+   * returned/have failed once.
+   */
+  bool cycle = false;
+
+  /**
+   * The number of consecutive faults after which a resource is disabled until
+   * faultDisableDuration has passed. Zero to never disable resources.
+   *
+   * Defaults to zero.
+   */
+  ushort faultDisableCount = 0;
+
+  /**
+   * The duration for which a resource is no longer considered after it has
+   * failed too often.
+   *
+   * Defaults to one second.
+   */
+  Duration faultDisableDuration = dur!"seconds"(1);
+
+private:
+  Resource[] resources_;
+  FaultInfo[Resource] faultInfos_;
+}
+
+private {
+  struct FaultInfo {
+    ushort count;
+    TickDuration resetTime;
+  }
+}
+
+import std.datetime;
+import thrift.base;
+
+unittest {
+  import core.thread;
+
+  auto a = new Object;
+  auto b = new Object;
+  auto c = new Object;
+  auto objs = [a, b, c];
+  auto pool = new TResourcePool!Object(objs);
+  pool.permute = false;
+  pool.faultDisableDuration = dur!"msecs"(5);
+  Object dummyRes = void;
+  Duration dummyDur = void;
+
+  {
+    auto r = pool[];
+
+    foreach (i, o; objs) {
+      enforce(!r.empty);
+      enforce(r.front == o);
+      r.popFront();
+    }
+
+    enforce(r.empty);
+    enforce(!r.willBecomeNonempty(dummyRes, dummyDur));
+  }
+
+  {
+    pool.faultDisableCount = 2;
+
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+    enforce(pool[].front == a);
+    pool.recordSuccess(a);
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+
+    auto r = pool[];
+    enforce(r.front == b);
+    r.popFront();
+    enforce(r.front == c);
+    r.popFront();
+    enforce(r.empty);
+    enforce(!r.willBecomeNonempty(dummyRes, dummyDur));
+
+    Thread.sleep(dur!"msecs"(5));
+    // Not in cycle mode, has to be still empty after the timeouts expired.
+    enforce(r.empty);
+    enforce(!r.willBecomeNonempty(dummyRes, dummyDur));
+
+    foreach (o; objs) pool.recordSuccess(o);
+  }
+
+  {
+    pool.faultDisableCount = 1;
+
+    pool.recordFault(a);
+    Thread.sleep(dur!"usecs"(1));
+    pool.recordFault(b);
+    Thread.sleep(dur!"usecs"(1));
+    pool.recordFault(c);
+
+    auto r = pool[];
+    enforce(r.empty);
+    enforce(!r.willBecomeNonempty(dummyRes, dummyDur));
+
+    foreach (o; objs) pool.recordSuccess(o);
+  }
+
+  pool.cycle = true;
+
+  {
+    auto r = pool[];
+
+    foreach (o; objs ~ objs) {
+      enforce(!r.empty);
+      enforce(r.front == o);
+      r.popFront();
+    }
+  }
+
+  {
+    pool.faultDisableCount = 2;
+
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+    enforce(pool[].front == a);
+    pool.recordSuccess(a);
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+    enforce(pool[].front == a);
+    pool.recordFault(a);
+
+    auto r = pool[];
+    enforce(r.front == b);
+    r.popFront();
+    enforce(r.front == c);
+    r.popFront();
+    enforce(r.front == b);
+
+    Thread.sleep(dur!"msecs"(5));
+
+    r.popFront();
+    enforce(r.front == c);
+
+    r.popFront();
+    enforce(r.front == a);
+
+    enforce(pool[].front == a);
+
+    foreach (o; objs) pool.recordSuccess(o);
+  }
+
+  {
+    pool.faultDisableCount = 1;
+
+    pool.recordFault(a);
+    Thread.sleep(dur!"usecs"(1));
+    pool.recordFault(b);
+    Thread.sleep(dur!"usecs"(1));
+    pool.recordFault(c);
+
+    auto r = pool[];
+    enforce(r.empty);
+
+    Object nextRes;
+    Duration nextWait;
+    enforce(r.willBecomeNonempty(nextRes, nextWait));
+    enforce(nextRes == a);
+    enforce(nextWait > dur!"hnsecs"(0));
+
+    foreach (o; objs) pool.recordSuccess(o);
+  }
+}

Added: thrift/trunk/lib/d/src/thrift/internal/socket.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/socket.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/socket.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/socket.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Abstractions over OS-dependent socket functionality.
+ */
+module thrift.internal.socket;
+
+import std.conv : to;
+
+// FreeBSD and OS X return -1 and set ECONNRESET if socket was closed by
+// the other side, we need to check for that before throwing an exception.
+version (FreeBSD) {
+  enum connresetOnPeerShutdown = true;
+} else version (OSX) {
+  enum connresetOnPeerShutdown = true;
+} else {
+  enum connresetOnPeerShutdown = false;
+}
+
+version (Win32) {
+  import std.c.windows.winsock : WSAGetLastError, WSAEINTR, WSAEWOULDBLOCK;
+  import std.windows.syserror : sysErrorString;
+
+  // These are unfortunately not defined in std.c.windows.winsock, see
+  // http://msdn.microsoft.com/en-us/library/ms740668.aspx.
+  enum WSAECONNRESET = 10054;
+  enum WSAENOTCONN = 10057;
+  enum WSAETIMEDOUT = 10060;
+} else {
+  import core.stdc.errno : getErrno, EAGAIN, ECONNRESET, EINPROGRESS, EINTR,
+    ENOTCONN, EPIPE;
+  import core.stdc.string : strerror;
+}
+
+/*
+ * CONNECT_INPROGRESS_ERRNO: set by connect() for non-blocking sockets if the
+ *   connection could not be immediately established.
+ * INTERRUPTED_ERRNO: set when blocking system calls are interrupted by
+ *   signals or similar.
+ * TIMEOUT_ERRNO: set when a socket timeout has been exceeded.
+ * WOULD_BLOCK_ERRNO: set when send/recv would block on non-blocking sockets.
+ *
+ * isSocetCloseErrno(errno): returns true if errno indicates that the socket
+ *   is logically in closed state now.
+ */
+version (Win32) {
+  alias WSAGetLastError getSocketErrno;
+  enum CONNECT_INPROGRESS_ERRNO = WSAEWOULDBLOCK;
+  enum INTERRUPTED_ERRNO = WSAEINTR;
+  enum TIMEOUT_ERRNO = WSAETIMEDOUT;
+  enum WOULD_BLOCK_ERRNO = WSAEWOULDBLOCK;
+
+  bool isSocketCloseErrno(typeof(getSocketErrno()) errno) {
+    return (errno == WSAECONNRESET || errno == WSAENOTCONN);
+  }
+} else {
+  alias getErrno getSocketErrno;
+  enum CONNECT_INPROGRESS_ERRNO = EINPROGRESS;
+  enum INTERRUPTED_ERRNO = EINTR;
+  enum WOULD_BLOCK_ERRNO = EAGAIN;
+
+  // TODO: The C++ TSocket implementation mentions that EAGAIN can also be
+  // set (undocumentedly) in out of resource conditions; it would be a good
+  // idea to contact the original authors of the C++ code for details and adapt
+  // the code accordingly.
+  enum TIMEOUT_ERRNO = EAGAIN;
+
+  bool isSocketCloseErrno(typeof(getSocketErrno()) errno) {
+    return (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN);
+  }
+}
+
+string socketErrnoString(uint errno) {
+  version (Win32) {
+    return sysErrorString(errno);
+  } else {
+    return to!string(strerror(errno));
+  }
+}

Added: thrift/trunk/lib/d/src/thrift/internal/ssl.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/ssl.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/ssl.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/ssl.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.internal.ssl;
+
+import core.memory : GC;
+import core.stdc.config;
+import core.stdc.errno : getErrno;
+import core.stdc.string : strerror;
+import deimos.openssl.err;
+import deimos.openssl.ssl;
+import deimos.openssl.x509v3;
+import std.array : empty, appender;
+import std.conv : to;
+import std.socket : Address;
+import thrift.transport.ssl;
+
+/**
+ * Checks if the peer is authorized after the SSL handshake has been
+ * completed on the given conncetion and throws an TSSLException if not.
+ *
+ * Params:
+ *   ssl = The SSL connection to check.
+ *   accessManager = The access manager to check the peer againts.
+ *   peerAddress = The (IP) address of the peer.
+ *   hostName = The host name of the peer.
+ */
+void authorize(SSL* ssl, TAccessManager accessManager,
+  Address peerAddress, lazy string hostName
+) {
+  alias TAccessManager.Decision Decision;
+
+  auto rc = SSL_get_verify_result(ssl);
+  if (rc != X509_V_OK) {
+    throw new TSSLException("SSL_get_verify_result(): " ~
+      to!string(X509_verify_cert_error_string(rc)));
+  }
+
+  auto cert = SSL_get_peer_certificate(ssl);
+  if (cert is null) {
+    // Certificate is not present.
+    if (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
+      throw new TSSLException(
+        "Authorize: Required certificate not present.");
+    }
+
+    // If we don't have an access manager set, we don't intend to authorize
+    // the client, so everything's fine.
+    if (accessManager) {
+      throw new TSSLException(
+        "Authorize: Certificate required for authorization.");
+    }
+    return;
+  }
+
+  if (accessManager is null) {
+    // No access manager set, can return immediately as the cert is valid
+    // and all peers are authorized.
+    X509_free(cert);
+    return;
+  }
+
+  // both certificate and access manager are present
+  auto decision = accessManager.verify(peerAddress);
+
+  if (decision != Decision.SKIP) {
+    X509_free(cert);
+    if (decision != Decision.ALLOW) {
+      throw new TSSLException("Authorize: Access denied based on remote IP.");
+    }
+    return;
+  }
+
+  // Check subjectAltName(s), if present.
+  auto alternatives = cast(STACK_OF!(GENERAL_NAME)*)
+    X509_get_ext_d2i(cert, NID_subject_alt_name, null, null);
+  if (alternatives != null) {
+    auto count = sk_GENERAL_NAME_num(alternatives);
+    for (int i = 0; decision == Decision.SKIP && i < count; i++) {
+      auto name = sk_GENERAL_NAME_value(alternatives, i);
+      if (name is null) {
+        continue;
+      }
+      auto data = ASN1_STRING_data(name.d.ia5);
+      auto length = ASN1_STRING_length(name.d.ia5);
+      switch (name.type) {
+        case GENERAL_NAME.GEN_DNS:
+          decision = accessManager.verify(hostName, cast(char[])data[0 .. length]);
+          break;
+        case GENERAL_NAME.GEN_IPADD:
+          decision = accessManager.verify(peerAddress, data[0 .. length]);
+          break;
+        default:
+          // Do nothing.
+      }
+    }
+
+    // DMD @@BUG@@: Empty template arguments parens should not be needed.
+    sk_GENERAL_NAME_pop_free!()(alternatives, &GENERAL_NAME_free);
+  }
+
+  // If we are alredy done, return.
+  if (decision != Decision.SKIP) {
+    X509_free(cert);
+    if (decision != Decision.ALLOW) {
+      throw new TSSLException("Authorize: Access denied.");
+    }
+    return;
+  }
+
+  // Check commonName.
+  auto name = X509_get_subject_name(cert);
+  if (name !is null) {
+    X509_NAME_ENTRY* entry;
+    char* utf8;
+    int last = -1;
+    while (decision == Decision.SKIP) {
+      last = X509_NAME_get_index_by_NID(name, NID_commonName, last);
+      if (last == -1)
+        break;
+      entry = X509_NAME_get_entry(name, last);
+      if (entry is null)
+        continue;
+      auto common = X509_NAME_ENTRY_get_data(entry);
+      auto size = ASN1_STRING_to_UTF8(&utf8, common);
+      decision = accessManager.verify(hostName, utf8[0 .. size]);
+      CRYPTO_free(utf8);
+    }
+  }
+  X509_free(cert);
+  if (decision != Decision.ALLOW) {
+    throw new TSSLException("Authorize: Could not authorize peer.");
+  }
+}
+
+/*
+ * OpenSSL error information used for storing D exceptions on the OpenSSL
+ * error stack.
+ */
+enum ERR_LIB_D_EXCEPTION = ERR_LIB_USER;
+enum ERR_F_D_EXCEPTION = 0; // function id - what to use here?
+enum ERR_R_D_EXCEPTION = 1234; // 99 and above are reserved for applications
+enum ERR_FILE_D_EXCEPTION = "d_exception";
+enum ERR_LINE_D_EXCEPTION = 0;
+enum ERR_FLAGS_D_EXCEPTION = 0;
+
+/**
+ * Returns an exception for the last.
+ *
+ * Params:
+ *   location = An optional "location" to add to the error message (typically
+ *     the last SSL API call).
+ */
+Exception getSSLException(string location = null, string clientFile = __FILE__,
+  size_t clientLine = __LINE__
+) {
+  // We can return either an exception saved from D BIO code, or a "true"
+  // OpenSSL error. Because there can possibly be more than one error on the
+  // error stack, we have to fetch all of them, and pick the last, i.e. newest
+  // one. We concatenate multiple successive OpenSSL error messages into a
+  // single one, but always just return the last D expcetion.
+  string message; // Probably better use an Appender here.
+  bool hadMessage;
+  Exception exception;
+
+  void initMessage() {
+    message.clear();
+    hadMessage = false;
+    if (!location.empty) {
+      message ~= location;
+      message ~= ": ";
+    }
+  }
+  initMessage();
+
+  auto errn = getErrno();
+
+  const(char)* file = void;
+  int line = void;
+  const(char)* data = void;
+  int flags = void;
+  c_ulong code = void;
+  while ((code = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
+    if (ERR_GET_REASON(code) == ERR_R_D_EXCEPTION) {
+      initMessage();
+      GC.removeRoot(cast(void*)data);
+      exception = cast(Exception)data;
+    } else {
+      exception = null;
+
+      if (hadMessage) {
+        message ~= ", ";
+      }
+
+      auto reason = ERR_reason_error_string(code);
+      if (reason) {
+        message ~= "SSL error: " ~ to!string(reason);
+      } else {
+        message ~= "SSL error #" ~ to!string(code);
+      }
+
+      hadMessage = true;
+    }
+  }
+
+  // If the last item from the stack was a D exception, throw it.
+  if (exception) return exception;
+
+  // We are dealing with an OpenSSL error that doesn't root in a D exception.
+  if (!hadMessage) {
+    // If we didn't get an actual error from the stack yet, try errno.
+    string errnString;
+    if (errn != 0) {
+      errnString = to!string(strerror(errn));
+    }
+    if (errnString.empty) {
+      message ~= "Unknown error";
+    } else {
+      message ~= errnString;
+    }
+  }
+
+  message ~= ".";
+  return new TSSLException(message, clientFile, clientLine);
+}

Added: thrift/trunk/lib/d/src/thrift/internal/ssl_bio.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/ssl_bio.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/ssl_bio.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/ssl_bio.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides a SSL BIO implementation wrapping a Thrift transport.
+ *
+ * This way, SSL I/O can be relayed over Thrift transport without introducing
+ * an additional layer of buffering, especially for the non-blocking
+ * transports.
+ *
+ * For the Thrift transport incarnations of the SSL entities, "tt" is used as
+ * prefix for clarity.
+ */
+module thrift.internal.ssl_bio;
+
+import core.stdc.config;
+import core.stdc.string : strlen;
+import core.memory : GC;
+import deimos.openssl.bio;
+import deimos.openssl.err;
+import thrift.base;
+import thrift.internal.ssl;
+import thrift.transport.base;
+
+/**
+ * Creates an SSL BIO object wrapping the given transport.
+ *
+ * Exceptions thrown by the transport are pushed onto the OpenSSL error stack,
+ * using the location/reason values from thrift.internal.ssl.ERR_*_D_EXCEPTION.
+ *
+ * The transport is assumed to be ready for reading and writing when the BIO
+ * functions are called, it is not opened by the implementation.
+ *
+ * Params:
+ *   transport = The transport to wrap.
+ *   closeTransport = Whether the close the transport when the SSL BIO is
+ *     closed.
+ */
+BIO* createTTransportBIO(TTransport transport, bool closeTransport) {
+  auto result = BIO_new(cast(BIO_METHOD*)&ttBioMethod);
+  if (!result) return null;
+
+  GC.addRoot(cast(void*)transport);
+  BIO_set_fd(result, closeTransport, cast(c_long)cast(void*)transport);
+
+  return result;
+}
+
+private {
+  // Helper to get the Thrift transport assigned with the given BIO.
+  TTransport trans(BIO* b) nothrow {
+    auto result = cast(TTransport)b.ptr;
+    assert(result);
+    return result;
+  }
+
+  void setError(Exception e) nothrow {
+    ERR_put_error(ERR_LIB_D_EXCEPTION, ERR_F_D_EXCEPTION, ERR_R_D_EXCEPTION,
+      ERR_FILE_D_EXCEPTION, ERR_LINE_D_EXCEPTION);
+    try { GC.addRoot(cast(void*)e); } catch {}
+    ERR_set_error_data(cast(char*)e, ERR_FLAGS_D_EXCEPTION);
+  }
+
+  extern(C) int ttWrite(BIO* b, const(char)* data, int length) nothrow {
+    assert(b);
+    if (!data || length <= 0) return 0;
+    try {
+      trans(b).write((cast(ubyte*)data)[0 .. length]);
+      return length;
+    } catch (Exception e) {
+      setError(e);
+      return -1;
+    }
+  }
+
+  extern(C) int ttRead(BIO* b, char* data, int length) nothrow {
+    assert(b);
+    if (!data || length <= 0) return 0;
+    try {
+      return cast(int)trans(b).read((cast(ubyte*)data)[0 .. length]);
+    } catch (Exception e) {
+      setError(e);
+      return -1;
+    }
+  }
+
+  extern(C) int ttPuts(BIO* b, const(char)* str) nothrow {
+    return ttWrite(b, str, cast(int)strlen(str));
+  }
+
+  extern(C) c_long ttCtrl(BIO* b, int cmd, c_long num, void* ptr) nothrow {
+    assert(b);
+
+    switch (cmd) {
+      case BIO_C_SET_FD:
+        // Note that close flag and "fd" are actually reversed here because we
+        // need 64 bit width for the pointer – should probably drop BIO_set_fd
+        // altogether.
+        ttDestroy(b);
+        b.ptr = cast(void*)num;
+        b.shutdown = cast(int)ptr;
+        b.init_ = 1;
+        return 1;
+      case BIO_C_GET_FD:
+        if (!b.init_) return -1;
+        *(cast(void**)ptr) = b.ptr;
+        return cast(c_long)b.ptr;
+      case BIO_CTRL_GET_CLOSE:
+        return b.shutdown;
+      case BIO_CTRL_SET_CLOSE:
+        b.shutdown = cast(int)num;
+        return 1;
+      case BIO_CTRL_FLUSH:
+        try {
+          trans(b).flush();
+          return 1;
+        } catch (Exception e) {
+          setError(e);
+          return -1;
+        }
+      case BIO_CTRL_DUP:
+        // Seems like we have nothing to do on duplication, but couldn't find
+        // any documentation if this actually ever happens during normal SSL
+        // usage.
+        return 1;
+      default:
+        return 0;
+    }
+  }
+
+  extern(C) int ttCreate(BIO* b) nothrow {
+    assert(b);
+    b.init_ = 0;
+    b.num = 0; // User-defined number field, unused here.
+    b.ptr = null;
+    b.flags = 0;
+    return 1;
+  }
+
+  extern(C) int ttDestroy(BIO* b) nothrow {
+    if (!b) return 0;
+
+    int rc = 1;
+    if (b.shutdown) {
+      if (b.init_) {
+        try {
+          trans(b).close();
+          GC.removeRoot(cast(void*)trans(b));
+          b.ptr = null;
+        } catch (Exception e) {
+          setError(e);
+          rc = -1;
+        }
+      }
+      b.init_ = 0;
+      b.flags = 0;
+    }
+
+    return rc;
+  }
+
+  immutable BIO_METHOD ttBioMethod = {
+    BIO_TYPE_SOURCE_SINK,
+    "TTransport",
+    &ttWrite,
+    &ttRead,
+    &ttPuts,
+    null, // gets
+    &ttCtrl,
+    &ttCreate,
+    &ttDestroy,
+    null // callback_ctrl
+  };
+}

Added: thrift/trunk/lib/d/src/thrift/internal/test/protocol.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/test/protocol.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/test/protocol.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/test/protocol.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.internal.test.protocol;
+
+import std.exception;
+import thrift.transport.memory;
+import thrift.protocol.base;
+
+version (unittest):
+
+void testContainerSizeLimit(Protocol)() if (isTProtocol!Protocol) {
+  auto buffer = new TMemoryBuffer;
+  auto prot = new Protocol(buffer);
+
+  // Make sure reading fails if a container larger than the size limit is read.
+  prot.containerSizeLimit = 3;
+
+  {
+    prot.writeListBegin(TList(TType.I32, 4));
+    prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol.
+    prot.reset();
+
+    auto e = cast(TProtocolException)collectException(prot.readListBegin());
+    enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT);
+    prot.reset();
+    buffer.reset();
+  }
+
+  {
+    prot.writeMapBegin(TMap(TType.I32, TType.I32, 4));
+    prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol.
+    prot.reset();
+
+    auto e = cast(TProtocolException)collectException(prot.readMapBegin());
+    enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT);
+    prot.reset();
+    buffer.reset();
+  }
+
+  {
+    prot.writeSetBegin(TSet(TType.I32, 4));
+    prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol.
+    prot.reset();
+
+    auto e = cast(TProtocolException)collectException(prot.readSetBegin());
+    enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT);
+    prot.reset();
+    buffer.reset();
+  }
+
+  // Make sure reading works if the containers are smaller than the limit or
+  // no limit is set.
+  foreach (limit; [3, 0, -1]) {
+    prot.containerSizeLimit = limit;
+
+    {
+      prot.writeListBegin(TList(TType.I32, 2));
+      prot.writeI32(0);
+      prot.writeI32(1);
+      prot.writeListEnd();
+      prot.reset();
+
+      auto list = prot.readListBegin();
+      enforce(list.elemType == TType.I32);
+      enforce(list.size == 2);
+      enforce(prot.readI32() == 0);
+      enforce(prot.readI32() == 1);
+      prot.readListEnd();
+
+      prot.reset();
+      buffer.reset();
+    }
+
+    {
+      prot.writeMapBegin(TMap(TType.I32, TType.I32, 2));
+      prot.writeI32(0);
+      prot.writeI32(1);
+      prot.writeI32(2);
+      prot.writeI32(3);
+      prot.writeMapEnd();
+      prot.reset();
+
+      auto map = prot.readMapBegin();
+      enforce(map.keyType == TType.I32);
+      enforce(map.valueType == TType.I32);
+      enforce(map.size == 2);
+      enforce(prot.readI32() == 0);
+      enforce(prot.readI32() == 1);
+      enforce(prot.readI32() == 2);
+      enforce(prot.readI32() == 3);
+      prot.readMapEnd();
+
+      prot.reset();
+      buffer.reset();
+    }
+
+    {
+      prot.writeSetBegin(TSet(TType.I32, 2));
+      prot.writeI32(0);
+      prot.writeI32(1);
+      prot.writeSetEnd();
+      prot.reset();
+
+      auto set = prot.readSetBegin();
+      enforce(set.elemType == TType.I32);
+      enforce(set.size == 2);
+      enforce(prot.readI32() == 0);
+      enforce(prot.readI32() == 1);
+      prot.readSetEnd();
+
+      prot.reset();
+      buffer.reset();
+    }
+  }
+}
+
+void testStringSizeLimit(Protocol)() if (isTProtocol!Protocol) {
+  auto buffer = new TMemoryBuffer;
+  auto prot = new Protocol(buffer);
+
+  // Make sure reading fails if a string larger than the size limit is read.
+  prot.stringSizeLimit = 3;
+
+  {
+    prot.writeString("asdf");
+    prot.reset();
+
+    auto e = cast(TProtocolException)collectException(prot.readString());
+    enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT);
+    prot.reset();
+    buffer.reset();
+  }
+
+  {
+    prot.writeBinary([1, 2, 3, 4]);
+    prot.reset();
+
+    auto e = cast(TProtocolException)collectException(prot.readBinary());
+    enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT);
+    prot.reset();
+    buffer.reset();
+  }
+
+  // Make sure reading works if the containers are smaller than the limit or
+  // no limit is set.
+  foreach (limit; [3, 0, -1]) {
+    prot.containerSizeLimit = limit;
+
+    {
+      prot.writeString("as");
+      prot.reset();
+
+      enforce(prot.readString() == "as");
+      prot.reset();
+      buffer.reset();
+    }
+
+    {
+      prot.writeBinary([1, 2]);
+      prot.reset();
+
+      enforce(prot.readBinary() == [1, 2]);
+      prot.reset();
+      buffer.reset();
+    }
+  }
+}

Added: thrift/trunk/lib/d/src/thrift/internal/test/server.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/test/server.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/test/server.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/test/server.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.internal.test.server;
+
+import core.sync.condition;
+import core.sync.mutex;
+import core.thread : Thread;
+import std.datetime;
+import std.exception : enforce;
+import std.typecons : WhiteHole;
+import std.variant : Variant;
+import thrift.protocol.base;
+import thrift.protocol.binary;
+import thrift.protocol.processor;
+import thrift.server.base;
+import thrift.server.transport.socket;
+import thrift.transport.base;
+import thrift.util.cancellation;
+
+version(unittest):
+
+/**
+ * Tests if serving is stopped correctly if the cancellation passed to serve()
+ * is triggered.
+ *
+ * Because the tests are run many times in a loop, this is indirectly also a
+ * test whether socket, etc. handles are cleaned up correctly, because the
+ * application will likely run out of handles otherwise.
+ */
+void testServeCancel(Server)(void delegate(Server) serverSetup = null) if (
+  is(Server : TServer)
+) {
+  auto proc = new WhiteHole!TProcessor;
+  auto tf = new TTransportFactory;
+  auto pf = new TBinaryProtocolFactory!();
+
+  // Need a special case for TNonblockingServer which doesn't use
+  // TServerTransport.
+  static if (__traits(compiles, new Server(proc, 0, tf, pf))) {
+    auto server = new Server(proc, 0, tf, pf);
+  } else {
+    auto server = new Server(proc, new TServerSocket(0), tf, pf);
+  }
+
+  // On Windows, we use TCP sockets to replace socketpair(). Since they stay
+  // in TIME_WAIT for some time even if they are properly closed, we have to use
+  // a lower number of iterations to avoid running out of ports/buffer space.
+  version (Windows) {
+    enum ITERATIONS = 100;
+  } else {
+    enum ITERATIONS = 10000;
+  }
+
+  if (serverSetup) serverSetup(server);
+
+  auto servingMutex = new Mutex;
+  auto servingCondition = new Condition(servingMutex);
+  auto doneMutex = new Mutex;
+  auto doneCondition = new Condition(doneMutex);
+
+  class CancellingHandler : TServerEventHandler {
+    void preServe() {
+      synchronized (servingMutex) {
+        servingCondition.notifyAll();
+      }
+    }
+    Variant createContext(TProtocol input, TProtocol output) { return Variant.init; }
+    void deleteContext(Variant serverContext, TProtocol input, TProtocol output) {}
+    void preProcess(Variant serverContext, TTransport transport) {}
+  }
+  server.eventHandler = new CancellingHandler;
+
+  foreach (i; 0 .. ITERATIONS) {
+    synchronized (servingMutex) {
+      auto cancel = new TCancellationOrigin;
+      synchronized (doneMutex) {
+        auto serverThread = new Thread({
+          server.serve(cancel);
+          synchronized (doneMutex) {
+            doneCondition.notifyAll();
+          }
+        });
+        serverThread.isDaemon = true;
+        serverThread.start();
+
+        servingCondition.wait();
+
+        cancel.trigger();
+        enforce(doneCondition.wait(dur!"msecs"(100)));
+        serverThread.join();
+      }
+    }
+  }
+}

Added: thrift/trunk/lib/d/src/thrift/internal/traits.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/internal/traits.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/internal/traits.d (added)
+++ thrift/trunk/lib/d/src/thrift/internal/traits.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module thrift.internal.traits;
+
+import std.traits;
+
+/**
+ * Adds or removes attributes from the given function (pointer) or delegate
+ * type.
+ *
+ * Besides the base type, two std.traits.FunctionAttribute bitfields are
+ * accepted, representing the attributes to add to the function signature
+ * resp. to remove from it. If an attribute appears in both fields, an error
+ * is raised.
+ *
+ * Params:
+ *   T = The base type.
+ *   setAttrs = The attributes to add to the type, if not already present.
+ *   clearAttrs = The attributes to remove from the type, if present.
+ */
+template ChangeFuncAttrs(
+  T,
+  FunctionAttribute setAttrs = FunctionAttribute.none,
+  FunctionAttribute clearAttrs = FunctionAttribute.none
+) if (isFunctionPointer!T || isDelegate!T) {
+  static assert(!(setAttrs & clearAttrs),
+    "Cannot set and clear attributes at the same time.");
+  mixin({
+    enum newAttrs = (functionAttributes!T | setAttrs) & ~clearAttrs;
+    static assert(!(newAttrs & FunctionAttribute.trusted) ||
+      !(newAttrs & FunctionAttribute.safe),
+      "Cannot have a function/delegate that is both trusted and safe.");
+
+    string result = "alias ";
+
+    static if (functionLinkage!T != "D") {
+      result ~= "extern(" ~ functionLinkage!T ~ ") ";
+    }
+
+    static if (newAttrs & FunctionAttribute.ref_) {
+      result ~= "ref ";
+    }
+
+    result ~= "ReturnType!T";
+
+    static if (isDelegate!T) {
+      result ~= " delegate";
+    } else {
+      result ~= " function";
+    }
+
+    result ~= "(ParameterTypeTuple!T)";
+
+    static if (newAttrs & FunctionAttribute.pure_) {
+      result ~= " pure";
+    }
+    static if (newAttrs & FunctionAttribute.nothrow_) {
+      result ~= " nothrow";
+    }
+    static if (newAttrs & FunctionAttribute.property) {
+      result ~= " @property";
+    }
+    static if (newAttrs & FunctionAttribute.trusted) {
+      result ~= " @trusted";
+    }
+    static if (newAttrs & FunctionAttribute.safe) {
+      result ~= " @safe";
+    }
+
+    result ~= " ChangeFuncAttrs;";
+    return result;
+  }());
+}
+
+/// Ditto
+template ChangeFuncAttrs(
+  T,
+  FunctionAttribute setAttrs = FunctionAttribute.none,
+  FunctionAttribute clearAttrs = FunctionAttribute.none
+) if (is(T == function)) {
+  // To avoid a lot of syntactic headaches, we just use the above version to
+  // operate on the corresponding function pointer type and then remove the
+  // pointer again.
+  alias FunctionTypeOf!(ChangeFuncAttrs!(T*, setAttrs, clearAttrs))
+    ChangeFuncAttrs;
+}
+
+version (unittest) {
+  import std.algorithm;
+  import std.metastrings;
+  import std.typetuple;
+}
+unittest {
+  alias FunctionAttribute FA;
+  foreach (T0; TypeTuple!(
+    int function(int),
+    int delegate(int),
+    FunctionTypeOf!(int function(int))
+  )) {
+    alias ChangeFuncAttrs!(T0, FA.safe) T1;
+    static assert(functionAttributes!T1 == FA.safe);
+
+    alias ChangeFuncAttrs!(T1, FA.nothrow_ | FA.ref_, FA.safe) T2;
+    static assert(functionAttributes!T2 == (FA.nothrow_ | FA.ref_));
+
+    enum allAttrs = reduce!"a | b"([EnumMembers!FA]) & ~FA.safe;
+
+    alias ChangeFuncAttrs!(T2, allAttrs) T3;
+    static assert(functionAttributes!T3 == allAttrs);
+
+    alias ChangeFuncAttrs!(T3, FA.none, allAttrs) T4;
+    static assert(is(T4 == T0));
+  }
+}
+
+/**
+ * Adds »nothrow« to the type of the passed function pointer/delegate, if it
+ * is not already present.
+ *
+ * Technically, assumeNothrow just performs a cast, but using it has the
+ * advantage of being explicitly about the operation that is performed.
+ */
+auto assumeNothrow(T)(T t) if (isFunctionPointer!T || isDelegate!T) {
+  return cast(ChangeFuncAttrs!(T, FunctionAttribute.nothrow_))t;
+}