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;
+}