Multiprocessing HTTP get requests with a Tornado/Python3 API Service - python-3.x

I build two different Tornado/Python3 API services.
Service1 opens several different threads and semi-parallel sends a GET request to Service2.
However, Service2 (Tornado/Python3) then proceeds to sequentially process the GET request.
Code example of Service2:
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Some Microservice v1")
def make_app():
return tornado.web.Application([
(r"/v1", MainHandler),
(r"/v1/addfile", AddHandler, dict(filepaths = filepaths)),
(r"/v1/getfiles", GetHandler, dict(filepaths = filepaths)),
(r"/v1/getfile", GetFileHandler, dict(filepaths = filepaths)),
])
if __name__ == "__main__":
app = make_app()
app.listen(8887, address='127.0.0.1')
tornado.ioloop.IOLoop.current().start()
I have tried to manually fork processes, to achieve better performance, but this did not work.
if __name__ == "__main__":
#app = make_app()
#app.listen(8887, address='127.0.0.1')
#tornado.ioloop.IOLoop.current().start()
sockets = tornado.netutil.bind_sockets(8887)
tornado.process.fork_processes(8)
server = tornado.httpserver.HTTPServer(app)
server.add_sockets(sockets)
tornado.ioloop.IOLoop.instance().start()
What am I missing here? Do I actually need to go into each Handler class and define coroutines and callbacks?
Greatful for help,
Thank you.

Working as intended. My Service1 was using up all my cores on the test environment, that is why there were no cores left for Service2.

Related

Not able to access API endpoints and swagger URL when asyncio.run() used in main

not able to access the swagger endpoint http://127.0.0.1:8000/docs when using asyncio.run(startup()) in main but background tasks start running in the background.
app = FastAPI()
monitor_data = [
{"url": "https://endpoint_1/", "type": "https", "Interval": 30},
{"url": "https://endpoint_2/", "type": "https", "Interval": 60}
]
#app.get('/')
async def amialive():
return "Live"
async def monitor(url, interval):
while True:
response = requests.get(url)
print(f"{url}: {response.status_code})")
await asyncio.sleep(interval)
async def startup():
tasks = []
for data in monitor_data:
url = data["url"]
interval = data["Interval"]
task = asyncio.create_task(monitor(url, interval))
tasks.append(task)
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(startup())
# asyncio.create_task(startup())
uvicorn.run("network_monitoring:app", host="0.0.0.0", port=8000 ,reload=True)
And when I remove asyncio.run(startup()) from the main, able to access the API endpoint as well as swagger.
is there a way where I can run the background tasks on the startup of the script and use the endpoint as well at the same time? thanks in advance.
Your function startup awaits monitor, which is an infinite loop, which means asyncio.run(startup()) will block and run forever.
To remove the blocking behavior, simply remove await asyncio.gather(*tasks) at the end of monitor. Since its purpose is to create background tasks, and asyncio.create_task does that, it's not needed.
However, that's not enough. When calling uvicorn.run, that will start a new event loop (even a new process), and you want to run your background tasks in the same event loop. To do this, you can use FastAPI's startup event: add the startup decorator #app.on_event("startup") to your startup function
#app.on_event("startup")
async def startup():
...
if __name__ == "__main__":
uvicorn.run("network_monitoring:app", host="0.0.0.0", port=8000, reload=True)

how to put a method in to thread and use it when performing the test

I have this part of code which is doing psubscribe to redis. I want to run this part of code in a thread an working in the background while the other part of code will check some notifications from this below.
def psubscribe(context, param1, param2, param3):
context.test_config = load_config()
RedisConnector(context.test_config["redis_host"],
context.test_config["redis_db_index"])
redis_notification_subscriber_connector = RedisConnector(context.test_config["notification__redis_host"],
int(param3),
int(context.test_config[
"notification_redis_port"]))
context.redis_connectors = redis_notification_connector.psubscribe_to_redis_event(param1,
timeout_seconds=int(
param2)
)
what I have done till now: but its not running :(
context.t = threading.Thread(target=psubscribe, args=['param1', 'param2', 'param3'])
context.t.start()
It is actually working. I think you didn't need actually to pass context variable to your psubscribe function.
Here is an example:
Start http server that listens on port 8000 as a background thread
Send http requests to it and validate response
Feature scenario:
Scenario: Run background process and validate responses
Given Start background process
Then Validate outputs
background_steps.py file:
import threading
import logging
from behave import *
from features.steps.utils import run_server
import requests
#given("Start background process")
def step_impl(context):
context.t = threading.Thread(target=run_server, args=[8000])
context.t.daemon = True
context.t.start()
#then("Validate outputs")
def step_impl(context):
response = requests.get('http://127.0.0.1:8000')
assert response.status_code == 501
utils.py file
from http.server import HTTPServer, BaseHTTPRequestHandler
def run_server(port, server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
httpd.serve_forever()

Passing an external queue to flask route function?

I'm trying to modify flask request callback so it can communicate with other code while executing the callback. The example explains it better:
from flask import Flask, request
from queue import Queue
flask_input_queue = Queue()
flask_output_queue = Queue()
app = Flask(__name__)
#app.route("/voice", methods=['GET', 'POST'])
def voice():
# The receiver on the other end gets notified we got a request
flask_output_queue.put(str(request))
# This blocks until the external party responds with something
response = flask_input_queue.get()
# But how do the queues end up in the function scope to begin with?
return response
app.run(debug=True)
Here the external code would have a channel using the queues into the web server. This allows me to completely abstract the concept of the web server on the other part of the code.
However for that, I need to be able to pass information to the callback method in ways other that just URLs. Frankly it doesn't have to be a queue other IPC mechanisms will also work ok but they all rely on having a way to pass data into the callback.
Is there a way to do that in flask?
The _URLCallbackClass in combination with add_url_rule is used instead of the decorator. That _URLCallbackClass gets the queue as instance attributes. Given that the actual callback function is the method of _URLCallbackClass, we smuggled the queues into the callback function.
The rest of the complexity just arises from providing a working example.
logging.basicConfig(format='[Thread: %(threadName)s-%(thread)d] %(message)s', level=logging.INFO) [0/0]
logger = logging.getLogger(__name__)
class ControllableServer(threading.Thread):
class _URLCallbackClass():
def __init__(self, input_queue, output_queue):
self.input_queue = input_queue
self.output_queue = output_queue
def url_callback(self):
self.output_queue.put("[URL callback] I just got called")
response_from_the_queue = self.input_queue.get()
return Response(response_from_the_queue, 200)
def __init__(self, input_queue, output_queue):
super().__init__(daemon=True)
self.input_queue = input_queue
self.output_queue = output_queue
self._flask = Flask(__name__)
def run(self):
callback_class = ControllableServer._URLCallbackClass(self.input_queue, self.output_queue)
self._flask.add_url_rule('/endpoint', 'url_callback', callback_class.url_callback)
logger.info(f"Starting flask")
self._flask.run()
def call_URL_in_separate_thread(url):
def call_URL(url):
logger.info(f"Calling {url}")
response = requests.get(url)
logger.info(f"Got response: {response.text}")
return response.text
url_caller_thread = threading.Thread(target=call_URL, args=(url,))
url_caller_thread.start()
if __name__ == "__main__":
flask_input_queue = Queue()
flask_output_queue = Queue()
controllable_server = ControllableServer(flask_input_queue, flask_output_queue)
controllable_server.start()
call_URL_in_separate_thread("http://127.0.0.1:5000/endpoint")
message_from_within_the_callback = flask_output_queue.get()
logger.info(f"Got message from queue: {message_from_within_the_callback}")
message_to_the_callback = "I come from the outside !###$#"
flask_input_queue.put(message_to_the_callback)
logger.info(f"Sending message to queue: {message_to_the_callback}")
Output:
[Thread: Thread-1-140465413375744] Starting flask
[Thread: Thread-2-140465404983040] Calling http://127.0.0.1:5000/endpoint
* Serving Flask app "basic_flask_passing_variable" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
[Thread: Thread-1-140465413375744] * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[Thread: MainThread-140465450415936] Got message from queue: [URL callback] I just got called
[Thread: MainThread-140465450415936] Sending message to queue: I come from the outside !###$#
[Thread: Thread-3-140465396041472] 127.0.0.1 - - [03/Mar/2020 18:33:32] "GET /endpoint HTTP/1.1" 200 -
[Thread: Thread-2-140465404983040] Got response: I come from the outside !###$#

Run actions on Tornado main loop, after it starts

I'm creating a python3 tornado web server that may listen to an MQTT broker and whenever listens a new message from it, broadcasts it to the connected browsers, through web sockets. However, seems that Tornado doesn't like calls to its API from a thread different to IOLoop.current() and I can't figure out another solution...
I've already tried to write some code. I've put the whole MQTT client (in this case called PMCU client), on a separated thread which loops and listens to MQTT notifications.
def on_pmcu_data(data):
for websocket_client in websocket_clients:
print("Sending websocket message")
websocket_client.write_message(data) # Here it stuck!
print("Sent")
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
websocket_clients.append(self)
def on_close(self):
websocket_clients.remove(self)
def make_app():
return tornado.web.Application([
(r'/ws', WebSocketHandler)
])
if __name__ == "__main__":
main_loop = IOLoop().current()
pmcu_client = PMCUClient(on_pmcu_data)
threading.Thread(target=lambda: pmcu_client.listen("5.4.3.2")).start()
app = make_app()
app.listen(8080)
main_loop.start()
However as I said, seems that calls to Tornado API outside the IOLoop.current() blocks: the code above only prints Sending websocket message.
My intent is to run websocket_client.write_message(data) on IOLoop.current() event loop. But seems that the function IOLoop.current().spawn_callback(lambda: websocket_client.write_message(data)) not works after IOLoop.current() has started. How could I achieve that?
I know that I have a huge misunderstanding of IOLoop, asyncio, on which it depends, and python3 async.
on_pmcu_data is being called in a separate thread but the websocket is controlled by Tornado's event loop. You can't write to a websocket from a thread unless you have access to the event loop.
You'll need to ask the IOLoop to write the data to websockets.
Solution 1:
For simple cases, if you don't want to change much in the code, you can do this:
if __name__ == "__main__":
main_loop = IOLoop().current()
on_pmcu_data_callback = lambda data: main_loop.add_callback(on_pmcu_data, data)
pmcu_client = PMCUClient(on_pmcu_data_callback)
...
This should solve your problem.
Solution 2:
For more elaborate cases, you can pass the main_loop to PMCUClient class and then use add_callback (or spawn_callback) to run on_pmcu_data.
Example:
if __name__ == "__main__":
main_loop = IOLoop().current()
pmcu_client = PMCUClient(on_pmcu_data, main_loop) # also pass the main loop
...
Then in PMCUCLient class:
class PMCUClient:
def __init__(self, on_pmcu_data, main_loop):
...
self.main_loop = main_loop
def lister(...):
...
self.main_loop.add_callback(self.on_pmcu_data, data)

aiohttp test with several servers

Trying to do a test that communicates with several instances of a web-server (which also communicates between them). But the second one seems to override the first however I try. Any suggestions of how to solve this.
So far I have this:
import os
from aiohttp.test_utils import TestClient, TestServer, loop_context
import pytest
from http import HTTPStatus as hs
from mycode import main
#pytest.fixture
def cli(loop):
app = main(["-t", "--location", "global", "-g"])
srv = TestServer(app, port=40080)
client = TestClient(srv, loop=loop)
loop.run_until_complete(client.start_server())
return client
#pytest.fixture
def cli_edge(loop):
app = main(["-t", "--location", "edge", "-j", "http://127.0.0.1:40080"])
srv = TestServer(app, port=40081)
client = TestClient(srv, loop=loop)
loop.run_until_complete(client.start_server())
return client
async def test_edge_namespace(cli, cli_edge):
resp = await cli.put('/do/something', json={})
assert resp.status in [hs.OK, hs.CREATED, hs.NO_CONTENT]
resp = await cli_edge.get('/do/something')
assert resp.status in [hs.OK, hs.CREATED, hs.NO_CONTENT]
The above call to cli.put goes to the server intended for cli_edge. I will have several more tests that should communicate with the servers.
Using Python 3.7 and pytest with asyncio and aiohttp extensions.
The suggested code works, the error was elsewhere in the server implementation.
You can add:
def fin():
loop.run_until_complete(srv.close())
loop.run_until_complete(client.close())
request.addfinalizer(fin)
and the request param in the pytest fixtures to close connections nicely.

Resources