blob: 86abdba531ba6a30a820906a4f7fc16c805d5bec [file] [log] [blame]
Zsolt Haraszticd22adc2016-10-25 00:13:06 -07001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Utilities to handle gRPC server and client side code in a Twisted environment
19"""
20from concurrent.futures import Future
21from twisted.internet import reactor
22from twisted.internet.defer import Deferred
23
24
25def twisted_async(func):
26 """
27 This decorator can be used to implement a gRPC method on the twisted
28 thread, allowing asynchronous programming in Twisted while serving
29 a gRPC call.
30
31 gRPC methods normally are called on the futures.ThreadPool threads,
32 so these methods cannot directly use Twisted protocol constructs.
33 If the implementation of the methods needs to touch Twisted, it is
34 safer (or mandatory) to wrap the method with this decorator, which will
35 call the inner method from the external thread and ensure that the
36 result is passed back to the foreign thread.
37
38 Example usage:
39
40 When implementing a gRPC server, typical pattern is:
41
42 class SpamService(SpamServicer):
43
44 def GetBadSpam(self, request, context):
45 '''this is called from a ThreadPoolExecutor thread'''
46 # generally unsafe to make Twisted calls
47
48 @twisted_async
49 def GetSpamSafely(self, request, context):
50 '''this method now is executed on the Twisted main thread
51 # safe to call any Twisted protocol functions
52
53 @twisted_async
54 @inlineCallbacks
55 def GetAsyncSpam(self, request, context):
56 '''this generator can use inlineCallbacks Twisted style'''
57 result = yield some_async_twisted_call(request)
58 returnValue(result)
59
60 """
61 def in_thread_wrapper(*args, **kw):
62
63 f = Future()
64
65 def twisted_wrapper():
66 try:
67 d = func(*args, **kw)
68 if isinstance(d, Deferred):
69
70 def _done(result):
71 f.set_result(result)
72 f.done()
73
74 def _error(e):
75 f.set_exception(e)
76 f.done()
77
78 d.addCallback(_done)
79 d.addErrback(_error)
80
81 else:
82 f.set_result(d)
83 f.done()
84
85 except Exception, e:
86 f.set_exception(e)
87 f.done()
88
89 reactor.callFromThread(twisted_wrapper)
90 result = f.result()
91
92 return result
93
94 return in_thread_wrapper
95
96