mirror of https://github.com/grpc/grpc.git
commit
bb88b9007f
113 changed files with 6605 additions and 539 deletions
@ -0,0 +1,222 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
|
||||||
|
// Copyright 2015, Google Inc. |
||||||
|
// All rights reserved. |
||||||
|
// |
||||||
|
// Redistribution and use in source and binary forms, with or without |
||||||
|
// modification, are permitted provided that the following conditions are |
||||||
|
// met: |
||||||
|
// |
||||||
|
// * Redistributions of source code must retain the above copyright |
||||||
|
// notice, this list of conditions and the following disclaimer. |
||||||
|
// * Redistributions in binary form must reproduce the above |
||||||
|
// copyright notice, this list of conditions and the following disclaimer |
||||||
|
// in the documentation and/or other materials provided with the |
||||||
|
// distribution. |
||||||
|
// * Neither the name of Google Inc. nor the names of its |
||||||
|
// contributors may be used to endorse or promote products derived from |
||||||
|
// this software without specific prior written permission. |
||||||
|
// |
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using Grpc.Core.Internal; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace Grpc.Core.Internal.Tests |
||||||
|
{ |
||||||
|
public class AsyncCallTest |
||||||
|
{ |
||||||
|
Channel channel; |
||||||
|
FakeNativeCall fakeCall; |
||||||
|
AsyncCall<string, string> asyncCall; |
||||||
|
|
||||||
|
[SetUp] |
||||||
|
public void Init() |
||||||
|
{ |
||||||
|
channel = new Channel("localhost", Credentials.Insecure); |
||||||
|
|
||||||
|
fakeCall = new FakeNativeCall(); |
||||||
|
|
||||||
|
var callDetails = new CallInvocationDetails<string, string>(channel, "someMethod", null, Marshallers.StringMarshaller, Marshallers.StringMarshaller, new CallOptions()); |
||||||
|
asyncCall = new AsyncCall<string, string>(callDetails, fakeCall); |
||||||
|
} |
||||||
|
|
||||||
|
[TearDown] |
||||||
|
public void Cleanup() |
||||||
|
{ |
||||||
|
channel.ShutdownAsync().Wait(); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void AsyncUnary_CompletionSuccess() |
||||||
|
{ |
||||||
|
var resultTask = asyncCall.UnaryCallAsync("abc"); |
||||||
|
fakeCall.UnaryResponseClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()), new byte[] { 1, 2, 3 }, new Metadata()); |
||||||
|
Assert.IsTrue(resultTask.IsCompleted); |
||||||
|
Assert.IsTrue(fakeCall.IsDisposed); |
||||||
|
Assert.AreEqual(Status.DefaultSuccess, asyncCall.GetStatus()); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void AsyncUnary_CompletionFailure() |
||||||
|
{ |
||||||
|
var resultTask = asyncCall.UnaryCallAsync("abc"); |
||||||
|
fakeCall.UnaryResponseClientHandler(false, new ClientSideStatus(new Status(StatusCode.Internal, ""), null), new byte[] { 1, 2, 3 }, new Metadata()); |
||||||
|
|
||||||
|
Assert.IsTrue(resultTask.IsCompleted); |
||||||
|
Assert.IsTrue(fakeCall.IsDisposed); |
||||||
|
|
||||||
|
Assert.AreEqual(StatusCode.Internal, asyncCall.GetStatus().StatusCode); |
||||||
|
Assert.IsNull(asyncCall.GetTrailers()); |
||||||
|
var ex = Assert.Throws<RpcException>(() => resultTask.GetAwaiter().GetResult()); |
||||||
|
Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode); |
||||||
|
} |
||||||
|
|
||||||
|
internal class FakeNativeCall : INativeCall |
||||||
|
{ |
||||||
|
public UnaryResponseClientHandler UnaryResponseClientHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public ReceivedStatusOnClientHandler ReceivedStatusOnClientHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public ReceivedMessageHandler ReceivedMessageHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public ReceivedResponseHeadersHandler ReceivedResponseHeadersHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public SendCompletionHandler SendCompletionHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsCancelled |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsDisposed |
||||||
|
{ |
||||||
|
get; |
||||||
|
set; |
||||||
|
} |
||||||
|
|
||||||
|
public void Cancel() |
||||||
|
{ |
||||||
|
IsCancelled = true; |
||||||
|
} |
||||||
|
|
||||||
|
public void CancelWithStatus(Status status) |
||||||
|
{ |
||||||
|
IsCancelled = true; |
||||||
|
} |
||||||
|
|
||||||
|
public string GetPeer() |
||||||
|
{ |
||||||
|
return "PEER"; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) |
||||||
|
{ |
||||||
|
UnaryResponseClientHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) |
||||||
|
{ |
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray) |
||||||
|
{ |
||||||
|
UnaryResponseClientHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) |
||||||
|
{ |
||||||
|
ReceivedStatusOnClientHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray) |
||||||
|
{ |
||||||
|
ReceivedStatusOnClientHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartReceiveMessage(ReceivedMessageHandler callback) |
||||||
|
{ |
||||||
|
ReceivedMessageHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback) |
||||||
|
{ |
||||||
|
ReceivedResponseHeadersHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray) |
||||||
|
{ |
||||||
|
SendCompletionHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata) |
||||||
|
{ |
||||||
|
SendCompletionHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartSendCloseFromClient(SendCompletionHandler callback) |
||||||
|
{ |
||||||
|
SendCompletionHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) |
||||||
|
{ |
||||||
|
SendCompletionHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void StartServerSide(ReceivedCloseOnServerHandler callback) |
||||||
|
{ |
||||||
|
ReceivedCloseOnServerHandler = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
IsDisposed = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
|
||||||
|
// Copyright 2015, Google Inc. |
||||||
|
// All rights reserved. |
||||||
|
// |
||||||
|
// Redistribution and use in source and binary forms, with or without |
||||||
|
// modification, are permitted provided that the following conditions are |
||||||
|
// met: |
||||||
|
// |
||||||
|
// * Redistributions of source code must retain the above copyright |
||||||
|
// notice, this list of conditions and the following disclaimer. |
||||||
|
// * Redistributions in binary form must reproduce the above |
||||||
|
// copyright notice, this list of conditions and the following disclaimer |
||||||
|
// in the documentation and/or other materials provided with the |
||||||
|
// distribution. |
||||||
|
// * Neither the name of Google Inc. nor the names of its |
||||||
|
// contributors may be used to endorse or promote products derived from |
||||||
|
// this software without specific prior written permission. |
||||||
|
// |
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Grpc.Core; |
||||||
|
using Grpc.Core.Internal; |
||||||
|
using Grpc.Core.Utils; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace Grpc.Core.Tests |
||||||
|
{ |
||||||
|
public class MetadataTest |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void AsciiEntry() |
||||||
|
{ |
||||||
|
var entry = new Metadata.Entry("ABC", "XYZ"); |
||||||
|
Assert.IsFalse(entry.IsBinary); |
||||||
|
Assert.AreEqual("abc", entry.Key); // key is in lowercase. |
||||||
|
Assert.AreEqual("XYZ", entry.Value); |
||||||
|
CollectionAssert.AreEqual(new[] { (byte)'X', (byte)'Y', (byte)'Z' }, entry.ValueBytes); |
||||||
|
|
||||||
|
Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc-bin", "xyz")); |
||||||
|
|
||||||
|
Assert.AreEqual("[Entry: key=abc, value=XYZ]", entry.ToString()); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void BinaryEntry() |
||||||
|
{ |
||||||
|
var bytes = new byte[] { 1, 2, 3 }; |
||||||
|
var entry = new Metadata.Entry("ABC-BIN", bytes); |
||||||
|
Assert.IsTrue(entry.IsBinary); |
||||||
|
Assert.AreEqual("abc-bin", entry.Key); // key is in lowercase. |
||||||
|
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; }); |
||||||
|
CollectionAssert.AreEqual(bytes, entry.ValueBytes); |
||||||
|
|
||||||
|
Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc", bytes)); |
||||||
|
|
||||||
|
Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString()); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Entry_ConstructionPreconditions() |
||||||
|
{ |
||||||
|
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry(null, "xyz")); |
||||||
|
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc", (string)null)); |
||||||
|
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc-bin", (byte[])null)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Entry_Immutable() |
||||||
|
{ |
||||||
|
var origBytes = new byte[] { 1, 2, 3 }; |
||||||
|
var bytes = new byte[] { 1, 2, 3 }; |
||||||
|
var entry = new Metadata.Entry("ABC-BIN", bytes); |
||||||
|
bytes[0] = 255; // changing the array passed to constructor should have any effect. |
||||||
|
CollectionAssert.AreEqual(origBytes, entry.ValueBytes); |
||||||
|
|
||||||
|
entry.ValueBytes[0] = 255; |
||||||
|
CollectionAssert.AreEqual(origBytes, entry.ValueBytes); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Entry_CreateUnsafe_Ascii() |
||||||
|
{ |
||||||
|
var bytes = new byte[] { (byte)'X', (byte)'y' }; |
||||||
|
var entry = Metadata.Entry.CreateUnsafe("abc", bytes); |
||||||
|
Assert.IsFalse(entry.IsBinary); |
||||||
|
Assert.AreEqual("abc", entry.Key); |
||||||
|
Assert.AreEqual("Xy", entry.Value); |
||||||
|
CollectionAssert.AreEqual(bytes, entry.ValueBytes); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Entry_CreateUnsafe_Binary() |
||||||
|
{ |
||||||
|
var bytes = new byte[] { 1, 2, 3 }; |
||||||
|
var entry = Metadata.Entry.CreateUnsafe("abc-bin", bytes); |
||||||
|
Assert.IsTrue(entry.IsBinary); |
||||||
|
Assert.AreEqual("abc-bin", entry.Key); |
||||||
|
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; }); |
||||||
|
CollectionAssert.AreEqual(bytes, entry.ValueBytes); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
#region Copyright notice and license |
||||||
|
|
||||||
|
// Copyright 2015, Google Inc. |
||||||
|
// All rights reserved. |
||||||
|
// |
||||||
|
// Redistribution and use in source and binary forms, with or without |
||||||
|
// modification, are permitted provided that the following conditions are |
||||||
|
// met: |
||||||
|
// |
||||||
|
// * Redistributions of source code must retain the above copyright |
||||||
|
// notice, this list of conditions and the following disclaimer. |
||||||
|
// * Redistributions in binary form must reproduce the above |
||||||
|
// copyright notice, this list of conditions and the following disclaimer |
||||||
|
// in the documentation and/or other materials provided with the |
||||||
|
// distribution. |
||||||
|
// * Neither the name of Google Inc. nor the names of its |
||||||
|
// contributors may be used to endorse or promote products derived from |
||||||
|
// this software without specific prior written permission. |
||||||
|
// |
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Grpc.Core; |
||||||
|
using Grpc.Core.Internal; |
||||||
|
using Grpc.Core.Utils; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace Grpc.Core.Tests |
||||||
|
{ |
||||||
|
public class ShutdownTest |
||||||
|
{ |
||||||
|
const string Host = "127.0.0.1"; |
||||||
|
|
||||||
|
MockServiceHelper helper; |
||||||
|
Server server; |
||||||
|
Channel channel; |
||||||
|
|
||||||
|
[SetUp] |
||||||
|
public void Init() |
||||||
|
{ |
||||||
|
helper = new MockServiceHelper(Host); |
||||||
|
server = helper.GetServer(); |
||||||
|
server.Start(); |
||||||
|
channel = helper.GetChannel(); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public async Task AbandonedCall() |
||||||
|
{ |
||||||
|
helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) => |
||||||
|
{ |
||||||
|
await requestStream.ToListAsync(); |
||||||
|
}); |
||||||
|
|
||||||
|
var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(1)))); |
||||||
|
|
||||||
|
channel.ShutdownAsync().Wait(); |
||||||
|
server.ShutdownAsync().Wait(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
|
@ -0,0 +1,59 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Private constants for the package.""" |
||||||
|
|
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
from grpc.framework.interfaces.links import links |
||||||
|
|
||||||
|
TICKET_SUBSCRIPTION_FOR_BASE_SUBSCRIPTION_KIND = { |
||||||
|
base.Subscription.Kind.NONE: links.Ticket.Subscription.NONE, |
||||||
|
base.Subscription.Kind.TERMINATION_ONLY: |
||||||
|
links.Ticket.Subscription.TERMINATION, |
||||||
|
base.Subscription.Kind.FULL: links.Ticket.Subscription.FULL, |
||||||
|
} |
||||||
|
|
||||||
|
# Mapping from abortive operation outcome to ticket termination to be |
||||||
|
# sent to the other side of the operation, or None to indicate that no |
||||||
|
# ticket should be sent to the other side in the event of such an |
||||||
|
# outcome. |
||||||
|
ABORTION_OUTCOME_TO_TICKET_TERMINATION = { |
||||||
|
base.Outcome.CANCELLED: links.Ticket.Termination.CANCELLATION, |
||||||
|
base.Outcome.EXPIRED: links.Ticket.Termination.EXPIRATION, |
||||||
|
base.Outcome.LOCAL_SHUTDOWN: links.Ticket.Termination.SHUTDOWN, |
||||||
|
base.Outcome.REMOTE_SHUTDOWN: None, |
||||||
|
base.Outcome.RECEPTION_FAILURE: links.Ticket.Termination.RECEPTION_FAILURE, |
||||||
|
base.Outcome.TRANSMISSION_FAILURE: None, |
||||||
|
base.Outcome.LOCAL_FAILURE: links.Ticket.Termination.LOCAL_FAILURE, |
||||||
|
base.Outcome.REMOTE_FAILURE: links.Ticket.Termination.REMOTE_FAILURE, |
||||||
|
} |
||||||
|
|
||||||
|
INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Core) internal error! )-:' |
||||||
|
TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE = ( |
||||||
|
'Exception calling termination callback!') |
@ -0,0 +1,92 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for operation context.""" |
||||||
|
|
||||||
|
import time |
||||||
|
|
||||||
|
# _interfaces is referenced from specification in this module. |
||||||
|
from grpc.framework.core import _interfaces # pylint: disable=unused-import |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
|
||||||
|
class OperationContext(base.OperationContext): |
||||||
|
"""An implementation of interfaces.OperationContext.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, lock, termination_manager, transmission_manager, |
||||||
|
expiration_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
|
||||||
|
def _abort(self, outcome): |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._termination_manager.abort(outcome) |
||||||
|
self._transmission_manager.abort(outcome) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
|
||||||
|
def outcome(self): |
||||||
|
"""See base.OperationContext.outcome for specification.""" |
||||||
|
with self._lock: |
||||||
|
return self._termination_manager.outcome |
||||||
|
|
||||||
|
def add_termination_callback(self, callback): |
||||||
|
"""See base.OperationContext.add_termination_callback.""" |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._termination_manager.add_callback(callback) |
||||||
|
return None |
||||||
|
else: |
||||||
|
return self._termination_manager.outcome |
||||||
|
|
||||||
|
def time_remaining(self): |
||||||
|
"""See base.OperationContext.time_remaining for specification.""" |
||||||
|
with self._lock: |
||||||
|
deadline = self._expiration_manager.deadline() |
||||||
|
return max(0.0, deadline - time.time()) |
||||||
|
|
||||||
|
def cancel(self): |
||||||
|
"""See base.OperationContext.cancel for specification.""" |
||||||
|
self._abort(base.Outcome.CANCELLED) |
||||||
|
|
||||||
|
def fail(self, exception): |
||||||
|
"""See base.OperationContext.fail for specification.""" |
||||||
|
self._abort(base.Outcome.LOCAL_FAILURE) |
@ -0,0 +1,97 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for handling emitted values.""" |
||||||
|
|
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
|
||||||
|
class EmissionManager(_interfaces.EmissionManager): |
||||||
|
"""An EmissionManager implementation.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, lock, termination_manager, transmission_manager, |
||||||
|
expiration_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
self._ingestion_manager = None |
||||||
|
|
||||||
|
self._initial_metadata_seen = False |
||||||
|
self._payload_seen = False |
||||||
|
self._completion_seen = False |
||||||
|
|
||||||
|
def set_ingestion_manager(self, ingestion_manager): |
||||||
|
"""Sets the ingestion manager with which this manager will cooperate. |
||||||
|
|
||||||
|
Args: |
||||||
|
ingestion_manager: The _interfaces.IngestionManager for the operation. |
||||||
|
""" |
||||||
|
self._ingestion_manager = ingestion_manager |
||||||
|
|
||||||
|
def advance( |
||||||
|
self, initial_metadata=None, payload=None, completion=None, |
||||||
|
allowance=None): |
||||||
|
initial_metadata_present = initial_metadata is not None |
||||||
|
payload_present = payload is not None |
||||||
|
completion_present = completion is not None |
||||||
|
allowance_present = allowance is not None |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
if (initial_metadata_present and ( |
||||||
|
self._initial_metadata_seen or self._payload_seen or |
||||||
|
self._completion_seen) or |
||||||
|
payload_present and self._completion_seen or |
||||||
|
completion_present and self._completion_seen or |
||||||
|
allowance_present and allowance <= 0): |
||||||
|
self._termination_manager.abort(base.Outcome.LOCAL_FAILURE) |
||||||
|
self._transmission_manager.abort(base.Outcome.LOCAL_FAILURE) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
else: |
||||||
|
self._initial_metadata_seen |= initial_metadata_present |
||||||
|
self._payload_seen |= payload_present |
||||||
|
self._completion_seen |= completion_present |
||||||
|
if completion_present: |
||||||
|
self._termination_manager.emission_complete() |
||||||
|
self._ingestion_manager.local_emissions_done() |
||||||
|
self._transmission_manager.advance( |
||||||
|
initial_metadata, payload, completion, allowance) |
||||||
|
if allowance_present: |
||||||
|
self._ingestion_manager.add_local_allowance(allowance) |
@ -0,0 +1,251 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Implementation of base.End.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
import enum |
||||||
|
import threading |
||||||
|
import uuid |
||||||
|
|
||||||
|
from grpc.framework.core import _operation |
||||||
|
from grpc.framework.core import _utilities |
||||||
|
from grpc.framework.foundation import callable_util |
||||||
|
from grpc.framework.foundation import later |
||||||
|
from grpc.framework.foundation import logging_pool |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
from grpc.framework.interfaces.links import links |
||||||
|
from grpc.framework.interfaces.links import utilities |
||||||
|
|
||||||
|
_IDLE_ACTION_EXCEPTION_LOG_MESSAGE = 'Exception calling idle action!' |
||||||
|
|
||||||
|
|
||||||
|
class End(base.End, links.Link): |
||||||
|
"""A bridge between base.End and links.Link. |
||||||
|
|
||||||
|
Implementations of this interface translate arriving tickets into |
||||||
|
calls on application objects implementing base interfaces and |
||||||
|
translate calls from application objects implementing base interfaces |
||||||
|
into tickets sent to a joined link. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
|
||||||
|
class _Cycle(object): |
||||||
|
"""State for a single start-stop End lifecycle.""" |
||||||
|
|
||||||
|
def __init__(self, pool): |
||||||
|
self.pool = pool |
||||||
|
self.grace = False |
||||||
|
self.futures = [] |
||||||
|
self.operations = {} |
||||||
|
self.idle_actions = [] |
||||||
|
|
||||||
|
|
||||||
|
def _abort(operations): |
||||||
|
for operation in operations: |
||||||
|
operation.abort(base.Outcome.LOCAL_SHUTDOWN) |
||||||
|
|
||||||
|
|
||||||
|
def _cancel_futures(futures): |
||||||
|
for future in futures: |
||||||
|
futures.cancel() |
||||||
|
|
||||||
|
|
||||||
|
def _future_shutdown(lock, cycle, event): |
||||||
|
def in_future(): |
||||||
|
with lock: |
||||||
|
_abort(cycle.operations.values()) |
||||||
|
_cancel_futures(cycle.futures) |
||||||
|
pool = cycle.pool |
||||||
|
cycle.pool.shutdown(wait=True) |
||||||
|
return in_future |
||||||
|
|
||||||
|
|
||||||
|
def _termination_action(lock, stats, operation_id, cycle): |
||||||
|
"""Constructs the termination action for a single operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
lock: A lock to hold during the termination action. |
||||||
|
states: A mapping from base.Outcome values to integers to increment with |
||||||
|
the outcome given to the termination action. |
||||||
|
operation_id: The operation ID for the termination action. |
||||||
|
cycle: A _Cycle value to be updated during the termination action. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A callable that takes an operation outcome as its sole parameter and that |
||||||
|
should be used as the termination action for the operation associated |
||||||
|
with the given operation ID. |
||||||
|
""" |
||||||
|
def termination_action(outcome): |
||||||
|
with lock: |
||||||
|
stats[outcome] += 1 |
||||||
|
cycle.operations.pop(operation_id, None) |
||||||
|
if not cycle.operations: |
||||||
|
for action in cycle.idle_actions: |
||||||
|
cycle.pool.submit(action) |
||||||
|
cycle.idle_actions = [] |
||||||
|
if cycle.grace: |
||||||
|
_cancel_futures(cycle.futures) |
||||||
|
return termination_action |
||||||
|
|
||||||
|
|
||||||
|
class _End(End): |
||||||
|
"""An End implementation.""" |
||||||
|
|
||||||
|
def __init__(self, servicer_package): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer_package: A _ServicerPackage for servicing operations or None if |
||||||
|
this end will not be used to service operations. |
||||||
|
""" |
||||||
|
self._lock = threading.Condition() |
||||||
|
self._servicer_package = servicer_package |
||||||
|
|
||||||
|
self._stats = {outcome: 0 for outcome in base.Outcome} |
||||||
|
|
||||||
|
self._mate = None |
||||||
|
|
||||||
|
self._cycle = None |
||||||
|
|
||||||
|
def start(self): |
||||||
|
"""See base.End.start for specification.""" |
||||||
|
with self._lock: |
||||||
|
if self._cycle is not None: |
||||||
|
raise ValueError('Tried to start a not-stopped End!') |
||||||
|
else: |
||||||
|
self._cycle = _Cycle(logging_pool.pool(1)) |
||||||
|
|
||||||
|
def stop(self, grace): |
||||||
|
"""See base.End.stop for specification.""" |
||||||
|
with self._lock: |
||||||
|
if self._cycle is None: |
||||||
|
event = threading.Event() |
||||||
|
event.set() |
||||||
|
return event |
||||||
|
elif not self._cycle.operations: |
||||||
|
event = threading.Event() |
||||||
|
self._cycle.pool.submit(event.set) |
||||||
|
self._cycle.pool.shutdown(wait=False) |
||||||
|
self._cycle = None |
||||||
|
return event |
||||||
|
else: |
||||||
|
self._cycle.grace = True |
||||||
|
event = threading.Event() |
||||||
|
self._cycle.idle_actions.append(event.set) |
||||||
|
if 0 < grace: |
||||||
|
future = later.later( |
||||||
|
grace, _future_shutdown(self._lock, self._cycle, event)) |
||||||
|
self._cycle.futures.append(future) |
||||||
|
else: |
||||||
|
_abort(self._cycle.operations.values()) |
||||||
|
return event |
||||||
|
|
||||||
|
def operate( |
||||||
|
self, group, method, subscription, timeout, initial_metadata=None, |
||||||
|
payload=None, completion=None): |
||||||
|
"""See base.End.operate for specification.""" |
||||||
|
operation_id = uuid.uuid4() |
||||||
|
with self._lock: |
||||||
|
if self._cycle is None or self._cycle.grace: |
||||||
|
raise ValueError('Can\'t operate on stopped or stopping End!') |
||||||
|
termination_action = _termination_action( |
||||||
|
self._lock, self._stats, operation_id, self._cycle) |
||||||
|
operation = _operation.invocation_operate( |
||||||
|
operation_id, group, method, subscription, timeout, initial_metadata, |
||||||
|
payload, completion, self._mate.accept_ticket, termination_action, |
||||||
|
self._cycle.pool) |
||||||
|
self._cycle.operations[operation_id] = operation |
||||||
|
return operation.context, operation.operator |
||||||
|
|
||||||
|
def operation_stats(self): |
||||||
|
"""See base.End.operation_stats for specification.""" |
||||||
|
with self._lock: |
||||||
|
return dict(self._stats) |
||||||
|
|
||||||
|
def add_idle_action(self, action): |
||||||
|
"""See base.End.add_idle_action for specification.""" |
||||||
|
with self._lock: |
||||||
|
if self._cycle is None: |
||||||
|
raise ValueError('Can\'t add idle action to stopped End!') |
||||||
|
action_with_exceptions_logged = callable_util.with_exceptions_logged( |
||||||
|
action, _IDLE_ACTION_EXCEPTION_LOG_MESSAGE) |
||||||
|
if self._cycle.operations: |
||||||
|
self._cycle.idle_actions.append(action_with_exceptions_logged) |
||||||
|
else: |
||||||
|
self._cycle.pool.submit(action_with_exceptions_logged) |
||||||
|
|
||||||
|
def accept_ticket(self, ticket): |
||||||
|
"""See links.Link.accept_ticket for specification.""" |
||||||
|
with self._lock: |
||||||
|
if self._cycle is not None and not self._cycle.grace: |
||||||
|
operation = self._cycle.operations.get(ticket.operation_id) |
||||||
|
if operation is not None: |
||||||
|
operation.handle_ticket(ticket) |
||||||
|
elif self._servicer_package is not None: |
||||||
|
termination_action = _termination_action( |
||||||
|
self._lock, self._stats, ticket.operation_id, self._cycle) |
||||||
|
operation = _operation.service_operate( |
||||||
|
self._servicer_package, ticket, self._mate.accept_ticket, |
||||||
|
termination_action, self._cycle.pool) |
||||||
|
if operation is not None: |
||||||
|
self._cycle.operations[ticket.operation_id] = operation |
||||||
|
|
||||||
|
def join_link(self, link): |
||||||
|
"""See links.Link.join_link for specification.""" |
||||||
|
with self._lock: |
||||||
|
self._mate = utilities.NULL_LINK if link is None else link |
||||||
|
|
||||||
|
|
||||||
|
def serviceless_end_link(): |
||||||
|
"""Constructs an End usable only for invoking operations. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An End usable for translating operations into ticket exchange. |
||||||
|
""" |
||||||
|
return _End(None) |
||||||
|
|
||||||
|
|
||||||
|
def serviceful_end_link(servicer, default_timeout, maximum_timeout): |
||||||
|
"""Constructs an End capable of servicing operations. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer: An interfaces.Servicer for servicing operations. |
||||||
|
default_timeout: A length of time in seconds to be used as the default |
||||||
|
time alloted for a single operation. |
||||||
|
maximum_timeout: A length of time in seconds to be used as the maximum |
||||||
|
time alloted for a single operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An End capable of servicing the operations requested of it through ticket |
||||||
|
exchange. |
||||||
|
""" |
||||||
|
return _End( |
||||||
|
_utilities.ServicerPackage(servicer, default_timeout, maximum_timeout)) |
@ -0,0 +1,152 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for operation expiration.""" |
||||||
|
|
||||||
|
import time |
||||||
|
|
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.foundation import later |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
|
||||||
|
class _ExpirationManager(_interfaces.ExpirationManager): |
||||||
|
"""An implementation of _interfaces.ExpirationManager.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, commencement, timeout, maximum_timeout, lock, termination_manager, |
||||||
|
transmission_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
commencement: The time in seconds since the epoch at which the operation |
||||||
|
began. |
||||||
|
timeout: A length of time in seconds to allow for the operation to run. |
||||||
|
maximum_timeout: The maximum length of time in seconds to allow for the |
||||||
|
operation to run despite what is requested via this object's |
||||||
|
change_timout method. |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._commencement = commencement |
||||||
|
self._maximum_timeout = maximum_timeout |
||||||
|
|
||||||
|
self._timeout = timeout |
||||||
|
self._deadline = commencement + timeout |
||||||
|
self._index = None |
||||||
|
self._future = None |
||||||
|
|
||||||
|
def _expire(self, index): |
||||||
|
def expire(): |
||||||
|
with self._lock: |
||||||
|
if self._future is not None and index == self._index: |
||||||
|
self._future = None |
||||||
|
self._termination_manager.expire() |
||||||
|
self._transmission_manager.abort(base.Outcome.EXPIRED) |
||||||
|
return expire |
||||||
|
|
||||||
|
def start(self): |
||||||
|
self._index = 0 |
||||||
|
self._future = later.later(self._timeout, self._expire(0)) |
||||||
|
|
||||||
|
def change_timeout(self, timeout): |
||||||
|
if self._future is not None and timeout != self._timeout: |
||||||
|
self._future.cancel() |
||||||
|
new_timeout = min(timeout, self._maximum_timeout) |
||||||
|
new_index = self._index + 1 |
||||||
|
self._timeout = new_timeout |
||||||
|
self._deadline = self._commencement + new_timeout |
||||||
|
self._index = new_index |
||||||
|
delay = self._deadline - time.time() |
||||||
|
self._future = later.later(delay, self._expire(new_index)) |
||||||
|
if new_timeout != timeout: |
||||||
|
self._transmission_manager.timeout(new_timeout) |
||||||
|
|
||||||
|
def deadline(self): |
||||||
|
return self._deadline |
||||||
|
|
||||||
|
def terminate(self): |
||||||
|
if self._future: |
||||||
|
self._future.cancel() |
||||||
|
self._future = None |
||||||
|
self._deadline_index = None |
||||||
|
|
||||||
|
|
||||||
|
def invocation_expiration_manager( |
||||||
|
timeout, lock, termination_manager, transmission_manager): |
||||||
|
"""Creates an _interfaces.ExpirationManager appropriate for front-side use. |
||||||
|
|
||||||
|
Args: |
||||||
|
timeout: A length of time in seconds to allow for the operation to run. |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An _interfaces.ExpirationManager appropriate for invocation-side use. |
||||||
|
""" |
||||||
|
expiration_manager = _ExpirationManager( |
||||||
|
time.time(), timeout, timeout, lock, termination_manager, |
||||||
|
transmission_manager) |
||||||
|
expiration_manager.start() |
||||||
|
return expiration_manager |
||||||
|
|
||||||
|
|
||||||
|
def service_expiration_manager( |
||||||
|
timeout, default_timeout, maximum_timeout, lock, termination_manager, |
||||||
|
transmission_manager): |
||||||
|
"""Creates an _interfaces.ExpirationManager appropriate for back-side use. |
||||||
|
|
||||||
|
Args: |
||||||
|
timeout: A length of time in seconds to allow for the operation to run. May |
||||||
|
be None in which case default_timeout will be used. |
||||||
|
default_timeout: The default length of time in seconds to allow for the |
||||||
|
operation to run if the front-side customer has not specified such a value |
||||||
|
(or if the value they specified is not yet known). |
||||||
|
maximum_timeout: The maximum length of time in seconds to allow for the |
||||||
|
operation to run. |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An _interfaces.ExpirationManager appropriate for service-side use. |
||||||
|
""" |
||||||
|
expiration_manager = _ExpirationManager( |
||||||
|
time.time(), default_timeout if timeout is None else timeout, |
||||||
|
maximum_timeout, lock, termination_manager, transmission_manager) |
||||||
|
expiration_manager.start() |
||||||
|
return expiration_manager |
@ -0,0 +1,410 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for ingestion during an operation.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
import collections |
||||||
|
|
||||||
|
from grpc.framework.core import _constants |
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.foundation import abandonment |
||||||
|
from grpc.framework.foundation import callable_util |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
_CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE = 'Exception initializing ingestion!' |
||||||
|
_INGESTION_EXCEPTION_LOG_MESSAGE = 'Exception during ingestion!' |
||||||
|
|
||||||
|
|
||||||
|
class _SubscriptionCreation(collections.namedtuple( |
||||||
|
'_SubscriptionCreation', ('subscription', 'remote_error', 'abandoned'))): |
||||||
|
"""A sum type for the outcome of ingestion initialization. |
||||||
|
|
||||||
|
Either subscription will be non-None, remote_error will be True, or abandoned |
||||||
|
will be True. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
subscription: A base.Subscription describing the customer's interest in |
||||||
|
operation values from the other side. |
||||||
|
remote_error: A boolean indicating that the subscription could not be |
||||||
|
created due to an error on the remote side of the operation. |
||||||
|
abandoned: A boolean indicating that subscription creation was abandoned. |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class _SubscriptionCreator(object): |
||||||
|
"""Common specification of subscription-creating behavior.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def create(self, group, method): |
||||||
|
"""Creates the base.Subscription of the local customer. |
||||||
|
|
||||||
|
Any exceptions raised by this method should be attributed to and treated as |
||||||
|
defects in the customer code called by this method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the operation. |
||||||
|
method: The method identifier of the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A _SubscriptionCreation describing the result of subscription creation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class _ServiceSubscriptionCreator(_SubscriptionCreator): |
||||||
|
"""A _SubscriptionCreator appropriate for service-side use.""" |
||||||
|
|
||||||
|
def __init__(self, servicer, operation_context, output_operator): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer: The base.Servicer that will service the operation. |
||||||
|
operation_context: A base.OperationContext for the operation to be passed |
||||||
|
to the customer. |
||||||
|
output_operator: A base.Operator for the operation to be passed to the |
||||||
|
customer and to be called by the customer to accept operation data |
||||||
|
emitted by the customer. |
||||||
|
""" |
||||||
|
self._servicer = servicer |
||||||
|
self._operation_context = operation_context |
||||||
|
self._output_operator = output_operator |
||||||
|
|
||||||
|
def create(self, group, method): |
||||||
|
try: |
||||||
|
subscription = self._servicer.service( |
||||||
|
group, method, self._operation_context, self._output_operator) |
||||||
|
except base.NoSuchMethodError: |
||||||
|
return _SubscriptionCreation(None, True, False) |
||||||
|
except abandonment.Abandoned: |
||||||
|
return _SubscriptionCreation(None, False, True) |
||||||
|
else: |
||||||
|
return _SubscriptionCreation(subscription, False, False) |
||||||
|
|
||||||
|
|
||||||
|
def _wrap(behavior): |
||||||
|
def wrapped(*args, **kwargs): |
||||||
|
try: |
||||||
|
behavior(*args, **kwargs) |
||||||
|
except abandonment.Abandoned: |
||||||
|
return False |
||||||
|
else: |
||||||
|
return True |
||||||
|
return wrapped |
||||||
|
|
||||||
|
|
||||||
|
class _IngestionManager(_interfaces.IngestionManager): |
||||||
|
"""An implementation of _interfaces.IngestionManager.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, lock, pool, subscription, subscription_creator, termination_manager, |
||||||
|
transmission_manager, expiration_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
lock: The operation-wide lock. |
||||||
|
pool: A thread pool in which to execute customer code. |
||||||
|
subscription: A base.Subscription describing the customer's interest in |
||||||
|
operation values from the other side. May be None if |
||||||
|
subscription_creator is not None. |
||||||
|
subscription_creator: A _SubscriptionCreator wrapping the portion of |
||||||
|
customer code that when called returns the base.Subscription describing |
||||||
|
the customer's interest in operation values from the other side. May be |
||||||
|
None if subscription is not None. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._pool = pool |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
|
||||||
|
if subscription is None: |
||||||
|
self._subscription_creator = subscription_creator |
||||||
|
self._wrapped_operator = None |
||||||
|
elif subscription.kind is base.Subscription.Kind.FULL: |
||||||
|
self._subscription_creator = None |
||||||
|
self._wrapped_operator = _wrap(subscription.operator.advance) |
||||||
|
else: |
||||||
|
# TODO(nathaniel): Support other subscriptions. |
||||||
|
raise ValueError('Unsupported subscription "%s"!' % subscription.kind) |
||||||
|
self._pending_initial_metadata = None |
||||||
|
self._pending_payloads = [] |
||||||
|
self._pending_completion = None |
||||||
|
self._local_allowance = 1 |
||||||
|
# A nonnegative integer or None, with None indicating that the local |
||||||
|
# customer is done emitting anyway so there's no need to bother it by |
||||||
|
# informing it that the remote customer has granted it further permission to |
||||||
|
# emit. |
||||||
|
self._remote_allowance = 0 |
||||||
|
self._processing = False |
||||||
|
|
||||||
|
def _abort_internal_only(self): |
||||||
|
self._subscription_creator = None |
||||||
|
self._wrapped_operator = None |
||||||
|
self._pending_initial_metadata = None |
||||||
|
self._pending_payloads = None |
||||||
|
self._pending_completion = None |
||||||
|
|
||||||
|
def _abort_and_notify(self, outcome): |
||||||
|
self._abort_internal_only() |
||||||
|
self._termination_manager.abort(outcome) |
||||||
|
self._transmission_manager.abort(outcome) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
|
||||||
|
def _operator_next(self): |
||||||
|
"""Computes the next step for full-subscription ingestion. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An initial_metadata, payload, completion, allowance, continue quintet |
||||||
|
indicating what operation values (if any) are available to pass into |
||||||
|
customer code and whether or not there is anything immediately |
||||||
|
actionable to call customer code to do. |
||||||
|
""" |
||||||
|
if self._wrapped_operator is None: |
||||||
|
return None, None, None, None, False |
||||||
|
else: |
||||||
|
initial_metadata, payload, completion, allowance, action = [None] * 5 |
||||||
|
if self._pending_initial_metadata is not None: |
||||||
|
initial_metadata = self._pending_initial_metadata |
||||||
|
self._pending_initial_metadata = None |
||||||
|
action = True |
||||||
|
if self._pending_payloads and 0 < self._local_allowance: |
||||||
|
payload = self._pending_payloads.pop(0) |
||||||
|
self._local_allowance -= 1 |
||||||
|
action = True |
||||||
|
if not self._pending_payloads and self._pending_completion is not None: |
||||||
|
completion = self._pending_completion |
||||||
|
self._pending_completion = None |
||||||
|
action = True |
||||||
|
if self._remote_allowance is not None and 0 < self._remote_allowance: |
||||||
|
allowance = self._remote_allowance |
||||||
|
self._remote_allowance = 0 |
||||||
|
action = True |
||||||
|
return initial_metadata, payload, completion, allowance, bool(action) |
||||||
|
|
||||||
|
def _operator_process( |
||||||
|
self, wrapped_operator, initial_metadata, payload, |
||||||
|
completion, allowance): |
||||||
|
while True: |
||||||
|
advance_outcome = callable_util.call_logging_exceptions( |
||||||
|
wrapped_operator, _INGESTION_EXCEPTION_LOG_MESSAGE, |
||||||
|
initial_metadata=initial_metadata, payload=payload, |
||||||
|
completion=completion, allowance=allowance) |
||||||
|
if advance_outcome.exception is None: |
||||||
|
if advance_outcome.return_value: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is not None: |
||||||
|
return |
||||||
|
if completion is not None: |
||||||
|
self._termination_manager.ingestion_complete() |
||||||
|
initial_metadata, payload, completion, allowance, moar = ( |
||||||
|
self._operator_next()) |
||||||
|
if not moar: |
||||||
|
self._processing = False |
||||||
|
return |
||||||
|
else: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._abort_and_notify(base.Outcome.LOCAL_FAILURE) |
||||||
|
return |
||||||
|
else: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._abort_and_notify(base.Outcome.LOCAL_FAILURE) |
||||||
|
return |
||||||
|
|
||||||
|
def _operator_post_create(self, subscription): |
||||||
|
wrapped_operator = _wrap(subscription.operator.advance) |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is not None: |
||||||
|
return |
||||||
|
self._wrapped_operator = wrapped_operator |
||||||
|
self._subscription_creator = None |
||||||
|
metadata, payload, completion, allowance, moar = self._operator_next() |
||||||
|
if not moar: |
||||||
|
self._processing = False |
||||||
|
return |
||||||
|
self._operator_process( |
||||||
|
wrapped_operator, metadata, payload, completion, allowance) |
||||||
|
|
||||||
|
def _create(self, subscription_creator, group, name): |
||||||
|
outcome = callable_util.call_logging_exceptions( |
||||||
|
subscription_creator.create, _CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE, |
||||||
|
group, name) |
||||||
|
if outcome.return_value is None: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._abort_and_notify(base.Outcome.LOCAL_FAILURE) |
||||||
|
elif outcome.return_value.abandoned: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._abort_and_notify(base.Outcome.LOCAL_FAILURE) |
||||||
|
elif outcome.return_value.remote_error: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._abort_and_notify(base.Outcome.REMOTE_FAILURE) |
||||||
|
elif outcome.return_value.subscription.kind is base.Subscription.Kind.FULL: |
||||||
|
self._operator_post_create(outcome.return_value.subscription) |
||||||
|
else: |
||||||
|
# TODO(nathaniel): Support other subscriptions. |
||||||
|
raise ValueError( |
||||||
|
'Unsupported "%s"!' % outcome.return_value.subscription.kind) |
||||||
|
|
||||||
|
def _store_advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
if initial_metadata is not None: |
||||||
|
self._pending_initial_metadata = initial_metadata |
||||||
|
if payload is not None: |
||||||
|
self._pending_payloads.append(payload) |
||||||
|
if completion is not None: |
||||||
|
self._pending_completion = completion |
||||||
|
if allowance is not None and self._remote_allowance is not None: |
||||||
|
self._remote_allowance += allowance |
||||||
|
|
||||||
|
def _operator_advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
if self._processing: |
||||||
|
self._store_advance(initial_metadata, payload, completion, allowance) |
||||||
|
else: |
||||||
|
action = False |
||||||
|
if initial_metadata is not None: |
||||||
|
action = True |
||||||
|
if payload is not None: |
||||||
|
if 0 < self._local_allowance: |
||||||
|
self._local_allowance -= 1 |
||||||
|
action = True |
||||||
|
else: |
||||||
|
self._pending_payloads.append(payload) |
||||||
|
payload = False |
||||||
|
if completion is not None: |
||||||
|
if self._pending_payloads: |
||||||
|
self._pending_completion = completion |
||||||
|
else: |
||||||
|
action = True |
||||||
|
if allowance is not None and self._remote_allowance is not None: |
||||||
|
allowance += self._remote_allowance |
||||||
|
self._remote_allowance = 0 |
||||||
|
action = True |
||||||
|
if action: |
||||||
|
self._pool.submit( |
||||||
|
callable_util.with_exceptions_logged( |
||||||
|
self._operator_process, _constants.INTERNAL_ERROR_LOG_MESSAGE), |
||||||
|
self._wrapped_operator, initial_metadata, payload, completion, |
||||||
|
allowance) |
||||||
|
|
||||||
|
def set_group_and_method(self, group, method): |
||||||
|
"""See _interfaces.IngestionManager.set_group_and_method for spec.""" |
||||||
|
if self._subscription_creator is not None and not self._processing: |
||||||
|
self._pool.submit( |
||||||
|
callable_util.with_exceptions_logged( |
||||||
|
self._create, _constants.INTERNAL_ERROR_LOG_MESSAGE), |
||||||
|
self._subscription_creator, group, method) |
||||||
|
self._processing = True |
||||||
|
|
||||||
|
def add_local_allowance(self, allowance): |
||||||
|
"""See _interfaces.IngestionManager.add_local_allowance for spec.""" |
||||||
|
if any((self._subscription_creator, self._wrapped_operator,)): |
||||||
|
self._local_allowance += allowance |
||||||
|
if not self._processing: |
||||||
|
initial_metadata, payload, completion, allowance, moar = ( |
||||||
|
self._operator_next()) |
||||||
|
if moar: |
||||||
|
self._pool.submit( |
||||||
|
callable_util.with_exceptions_logged( |
||||||
|
self._operator_process, |
||||||
|
_constants.INTERNAL_ERROR_LOG_MESSAGE), |
||||||
|
initial_metadata, payload, completion, allowance) |
||||||
|
|
||||||
|
def local_emissions_done(self): |
||||||
|
self._remote_allowance = None |
||||||
|
|
||||||
|
def advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
"""See _interfaces.IngestionManager.advance for specification.""" |
||||||
|
if self._subscription_creator is not None: |
||||||
|
self._store_advance(initial_metadata, payload, completion, allowance) |
||||||
|
elif self._wrapped_operator is not None: |
||||||
|
self._operator_advance(initial_metadata, payload, completion, allowance) |
||||||
|
|
||||||
|
|
||||||
|
def invocation_ingestion_manager( |
||||||
|
subscription, lock, pool, termination_manager, transmission_manager, |
||||||
|
expiration_manager): |
||||||
|
"""Creates an IngestionManager appropriate for invocation-side use. |
||||||
|
|
||||||
|
Args: |
||||||
|
subscription: A base.Subscription indicating the customer's interest in the |
||||||
|
data and results from the service-side of the operation. |
||||||
|
lock: The operation-wide lock. |
||||||
|
pool: A thread pool in which to execute customer code. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An IngestionManager appropriate for invocation-side use. |
||||||
|
""" |
||||||
|
return _IngestionManager( |
||||||
|
lock, pool, subscription, None, termination_manager, transmission_manager, |
||||||
|
expiration_manager) |
||||||
|
|
||||||
|
|
||||||
|
def service_ingestion_manager( |
||||||
|
servicer, operation_context, output_operator, lock, pool, |
||||||
|
termination_manager, transmission_manager, expiration_manager): |
||||||
|
"""Creates an IngestionManager appropriate for service-side use. |
||||||
|
|
||||||
|
The returned IngestionManager will require its set_group_and_name method to be |
||||||
|
called before its advance method may be called. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer: A base.Servicer for servicing the operation. |
||||||
|
operation_context: A base.OperationContext for the operation to be passed to |
||||||
|
the customer. |
||||||
|
output_operator: A base.Operator for the operation to be passed to the |
||||||
|
customer and to be called by the customer to accept operation data output |
||||||
|
by the customer. |
||||||
|
lock: The operation-wide lock. |
||||||
|
pool: A thread pool in which to execute customer code. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An IngestionManager appropriate for service-side use. |
||||||
|
""" |
||||||
|
subscription_creator = _ServiceSubscriptionCreator( |
||||||
|
servicer, operation_context, output_operator) |
||||||
|
return _IngestionManager( |
||||||
|
lock, pool, None, subscription_creator, termination_manager, |
||||||
|
transmission_manager, expiration_manager) |
@ -0,0 +1,308 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Package-internal interfaces.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
|
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
|
||||||
|
class TerminationManager(object): |
||||||
|
"""An object responsible for handling the termination of an operation. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
outcome: None if the operation is active or a base.Outcome value if it has |
||||||
|
terminated. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def add_callback(self, callback): |
||||||
|
"""Registers a callback to be called on operation termination. |
||||||
|
|
||||||
|
If the operation has already terminated the callback will not be called. |
||||||
|
|
||||||
|
Args: |
||||||
|
callback: A callable that will be passed an interfaces.Outcome value. |
||||||
|
|
||||||
|
Returns: |
||||||
|
None if the operation has not yet terminated and the passed callback will |
||||||
|
be called when it does, or a base.Outcome value describing the operation |
||||||
|
termination if the operation has terminated and the callback will not be |
||||||
|
called as a result of this method call. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def emission_complete(self): |
||||||
|
"""Indicates that emissions from customer code have completed.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def transmission_complete(self): |
||||||
|
"""Indicates that transmissions to the remote end are complete. |
||||||
|
|
||||||
|
Returns: |
||||||
|
True if the operation has terminated or False if the operation remains |
||||||
|
ongoing. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def reception_complete(self): |
||||||
|
"""Indicates that reception from the other side is complete.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def ingestion_complete(self): |
||||||
|
"""Indicates that customer code ingestion of received values is complete.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def expire(self): |
||||||
|
"""Indicates that the operation must abort because it has taken too long.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def abort(self, outcome): |
||||||
|
"""Indicates that the operation must abort for the indicated reason. |
||||||
|
|
||||||
|
Args: |
||||||
|
outcome: An interfaces.Outcome indicating operation abortion. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class TransmissionManager(object): |
||||||
|
"""A manager responsible for transmitting to the other end of an operation.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def kick_off( |
||||||
|
self, group, method, timeout, initial_metadata, payload, completion, |
||||||
|
allowance): |
||||||
|
"""Transmits the values associated with operation invocation.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
"""Accepts values for transmission to the other end of the operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
initial_metadata: An initial metadata value to be transmitted to the other |
||||||
|
side of the operation. May only ever be non-None once. |
||||||
|
payload: A payload value. |
||||||
|
completion: A base.Completion value. May only ever be non-None in the last |
||||||
|
transmission to be made to the other side. |
||||||
|
allowance: A positive integer communicating the number of additional |
||||||
|
payloads allowed to be transmitted from the other side to this side of |
||||||
|
the operation, or None if no additional allowance is being granted in |
||||||
|
this call. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def timeout(self, timeout): |
||||||
|
"""Accepts for transmission to the other side a new timeout value. |
||||||
|
|
||||||
|
Args: |
||||||
|
timeout: A positive float used as the new timeout value for the operation |
||||||
|
to be transmitted to the other side. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def allowance(self, allowance): |
||||||
|
"""Indicates to this manager that the remote customer is allowing payloads. |
||||||
|
|
||||||
|
Args: |
||||||
|
allowance: A positive integer indicating the number of additional payloads |
||||||
|
the remote customer is allowing to be transmitted from this side of the |
||||||
|
operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def remote_complete(self): |
||||||
|
"""Indicates to this manager that data from the remote side is complete.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def abort(self, outcome): |
||||||
|
"""Indicates that the operation has aborted. |
||||||
|
|
||||||
|
Args: |
||||||
|
outcome: An interfaces.Outcome for the operation. If None, indicates that |
||||||
|
the operation abortion should not be communicated to the other side of |
||||||
|
the operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class ExpirationManager(object): |
||||||
|
"""A manager responsible for aborting the operation if it runs out of time.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def change_timeout(self, timeout): |
||||||
|
"""Changes the timeout allotted for the operation. |
||||||
|
|
||||||
|
Operation duration is always measure from the beginning of the operation; |
||||||
|
calling this method changes the operation's allotted time to timeout total |
||||||
|
seconds, not timeout seconds from the time of this method call. |
||||||
|
|
||||||
|
Args: |
||||||
|
timeout: A length of time in seconds to allow for the operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def deadline(self): |
||||||
|
"""Returns the time until which the operation is allowed to run. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The time (seconds since the epoch) at which the operation will expire. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def terminate(self): |
||||||
|
"""Indicates to this manager that the operation has terminated.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class EmissionManager(base.Operator): |
||||||
|
"""A manager of values emitted by customer code.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def advance( |
||||||
|
self, initial_metadata=None, payload=None, completion=None, |
||||||
|
allowance=None): |
||||||
|
"""Accepts a value emitted by customer code. |
||||||
|
|
||||||
|
This method should only be called by customer code. |
||||||
|
|
||||||
|
Args: |
||||||
|
initial_metadata: An initial metadata value emitted by the local customer |
||||||
|
to be sent to the other side of the operation. |
||||||
|
payload: A payload value emitted by the local customer to be sent to the |
||||||
|
other side of the operation. |
||||||
|
completion: A Completion value emitted by the local customer to be sent to |
||||||
|
the other side of the operation. |
||||||
|
allowance: A positive integer indicating an additional number of payloads |
||||||
|
that the local customer is willing to accept from the other side of the |
||||||
|
operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class IngestionManager(object): |
||||||
|
"""A manager responsible for executing customer code. |
||||||
|
|
||||||
|
This name of this manager comes from its responsibility to pass successive |
||||||
|
values from the other side of the operation into the code of the local |
||||||
|
customer. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def set_group_and_method(self, group, method): |
||||||
|
"""Communicates to this IngestionManager the operation group and method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the operation. |
||||||
|
method: The method identifier of the operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def add_local_allowance(self, allowance): |
||||||
|
"""Communicates to this IngestionManager that more payloads may be ingested. |
||||||
|
|
||||||
|
Args: |
||||||
|
allowance: A positive integer indicating an additional number of payloads |
||||||
|
that the local customer is willing to ingest. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def local_emissions_done(self): |
||||||
|
"""Indicates to this manager that local emissions are done.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
"""Advances the operation by passing values to the local customer.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class ReceptionManager(object): |
||||||
|
"""A manager responsible for receiving tickets from the other end.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def receive_ticket(self, ticket): |
||||||
|
"""Handle a ticket from the other side of the operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
ticket: An interfaces.BackToFrontTicket or interfaces.FrontToBackTicket |
||||||
|
appropriate to this end of the operation and this object. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class Operation(object): |
||||||
|
"""An ongoing operation. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
context: A base.OperationContext object for the operation. |
||||||
|
operator: A base.Operator object for the operation for use by the customer |
||||||
|
of the operation. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def handle_ticket(self, ticket): |
||||||
|
"""Handle a ticket from the other side of the operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
ticket: A links.Ticket from the other side of the operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def abort(self, outcome): |
||||||
|
"""Aborts the operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
outcome: A base.Outcome value indicating operation abortion. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
@ -0,0 +1,192 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Implementation of operations.""" |
||||||
|
|
||||||
|
import threading |
||||||
|
|
||||||
|
# _utilities is referenced from specification in this module. |
||||||
|
from grpc.framework.core import _context |
||||||
|
from grpc.framework.core import _emission |
||||||
|
from grpc.framework.core import _expiration |
||||||
|
from grpc.framework.core import _ingestion |
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.core import _reception |
||||||
|
from grpc.framework.core import _termination |
||||||
|
from grpc.framework.core import _transmission |
||||||
|
from grpc.framework.core import _utilities # pylint: disable=unused-import |
||||||
|
|
||||||
|
|
||||||
|
class _EasyOperation(_interfaces.Operation): |
||||||
|
"""A trivial implementation of interfaces.Operation.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, lock, termination_manager, transmission_manager, expiration_manager, |
||||||
|
context, operator, reception_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
lock: The operation-wide lock. |
||||||
|
termination_manager: The _interfaces.TerminationManager for the operation. |
||||||
|
transmission_manager: The _interfaces.TransmissionManager for the |
||||||
|
operation. |
||||||
|
expiration_manager: The _interfaces.ExpirationManager for the operation. |
||||||
|
context: A base.OperationContext for use by the customer during the |
||||||
|
operation. |
||||||
|
operator: A base.Operator for use by the customer during the operation. |
||||||
|
reception_manager: The _interfaces.ReceptionManager for the operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
self._reception_manager = reception_manager |
||||||
|
|
||||||
|
self.context = context |
||||||
|
self.operator = operator |
||||||
|
|
||||||
|
def handle_ticket(self, ticket): |
||||||
|
with self._lock: |
||||||
|
self._reception_manager.receive_ticket(ticket) |
||||||
|
|
||||||
|
def abort(self, outcome): |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._termination_manager.abort(outcome) |
||||||
|
self._transmission_manager.abort(outcome) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
|
||||||
|
|
||||||
|
def invocation_operate( |
||||||
|
operation_id, group, method, subscription, timeout, initial_metadata, |
||||||
|
payload, completion, ticket_sink, termination_action, pool): |
||||||
|
"""Constructs objects necessary for front-side operation management. |
||||||
|
|
||||||
|
Args: |
||||||
|
operation_id: An object identifying the operation. |
||||||
|
group: The group identifier of the operation. |
||||||
|
method: The method identifier of the operation. |
||||||
|
subscription: A base.Subscription describing the customer's interest in the |
||||||
|
results of the operation. |
||||||
|
timeout: A length of time in seconds to allow for the operation. |
||||||
|
initial_metadata: An initial metadata value to be sent to the other side of |
||||||
|
the operation. May be None if the initial metadata will be passed later or |
||||||
|
if there will be no initial metadata passed at all. |
||||||
|
payload: The first payload value to be transmitted to the other side. May be |
||||||
|
None if there is no such value or if the customer chose not to pass it at |
||||||
|
operation invocation. |
||||||
|
completion: A base.Completion value indicating the end of values passed to |
||||||
|
the other side of the operation. |
||||||
|
ticket_sink: A callable that accepts links.Tickets and delivers them to the |
||||||
|
other side of the operation. |
||||||
|
termination_action: A callable that accepts the outcome of the operation as |
||||||
|
a base.Outcome value to be called on operation completion. |
||||||
|
pool: A thread pool with which to do the work of the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An _interfaces.Operation for the operation. |
||||||
|
""" |
||||||
|
lock = threading.Lock() |
||||||
|
with lock: |
||||||
|
termination_manager = _termination.invocation_termination_manager( |
||||||
|
termination_action, pool) |
||||||
|
transmission_manager = _transmission.TransmissionManager( |
||||||
|
operation_id, ticket_sink, lock, pool, termination_manager) |
||||||
|
expiration_manager = _expiration.invocation_expiration_manager( |
||||||
|
timeout, lock, termination_manager, transmission_manager) |
||||||
|
operation_context = _context.OperationContext( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager) |
||||||
|
emission_manager = _emission.EmissionManager( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager) |
||||||
|
ingestion_manager = _ingestion.invocation_ingestion_manager( |
||||||
|
subscription, lock, pool, termination_manager, transmission_manager, |
||||||
|
expiration_manager) |
||||||
|
reception_manager = _reception.ReceptionManager( |
||||||
|
termination_manager, transmission_manager, expiration_manager, |
||||||
|
ingestion_manager) |
||||||
|
|
||||||
|
termination_manager.set_expiration_manager(expiration_manager) |
||||||
|
transmission_manager.set_expiration_manager(expiration_manager) |
||||||
|
emission_manager.set_ingestion_manager(ingestion_manager) |
||||||
|
|
||||||
|
transmission_manager.kick_off( |
||||||
|
group, method, timeout, initial_metadata, payload, completion, None) |
||||||
|
|
||||||
|
return _EasyOperation( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager, |
||||||
|
operation_context, emission_manager, reception_manager) |
||||||
|
|
||||||
|
|
||||||
|
def service_operate( |
||||||
|
servicer_package, ticket, ticket_sink, termination_action, pool): |
||||||
|
"""Constructs an Operation for service of an operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer_package: A _utilities.ServicerPackage to be used servicing the |
||||||
|
operation. |
||||||
|
ticket: The first links.Ticket received for the operation. |
||||||
|
ticket_sink: A callable that accepts links.Tickets and delivers them to the |
||||||
|
other side of the operation. |
||||||
|
termination_action: A callable that accepts the outcome of the operation as |
||||||
|
a base.Outcome value to be called on operation completion. |
||||||
|
pool: A thread pool with which to do the work of the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An _interfaces.Operation for the operation. |
||||||
|
""" |
||||||
|
lock = threading.Lock() |
||||||
|
with lock: |
||||||
|
termination_manager = _termination.service_termination_manager( |
||||||
|
termination_action, pool) |
||||||
|
transmission_manager = _transmission.TransmissionManager( |
||||||
|
ticket.operation_id, ticket_sink, lock, pool, termination_manager) |
||||||
|
expiration_manager = _expiration.service_expiration_manager( |
||||||
|
ticket.timeout, servicer_package.default_timeout, |
||||||
|
servicer_package.maximum_timeout, lock, termination_manager, |
||||||
|
transmission_manager) |
||||||
|
operation_context = _context.OperationContext( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager) |
||||||
|
emission_manager = _emission.EmissionManager( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager) |
||||||
|
ingestion_manager = _ingestion.service_ingestion_manager( |
||||||
|
servicer_package.servicer, operation_context, emission_manager, lock, |
||||||
|
pool, termination_manager, transmission_manager, expiration_manager) |
||||||
|
reception_manager = _reception.ReceptionManager( |
||||||
|
termination_manager, transmission_manager, expiration_manager, |
||||||
|
ingestion_manager) |
||||||
|
|
||||||
|
termination_manager.set_expiration_manager(expiration_manager) |
||||||
|
transmission_manager.set_expiration_manager(expiration_manager) |
||||||
|
emission_manager.set_ingestion_manager(ingestion_manager) |
||||||
|
|
||||||
|
reception_manager.receive_ticket(ticket) |
||||||
|
|
||||||
|
return _EasyOperation( |
||||||
|
lock, termination_manager, transmission_manager, expiration_manager, |
||||||
|
operation_context, emission_manager, reception_manager) |
@ -0,0 +1,137 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for ticket reception.""" |
||||||
|
|
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
from grpc.framework.interfaces.base import utilities |
||||||
|
from grpc.framework.interfaces.links import links |
||||||
|
|
||||||
|
_REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME = { |
||||||
|
links.Ticket.Termination.CANCELLATION: base.Outcome.CANCELLED, |
||||||
|
links.Ticket.Termination.EXPIRATION: base.Outcome.EXPIRED, |
||||||
|
links.Ticket.Termination.SHUTDOWN: base.Outcome.REMOTE_SHUTDOWN, |
||||||
|
links.Ticket.Termination.RECEPTION_FAILURE: base.Outcome.RECEPTION_FAILURE, |
||||||
|
links.Ticket.Termination.TRANSMISSION_FAILURE: |
||||||
|
base.Outcome.TRANSMISSION_FAILURE, |
||||||
|
links.Ticket.Termination.LOCAL_FAILURE: base.Outcome.REMOTE_FAILURE, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class ReceptionManager(_interfaces.ReceptionManager): |
||||||
|
"""A ReceptionManager based around a _Receiver passed to it.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, termination_manager, transmission_manager, expiration_manager, |
||||||
|
ingestion_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
termination_manager: The operation's _interfaces.TerminationManager. |
||||||
|
transmission_manager: The operation's _interfaces.TransmissionManager. |
||||||
|
expiration_manager: The operation's _interfaces.ExpirationManager. |
||||||
|
ingestion_manager: The operation's _interfaces.IngestionManager. |
||||||
|
""" |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._transmission_manager = transmission_manager |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
self._ingestion_manager = ingestion_manager |
||||||
|
|
||||||
|
self._lowest_unseen_sequence_number = 0 |
||||||
|
self._out_of_sequence_tickets = {} |
||||||
|
self._aborted = False |
||||||
|
|
||||||
|
def _abort(self, outcome): |
||||||
|
self._aborted = True |
||||||
|
self._termination_manager.abort(outcome) |
||||||
|
self._transmission_manager.abort(outcome) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
|
||||||
|
def _sequence_failure(self, ticket): |
||||||
|
"""Determines a just-arrived ticket's sequential legitimacy. |
||||||
|
|
||||||
|
Args: |
||||||
|
ticket: A just-arrived ticket. |
||||||
|
|
||||||
|
Returns: |
||||||
|
True if the ticket is sequentially legitimate; False otherwise. |
||||||
|
""" |
||||||
|
if ticket.sequence_number < self._lowest_unseen_sequence_number: |
||||||
|
return True |
||||||
|
elif ticket.sequence_number in self._out_of_sequence_tickets: |
||||||
|
return True |
||||||
|
else: |
||||||
|
return False |
||||||
|
|
||||||
|
def _process_one(self, ticket): |
||||||
|
if ticket.sequence_number == 0: |
||||||
|
self._ingestion_manager.set_group_and_method(ticket.group, ticket.method) |
||||||
|
if ticket.timeout is not None: |
||||||
|
self._expiration_manager.change_timeout(ticket.timeout) |
||||||
|
if ticket.termination is None: |
||||||
|
completion = None |
||||||
|
else: |
||||||
|
completion = utilities.completion( |
||||||
|
ticket.terminal_metadata, ticket.code, ticket.message) |
||||||
|
self._ingestion_manager.advance( |
||||||
|
ticket.initial_metadata, ticket.payload, completion, ticket.allowance) |
||||||
|
if ticket.allowance is not None: |
||||||
|
self._transmission_manager.allowance(ticket.allowance) |
||||||
|
|
||||||
|
def _process(self, ticket): |
||||||
|
"""Process those tickets ready to be processed. |
||||||
|
|
||||||
|
Args: |
||||||
|
ticket: A just-arrived ticket the sequence number of which matches this |
||||||
|
_ReceptionManager's _lowest_unseen_sequence_number field. |
||||||
|
""" |
||||||
|
while True: |
||||||
|
self._process_one(ticket) |
||||||
|
next_ticket = self._out_of_sequence_tickets.pop( |
||||||
|
ticket.sequence_number + 1, None) |
||||||
|
if next_ticket is None: |
||||||
|
self._lowest_unseen_sequence_number = ticket.sequence_number + 1 |
||||||
|
return |
||||||
|
else: |
||||||
|
ticket = next_ticket |
||||||
|
|
||||||
|
def receive_ticket(self, ticket): |
||||||
|
"""See _interfaces.ReceptionManager.receive_ticket for specification.""" |
||||||
|
if self._aborted: |
||||||
|
return |
||||||
|
elif self._sequence_failure(ticket): |
||||||
|
self._abort(base.Outcome.RECEPTION_FAILURE) |
||||||
|
elif ticket.termination not in (None, links.Ticket.Termination.COMPLETION): |
||||||
|
outcome = _REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME[ticket.termination] |
||||||
|
self._abort(outcome) |
||||||
|
elif ticket.sequence_number == self._lowest_unseen_sequence_number: |
||||||
|
self._process(ticket) |
||||||
|
else: |
||||||
|
self._out_of_sequence_tickets[ticket.sequence_number] = ticket |
@ -0,0 +1,212 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for operation termination.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
|
||||||
|
from grpc.framework.core import _constants |
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.foundation import callable_util |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
|
||||||
|
|
||||||
|
def _invocation_completion_predicate( |
||||||
|
unused_emission_complete, unused_transmission_complete, |
||||||
|
unused_reception_complete, ingestion_complete): |
||||||
|
return ingestion_complete |
||||||
|
|
||||||
|
|
||||||
|
def _service_completion_predicate( |
||||||
|
unused_emission_complete, transmission_complete, unused_reception_complete, |
||||||
|
unused_ingestion_complete): |
||||||
|
return transmission_complete |
||||||
|
|
||||||
|
|
||||||
|
class TerminationManager(_interfaces.TerminationManager): |
||||||
|
"""A _interfaces.TransmissionManager on which another manager may be set.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def set_expiration_manager(self, expiration_manager): |
||||||
|
"""Sets the expiration manager with which this manager will interact. |
||||||
|
|
||||||
|
Args: |
||||||
|
expiration_manager: The _interfaces.ExpirationManager associated with the |
||||||
|
current operation. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class _TerminationManager(TerminationManager): |
||||||
|
"""An implementation of TerminationManager.""" |
||||||
|
|
||||||
|
def __init__(self, predicate, action, pool): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
predicate: One of _invocation_completion_predicate or |
||||||
|
_service_completion_predicate to be used to determine when the operation |
||||||
|
has completed. |
||||||
|
action: A behavior to pass the operation outcome on operation termination. |
||||||
|
pool: A thread pool. |
||||||
|
""" |
||||||
|
self._predicate = predicate |
||||||
|
self._action = action |
||||||
|
self._pool = pool |
||||||
|
self._expiration_manager = None |
||||||
|
|
||||||
|
self.outcome = None |
||||||
|
self._callbacks = [] |
||||||
|
|
||||||
|
self._emission_complete = False |
||||||
|
self._transmission_complete = False |
||||||
|
self._reception_complete = False |
||||||
|
self._ingestion_complete = False |
||||||
|
|
||||||
|
def set_expiration_manager(self, expiration_manager): |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
|
||||||
|
def _terminate_internal_only(self, outcome): |
||||||
|
"""Terminates the operation. |
||||||
|
|
||||||
|
Args: |
||||||
|
outcome: A base.Outcome describing the outcome of the operation. |
||||||
|
""" |
||||||
|
self.outcome = outcome |
||||||
|
callbacks = list(self._callbacks) |
||||||
|
self._callbacks = None |
||||||
|
|
||||||
|
act = callable_util.with_exceptions_logged( |
||||||
|
self._action, _constants.INTERNAL_ERROR_LOG_MESSAGE) |
||||||
|
|
||||||
|
if outcome is base.Outcome.LOCAL_FAILURE: |
||||||
|
self._pool.submit(act, outcome) |
||||||
|
else: |
||||||
|
def call_callbacks_and_act(callbacks, outcome): |
||||||
|
for callback in callbacks: |
||||||
|
callback_outcome = callable_util.call_logging_exceptions( |
||||||
|
callback, _constants.TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE, |
||||||
|
outcome) |
||||||
|
if callback_outcome.exception is not None: |
||||||
|
outcome = base.Outcome.LOCAL_FAILURE |
||||||
|
break |
||||||
|
act(outcome) |
||||||
|
|
||||||
|
self._pool.submit( |
||||||
|
callable_util.with_exceptions_logged( |
||||||
|
call_callbacks_and_act, _constants.INTERNAL_ERROR_LOG_MESSAGE), |
||||||
|
callbacks, outcome) |
||||||
|
|
||||||
|
def _terminate_and_notify(self, outcome): |
||||||
|
self._terminate_internal_only(outcome) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
|
||||||
|
def _perhaps_complete(self): |
||||||
|
if self._predicate( |
||||||
|
self._emission_complete, self._transmission_complete, |
||||||
|
self._reception_complete, self._ingestion_complete): |
||||||
|
self._terminate_and_notify(base.Outcome.COMPLETED) |
||||||
|
return True |
||||||
|
else: |
||||||
|
return False |
||||||
|
|
||||||
|
def is_active(self): |
||||||
|
"""See _interfaces.TerminationManager.is_active for specification.""" |
||||||
|
return self.outcome is None |
||||||
|
|
||||||
|
def add_callback(self, callback): |
||||||
|
"""See _interfaces.TerminationManager.add_callback for specification.""" |
||||||
|
if self.outcome is None: |
||||||
|
self._callbacks.append(callback) |
||||||
|
return None |
||||||
|
else: |
||||||
|
return self.outcome |
||||||
|
|
||||||
|
def emission_complete(self): |
||||||
|
"""See superclass method for specification.""" |
||||||
|
if self.outcome is None: |
||||||
|
self._emission_complete = True |
||||||
|
self._perhaps_complete() |
||||||
|
|
||||||
|
def transmission_complete(self): |
||||||
|
"""See superclass method for specification.""" |
||||||
|
if self.outcome is None: |
||||||
|
self._transmission_complete = True |
||||||
|
return self._perhaps_complete() |
||||||
|
else: |
||||||
|
return False |
||||||
|
|
||||||
|
def reception_complete(self): |
||||||
|
"""See superclass method for specification.""" |
||||||
|
if self.outcome is None: |
||||||
|
self._reception_complete = True |
||||||
|
self._perhaps_complete() |
||||||
|
|
||||||
|
def ingestion_complete(self): |
||||||
|
"""See superclass method for specification.""" |
||||||
|
if self.outcome is None: |
||||||
|
self._ingestion_complete = True |
||||||
|
self._perhaps_complete() |
||||||
|
|
||||||
|
def expire(self): |
||||||
|
"""See _interfaces.TerminationManager.expire for specification.""" |
||||||
|
self._terminate_internal_only(base.Outcome.EXPIRED) |
||||||
|
|
||||||
|
def abort(self, outcome): |
||||||
|
"""See _interfaces.TerminationManager.abort for specification.""" |
||||||
|
self._terminate_and_notify(outcome) |
||||||
|
|
||||||
|
|
||||||
|
def invocation_termination_manager(action, pool): |
||||||
|
"""Creates a TerminationManager appropriate for invocation-side use. |
||||||
|
|
||||||
|
Args: |
||||||
|
action: An action to call on operation termination. |
||||||
|
pool: A thread pool in which to execute the passed action and any |
||||||
|
termination callbacks that are registered during the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A TerminationManager appropriate for invocation-side use. |
||||||
|
""" |
||||||
|
return _TerminationManager(_invocation_completion_predicate, action, pool) |
||||||
|
|
||||||
|
|
||||||
|
def service_termination_manager(action, pool): |
||||||
|
"""Creates a TerminationManager appropriate for service-side use. |
||||||
|
|
||||||
|
Args: |
||||||
|
action: An action to call on operation termination. |
||||||
|
pool: A thread pool in which to execute the passed action and any |
||||||
|
termination callbacks that are registered during the operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A TerminationManager appropriate for service-side use. |
||||||
|
""" |
||||||
|
return _TerminationManager(_service_completion_predicate, action, pool) |
@ -0,0 +1,294 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""State and behavior for ticket transmission during an operation.""" |
||||||
|
|
||||||
|
from grpc.framework.core import _constants |
||||||
|
from grpc.framework.core import _interfaces |
||||||
|
from grpc.framework.foundation import callable_util |
||||||
|
from grpc.framework.interfaces.base import base |
||||||
|
from grpc.framework.interfaces.links import links |
||||||
|
|
||||||
|
_TRANSMISSION_EXCEPTION_LOG_MESSAGE = 'Exception during transmission!' |
||||||
|
|
||||||
|
|
||||||
|
def _explode_completion(completion): |
||||||
|
if completion is None: |
||||||
|
return None, None, None, None |
||||||
|
else: |
||||||
|
return ( |
||||||
|
completion.terminal_metadata, completion.code, completion.message, |
||||||
|
links.Ticket.Termination.COMPLETION) |
||||||
|
|
||||||
|
|
||||||
|
class TransmissionManager(_interfaces.TransmissionManager): |
||||||
|
"""An _interfaces.TransmissionManager that sends links.Tickets.""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, operation_id, ticket_sink, lock, pool, termination_manager): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
operation_id: The operation's ID. |
||||||
|
ticket_sink: A callable that accepts tickets and sends them to the other |
||||||
|
side of the operation. |
||||||
|
lock: The operation-servicing-wide lock object. |
||||||
|
pool: A thread pool in which the work of transmitting tickets will be |
||||||
|
performed. |
||||||
|
termination_manager: The _interfaces.TerminationManager associated with |
||||||
|
this operation. |
||||||
|
""" |
||||||
|
self._lock = lock |
||||||
|
self._pool = pool |
||||||
|
self._ticket_sink = ticket_sink |
||||||
|
self._operation_id = operation_id |
||||||
|
self._termination_manager = termination_manager |
||||||
|
self._expiration_manager = None |
||||||
|
|
||||||
|
self._lowest_unused_sequence_number = 0 |
||||||
|
self._remote_allowance = 1 |
||||||
|
self._remote_complete = False |
||||||
|
self._timeout = None |
||||||
|
self._local_allowance = 0 |
||||||
|
self._initial_metadata = None |
||||||
|
self._payloads = [] |
||||||
|
self._completion = None |
||||||
|
self._aborted = False |
||||||
|
self._abortion_outcome = None |
||||||
|
self._transmitting = False |
||||||
|
|
||||||
|
def set_expiration_manager(self, expiration_manager): |
||||||
|
"""Sets the ExpirationManager with which this manager will cooperate.""" |
||||||
|
self._expiration_manager = expiration_manager |
||||||
|
|
||||||
|
def _next_ticket(self): |
||||||
|
"""Creates the next ticket to be transmitted. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A links.Ticket to be sent to the other side of the operation or None if |
||||||
|
there is nothing to be sent at this time. |
||||||
|
""" |
||||||
|
if self._aborted: |
||||||
|
if self._abortion_outcome is None: |
||||||
|
return None |
||||||
|
else: |
||||||
|
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION[ |
||||||
|
self._abortion_outcome] |
||||||
|
if termination is None: |
||||||
|
return None |
||||||
|
else: |
||||||
|
self._abortion_outcome = None |
||||||
|
return links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, |
||||||
|
None, None, None, None, None, None, None, None, None, |
||||||
|
termination) |
||||||
|
|
||||||
|
action = False |
||||||
|
# TODO(nathaniel): Support other subscriptions. |
||||||
|
local_subscription = links.Ticket.Subscription.FULL |
||||||
|
timeout = self._timeout |
||||||
|
if timeout is not None: |
||||||
|
self._timeout = None |
||||||
|
action = True |
||||||
|
if self._local_allowance <= 0: |
||||||
|
allowance = None |
||||||
|
else: |
||||||
|
allowance = self._local_allowance |
||||||
|
self._local_allowance = 0 |
||||||
|
action = True |
||||||
|
initial_metadata = self._initial_metadata |
||||||
|
if initial_metadata is not None: |
||||||
|
self._initial_metadata = None |
||||||
|
action = True |
||||||
|
if not self._payloads or self._remote_allowance <= 0: |
||||||
|
payload = None |
||||||
|
else: |
||||||
|
payload = self._payloads.pop(0) |
||||||
|
self._remote_allowance -= 1 |
||||||
|
action = True |
||||||
|
if self._completion is None or self._payloads: |
||||||
|
terminal_metadata, code, message, termination = None, None, None, None |
||||||
|
else: |
||||||
|
terminal_metadata, code, message, termination = _explode_completion( |
||||||
|
self._completion) |
||||||
|
self._completion = None |
||||||
|
action = True |
||||||
|
|
||||||
|
if action: |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, None, |
||||||
|
local_subscription, timeout, allowance, initial_metadata, payload, |
||||||
|
terminal_metadata, code, message, termination) |
||||||
|
self._lowest_unused_sequence_number += 1 |
||||||
|
return ticket |
||||||
|
else: |
||||||
|
return None |
||||||
|
|
||||||
|
def _transmit(self, ticket): |
||||||
|
"""Commences the transmission loop sending tickets. |
||||||
|
|
||||||
|
Args: |
||||||
|
ticket: A links.Ticket to be sent to the other side of the operation. |
||||||
|
""" |
||||||
|
def transmit(ticket): |
||||||
|
while True: |
||||||
|
transmission_outcome = callable_util.call_logging_exceptions( |
||||||
|
self._ticket_sink, _TRANSMISSION_EXCEPTION_LOG_MESSAGE, ticket) |
||||||
|
if transmission_outcome.exception is None: |
||||||
|
with self._lock: |
||||||
|
if ticket.termination is links.Ticket.Termination.COMPLETION: |
||||||
|
self._termination_manager.transmission_complete() |
||||||
|
ticket = self._next_ticket() |
||||||
|
if ticket is None: |
||||||
|
self._transmitting = False |
||||||
|
return |
||||||
|
else: |
||||||
|
with self._lock: |
||||||
|
if self._termination_manager.outcome is None: |
||||||
|
self._termination_manager.abort(base.Outcome.TRANSMISSION_FAILURE) |
||||||
|
self._expiration_manager.terminate() |
||||||
|
return |
||||||
|
|
||||||
|
self._pool.submit(callable_util.with_exceptions_logged( |
||||||
|
transmit, _constants.INTERNAL_ERROR_LOG_MESSAGE), ticket) |
||||||
|
self._transmitting = True |
||||||
|
|
||||||
|
def kick_off( |
||||||
|
self, group, method, timeout, initial_metadata, payload, completion, |
||||||
|
allowance): |
||||||
|
"""See _interfaces.TransmissionManager.kickoff for specification.""" |
||||||
|
# TODO(nathaniel): Support other subscriptions. |
||||||
|
subscription = links.Ticket.Subscription.FULL |
||||||
|
terminal_metadata, code, message, termination = _explode_completion( |
||||||
|
completion) |
||||||
|
self._remote_allowance = 1 if payload is None else 0 |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, 0, group, method, subscription, timeout, allowance, |
||||||
|
initial_metadata, payload, terminal_metadata, code, message, |
||||||
|
termination) |
||||||
|
self._lowest_unused_sequence_number = 1 |
||||||
|
self._transmit(ticket) |
||||||
|
|
||||||
|
def advance(self, initial_metadata, payload, completion, allowance): |
||||||
|
"""See _interfaces.TransmissionManager.advance for specification.""" |
||||||
|
effective_initial_metadata = initial_metadata |
||||||
|
effective_payload = payload |
||||||
|
effective_completion = completion |
||||||
|
if allowance is not None and not self._remote_complete: |
||||||
|
effective_allowance = allowance |
||||||
|
else: |
||||||
|
effective_allowance = None |
||||||
|
if self._transmitting: |
||||||
|
if effective_initial_metadata is not None: |
||||||
|
self._initial_metadata = effective_initial_metadata |
||||||
|
if effective_payload is not None: |
||||||
|
self._payloads.append(effective_payload) |
||||||
|
if effective_completion is not None: |
||||||
|
self._completion = effective_completion |
||||||
|
if effective_allowance is not None: |
||||||
|
self._local_allowance += effective_allowance |
||||||
|
else: |
||||||
|
if effective_payload is not None: |
||||||
|
if 0 < self._remote_allowance: |
||||||
|
ticket_payload = effective_payload |
||||||
|
self._remote_allowance -= 1 |
||||||
|
else: |
||||||
|
self._payloads.append(effective_payload) |
||||||
|
ticket_payload = None |
||||||
|
else: |
||||||
|
ticket_payload = None |
||||||
|
if effective_completion is not None and not self._payloads: |
||||||
|
ticket_completion = effective_completion |
||||||
|
else: |
||||||
|
self._completion = effective_completion |
||||||
|
ticket_completion = None |
||||||
|
if any( |
||||||
|
(effective_initial_metadata, ticket_payload, ticket_completion, |
||||||
|
effective_allowance)): |
||||||
|
terminal_metadata, code, message, termination = _explode_completion( |
||||||
|
completion) |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, None, |
||||||
|
None, None, allowance, effective_initial_metadata, ticket_payload, |
||||||
|
terminal_metadata, code, message, termination) |
||||||
|
self._lowest_unused_sequence_number += 1 |
||||||
|
self._transmit(ticket) |
||||||
|
|
||||||
|
def timeout(self, timeout): |
||||||
|
"""See _interfaces.TransmissionManager.timeout for specification.""" |
||||||
|
if self._transmitting: |
||||||
|
self._timeout = timeout |
||||||
|
else: |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, None, |
||||||
|
None, timeout, None, None, None, None, None, None, None) |
||||||
|
self._lowest_unused_sequence_number += 1 |
||||||
|
self._transmit(ticket) |
||||||
|
|
||||||
|
def allowance(self, allowance): |
||||||
|
"""See _interfaces.TransmissionManager.allowance for specification.""" |
||||||
|
if self._transmitting or not self._payloads: |
||||||
|
self._remote_allowance += allowance |
||||||
|
else: |
||||||
|
self._remote_allowance += allowance - 1 |
||||||
|
payload = self._payloads.pop(0) |
||||||
|
if self._payloads: |
||||||
|
completion = None |
||||||
|
else: |
||||||
|
completion = self._completion |
||||||
|
self._completion = None |
||||||
|
terminal_metadata, code, message, termination = _explode_completion( |
||||||
|
completion) |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, None, |
||||||
|
None, None, None, None, payload, terminal_metadata, code, message, |
||||||
|
termination) |
||||||
|
self._lowest_unused_sequence_number += 1 |
||||||
|
self._transmit(ticket) |
||||||
|
|
||||||
|
def remote_complete(self): |
||||||
|
"""See _interfaces.TransmissionManager.remote_complete for specification.""" |
||||||
|
self._remote_complete = True |
||||||
|
self._local_allowance = 0 |
||||||
|
|
||||||
|
def abort(self, outcome): |
||||||
|
"""See _interfaces.TransmissionManager.abort for specification.""" |
||||||
|
if self._transmitting: |
||||||
|
self._aborted, self._abortion_outcome = True, outcome |
||||||
|
else: |
||||||
|
self._aborted = True |
||||||
|
if outcome is not None: |
||||||
|
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION[ |
||||||
|
outcome] |
||||||
|
if termination is not None: |
||||||
|
ticket = links.Ticket( |
||||||
|
self._operation_id, self._lowest_unused_sequence_number, None, |
||||||
|
None, None, None, None, None, None, None, None, None, |
||||||
|
termination) |
||||||
|
self._transmit(ticket) |
@ -0,0 +1,46 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Package-internal utilities.""" |
||||||
|
|
||||||
|
import collections |
||||||
|
|
||||||
|
|
||||||
|
class ServicerPackage( |
||||||
|
collections.namedtuple( |
||||||
|
'ServicerPackage', ('servicer', 'default_timeout', 'maximum_timeout'))): |
||||||
|
"""A trivial bundle class. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
servicer: A base.Servicer. |
||||||
|
default_timeout: A float indicating the length of time in seconds to allow |
||||||
|
for an operation invoked without a timeout. |
||||||
|
maximum_timeout: A float indicating the maximum length of time in seconds to |
||||||
|
allow for an operation. |
||||||
|
""" |
@ -0,0 +1,62 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Entry points into the ticket-exchange-based base layer implementation.""" |
||||||
|
|
||||||
|
# base and links are referenced from specification in this module. |
||||||
|
from grpc.framework.core import _end |
||||||
|
from grpc.framework.interfaces.base import base # pylint: disable=unused-import |
||||||
|
from grpc.framework.interfaces.links import links # pylint: disable=unused-import |
||||||
|
|
||||||
|
|
||||||
|
def invocation_end_link(): |
||||||
|
"""Creates a base.End-links.Link suitable for operation invocation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a base.End and a links.Link, that supports operation |
||||||
|
invocation, and that translates operation invocation into ticket exchange. |
||||||
|
""" |
||||||
|
return _end.serviceless_end_link() |
||||||
|
|
||||||
|
|
||||||
|
def service_end_link(servicer, default_timeout, maximum_timeout): |
||||||
|
"""Creates a base.End-links.Link suitable for operation service. |
||||||
|
|
||||||
|
Args: |
||||||
|
servicer: A base.Servicer for servicing operations. |
||||||
|
default_timeout: A length of time in seconds to be used as the default |
||||||
|
time alloted for a single operation. |
||||||
|
maximum_timeout: A length of time in seconds to be used as the maximum |
||||||
|
time alloted for a single operation. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a base.End and a links.Link and that services |
||||||
|
operations that arrive at it through ticket exchange. |
||||||
|
""" |
||||||
|
return _end.serviceful_end_link(servicer, default_timeout, maximum_timeout) |
@ -0,0 +1,30 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
|
@ -0,0 +1,933 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Interfaces defining the Face layer of RPC Framework.""" |
||||||
|
|
||||||
|
import abc |
||||||
|
import collections |
||||||
|
import enum |
||||||
|
|
||||||
|
# cardinality, style, abandonment, future, and stream are |
||||||
|
# referenced from specification in this module. |
||||||
|
from grpc.framework.common import cardinality # pylint: disable=unused-import |
||||||
|
from grpc.framework.common import style # pylint: disable=unused-import |
||||||
|
from grpc.framework.foundation import abandonment # pylint: disable=unused-import |
||||||
|
from grpc.framework.foundation import future # pylint: disable=unused-import |
||||||
|
from grpc.framework.foundation import stream # pylint: disable=unused-import |
||||||
|
|
||||||
|
|
||||||
|
class NoSuchMethodError(Exception): |
||||||
|
"""Raised by customer code to indicate an unrecognized method. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
group: The group of the unrecognized method. |
||||||
|
name: The name of the unrecognized method. |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__(self, group, method): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the unrecognized RPC name. |
||||||
|
method: The method identifier of the unrecognized RPC name. |
||||||
|
""" |
||||||
|
super(NoSuchMethodError, self).__init__() |
||||||
|
self.group = group |
||||||
|
self.method = method |
||||||
|
|
||||||
|
def __repr__(self): |
||||||
|
return 'face.NoSuchMethodError(%s, %s)' % (self.group, self.method,) |
||||||
|
|
||||||
|
|
||||||
|
class Abortion( |
||||||
|
collections.namedtuple( |
||||||
|
'Abortion', |
||||||
|
('kind', 'initial_metadata', 'terminal_metadata', 'code', 'details',))): |
||||||
|
"""A value describing RPC abortion. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
kind: A Kind value identifying how the RPC failed. |
||||||
|
initial_metadata: The initial metadata from the other side of the RPC or |
||||||
|
None if no initial metadata value was received. |
||||||
|
terminal_metadata: The terminal metadata from the other side of the RPC or |
||||||
|
None if no terminal metadata value was received. |
||||||
|
code: The code value from the other side of the RPC or None if no code value |
||||||
|
was received. |
||||||
|
details: The details value from the other side of the RPC or None if no |
||||||
|
details value was received. |
||||||
|
""" |
||||||
|
|
||||||
|
@enum.unique |
||||||
|
class Kind(enum.Enum): |
||||||
|
"""Types of RPC abortion.""" |
||||||
|
|
||||||
|
CANCELLED = 'cancelled' |
||||||
|
EXPIRED = 'expired' |
||||||
|
LOCAL_SHUTDOWN = 'local shutdown' |
||||||
|
REMOTE_SHUTDOWN = 'remote shutdown' |
||||||
|
NETWORK_FAILURE = 'network failure' |
||||||
|
LOCAL_FAILURE = 'local failure' |
||||||
|
REMOTE_FAILURE = 'remote failure' |
||||||
|
|
||||||
|
|
||||||
|
class AbortionError(Exception): |
||||||
|
"""Common super type for exceptions indicating RPC abortion. |
||||||
|
|
||||||
|
initial_metadata: The initial metadata from the other side of the RPC or |
||||||
|
None if no initial metadata value was received. |
||||||
|
terminal_metadata: The terminal metadata from the other side of the RPC or |
||||||
|
None if no terminal metadata value was received. |
||||||
|
code: The code value from the other side of the RPC or None if no code value |
||||||
|
was received. |
||||||
|
details: The details value from the other side of the RPC or None if no |
||||||
|
details value was received. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
def __init__(self, initial_metadata, terminal_metadata, code, details): |
||||||
|
super(AbortionError, self).__init__() |
||||||
|
self.initial_metadata = initial_metadata |
||||||
|
self.terminal_metadata = terminal_metadata |
||||||
|
self.code = code |
||||||
|
self.details = details |
||||||
|
|
||||||
|
|
||||||
|
class CancellationError(AbortionError): |
||||||
|
"""Indicates that an RPC has been cancelled.""" |
||||||
|
|
||||||
|
|
||||||
|
class ExpirationError(AbortionError): |
||||||
|
"""Indicates that an RPC has expired ("timed out").""" |
||||||
|
|
||||||
|
|
||||||
|
class LocalShutdownError(AbortionError): |
||||||
|
"""Indicates that an RPC has terminated due to local shutdown of RPCs.""" |
||||||
|
|
||||||
|
|
||||||
|
class RemoteShutdownError(AbortionError): |
||||||
|
"""Indicates that an RPC has terminated due to remote shutdown of RPCs.""" |
||||||
|
|
||||||
|
|
||||||
|
class NetworkError(AbortionError): |
||||||
|
"""Indicates that some error occurred on the network.""" |
||||||
|
|
||||||
|
|
||||||
|
class LocalError(AbortionError): |
||||||
|
"""Indicates that an RPC has terminated due to a local defect.""" |
||||||
|
|
||||||
|
|
||||||
|
class RemoteError(AbortionError): |
||||||
|
"""Indicates that an RPC has terminated due to a remote defect.""" |
||||||
|
|
||||||
|
|
||||||
|
class RpcContext(object): |
||||||
|
"""Provides RPC-related information and action.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def is_active(self): |
||||||
|
"""Describes whether the RPC is active or has terminated.""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def time_remaining(self): |
||||||
|
"""Describes the length of allowed time remaining for the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A nonnegative float indicating the length of allowed time in seconds |
||||||
|
remaining for the RPC to complete before it is considered to have timed |
||||||
|
out. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def add_abortion_callback(self, abortion_callback): |
||||||
|
"""Registers a callback to be called if the RPC is aborted. |
||||||
|
|
||||||
|
Args: |
||||||
|
abortion_callback: A callable to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def cancel(self): |
||||||
|
"""Cancels the RPC. |
||||||
|
|
||||||
|
Idempotent and has no effect if the RPC has already terminated. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class Call(RpcContext): |
||||||
|
"""Invocation-side utility object for an RPC.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def initial_metadata(self): |
||||||
|
"""Accesses the initial metadata from the service-side of the RPC. |
||||||
|
|
||||||
|
This method blocks until the value is available or is known not to have been |
||||||
|
emitted from the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The initial metadata object emitted by the service-side of the RPC, or |
||||||
|
None if there was no such value. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def terminal_metadata(self): |
||||||
|
"""Accesses the terminal metadata from the service-side of the RPC. |
||||||
|
|
||||||
|
This method blocks until the value is available or is known not to have been |
||||||
|
emitted from the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The terminal metadata object emitted by the service-side of the RPC, or |
||||||
|
None if there was no such value. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def code(self): |
||||||
|
"""Accesses the code emitted by the service-side of the RPC. |
||||||
|
|
||||||
|
This method blocks until the value is available or is known not to have been |
||||||
|
emitted from the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The code object emitted by the service-side of the RPC, or None if there |
||||||
|
was no such value. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def details(self): |
||||||
|
"""Accesses the details value emitted by the service-side of the RPC. |
||||||
|
|
||||||
|
This method blocks until the value is available or is known not to have been |
||||||
|
emitted from the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The details value emitted by the service-side of the RPC, or None if there |
||||||
|
was no such value. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class ServicerContext(RpcContext): |
||||||
|
"""A context object passed to method implementations.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def invocation_metadata(self): |
||||||
|
"""Accesses the metadata from the invocation-side of the RPC. |
||||||
|
|
||||||
|
This method blocks until the value is available or is known not to have been |
||||||
|
emitted from the invocation-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The metadata object emitted by the invocation-side of the RPC, or None if |
||||||
|
there was no such value. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def initial_metadata(self, initial_metadata): |
||||||
|
"""Accepts the service-side initial metadata value of the RPC. |
||||||
|
|
||||||
|
This method need not be called by method implementations if they have no |
||||||
|
service-side initial metadata to transmit. |
||||||
|
|
||||||
|
Args: |
||||||
|
initial_metadata: The service-side initial metadata value of the RPC to |
||||||
|
be transmitted to the invocation side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def terminal_metadata(self, terminal_metadata): |
||||||
|
"""Accepts the service-side terminal metadata value of the RPC. |
||||||
|
|
||||||
|
This method need not be called by method implementations if they have no |
||||||
|
service-side terminal metadata to transmit. |
||||||
|
|
||||||
|
Args: |
||||||
|
terminal_metadata: The service-side terminal metadata value of the RPC to |
||||||
|
be transmitted to the invocation side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def code(self, code): |
||||||
|
"""Accepts the service-side code of the RPC. |
||||||
|
|
||||||
|
This method need not be called by method implementations if they have no |
||||||
|
code to transmit. |
||||||
|
|
||||||
|
Args: |
||||||
|
code: The code of the RPC to be transmitted to the invocation side of the |
||||||
|
RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def details(self, details): |
||||||
|
"""Accepts the service-side details of the RPC. |
||||||
|
|
||||||
|
This method need not be called by method implementations if they have no |
||||||
|
service-side details to transmit. |
||||||
|
|
||||||
|
Args: |
||||||
|
details: The service-side details value of the RPC to be transmitted to |
||||||
|
the invocation side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class ResponseReceiver(object): |
||||||
|
"""Invocation-side object used to accept the output of an RPC.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def initial_metadata(self, initial_metadata): |
||||||
|
"""Receives the initial metadata from the service-side of the RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
initial_metadata: The initial metadata object emitted from the |
||||||
|
service-side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def response(self, response): |
||||||
|
"""Receives a response from the service-side of the RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
response: A response object emitted from the service-side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def complete(self, terminal_metadata, code, details): |
||||||
|
"""Receives the completion values emitted from the service-side of the RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
terminal_metadata: The terminal metadata object emitted from the |
||||||
|
service-side of the RPC. |
||||||
|
code: The code object emitted from the service-side of the RPC. |
||||||
|
details: The details object emitted from the service-side of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class UnaryUnaryMultiCallable(object): |
||||||
|
"""Affords invoking a unary-unary RPC in any call style.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def __call__( |
||||||
|
self, request, timeout, metadata=None, with_call=False): |
||||||
|
"""Synchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
with_call: Whether or not to include return a Call for the RPC in addition |
||||||
|
to the reponse. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The response value for the RPC, and a Call for the RPC if with_call was |
||||||
|
set to True at invocation. |
||||||
|
|
||||||
|
Raises: |
||||||
|
AbortionError: Indicating that the RPC was aborted. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def future(self, request, timeout, metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and a future.Future. In the |
||||||
|
event of RPC completion, the return Future's result value will be the |
||||||
|
response value of the RPC. In the event of RPC abortion, the returned |
||||||
|
Future's exception value will be an AbortionError. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event( |
||||||
|
self, request, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request: The request value for the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A Call for the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class UnaryStreamMultiCallable(object): |
||||||
|
"""Affords invoking a unary-stream RPC in any call style.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def __call__(self, request, timeout, metadata=None): |
||||||
|
"""Invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and an iterator of response |
||||||
|
values. Drawing response values from the returned iterator may raise |
||||||
|
AbortionError indicating abortion of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event( |
||||||
|
self, request, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request: The request value for the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A Call object for the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class StreamUnaryMultiCallable(object): |
||||||
|
"""Affords invoking a stream-unary RPC in any call style.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def __call__( |
||||||
|
self, request_iterator, timeout, metadata=None, |
||||||
|
with_call=False): |
||||||
|
"""Synchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
with_call: Whether or not to include return a Call for the RPC in addition |
||||||
|
to the reponse. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The response value for the RPC, and a Call for the RPC if with_call was |
||||||
|
set to True at invocation. |
||||||
|
|
||||||
|
Raises: |
||||||
|
AbortionError: Indicating that the RPC was aborted. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def future(self, request_iterator, timeout, metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and a future.Future. In the |
||||||
|
event of RPC completion, the return Future's result value will be the |
||||||
|
response value of the RPC. In the event of RPC abortion, the returned |
||||||
|
Future's exception value will be an AbortionError. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event( |
||||||
|
self, receiver, abortion_callback, timeout, metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A single object that is both a Call object for the RPC and a |
||||||
|
stream.Consumer to which the request values of the RPC should be passed. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class StreamStreamMultiCallable(object): |
||||||
|
"""Affords invoking a stream-stream RPC in any call style.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def __call__(self, request_iterator, timeout, metadata=None): |
||||||
|
"""Invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and an iterator of response |
||||||
|
values. Drawing response values from the returned iterator may raise |
||||||
|
AbortionError indicating abortion of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event( |
||||||
|
self, receiver, abortion_callback, timeout, metadata=None): |
||||||
|
"""Asynchronously invokes the underlying RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of |
||||||
|
the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A single object that is both a Call object for the RPC and a |
||||||
|
stream.Consumer to which the request values of the RPC should be passed. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class MethodImplementation(object): |
||||||
|
"""A sum type that describes a method implementation. |
||||||
|
|
||||||
|
Attributes: |
||||||
|
cardinality: A cardinality.Cardinality value. |
||||||
|
style: A style.Service value. |
||||||
|
unary_unary_inline: The implementation of the method as a callable value |
||||||
|
that takes a request value and a ServicerContext object and returns a |
||||||
|
response value. Only non-None if cardinality is |
||||||
|
cardinality.Cardinality.UNARY_UNARY and style is style.Service.INLINE. |
||||||
|
unary_stream_inline: The implementation of the method as a callable value |
||||||
|
that takes a request value and a ServicerContext object and returns an |
||||||
|
iterator of response values. Only non-None if cardinality is |
||||||
|
cardinality.Cardinality.UNARY_STREAM and style is style.Service.INLINE. |
||||||
|
stream_unary_inline: The implementation of the method as a callable value |
||||||
|
that takes an iterator of request values and a ServicerContext object and |
||||||
|
returns a response value. Only non-None if cardinality is |
||||||
|
cardinality.Cardinality.STREAM_UNARY and style is style.Service.INLINE. |
||||||
|
stream_stream_inline: The implementation of the method as a callable value |
||||||
|
that takes an iterator of request values and a ServicerContext object and |
||||||
|
returns an iterator of response values. Only non-None if cardinality is |
||||||
|
cardinality.Cardinality.STREAM_STREAM and style is style.Service.INLINE. |
||||||
|
unary_unary_event: The implementation of the method as a callable value that |
||||||
|
takes a request value, a response callback to which to pass the response |
||||||
|
value of the RPC, and a ServicerContext. Only non-None if cardinality is |
||||||
|
cardinality.Cardinality.UNARY_UNARY and style is style.Service.EVENT. |
||||||
|
unary_stream_event: The implementation of the method as a callable value |
||||||
|
that takes a request value, a stream.Consumer to which to pass the |
||||||
|
response values of the RPC, and a ServicerContext. Only non-None if |
||||||
|
cardinality is cardinality.Cardinality.UNARY_STREAM and style is |
||||||
|
style.Service.EVENT. |
||||||
|
stream_unary_event: The implementation of the method as a callable value |
||||||
|
that takes a response callback to which to pass the response value of the |
||||||
|
RPC and a ServicerContext and returns a stream.Consumer to which the |
||||||
|
request values of the RPC should be passed. Only non-None if cardinality |
||||||
|
is cardinality.Cardinality.STREAM_UNARY and style is style.Service.EVENT. |
||||||
|
stream_stream_event: The implementation of the method as a callable value |
||||||
|
that takes a stream.Consumer to which to pass the response values of the |
||||||
|
RPC and a ServicerContext and returns a stream.Consumer to which the |
||||||
|
request values of the RPC should be passed. Only non-None if cardinality |
||||||
|
is cardinality.Cardinality.STREAM_STREAM and style is |
||||||
|
style.Service.EVENT. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
|
||||||
|
class MultiMethodImplementation(object): |
||||||
|
"""A general type able to service many methods.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def service(self, group, method, response_consumer, context): |
||||||
|
"""Services an RPC. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
response_consumer: A stream.Consumer to be called to accept the response |
||||||
|
values of the RPC. |
||||||
|
context: a ServicerContext object. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A stream.Consumer with which to accept the request values of the RPC. The |
||||||
|
consumer returned from this method may or may not be invoked to |
||||||
|
completion: in the case of RPC abortion, RPC Framework will simply stop |
||||||
|
passing values to this object. Implementations must not assume that this |
||||||
|
object will be called to completion of the request stream or even called |
||||||
|
at all. |
||||||
|
|
||||||
|
Raises: |
||||||
|
abandonment.Abandoned: May or may not be raised when the RPC has been |
||||||
|
aborted. |
||||||
|
NoSuchMethodError: If this MultiMethod does not recognize the given group |
||||||
|
and name for the RPC and is not able to service the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class GenericStub(object): |
||||||
|
"""Affords RPC invocation via generic methods.""" |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def blocking_unary_unary( |
||||||
|
self, group, method, request, timeout, metadata=None, |
||||||
|
with_call=False): |
||||||
|
"""Invokes a unary-request-unary-response method. |
||||||
|
|
||||||
|
This method blocks until either returning the response value of the RPC |
||||||
|
(in the event of RPC completion) or raising an exception (in the event of |
||||||
|
RPC abortion). |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
with_call: Whether or not to include return a Call for the RPC in addition |
||||||
|
to the reponse. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The response value for the RPC, and a Call for the RPC if with_call was |
||||||
|
set to True at invocation. |
||||||
|
|
||||||
|
Raises: |
||||||
|
AbortionError: Indicating that the RPC was aborted. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def future_unary_unary( |
||||||
|
self, group, method, request, timeout, metadata=None): |
||||||
|
"""Invokes a unary-request-unary-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and a future.Future. In the |
||||||
|
event of RPC completion, the return Future's result value will be the |
||||||
|
response value of the RPC. In the event of RPC abortion, the returned |
||||||
|
Future's exception value will be an AbortionError. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def inline_unary_stream( |
||||||
|
self, group, method, request, timeout, metadata=None): |
||||||
|
"""Invokes a unary-request-stream-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request: The request value for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and an iterator of response |
||||||
|
values. Drawing response values from the returned iterator may raise |
||||||
|
AbortionError indicating abortion of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def blocking_stream_unary( |
||||||
|
self, group, method, request_iterator, timeout, metadata=None, |
||||||
|
with_call=False): |
||||||
|
"""Invokes a stream-request-unary-response method. |
||||||
|
|
||||||
|
This method blocks until either returning the response value of the RPC |
||||||
|
(in the event of RPC completion) or raising an exception (in the event of |
||||||
|
RPC abortion). |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
with_call: Whether or not to include return a Call for the RPC in addition |
||||||
|
to the reponse. |
||||||
|
|
||||||
|
Returns: |
||||||
|
The response value for the RPC, and a Call for the RPC if with_call was |
||||||
|
set to True at invocation. |
||||||
|
|
||||||
|
Raises: |
||||||
|
AbortionError: Indicating that the RPC was aborted. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def future_stream_unary( |
||||||
|
self, group, method, request_iterator, timeout, metadata=None): |
||||||
|
"""Invokes a stream-request-unary-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and a future.Future. In the |
||||||
|
event of RPC completion, the return Future's result value will be the |
||||||
|
response value of the RPC. In the event of RPC abortion, the returned |
||||||
|
Future's exception value will be an AbortionError. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def inline_stream_stream( |
||||||
|
self, group, method, request_iterator, timeout, metadata=None): |
||||||
|
"""Invokes a stream-request-stream-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request_iterator: An iterator that yields request values for the RPC. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An object that is both a Call for the RPC and an iterator of response |
||||||
|
values. Drawing response values from the returned iterator may raise |
||||||
|
AbortionError indicating abortion of the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event_unary_unary( |
||||||
|
self, group, method, request, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Event-driven invocation of a unary-request-unary-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request: The request value for the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A Call for the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event_unary_stream( |
||||||
|
self, group, method, request, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Event-driven invocation of a unary-request-stream-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
request: The request value for the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A Call for the RPC. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event_stream_unary( |
||||||
|
self, group, method, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Event-driven invocation of a unary-request-unary-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A pair of a Call object for the RPC and a stream.Consumer to which the |
||||||
|
request values of the RPC should be passed. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def event_stream_stream( |
||||||
|
self, group, method, receiver, abortion_callback, timeout, |
||||||
|
metadata=None): |
||||||
|
"""Event-driven invocation of a unary-request-stream-response method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
receiver: A ResponseReceiver to be passed the response data of the RPC. |
||||||
|
abortion_callback: A callback to be called and passed an Abortion value |
||||||
|
in the event of RPC abortion. |
||||||
|
timeout: A duration of time in seconds to allow for the RPC. |
||||||
|
metadata: A metadata value to be passed to the service-side of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A pair of a Call object for the RPC and a stream.Consumer to which the |
||||||
|
request values of the RPC should be passed. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def unary_unary(self, group, method): |
||||||
|
"""Creates a UnaryUnaryMultiCallable for a unary-unary method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A UnaryUnaryMultiCallable value for the named unary-unary method. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def unary_stream(self, group, method): |
||||||
|
"""Creates a UnaryStreamMultiCallable for a unary-stream method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A UnaryStreamMultiCallable value for the name unary-stream method. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def stream_unary(self, group, method): |
||||||
|
"""Creates a StreamUnaryMultiCallable for a stream-unary method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A StreamUnaryMultiCallable value for the named stream-unary method. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def stream_stream(self, group, method): |
||||||
|
"""Creates a StreamStreamMultiCallable for a stream-stream method. |
||||||
|
|
||||||
|
Args: |
||||||
|
group: The group identifier of the RPC. |
||||||
|
method: The method identifier of the RPC. |
||||||
|
|
||||||
|
Returns: |
||||||
|
A StreamStreamMultiCallable value for the named stream-stream method. |
||||||
|
""" |
||||||
|
raise NotImplementedError() |
||||||
|
|
||||||
|
|
||||||
|
class DynamicStub(object): |
||||||
|
"""Affords RPC invocation via attributes corresponding to afforded methods. |
||||||
|
|
||||||
|
Instances of this type may be scoped to a single group so that attribute |
||||||
|
access is unambiguous. |
||||||
|
|
||||||
|
Instances of this type respond to attribute access as follows: if the |
||||||
|
requested attribute is the name of a unary-unary method, the value of the |
||||||
|
attribute will be a UnaryUnaryMultiCallable with which to invoke an RPC; if |
||||||
|
the requested attribute is the name of a unary-stream method, the value of the |
||||||
|
attribute will be a UnaryStreamMultiCallable with which to invoke an RPC; if |
||||||
|
the requested attribute is the name of a stream-unary method, the value of the |
||||||
|
attribute will be a StreamUnaryMultiCallable with which to invoke an RPC; and |
||||||
|
if the requested attribute is the name of a stream-stream method, the value of |
||||||
|
the attribute will be a StreamStreamMultiCallable with which to invoke an RPC. |
||||||
|
""" |
||||||
|
__metaclass__ = abc.ABCMeta |
@ -0,0 +1,178 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Utilities for RPC Framework's Face interface.""" |
||||||
|
|
||||||
|
import collections |
||||||
|
|
||||||
|
# stream is referenced from specification in this module. |
||||||
|
from grpc.framework.common import cardinality |
||||||
|
from grpc.framework.common import style |
||||||
|
from grpc.framework.foundation import stream # pylint: disable=unused-import |
||||||
|
from grpc.framework.interfaces.face import face |
||||||
|
|
||||||
|
|
||||||
|
class _MethodImplementation( |
||||||
|
face.MethodImplementation, |
||||||
|
collections.namedtuple( |
||||||
|
'_MethodImplementation', |
||||||
|
['cardinality', 'style', 'unary_unary_inline', 'unary_stream_inline', |
||||||
|
'stream_unary_inline', 'stream_stream_inline', 'unary_unary_event', |
||||||
|
'unary_stream_event', 'stream_unary_event', 'stream_stream_event',])): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
def unary_unary_inline(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a unary-unary RPC method as a callable value |
||||||
|
that takes a request value and an face.ServicerContext object and |
||||||
|
returns a response value. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.UNARY_UNARY, style.Service.INLINE, behavior, |
||||||
|
None, None, None, None, None, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def unary_stream_inline(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a unary-stream RPC method as a callable |
||||||
|
value that takes a request value and an face.ServicerContext object and |
||||||
|
returns an iterator of response values. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.UNARY_STREAM, style.Service.INLINE, None, |
||||||
|
behavior, None, None, None, None, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def stream_unary_inline(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a stream-unary RPC method as a callable |
||||||
|
value that takes an iterator of request values and an |
||||||
|
face.ServicerContext object and returns a response value. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.STREAM_UNARY, style.Service.INLINE, None, None, |
||||||
|
behavior, None, None, None, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def stream_stream_inline(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a stream-stream RPC method as a callable |
||||||
|
value that takes an iterator of request values and an |
||||||
|
face.ServicerContext object and returns an iterator of response values. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.STREAM_STREAM, style.Service.INLINE, None, None, |
||||||
|
None, behavior, None, None, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def unary_unary_event(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a unary-unary RPC method as a callable |
||||||
|
value that takes a request value, a response callback to which to pass |
||||||
|
the response value of the RPC, and an face.ServicerContext. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.UNARY_UNARY, style.Service.EVENT, None, None, |
||||||
|
None, None, behavior, None, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def unary_stream_event(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a unary-stream RPC method as a callable |
||||||
|
value that takes a request value, a stream.Consumer to which to pass the |
||||||
|
the response values of the RPC, and an face.ServicerContext. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.UNARY_STREAM, style.Service.EVENT, None, None, |
||||||
|
None, None, None, behavior, None, None) |
||||||
|
|
||||||
|
|
||||||
|
def stream_unary_event(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a stream-unary RPC method as a callable |
||||||
|
value that takes a response callback to which to pass the response value |
||||||
|
of the RPC and an face.ServicerContext and returns a stream.Consumer to |
||||||
|
which the request values of the RPC should be passed. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.STREAM_UNARY, style.Service.EVENT, None, None, |
||||||
|
None, None, None, None, behavior, None) |
||||||
|
|
||||||
|
|
||||||
|
def stream_stream_event(behavior): |
||||||
|
"""Creates an face.MethodImplementation for the given behavior. |
||||||
|
|
||||||
|
Args: |
||||||
|
behavior: The implementation of a stream-stream RPC method as a callable |
||||||
|
value that takes a stream.Consumer to which to pass the response values |
||||||
|
of the RPC and an face.ServicerContext and returns a stream.Consumer to |
||||||
|
which the request values of the RPC should be passed. |
||||||
|
|
||||||
|
Returns: |
||||||
|
An face.MethodImplementation derived from the given behavior. |
||||||
|
""" |
||||||
|
return _MethodImplementation( |
||||||
|
cardinality.Cardinality.STREAM_STREAM, style.Service.EVENT, None, None, |
||||||
|
None, None, None, None, None, behavior) |
@ -0,0 +1,165 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Tests the RPC Framework Core's implementation of the Base interface.""" |
||||||
|
|
||||||
|
import collections |
||||||
|
import logging |
||||||
|
import random |
||||||
|
import time |
||||||
|
import unittest |
||||||
|
|
||||||
|
from grpc._adapter import _intermediary_low |
||||||
|
from grpc._links import invocation |
||||||
|
from grpc._links import service |
||||||
|
from grpc.framework.core import implementations |
||||||
|
from grpc.framework.interfaces.base import utilities |
||||||
|
from grpc_test import test_common as grpc_test_common |
||||||
|
from grpc_test.framework.common import test_constants |
||||||
|
from grpc_test.framework.interfaces.base import test_cases |
||||||
|
from grpc_test.framework.interfaces.base import test_interfaces |
||||||
|
|
||||||
|
_INVOCATION_INITIAL_METADATA = ((b'0', b'abc'), (b'1', b'def'), (b'2', b'ghi'),) |
||||||
|
_SERVICE_INITIAL_METADATA = ((b'3', b'jkl'), (b'4', b'mno'), (b'5', b'pqr'),) |
||||||
|
_SERVICE_TERMINAL_METADATA = ((b'6', b'stu'), (b'7', b'vwx'), (b'8', b'yza'),) |
||||||
|
_CODE = _intermediary_low.Code.OK |
||||||
|
_MESSAGE = b'test message' |
||||||
|
|
||||||
|
|
||||||
|
class _SerializationBehaviors( |
||||||
|
collections.namedtuple( |
||||||
|
'_SerializationBehaviors', |
||||||
|
('request_serializers', 'request_deserializers', 'response_serializers', |
||||||
|
'response_deserializers',))): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class _Links( |
||||||
|
collections.namedtuple( |
||||||
|
'_Links', |
||||||
|
('invocation_end_link', 'invocation_grpc_link', 'service_grpc_link', |
||||||
|
'service_end_link'))): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
def _serialization_behaviors_from_serializations(serializations): |
||||||
|
request_serializers = {} |
||||||
|
request_deserializers = {} |
||||||
|
response_serializers = {} |
||||||
|
response_deserializers = {} |
||||||
|
for (group, method), serialization in serializations.iteritems(): |
||||||
|
request_serializers[group, method] = serialization.serialize_request |
||||||
|
request_deserializers[group, method] = serialization.deserialize_request |
||||||
|
response_serializers[group, method] = serialization.serialize_response |
||||||
|
response_deserializers[group, method] = serialization.deserialize_response |
||||||
|
return _SerializationBehaviors( |
||||||
|
request_serializers, request_deserializers, response_serializers, |
||||||
|
response_deserializers) |
||||||
|
|
||||||
|
|
||||||
|
class _Implementation(test_interfaces.Implementation): |
||||||
|
|
||||||
|
def instantiate(self, serializations, servicer): |
||||||
|
serialization_behaviors = _serialization_behaviors_from_serializations( |
||||||
|
serializations) |
||||||
|
invocation_end_link = implementations.invocation_end_link() |
||||||
|
service_end_link = implementations.service_end_link( |
||||||
|
servicer, test_constants.DEFAULT_TIMEOUT, |
||||||
|
test_constants.MAXIMUM_TIMEOUT) |
||||||
|
service_grpc_link = service.service_link( |
||||||
|
serialization_behaviors.request_deserializers, |
||||||
|
serialization_behaviors.response_serializers) |
||||||
|
port = service_grpc_link.add_port(0, None) |
||||||
|
channel = _intermediary_low.Channel('localhost:%d' % port, None) |
||||||
|
invocation_grpc_link = invocation.invocation_link( |
||||||
|
channel, b'localhost', |
||||||
|
serialization_behaviors.request_serializers, |
||||||
|
serialization_behaviors.response_deserializers) |
||||||
|
|
||||||
|
invocation_end_link.join_link(invocation_grpc_link) |
||||||
|
invocation_grpc_link.join_link(invocation_end_link) |
||||||
|
service_end_link.join_link(service_grpc_link) |
||||||
|
service_grpc_link.join_link(service_end_link) |
||||||
|
invocation_grpc_link.start() |
||||||
|
service_grpc_link.start() |
||||||
|
return invocation_end_link, service_end_link, ( |
||||||
|
invocation_grpc_link, service_grpc_link) |
||||||
|
|
||||||
|
def destantiate(self, memo): |
||||||
|
invocation_grpc_link, service_grpc_link = memo |
||||||
|
invocation_grpc_link.stop() |
||||||
|
service_grpc_link.stop_gracefully() |
||||||
|
|
||||||
|
def invocation_initial_metadata(self): |
||||||
|
return _INVOCATION_INITIAL_METADATA |
||||||
|
|
||||||
|
def service_initial_metadata(self): |
||||||
|
return _SERVICE_INITIAL_METADATA |
||||||
|
|
||||||
|
def invocation_completion(self): |
||||||
|
return utilities.completion(None, None, None) |
||||||
|
|
||||||
|
def service_completion(self): |
||||||
|
return utilities.completion(_SERVICE_TERMINAL_METADATA, _CODE, _MESSAGE) |
||||||
|
|
||||||
|
def metadata_transmitted(self, original_metadata, transmitted_metadata): |
||||||
|
return original_metadata is None or grpc_test_common.metadata_transmitted( |
||||||
|
original_metadata, transmitted_metadata) |
||||||
|
|
||||||
|
def completion_transmitted(self, original_completion, transmitted_completion): |
||||||
|
if (original_completion.terminal_metadata is not None and |
||||||
|
not grpc_test_common.metadata_transmitted( |
||||||
|
original_completion.terminal_metadata, |
||||||
|
transmitted_completion.terminal_metadata)): |
||||||
|
return False |
||||||
|
elif original_completion.code is not transmitted_completion.code: |
||||||
|
return False |
||||||
|
elif original_completion.message != transmitted_completion.message: |
||||||
|
return False |
||||||
|
else: |
||||||
|
return True |
||||||
|
|
||||||
|
|
||||||
|
def setUpModule(): |
||||||
|
logging.warn('setUpModule!') |
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule(): |
||||||
|
logging.warn('tearDownModule!') |
||||||
|
|
||||||
|
|
||||||
|
def load_tests(loader, tests, pattern): |
||||||
|
return unittest.TestSuite( |
||||||
|
tests=tuple( |
||||||
|
loader.loadTestsFromTestCase(test_case_class) |
||||||
|
for test_case_class in test_cases.test_cases(_Implementation()))) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
unittest.main(verbosity=2) |
@ -0,0 +1,30 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
|
@ -0,0 +1,96 @@ |
|||||||
|
# Copyright 2015, Google Inc. |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# Redistribution and use in source and binary forms, with or without |
||||||
|
# modification, are permitted provided that the following conditions are |
||||||
|
# met: |
||||||
|
# |
||||||
|
# * Redistributions of source code must retain the above copyright |
||||||
|
# notice, this list of conditions and the following disclaimer. |
||||||
|
# * Redistributions in binary form must reproduce the above |
||||||
|
# copyright notice, this list of conditions and the following disclaimer |
||||||
|
# in the documentation and/or other materials provided with the |
||||||
|
# distribution. |
||||||
|
# * Neither the name of Google Inc. nor the names of its |
||||||
|
# contributors may be used to endorse or promote products derived from |
||||||
|
# this software without specific prior written permission. |
||||||
|
# |
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
"""Tests the RPC Framework Core's implementation of the Base interface.""" |
||||||
|
|
||||||
|
import logging |
||||||
|
import random |
||||||
|
import time |
||||||
|
import unittest |
||||||
|
|
||||||
|
from grpc.framework.core import implementations |
||||||
|
from grpc.framework.interfaces.base import utilities |
||||||
|
from grpc_test.framework.common import test_constants |
||||||
|
from grpc_test.framework.interfaces.base import test_cases |
||||||
|
from grpc_test.framework.interfaces.base import test_interfaces |
||||||
|
|
||||||
|
|
||||||
|
class _Implementation(test_interfaces.Implementation): |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self._invocation_initial_metadata = object() |
||||||
|
self._service_initial_metadata = object() |
||||||
|
self._invocation_terminal_metadata = object() |
||||||
|
self._service_terminal_metadata = object() |
||||||
|
|
||||||
|
def instantiate(self, serializations, servicer): |
||||||
|
invocation = implementations.invocation_end_link() |
||||||
|
service = implementations.service_end_link( |
||||||
|
servicer, test_constants.DEFAULT_TIMEOUT, |
||||||
|
test_constants.MAXIMUM_TIMEOUT) |
||||||
|
invocation.join_link(service) |
||||||
|
service.join_link(invocation) |
||||||
|
return invocation, service, None |
||||||
|
|
||||||
|
def destantiate(self, memo): |
||||||
|
pass |
||||||
|
|
||||||
|
def invocation_initial_metadata(self): |
||||||
|
return self._invocation_initial_metadata |
||||||
|
|
||||||
|
def service_initial_metadata(self): |
||||||
|
return self._service_initial_metadata |
||||||
|
|
||||||
|
def invocation_completion(self): |
||||||
|
return utilities.completion(self._invocation_terminal_metadata, None, None) |
||||||
|
|
||||||
|
def service_completion(self): |
||||||
|
return utilities.completion(self._service_terminal_metadata, None, None) |
||||||
|
|
||||||
|
def metadata_transmitted(self, original_metadata, transmitted_metadata): |
||||||
|
return transmitted_metadata is original_metadata |
||||||
|
|
||||||
|
def completion_transmitted(self, original_completion, transmitted_completion): |
||||||
|
return ( |
||||||
|
(original_completion.terminal_metadata is |
||||||
|
transmitted_completion.terminal_metadata) and |
||||||
|
original_completion.code is transmitted_completion.code and |
||||||
|
original_completion.message is transmitted_completion.message |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def load_tests(loader, tests, pattern): |
||||||
|
return unittest.TestSuite( |
||||||
|
tests=tuple( |
||||||
|
loader.loadTestsFromTestCase(test_case_class) |
||||||
|
for test_case_class in test_cases.test_cases(_Implementation()))) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
unittest.main(verbosity=2) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue