Let's say I want to create a chat-like application. A client can send text to the server and vice versa. The order of text exchanges can be arbitrary.
The server depends on another stream which controls the server response stream.
The GRPC stream is exposed as a python generator. How can the server now wait for client input and input on the other stream at the same time? Normally one would use something like select(), but here we have generators.
I have some example code which implements the wanted behavior but requires an additional thread on the client and server side. How can I achieve the same result without a thread?
Proto:
syntax = 'proto3';
service Scenario {
rpc Chat(stream DPong) returns (stream DPong) {}
}
message DPong {
string name = 1;
}
Server:
import random
import string
import threading
import grpc
import scenario_pb2_grpc
import scenario_pb2
import time
from concurrent import futures
class Scenario(scenario_pb2_grpc.ScenarioServicer):
def Chat(self, request_iterator, context):
def stream():
while 1:
time.sleep(1)
yield random.choice(string.ascii_letters)
output_stream = stream()
def read_incoming():
while 1:
received = next(request_iterator)
print('received: {}'.format(received))
thread = threading.Thread(target=read_incoming)
thread.daemon = True
thread.start()
while 1:
yield scenario_pb2.DPong(name=next(output_stream))
if __name__ == '__main__':
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
scenario_pb2.add_ScenarioServicer_to_server(
Scenario(), server)
server.add_insecure_port('[::]:50052')
server.start()
print('listening ...')
while 1:
time.sleep(1)
Client
import threading
import grpc
import time
import scenario_pb2_grpc, scenario_pb2
def run():
channel = grpc.insecure_channel('localhost:50052')
stub = scenario_pb2_grpc.ScenarioStub(channel)
print('client connected')
def stream():
while 1:
yield scenario_pb2.DPong(name=input('$ '))
input_stream = stub.Chat(stream())
def read_incoming():
while 1:
print('received: {}'.format(next(input_stream).name))
thread = threading.Thread(target=read_incoming)
thread.daemon = True
thread.start()
while 1:
time.sleep(1)
if __name__ == '__main__':
print('client starting ...')
run()
It is not currently possible to do this without spending the threads that you're spending. We're thinking about implementing enhancements that would allow implementations to avoid taking another thread, but those would be months away at earliest.
Related
I am working on a project building an API that is able to send the live location of vehicles to a frontend.
I get this location data by subscribing to a ZMQ stream by running a while loop. This is all working and if I just run my stream as a script I can print all kinds of information to the terminal (I'll store those in a database later on).
I also have the FastAPI server up and running
Now what I'd like to do is:
At startup start the server so I can make API calls
Start the while loop and start receiving data from the ZMQ stream
What happens instead:
It seems either / or.
I can import a function with the while loop but this blocks the server from starting up
Or I can run the server with no means to start the stream
Here is my code:
# General FastAPI Imports
from fastapi import Depends, FastAPI, Request
from data_collection.livestream import enable_data_stream
from client_service import client_api
app = FastAPI()
app.include_router(client_api.router, prefix="/API/V1")
#app.get('/')
def read_root(request: Request):
return {"Hello": "World"}
The Stream:
from gzip import GzipFile
from io import BytesIO
import zmq
import xml.etree.ElementTree as ET
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
subscriber.connect("tcp://SERVER")
subscriber.setsockopt(zmq.SUBSCRIBE)
while True:
multipart = subscriber.recv_multipart()
address = multipart[0]
try:
contents = GzipFile('', 'r', 0, BytesIO(multipart[1])).read()
root = ET.fromstring(contents)
print("Updates Received:")
# Gets the timestamp
print('time', root[3].text)
print('X Coord: ', root[4][0][12].text)
print('Y Coord: ', root[4][0][13].text)
I tried looking into the multiprocess and threading implementations for python but I'm unsure how those tie in with starting the FastAPI process (as that's enabled from Uvicorn)
In the example below, the server and worker are started in separate processes because the While loop won't resolve. It seems that you were on the right track. In my example, I have these functions in one file, but there are no restrictions on someone breaking them out into their own files:
import uvicorn
import multiprocessing
import time
import zmq
import xml.etree.ElementTree as ET
from gzip import GzipFile
from io import BytesIO
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
def server():
uvicorn.run(app, host="localhost", port=8000)
def worker():
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
subscriber.connect("tcp://SERVER")
subscriber.setsockopt(zmq.SUBSCRIBE)
while True:
multipart = subscriber.recv_multipart()
address = multipart[0]
try:
contents = GzipFile('', 'r', 0, BytesIO(multipart[1])).read()
root = ET.fromstring(contents)
print("Updates Received:")
# Gets the timestamp
print('time', root[3].text)
print('X Coord: ', root[4][0][12].text)
print('Y Coord: ', root[4][0][13].text)
except Exception as e:
print(e)
print("Error: %s" % multipart[1])
break
if __name__ == '__main__':
# Runs server and worker in separate processes
p1 = multiprocessing.Process(target=server)
p1.start()
time.sleep(1) # Wait for server to start
p2 = multiprocessing.Process(target=worker)
p2.start()
p1.join()
p2.join()
I'm trying to create a client for testing my grpc server. In my grpc server I have a rpc NotificationStreaming() which streams a notification (unary-stream). Also I have bunch of synchronous rpc methods (unary-unary).
In the main() below first I establish a connection for the streaming in the separate process and then I perform unary-unary rpc requests sequentially. After each unary-unary rpc request I receive notifications via NOTIFICATION_QUEUE. The streaming stays empty until I call the fist unary-unary method create_project(stub), so I'm expecting to receive a first notification during this method.
The problem is that if I remove sleep(5) my program stuck at this line.
Please give me any ideas about how to use more wise way?
def _notification_stream(notification_queue):
with grpc.insecure_channel(settings.GRPC_PORT) as channel:
stub = main_pb2_grpc.MyAPIStub(channel)
try:
response_stream = stub.NotificationStreaming(Empty())
for notification in response_stream:
r = json_format.MessageToDict(notification, preserving_proto_field_name=True,
including_default_value_fields=True)
notification_queue.put(r['message'])
except grpc.RpcError as e:
misc.log(f"ERROR notification stream: {e}")
def notification_streaming(notification_queue):
_process = mp.Process(target=_notification_stream, daemon=True, kwargs={"notification_queue": notification_queue})
_process.start()
return _process.pid
def main():
NOTIFICATION_QUEUE = mp.Queue()
# start listening to the notification stream
notification_streaming(NOTIFICATION_QUEUE)
sleep(5)
with grpc.insecure_channel(settings.GRPC_PORT) as channel:
stub = main_pb2_grpc.MyAPIStub(channel)
create_project(stub)
while not NOTIFICATION_QUEUE.empty():
misc.log(f"\tnotification: {NOTIFICATION_QUEUE.get(block=True)}")
close_project(stub)
while not NOTIFICATION_QUEUE.empty():
misc.log(f"\tnotification: {NOTIFICATION_QUEUE.get(block=True)}")
load_project(stub)
while not NOTIFICATION_QUEUE.empty():
misc.log(f"\tnotification: {NOTIFICATION_QUEUE.get(block=True)}")
save_project(stub)
while not NOTIFICATION_QUEUE.empty():
misc.log(f"\tnotification: {NOTIFICATION_QUEUE.get(block=True)}")
...
if __name__ == '__main__':
main()
The idea is to make sure that the channel is established before creating a new channel. For this reason channel.subscribe(wait_for_ready_partial, try_to_connect=True) for checking the state of ChannelConnectivity and continue only when grpc.ChannelConnectivity.READY
NOTIFICATION_QUEUE = mp.Queue()
def _notification_stream(notification_queue):
def wait_for_ready(channel_connectivity, _notification_queue=None):
if channel_connectivity is grpc.ChannelConnectivity.READY:
_notification_queue.put(settings.STOP_WORD)
with grpc.insecure_channel(settings.GRPC_PORT) as channel:
wait_for_ready_partial = functools.partial(wait_for_ready, _notification_queue=notification_queue)
channel.subscribe(wait_for_ready_partial, try_to_connect=True)
stub = main_pb2_grpc.MyAPIStub(channel)
response_stream = stub.NotificationStreaming(Empty())
for notification in response_stream:
r = json_format.MessageToDict(notification, preserving_proto_field_name=True,
including_default_value_fields=True)
notification_queue.put(r['message'])
def notification_streaming(notification_queue):
_process = mp.Process(target=_notification_stream, daemon=True, kwargs={"notification_queue": notification_queue})
_process.start()
while True:
if notification_queue.get(block=True) == settings.STOP_WORD:
break
def main():
# establish the channel for Notification streaming (unary-stream)
notification_streaming(NOTIFICATION_QUEUE)
# the main grpc unary-unary intecations
with grpc.insecure_channel(settings.GRPC_PORT) as channel:
stub = main_pb2_grpc.MyAPIStub(channel)
create_project(stub)
close_project(stub)
load_project(stub)
...
if __name__ == '__main__':
main()
I have a simple Python program that I want to do three things:
Serve an HTTP document
Serve Websockets
Interact with the Websocket data
I am trying to use / grok asyncio. The issue is that I can't figure out how to access data acquired from a function in the main event loop.
For example in my code below I have two threads.
One thread is the HTTP server thread, one thread is the Websocket server thread and there is the main thread.
What I want to do is to print data captured in the websocket receiving thread in the main thread.
The only way I know how to do this is to use Queues to pass data between threads at which point I do not even know what the advantage of using asyncio is.
Similarly, it feels weird to pass the event loop to the serve_websocket function.
Can anyone please explain how to architect this to get data from the Websocket function into the main function?
It seems like / I want a way to do this without using the threading library at all, which seems possible. In an async project I would want to react to websocket events in different function than where they are called.
NOTE: I know there are other libraries for websockets and http serving with asyncio but this is an example to help me understarnd how to structure projects using this paradigm.
Thanks
#!/usr/bin/env python
import json
import socketserver
import threading
import http.server
import asyncio
import time
import websockets
SERVER_ADDRESS = '127.0.0.1'
HTTP_PORT = 8087
WEBSOCKET_PORT = 5678
def serve_http():
http_handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", HTTP_PORT), http_handler) as httpd:
print(f'HTTP server listening on port {HTTP_PORT}')
httpd.serve_forever()
def serve_websocket(server, event_loop):
print(f'Websocket server listening on port {WEBSOCKET_PORT}')
event_loop.run_until_complete(server)
event_loop.run_forever()
async def ws_callback(websocket, path):
while True:
data = await websocket.recv()
# How do I access parsed_data in the main function below
parsed_data = json.loads(data)
await websocket.send(data)
def main():
event_loop = asyncio.get_event_loop()
ws_server = websockets.serve(ws_callback, SERVER_ADDRESS, WEBSOCKET_PORT)
threading.Thread(target=serve_http, daemon=True).start()
threading.Thread(target=serve_websocket, args=(ws_server, event_loop), daemon=True).start()
try:
while True:
# Keep alive - this is where I want to access the data from ws_callback
# i.e.
# print(data.values)
time.sleep(.01)
except KeyboardInterrupt:
print('Exit called')
if __name__ == '__main__':
main()
I believe that you should not mix asyncio and multithreading without special need. And in your case, use only asyncio tools.
In this case, you have no problem sharing data between coroutines, because they all run on the same thread using cooperative multitasking.
Your code can be rewtitten as:
#!/usr/bin/env python
import json
import socketserver
import threading
import http.server
import asyncio
import time
import websockets
SERVER_ADDRESS = '127.0.0.1'
HTTP_PORT = 8087
WEBSOCKET_PORT = 5678
parsed_data = {}
async def handle_http(reader, writer):
data = await reader.read(100)
message = data.decode()
writer.write(data)
await writer.drain()
writer.close()
async def ws_callback(websocket, path):
global parsed_data
while True:
data = await websocket.recv()
# How do I access parsed_data in the main function below
parsed_data = json.loads(data)
await websocket.send(data)
async def main():
ws_server = await websockets.serve(ws_callback, SERVER_ADDRESS, WEBSOCKET_PORT)
print(f'Websocket server listening on port {WEBSOCKET_PORT}')
http_server = await asyncio.start_server(
handle_http, SERVER_ADDRESS, HTTP_PORT)
print(f'HTTP server listening on port {HTTP_PORT}')
try:
while True:
if parsed_data:
print(parsed_data.values())
await asyncio.sleep(0.1)
except KeyboardInterrupt:
print('Exit called')
if __name__ == '__main__':
asyncio.run(main())
I use a client-server socket connexion to transfer some data from my Python server. The problem I have at the moment is that the creation of the server socket block the programm because it can't connect to the client.
I tried to use async but I didn't succeed
from flask import *
import random
import socket
import json
app = Flask(__name__, static_url_path='')
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind(('',55555))
async def acceptConnexion():
while True:
socket.listen(10)
client, address = socket.accept()
print("{} connected".format( address ))
#app.route('/getInfos')
def getInfos():
global infosThymio
return json.dumps(infosThymio)
if __name__ == '__main__':
app.run()
I don't know where I could call my acceptConnexion() and I don't know how to manage to let this method turn in background until it can do the connexion with the client.
You can separate the accept call to a separate thread, in this way and let the main thread continue while the side thread waits on accept.
import socket
from threading import Thread
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind(('',55555))
def acceptConnexion():
print("running in thread")
while True:
socket.listen(10)
address = socket.accept()
print("{} connected".format( address ))
if __name__ == "__main__":
thread = Thread(target = acceptConnexion)
print("you can here do bla bla")
x = 1
print("x", x)
print("Main thread will wait here for thread to exit")
thread.join()
print("thread finished...exiting")
I have a server running a loop that reads data from a device and I want to send them to all clients who connect on a websocket on tornado.
I tried putting the loop inside the open function but then it can't handle on_close function or new connections.
What is best practice to do that?
#!/usr/bin/env python
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
class MyWebSocketServer(tornado.websocket.WebSocketHandler):
def open(self):
print('new connection'+self.request.remote_ip)
try:
while True:
'''
read and send data
'''
except Exception,error:
print "Error on Main: "+str(error)
def on_close(self):
print('connection closed'+self.request.remote_ip)
application=tornado.web.Application([(r'/ws',MyWebSocketServer),])
if __name__=="__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8000)
print('start')
tornado.ioloop.IOLoop.instance().start()
Thanks
Here's a full example about running your blocking code in a separate thread and broadcasting messages to all connected clients.
...
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=1) # spawn only 1 thread
class MyWebSocketServer(tornado.websocket.WebSocketHandler):
connections = set() # create a set to hold connections
def open(self):
# put the new connection in connections set
self.connections.add(self)
def on_close(self):
print('connection closed'+self.request.remote_ip)
print('new connection'+self.request.remote_ip)
# remove client from connections
self.connections.remove(self)
#classmethod
def send_message(cls, msg):
for client in cls.connections:
client.write_message(msg)
def read_from_serial(loop, msg_callback):
"""This function will read from serial
and will run in aseparate thread
`loop` is the IOLoop instance
`msg_allback` is the function that will be
called when new data is available from usb
"""
while True:
# your code ...
# ...
# when you get new data
# tell the IOLoop to schedule `msg_callback`
# to send the data to all clients
data = "new data"
loop.add_callback(msg_callback, data)
...
if __name__ == '__main__':
loop = tornado.ioloop.IOLoop.current()
msg_callback = MyWebSocketServer.send_message
# run `read_from_serial` in another thread
executor.submit(read_from_serial, loop, msg_callback)
...
loop.start()