簡體   English   中英

asyncio python 協程取消,而任務仍在等待從 redis 通道讀取

[英]asyncio python coroutine cancelled while task still pending reading from redis channel

我有多個 couroutine,每個都等待隊列中的內容開始處理。

隊列的內容由頻道訂閱者填充,他們的工作只是接收消息並將項目推送到適當的隊列中。

在數據被一個隊列處理器使用並生成新數據后,它被分派到適當的消息通道,在該通道中重復此過程,直到數據准備好中繼到提供它的 api。

import asyncio
from random import randint
from Models.ConsumerStrategies import Strategy
from Helpers.Log import Log
import Connectors.datastore as ds

import json
__name__ = "Consumer"

MIN = 1
MAX = 4

async def consume(configuration: dict, queue: str, processor: Strategy) -> None:
    """Consumes new items in queue and publish a message into the appropriate channel with the data generated for the next consumer,
    if no new content is available sleep for a random number of seconds between MIN and MAX global variables

    Args:
        configuration (dict): configuration dictionary
        queue (str): queue being consumed
        processor (Strategy): consumer strategy
    """
    
    logger = Log().get_logger(processor.__name__, configuration['logFolder'], configuration['logFormat'], configuration['USE'])
    while True:
        try:
            ds_handle = await ds.get_datastore_handle(ds.get_uri(conf=configuration))
            token = await ds_handle.lpop(queue)
            if token is not None:
                result = await processor.consume(json.loads(token), ds_handle)
                status = await processor.relay(result, ds_handle)
                logger.debug(status)
            else:
                wait_for = randint(MIN,MAX)
                logger.debug(f'queue: {queue} empty waiting: {wait_for} before retry')
                await asyncio.sleep(wait_for)
            ds_handle.close()
        except Exception as e:
            logger.error(f"{e}")
            logger.error(f"{e.with_traceback}")

我注意到的是,在運行 24 小時后,我收到了以下錯誤:

Task was destroyed but it is pending!
task: <Task pending name='Task-2' coro=<consume() running at Services/Consumer.py:26> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f86bc29cbe0>()]> cb=[_chain_future.<locals>._call_set_state() at asyncio/futures.py:391]>
Task was destroyed but it is pending!
task: <Task pending name='Task-426485' coro=<RedisConnection._read_data() done, defined at aioredis/connection.py:180> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f86bc29ccd0>()]> cb=[RedisConnection.__init__.<locals>.<lambda>() at aioredis/connection.py:168]>

我不確定如何解釋、解決或恢復,我的假設是首先我應該切換到 redis 流而不是使用通道和隊列。

但是,回到這個場景,我在不同的進程上有頻道訂閱者,而消費者在同一個進程中作為循環中的不同任務運行。

我假設這里發生的是,由於消費者基本上在某個時候輪詢隊列,連接池管理器或 redis 本身最終開始掛斷消費者的連接打開並被取消。

因為我沒有看到來自該隊列處理器的任何進一步消息,但我也看到了我不確定它可能來自消息閱讀器上的訂閱者 ensure_future 的 wait_for_future

import asyncio
from multiprocessing import process
from Helpers.Log import Log
import Services.Metas as metas
import Models.SubscriberStrategies as processor
import Connectors.datastore as ds_linker
import Models.Exceptions as Exceptions

async def subscriber(conf: dict, channel: str, processor: processor.Strategy) -> None:
    """Subscription handler. Receives the channel name, datastore connection and a parsing strategy.
    Creates a task that listens on the channel and process every message and processing strategy for the specific message

    Args:
        conf (dict): configuration dictionary
        channel (str): channel to subscribe to
        ds (aioredis.connection): connection handler to datastore
        processor (processor.Strategy): processor message handler
    """
    async def reader(ch):
        while await ch.wait_message():
            msg = await ch.get_json()
            await processor.handle_message(msg=msg)

    ds_uri = ds_linker.get_uri(conf=conf)
    ds = await ds_linker.get_datastore_handle(ds_uri)
    pub = await ds.subscribe(channel)
    ch = pub[0]
    tsk = asyncio.ensure_future(reader(ch))
    await tsk

我可以使用一些幫助來解決這個問題並正確理解引擎蓋下發生的事情。 謝謝

花了幾天時間才解決了這個問題,我在 aioredis github repo 的問題中發現了同樣問題的人。

因此,我必須通過 redis 的所有連接打開/關閉 go 以確保添加:

        ds_handle.close()
        await ds_handle.wait_closed()

我還着手改進消費者的異常管理:

while True:
    try:
        ds_handle = await ds.get_datastore_handle(ds.get_uri(conf=configuration))
        token = await ds_handle.lpop(queue)
        if token is not None:
            result = await processor.consume(json.loads(token), ds_handle)
            status = await processor.relay(result, ds_handle)
            logger.debug(status)
        else:
            wait_for = randint(MIN,MAX)
            logger.debug(f'queue: {queue} empty waiting: {wait_for} before retry')
            await asyncio.sleep(wait_for)   
    except Exception as e:
        logger.error(f"{e}")
        logger.error(f"{traceback.print_exc()}")
    finally:
        ds_handle.close()
        await ds_handle.wait_closed()

生產者也是如此:

try:
    async def reader(ch):
        while await ch.wait_message():
            msg = await ch.get_json()
            await processor.handle_message(msg=msg)

    ds_uri = ds_linker.get_uri(conf=conf)
    ds = await ds_linker.get_datastore_handle(ds_uri)
    pub = await ds.subscribe(channel)
    ch = pub[0]
    tsk = asyncio.ensure_future(reader(ch))
    await tsk
except Exception as e:
    logger.debug(f'{e}')
    logger.error(f'{traceback.format_exc()}')
finally:
    ds.close()
    await ds.wait_closed()

因此,redis 永遠不會有連接打開,隨着時間的流逝,這可能最終會殺死處理器的協程之一。

對我來說,它解決了這個問題,因為在我寫這篇文章的時候,已經超過 2 周的正常運行時間,沒有再報告過同類事故。

無論如何,還有一個新的 aioredis 主要版本,這確實是最近的新聞(這是在 1.3.1 和 2.0.0 上應該使用與 redis-py 相同的 model 工作,所以這次情況也發生了變化)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM