linod: asynchronous Lino daemon

The lino.modlib.linod plugin adds functionality for background work such as running system tasks, sending notifications, and logging to the lino.log from multiple processes. It uses the ASGI interface to run an asynchronous Lino daemon process.

What it does

The Lino daemon process is responsible for running the scheduled background jobs defined by your application. This includes for example:

  • Send out emails for notifications

  • Run daily maintenance tasks such as checkdata or checksummaries.


Applications can register a job using the dd.api.schedule_often() or dd.api.schedule_daily() decorators. For example (taken from lino.modlib.checkdata):

def checkdata(ar):
    """Run all data checkers."""

The code for the example above should be in one of your application's modules.

You can see a status of these jobs by running:

$ cd ~/projects/mysite
$ python linod

How to activate

A site maintainer can activate this plugin automatically by setting the use_lino site setting to True.


Whether to install the lino.modlib.linod plugin and activate asynchronous functionality.

>>> import lino
>>> lino.startup('lino_book.projects.noi1e.settings.demo')
>>> from lino.api.doctest import *

System tasks

class lino.modlib.linod.Procedure

Is basically a Job which contains a function func to run at default interval given by every_unit and every_value.

The default interval can be overridden by JobRule.


The function to run as a system task.


Callable[[BaseRequest], None]


The default unit of the interval at which the task func will run.




The default value of the interval at which the task func will run.




The time at which this task should run first.



run(self, ar)

Calls the function stored in func passing ar as a positional argument.


ar -- an instance of BaseRequest

class lino.modlib.linod.Procedures

The choicelist that contains instances Procedure as choices.

============================== ============================== ==============================
 value                          name                           text
------------------------------ ------------------------------ ------------------------------
 clear_too_old_job_history      clear_too_old_job_history      clear_too_old_job_history
 event_notification_scheduler   event_notification_scheduler   event_notification_scheduler
 checksummaries                 checksummaries                 checksummaries
 checkdata                      checkdata                      checkdata
 send_pending_emails_often      send_pending_emails_often      send_pending_emails_often
 send_pending_emails_daily      send_pending_emails_daily      send_pending_emails_daily
 clear_seen_messages            clear_seen_messages            clear_seen_messages
============================== ============================== ==============================
class lino.modlib.linod.LogLevels

A choicelist with the equivalent log levels from the logging module.

class lino.modlib.linod.JobRule

Overrides the recurrent rule of a Procedure.

A subclass of Sequenced and RecurrenceSet.


Some name for this job rule.




The pointer to an instance of Procedure.


Tells whether the task should be ignored.

cancelled is set to True if the tasks fails and raises an exception, otherwise can be set cancelled from the ui.




Tells what sort of messeages to put into the Job.message field.





run(self, ar, lgr=None) Job

Performs a routine job.

  • ar -- An instance of BaseRequest

  • lgr -- Logger obtained by calling logging.getLogger.


An instance of Job.

class lino.modlib.linod.Job

A historical record of a system task.


Tells at what time exactly this job started.




A pointer to an instance of JobRule which describes the nature of the task.


Stores information about the job, mostly logs.

class lino.modlib.linod.JobRules

The default actor for the JobRule model.

class lino.modlib.linod.Jobs

The default actor for the Jobs model.

class lino.modlib.linod.JobsByRule
class lino.modlib.linod.tasks.Tasks

The base class that manages the system tasks.


Gathers all the jobs defined using dd.api.schedule_often() and dd.api.schedule_daily() decorator.

This method is called at startup of the linod worker process. Also does cleaning up of removed tasks and reactivating the cancelled tasks.

>>> from lino.modlib.linod.tasks import Tasks
>>> tasks = Tasks()
>>> tasks.setup()
run(self) datetime.datetime

Runs the system tasks which are due for the current time.


an instance of datetime.datetime; tells the parent scope at what time the next call to run() method should be done.

status(self) list[str]

Returns the status of all the tasks.

>>> for task_status in tasks.status():
...     print(task_status)
Job rule #1 <Procedures.clear_too_old_job_history every 1 daily> next run at: ...
Job rule #2 <Procedures.event_notification_scheduler every 300 secondly> next run at: ...
Job rule #3 <Procedures.checksummaries every 1 daily> next run at: ...
Job rule #4 <Procedures.checkdata every 1 daily> next run at: ...
Job rule #5 <Procedures.send_pending_emails_often every 10 secondly> next run at: ...
Job rule #6 <Procedures.send_pending_emails_daily every 1 daily> next run at: ...
Job rule #7 <Procedures.clear_seen_messages every 1 daily> next run at: ...

Creates a JobRule for a given Procedure


procedure -- an instance of Procedure


Sets Job.cancelled to True for a given rule.


rule -- an instance of JobRule

class lino.modlib.linod.SetupTasks

An action to adjust the database objects to the latest changes.

Should be run only in development environment where no linod worker present. A production run of linod worker does this on each spin up by calling Tasks.setup.

class lino.modlib.linod.RunJob

An action to run a job immediately.

Simulating the daemon in a developer environment

To run the Lino daemon in a development environment run python runworker `linod_{{project_name}} here if your project name is my_project you will need to run python runworker linod_my_project. And afterwards to initiate the background tasks and logging you will have to run the python initiate_linod command in an another terminal.

Usage for developers

The lino.modlib.linod plugin is dependent on a running redis-server in the background and also requires the django-channels, channels-redis Python packages to be installed.

To install redis on a Debian-based Linux distribution run the following command as root:

$ apt update
$ apt install redis

To install the dependent python packages run the following command after activating your Python environment:

$ pip install django-channels channels-redis

Now you simply go to your project directory and invoke the admin command:

$ cd ~/projects/mysite
$ python runworker linod_mysite

This process will run as long as you don't kill it, e.g. until you hit Ctrl-C.

Open up another terminal window, on which invoke the following command to initiate the background task's cor-routine and logger cor-routine:

$ cd ~/projects/mysite
$ python initiate_linod