I currently have a WebRTC client running with an Python Flask backend. It receives video from an RTSP source and uses WebRTC to deliver it to a client.
I have an issue where when the client disconnects, the threads still run as they are looping forever, I'm able to stop part of the threads I made, but I believe asyncio loops are still creating some. Below is the relevant code, and I always seem to gain two extra threads when a client disconnects. For instance when it starts I have two, a client connects, I have 7, client disconnects, left running with 4.
# WEBRTC ===================================
# This is what runs forever, while the client is connected,
# MASSIVELY SCREWEY DUE TO ME NOT KNOWING HOW TO THREAD PROPERLY.
def webRtcWatchdog():
global rtcloop
global pingTime
global webRTCThread
global watchdog
time.sleep(5) # Wait at least 5 seconds for client to connect.
while True:
time.sleep(1)
# If timer exceeds value, then run thread.stop;
diffTime = float(time.time()) - float(pingTime)
# print("Diff Time: " + str(diffTime))
if (diffTime > 5.0):
print("Running Threads Before: " + str(active_count()))
rtcloop.stop()
webRTCThread.join()
break
print("Broken Watchdog, thread should be terminating now!")
async def webRtcStart():
# Get current loop
loop = asyncio.get_event_loop()
global rtspCredString
pingTime = None
# Set Media Source and decode offered data
player = MediaPlayer(rtspCredString) # In the future the media source will be the local relay docker container that has the camera in question, instead of from the camera itself.
params = ast.literal_eval((request.data).decode("UTF-8"))
# Set ICE Server to local server CURRENTLY STATIC
offer = RTCSessionDescription(sdp=params.get("sdp"), type=params.get("type"))
webRtcPeer = RTCPeerConnection(configuration=RTCConfiguration(
iceServers=[RTCIceServer(
urls=['stun:nvr.internal.my.domain'])]))
# Create Event Watcher On Data Channel To Know If Client Is Still Alive, AKA Ping - Pong
#webRtcPeer.on("datachannel")
def on_datachannel(channel):
#channel.on("message")
def on_message(message):
global pingTime
if isinstance(message, str) and message.startswith("ping"):
pingTime = time.time()
channel.send("pong" + message[4:])
elif ():
print("Closing Peer!")
webRtcPeer.close()
if (player.video):
webRtcPeer.addTrack(player.video)
if (player.audio):
webRtcPeer.addTrack(player.audio)
# Wait to Set Remote Description
await webRtcPeer.setRemoteDescription(offer)
# Generate Answer to Give To Peer
answer = await webRtcPeer.createAnswer()
# Set Description of Peer to answer.
await webRtcPeer.setLocalDescription(answer)
final = ("{0}").format(json.dumps(
{"sdp": (webRtcPeer.localDescription.sdp), "type": webRtcPeer.localDescription.type}
))
# Retirn response
return final
# When we grab a WebRTC offer
#app.route('/rtcoffer', methods=['GET', 'POST'])
#login_required
def webRTCOFFER():
global rtcloop
global webRTCThread
global watchdog
# Get Event Loop If It Exists, Create It If Not.
try:
rtcloop = asyncio.get_event_loop()
except RuntimeError:
rtcloop = asyncio.new_event_loop()
asyncio.set_event_loop(rtcloop)
# Run an event into that loop until it's complete and returns a value
t = rtcloop.run_until_complete(webRtcStart())
# Now create a timer that is reset by Ping-Pong.
# Continue running that loop forever to keep AioRTC Objects In Memory Executing, while shifting it to
# Another thread so we don't block the code.
webRTCThread = Thread(target=rtcloop.run_forever)
webRTCThread.start()
watchdog = Thread(target=webRtcWatchdog)
watchdog.start()
print("Current Number Of Running Threads: " + str(active_count())) # We currently don't ever stop the started threads as
# we are not currently monitoring Ping-Pong, will impletent. What this currently means is the program is unusable in production
# with the cascading threads never being cleaned up.
# Return Our Parsed SDP
return t.encode()
Related
I'm creating a real-time event detector. It reads a gaming stream and detects some events there like goals, timer updates and etc. All of the events should be sent via websocket, so I use python websockets and asyncio libraries. Code is the following:
main_stack = []
cap = cv2.VideoCapture("vid_2.mp4")
game = Game(main_stack)
async def handler(websocket):
while True:
ret, frame = cap.read()
await asyncio.sleep(0.05)
game.run_detectors(frame)
while main_stack:
elem = main_stack.pop()
await websocket.send(elem.get_json())
async def main():
async with websockets.serve(handler, "", 8765):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
Line game.run_detectors(frame) adds events to main stack if detected. Then if there are some events in the stack, they're sent via websocket. The problem is that when no client is connected, program freezes, missing events from translation which runs in real time. How can I change the code, so that the stack is updated independently of websocket. And websocket constantly checks updates and sends events if they're present in the stack?
You should run the game in a separate thread and add events to a queue, then pop them and send them in a websocket handler. Note that this will only work for a single connection, if you have multiple connections you need to write some kind of logic which holds the state (which events were already sent, etc.) for each of the connections. Without the additional logic only one of the connections will receive an event, not all of them.
Below is a basic example how it should look like.
from threading import Thread
from queue import Queue, Empty
import time
main_stack = Queue()
cap = cv2.VideoCapture("vid_2.mp4")
game = Game(main_stack) # Adapt logic in the Game class to put events in the queue
async def handler(websocket):
while True:
try:
elem = main_stack.get(timeout=1e-3) # Try to fetch an event from queue with a 1 ms timeout in order to prevent high CPU usage
except Empty:
continue # In case the queue is empty, repeat fetching
await websocket.send(elem.get_json())
async def main():
async with websockets.serve(handler, "", 8765):
await asyncio.Future()
def game_loop(game, cap):
while True:
ret, frame = cap.read()
game.run_detectors(frame)
time.sleep(0.05)
if __name__ == "__main__":
game_thread = Thread(target=game_loop, args=(game, cap), daemon=True)
game_thread.start()
asyncio.run(main())
my problem is as follows:
I wrote a program that subscribes to a topic, where 2 dictionaries with one key respectively arrive more times a second. On every message they change their value.
I save those dictionaries in a big buffer-dictionary called "Status". What I need is to save a "snapshot" of Status every second into a file.
I tried time.sleep(1) but it drifts. And I don't know how to handle the problem with a schedule due to the already existing client-loop...
I'm pretty new to python and mqtt and would appreciate your help
My code:
import paho.mqtt.client as mqtt
import time
import json
Status = {}
#create client instance
client = mqtt.Client(client_id=None, clean_session=True, transport="tcp")
#connect to broker
client.connect("my_broker", 1883)
#use subscribe() to subscribe to a topic and receive messages
client.subscribe("topic/#", qos=0)
def test1_callback(client, userdata, msg):
msg_dict = json.loads((msg.payload))
Status.update(msg_dict)
client.message_callback_add("topic/test1", test1_callback)
while True:
client.loop_start()
time.sleep(1)
client.loop_stop()
with open('Data.txt', 'a+') as file:
t = time.localtime()
Status["time"]= time.strftime("%H:%M:%S", t)
file.write(str(Status["time"]) + " ")
file.write(str(Status["key1"]) + " ")
file.write(str(Status["key2"]) + " ")
client.loop_start()
Instead of manually stopping the networking thread I would prefer using a timer which fires every second. In addition it might be a good idea to lock the data when storing it to a file - otherwise there might occur an update in between:
# ...
import threading
def test1_callback(client, userdata, msg):
msg_dict = json.loads((msg.payload))
lock.acquire()
Status.update(msg_dict)
lock.release()
def timer_event():
lock.acquire()
# save to file here
lock.release()
# restart timer
threading.Timer(1, timer_event).start()
Status = {}
lock = threading.Lock()
# client initialization
# ...
client.loop_start()
threading.Timer(1, timer_event).start()
while True:
pass
But this won't prevent your stored value to drift away because the topic is apparently published too frequently so your subscriber (or even the broker) is not able to handle a message fast enough.
So you might want to reduce the interval in which this topic is published. Also notice that you subscribed to a multi-level topic - even if the topics besides "topic/test1" are not handled in your code they still cause load for the broker and the subscribing client
I have a TCP server running and have a handler function which needs to take the contents of the request, add it to an asyncio queue and reply with an OK status.
On the background I have an async coroutine running that detects when a new item is added and performs some processing.
How do I put items in the asyncio queue from the handler function, which isn't and can't be an async coroutine?
I am running a DICOM server pynetdicom which listens on port 104 for incoming TCP requests (DICOM C-STORE specifically).
I need to save the contents of the request to a queue and return a a 0x0000 response so that the listener is available to the network.
This is modeled by a producer-consumer pattern.
I have tried to define a consumer co-routine consume_dicom() that is currently stuck in await queue.get() since I can't properly define the producer.
The producer needs to simply invoke queue.put(produce_item) but this happens inside a handle_store(event) function which is not part of the event_loop but is called every time a request is received by the server.
import asyncio
from pynetdicom import (
AE, evt,
StoragePresentationContexts
)
class PacsServer():
def __init__(self, par, listen=True):
# Initialize other stuff...
# Initialize DICOM server
ae = AE(ae_title='DICOM-NODE')
ae.supported_contexts = StoragePresentationContexts
# When a C-STORE request comes, it will be passed to self.handle_store
handlers = [(evt.EVT_C_STORE, self.handle_store)]
# Define queue
loop = asyncio.get_event_loop()
self.queue = asyncio.Queue(loop=loop)
# Define consumer
loop.create_task(self.consume_dicom(self.queue))
# Start server in the background with specified handlers
self.scp = ae.start_server(('', 104), block=False, evt_handlers=handlers)
# Start async loop
self.loop.run_forever()
def handle_store(self, event):
# Request handling
ds = event.dataset
# Here I want to add to the queue but this is not an async method
await queue.put(ds)
return 0x0000
async def consume_dicom(self, queue):
while True:
print(f"AWAITING FROM QUEUE")
ds = await queue.get()
do_some_processing(ds)
I would like to find a way to add items to the queue and return the OK status in the handle_store() function.
Since handle_store is running in a different thread, it needs to tell the event loop to enqueue the item. This is done with call_soon_threadsafe:
self.loop.call_soon_threadsafe(queue.put_nowait, ds)
Note that you need to call queue.put_nowait instead of queue.put because the former is a function rather than a coroutine. The function will always succeed for unbounded queues (the default), otherwise it will raise an exception if the queue is full.
TL;DR: Calling future.set_result doesn't immediately resolve loop.run_until_complete. Instead it blocks for an additional 5 seconds.
Full context:
In my project, I'm using autobahn and asyncio to send and receive messages with a websocket server. For my use case, I need a 2nd thread for websocket communication, since I have arbitrary blocking code that will be running in the main thread. The main thread also needs to be able to schedule messages for the communication thread to send back and forth with the server. My current goal is to send a message originating from the main thread and block until the response comes back, using the communication thread for all message passing.
Here is a snippet of my code:
import asyncio
import threading
from autobahn.asyncio.websocket import WebSocketClientFactory, WebSocketClientProtocol
CLIENT = None
class MyWebSocketClientProtocol(WebSocketClientProtocol):
# -------------- Boilerplate --------------
is_connected = False
msg_queue = []
msg_listeners = []
def onOpen(self):
self.is_connected = True
for msg in self.msg_queue[::]:
self.publish(msg)
def onClose(self, wasClean, code, reason):
is_connected = False
def onMessage(self, payload, isBinary):
for listener in self.msg_listeners:
listener(payload)
def publish(self, msg):
if not self.is_connected:
self.msg_queue.append(msg)
else:
self.sendMessage(msg.encode('utf-8'))
# /----------------------------------------
def send_and_wait(self):
future = asyncio.get_event_loop().create_future()
def listener(msg):
print('set result')
future.set_result(123)
self.msg_listeners.append(listener)
self.publish('hello')
return future
def worker(loop, ready):
asyncio.set_event_loop(loop)
factory = WebSocketClientFactory('ws://127.0.0.1:9000')
factory.protocol = MyWebSocketClientProtocol
transport, protocol = loop.run_until_complete(loop.create_connection(factory, '127.0.0.1', 9000))
global CLIENT
CLIENT = protocol
ready.set()
loop.run_forever()
if __name__ == '__main__':
# Set up communication thread to talk to the server
threaded_loop = asyncio.new_event_loop()
thread_is_ready = threading.Event()
thread = threading.Thread(target=worker, args=(threaded_loop, thread_is_ready))
thread.start()
thread_is_ready.wait()
# Send a message and wait for response
print('starting')
loop = asyncio.get_event_loop()
result = loop.run_until_complete(CLIENT.send_and_wait())
print('done') # this line gets called 5 seconds after it should
I'm using the autobahn echo server example to respond to my messages.
Problem: The WebSocketClientProtocol receives the response to its outgoing message and calls set_result on its pending future, but loop.run_until_complete blocks an additional ~4.9 seconds until eventually resolving.
I understand that run_until_complete also processes other pending events on the event loop. Is it possible that the main thread has somehow queued up a bunch of events that have to now get processed once I start the loop? Also, if I move run_until_complete into the communications thread or move the create_connection into the main thread, then the event loop doesn't block me.
Lastly, I tried to recreate this problem without using autobahn, but I couldn't cause the extra delay. I'm curious if maybe this is an issue with the nature of autobahn's callback timing (onMessage for example).
I'm trying to understand, is it possible to run the asyncio.Server instance while the event loop is already running by run_forever method (from a separate thread, of course).
As I understand, the server can be started either by loop.run_until_complete(asyncio.start_server(...)) or by
await asyncio.start_server(...), if the loop is already running.
The first way is not acceptable for me, since the loop is already running by run_forever method. But I also can't use the await expression, since I'm going to start it from outside the "loop area" (e.g. from the main method, which can't be marked as async, right?)
def loop_thread(loop):
asyncio.set_event_loop(loop)
try:
loop.run_forever()
finally:
loop.close()
print("loop clesed")
class SchedulerTestManager:
def __init__(self):
...
self.loop = asyncio.get_event_loop()
self.servers_loop_thread = threading.Thread(
target=loop_thread, args=(self.loop, ))
...
def start_test(self):
self.servers_loop_thread.start()
return self.servers_loop_thread
def add_router(self, router):
r = self.endpoint.add_router(router)
host = router.ConnectionParameters.Host
port = router.ConnectionParameters.Port
srv = TcpServer(host, port)
server_coro = asyncio.start_server(
self.handle_connection, self.host, self.port)
# does not work since add_router is not async
# self.server = await server_coro
# does not work, since the loop is already running
# self.server = self.loop.run_until_complete(server_coro)
return r
def maind():
st_manager = SchedulerTestManager()
thread = st_manager.start_test()
router = st_manager.add_router(router)
Of cource, the simplest solution is to add all routers (servers) before starting the test (running the loop). But I want try to implement it, so it would be possible to add a router when a test is already running. I thought the loop.call_soon (call_soon_threadsafe) methods can help me, but it seems the can't shedule a coroutine, but just a simple function.
Hope that my explanation is not very confusing. Thanks in advance!
For communicating between event loop executed in one thread and conventional old good threaded code executed in other thread you might use janus library.
It's a queue with two interfaces: async and thread-safe sync one.
This is usage example:
import asyncio
import janus
loop = asyncio.get_event_loop()
queue = janus.Queue(loop=loop)
def threaded(sync_q):
for i in range(100):
sync_q.put(i)
sync_q.join()
#asyncio.coroutine
def async_coro(async_q):
for i in range(100):
val = yield from async_q.get()
assert val == i
async_q.task_done()
fut = loop.run_in_executor(None, threaded, queue.sync_q)
loop.run_until_complete(async_coro(queue.async_q))
loop.run_until_complete(fut)
You may create a task waiting new messages from the queue in a loop and starting new servers on request. Other thread may push new message into the queue asking for a new server.