Wednesday, April 24, 2024

#5570 (Jane doesn’t send email notifications) is not trivial. It was (of course) caused by my changes in the background task runner. I am really still too naive regarding asynchronous programming!

Should we add test coverage for asynchronous operations. A unit test case in noi1r that creates a log directory (in order to activate the socket logger), launches a pm linod process and some django-admin command and then checks the content of the lino.log file…

Here is an excerpt from lino_xl.lib.invoicing.models:

class Task(Runnable, UserAuthored):

    target_journal = dd.ForeignKey(
        verbose_name=_("Target journal"),

    def __str__(self):
        return _("Make {}").format(self.target_journal)

class Runnable(Sequenced, RecurrenceSet):

    async def start_task(self, ar):

Arguments: (Unprintable Task(pk=1,error=SynchronousOnlyOperation(‘You cannot call this from an async context - use a thread or sync_to_async.’),)

Do we really need the asynchronous versions of ar.debug, and ar.warning?

Edit 20240426: Yes we do. Because these methods potentially lead to I/O operations. When calling them from an async context, code execution would potentially continue before they have done their work. I tried to reproduce the problem but without success. Maybe the problem exists only with Django?

Trying to show why the logging module needs an async interface.

import logging
logger = logging.getLogger()

import asyncio
from time import sleep

from asgiref.sync import sync_to_async

info =
ainfo = sync_to_async(

async def task1():
    info("- Run task1")

async def task2(name):
    info("- Run %s", name)

async def main():
    # await ainfo("Start task runner ")
    info("Start task runner ")
    count = 0
    while count < 2:
        count += 1
        # await ainfo("Start loop %s.", count)
        info("Start loop %s.", count)
        await task1()
        await task2("foo")
        await task2("bar")

    # await ainfo("Done task runner ")
    info("Done task runner ")

loop = asyncio.get_event_loop()

“If you want to call a part of Django that is still synchronous, you will need to wrap it in a sync_to_async() call. If you accidentally try to call a part of Django that is synchronous-only from an async view, you will trigger Django’s asynchronous safety protection to protect your data from corruption.”

“Transactions do not yet work in async mode. If you have a piece of code that needs transactions behavior, we recommend you write that piece as a single synchronous function and call it using sync_to_async().”