web-dev-qa-db-fra.com

Long délai dans l'utilisation de l'asyncio et websockets dans Python 3

Je rencontre un long délai (3 heures) (EDIT: le délai est bref au début, puis s’allonge tout au long de la journée) lors du traitement des données transférées d’un serveur Websocket vers mon client.py. Je sais que ce n'est pas retardé par le serveur.

Par exemple, toutes les 5 secondes, je vois l'événement-journal keep_alive et son horodatage respectif. Alors ça se passe bien. Mais quand je vois une trame de données traitée dans les journaux, c'est en réalité 3 heures après lorsque le serveur l'a envoyé. Est-ce que je fais quelque chose pour retarder ce processus? 

Est-ce que j'appelle correctement ma coroutine 'keep_alive'? keep_alive est juste un message au serveur pour maintenir la connexion active. Le serveur renvoie le message. De plus, est-ce que je me connecte trop? Cela risque-t-il de retarder le traitement (je ne le pense pas, car les événements de journalisation se produisent immédiatement). 

async def keep_alive(websocket):
                """
                 This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
                """
                await websocket.send('Hello')   
                await asyncio.sleep(5)

async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    async with websockets.connect( 
            'wss://{}:{}@localhost.urlname.com/ws'.format(user,pswd), ssl=True, ) as websocket:
        while True:    
            """
            Handle message from server.
            """
            message = await websocket.recv()
            if message.isdigit():
                # now = datetime.datetime.now()
                rotating_logger.info ('Keep alive message: {}'.format(str(message)))
            else:
                jasonified_message = json.loads(message)
                for key in jasonified_message:
                    rotating_logger.info ('{}: \n\t{}\n'.format(key,jasonified_message[key]))    
                """
                Store in a csv file.
                """
                try:            
                    convert_and_store(jasonified_message)
                except PermissionError:
                    convert_and_store(jasonified_message, divert = True)                        
            """
            Keep connection alive.
            """            
            await keep_alive(websocket)

"""
Logs any exceptions in logs file.
"""
try:
    asyncio.get_event_loop().run_until_complete(open_connection())
except Exception as e:
    rotating_logger.info (e)

EDIT: De la documentation - Je pense que cela a peut-être quelque chose à voir avec cela - mais je n’ai pas mis les points en corrélation.

Le paramètre max_queue définit la longueur maximale de la file d'attente que contient les messages entrants. La valeur par défaut est 32. 0 désactive le limite. Les messages sont ajoutés à une file d'attente en mémoire lorsqu'ils sont reçus; alors recv () sort de cette file. Afin d'éviter une mémoire excessive consommation lorsque les messages sont reçus plus rapidement qu'ils ne peuvent l'être traitée, la file d'attente doit être bornée. Si la file d'attente est pleine, le fichier Le protocole arrête le traitement des données entrantes jusqu'à ce que recv () soit appelé. Dans Dans cette situation, divers tampons de réception (au moins en asyncio et dans le système d’exploitation.) se rempliront, puis la fenêtre de réception TCP diminuera et ralentira. transmission vers le bas pour éviter la perte de paquets.

EDIT 28/09/2018: Je le teste sans le message de maintien en vie et cela ne semble pas être le problème. Pourrait-il être lié à la fonction convert_and_store ()? Est-ce que cela doit être asynchrone puis attendu?

def convert_and_store(data, divert = False, test = False):
    if test:
        data = b
    fields = data.keys()
    file_name =  parse_call_type(data, divert = divert)
    json_to_csv(data, file_name, fields)

EDIT 10/1/2018: Il semble que le message de maintien en vie et le fichier convert_and_store soient tous deux en cause; si je prolonge le message persistant à 60 secondes, convert_and_store ne lancera qu'une seule fois par 60 secondes. Donc convert_and_store attend le keep_alive () ...

8
Liam Hanninen

Pourrait-il être lié à la fonction convert_and_store ()?

Oui, ça pourrait être. Le code de blocage ne doit pas être appelé directement. Si une fonction effectue un calcul gourmand en ressources d'UC pendant une seconde, toutes les tâches asynchrones et les opérations IO seraient retardées d'une seconde.

Un exécuteur peut être utilisé pour exécuter un code de blocage dans un autre thread/processus:

import asyncio
import concurrent.futures
import time

def long_runned_job(x):
    time.sleep(2)
    print("Done ", x)

async def test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        for i in range(5):
            loop.run_in_executor(pool, long_runned_job, i)
            print(i, " is runned")
            await asyncio.sleep(0.5)
loop = asyncio.get_event_loop()
loop.run_until_complete(test())

Dans votre cas, cela devrait ressembler à quelque chose comme ça:

import concurrent.futures

async def open_connection_test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        async with websockets.connect(...) as websocket:
            while True:    
                ...
                loop.run_in_executor(pool, convert_and_store, args)

ÉDITÉ

Il semble que le message de maintien en vie et le fichier convert_and_store soient tous deux en cause

Vous pouvez exécuter keep_alive en arrière-plan:

async def keep_alive(ws):
    while ws.open:
        await ws.ping(...)   
        await asyncio.sleep(...)

async with websockets.connect(...) as websocket:
    asyncio.ensure_future(keep_alive(websocket))
    while True:    
        ...
3
Artemiy

Vous devez démarrer un nouveau thread pour cette fonction keep_alive().

Pour async-await, il est promis que toutes les tâches ont été effectuées avant de passer à l'étape suivante.

Ainsi, await keep_alive(websocket) bloque réellement le fil dans ce sens. Vous ne pouvez pas attendre le keep_alive ici pour que le processus puisse continuer, mais pour sûr, ce n’est pas ce que vous voulez, je suis sûr.

En fait, vous voulez deux délais, un pour communiquer avec le serveur, un pour maintenir le serveur en vie. Ils doivent être séparés car ils sont dans coroutine différente.

Donc, la bonne façon consiste à utiliser Thread et vous n'avez pas besoin d'utiliser asyncio dans ce cas, gardez les choses simples.

Tout d’abord, changez keep_alive() en suivant.

def keep_alive():
    """
        This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
    """
    while True:
        websocket.send('Hello') 
        time.sleep(1)

Dans open_connection_test()

async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    thread = threading.Thread(target=keep_alive, args=())
    thread.daemon = True   # Daemonize
    thread.start()
    async with websockets.connect(...) as websocket:
        ....
        #No need this line anymore.
        #await keep_alive(websocket) 
1
MatrixTai

Je pense que cela serait plus clair. Utilisez ThreadPoolExecutor pour que le code bloquant s'exécute en arrière-plan.

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(max_workers=4)

def convert_and_store(data, divert=False, test=False):
    loop = asyncio.get_event_loop()
    loop.run_in_executor(pool, _convert_and_store, divert, test)


def _convert_and_store(data, divert=False, test=False):
    if test:
        data = b
    fields = data.keys()
    file_name = parse_call_type(data, divert=divert)
    json_to_csv(data, file_name, fields)

asyncio envoyer garder en vie msg démo

async def kepp_alive(websocket):
    while True:
        await websocket.send_str(ping)
        await asyncio.sleep(10)
0
Kr.98