-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: native async based grpc & fastapi based rest for gateway #1348
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1348 +/- ##
==========================================
- Coverage 83.33% 82.49% -0.84%
==========================================
Files 104 106 +2
Lines 6869 6980 +111
==========================================
+ Hits 5724 5758 +34
- Misses 1145 1222 +77
Continue to review full report at Codecov.
|
jina/peapods/gateway.py
Outdated
class AsyncGatewayPea: | ||
def __init__(self, args): | ||
if not args.proxy and os.name != 'nt': | ||
os.unsetenv('http_proxy') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like users may get very mad at us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Unsetting http_proxy
and https_proxy
sounds really bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is needed since GNES @deepankarm @nan-wang @JoanFM please
- read the old code
- when you running grpc with http and https proxy you will find grpc won't work anymore.
- always keep in mind os.environ is set on process base not globally, there is nothing people get mad at us, because nothing change in global
- anytime, when you doubt if it adds any trouble to users, use warning.warn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please always understand grpc is on HTTP2 layer and that's why http proxy setting affects it. if you don't know about this, read grpc doc.
if you really want to solve this elegantly, i suggest looking for issue help/posting issue on grpc github. back in early 2019 i spent a day on this and this was the best solution. things may change, so don't be shy to leverage OSS community.
also please fix the misconception that os.environ changes anything globally. it is a variable shared by that process you spawned and all subprocesses. Fix this misconception is important otherwise all os.environ will look weird to you.
until then, keep this lines. Many companies have internal proxy, without this line, Jina won't work for them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure this init lives in another process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be moved into start?
jina/peapods/pod.py
Outdated
@@ -556,6 +556,25 @@ def start(self) -> 'GatewayPod': | |||
return self | |||
|
|||
|
|||
class AsyncGatewayPod(BasePod, AsyncExitStack): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this play nice with inheriting both ỀxitStack
and AsyncExitStack
(I think is a good point I may try to have a mixin in the refactor for ExitStack
so that it is more separated from BasePod
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class DualContext(ExitStack, AsyncExitStack):
def __init__(self) -> None:
super(ExitStack, self).__init__()
super(AsyncExitStack, self).__init__()
def start(self):
raise NotImplementedError
def close(self):
raise NotImplementedError
async def astart(self):
raise NotImplementedError
async def aclose(self):
raise NotImplementedError
def __enter__(self):
return self.start()
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
super(ExitStack, self).__exit__(exc_type, exc_val, exc_tb)
async def __aenter__(self):
return await self.astart()
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.aclose()
await super(AsyncExitStack, self).__aexit__(exc_type, exc_val, exc_tb)
class SyncContext(DualContext):
def start(self):
print('Sync')
def close(self):
print('Sync close')
class AsyncContext(DualContext):
async def astart(self):
print('Async')
async def aclose(self):
print('Async close')
with SyncContext():
pass
async def blah():
async with AsyncContext():
pass
asyncio.run(blah())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works, maybe we can use something similar
I think assuming is always local w.r.t the Flow is a valid assumption right now |
the test is broken |
* refactor(gateway): clean code, move file structure * refactor(proto): jina primitive types
while True: | ||
try: | ||
msg = zmqlet.sock.recv() | ||
if msg == b'TERMINATE': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when is the client sending TERMINATE
I haven't seen this happen, it is done between Peas but not from client right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyClient
would send a terminate signal to PyClientRuntime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should be the other way around right? PyClient does not know it lives inside a PyClientRuntime
? Why don't u use multiprocessing Events
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyClient
receives a stream of responses from gRPC servicer. To make sure the callback
functions are executed back in the main process, PyClient
sends each such response to PyClientRuntime
. Once the stream is completed, PyClient
sends a TERMINATE
signal to PyClientRuntime
. But you're right, this can be handled using an Event
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codecov reports missing test for some cases specially when using ctxt management
def __exit__(self, exc_type, exc_val, exc_tb): | ||
self.close() | ||
async def __aexit__(self, exc_type, exc_val, exc_tb): | ||
await self.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This aexit is not tested?
self.close() | ||
# avoid closing a client after a single `index`, `search` or `train` operation | ||
if 'close' in kwargs: | ||
await self.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test for this part exists?
await self.is_gateway_ready.wait() | ||
return self | ||
|
||
async def __aenter__(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No test using it as ctxt manager
Testing the context manager for |
Following changes are done in this PR
PyClient
to read fromgrpc.aio.channel
Flask + gevent
toFastAPI + uvicorn
based REST gatewaySide Effects:
gRPC
AsyncExecutor
to native asyncio for gRPCPyClient
was sync. With this PR,PyClient
becomes async (call
,call_unary
,dry_run
,index
,search
,train
are now coroutines.REST
FastAPI
&uvicorn
support native async capabilities, rather than solving it in a hacky way withgevent.WSGIServer
.websockets
onFastAPI
server.PyClient
PyClient
(and hencepy_client
) is now an awaitable.PyClientRuntime
, which executesPyClient
to a separate process to avoid an event loop in the main process.PyClient
(optionally) sends the gRPC response to a zmq socket, from which main process can read & execute callbacks. (This is the default way of when we doflow.index
,flow.search
etc)JinaD
def
toasync def
for jinad (Difference)jinad
nowCtrlZmqlet
gateway-grpc
,gateway-rest
,remote
,client
)Jupyter Notebook
event loops
in the main process (avoiding collision with Jupyter's default event loop). Fixes Jupyter notebook & lab kernel restarting without obvious reason when working with Jina #1277Pending
pytest.asyncio
tests forPyClient
would still cause segmentation faults, as that bringsgrpc.aio
channel (hence the event loop) on main process. This needs to be handled.jinad
(def
toasync def
)await
vsrun_until_complete
a coroutine)