web-dev-qa-db-fra.com

travailleur d'arrêt de céleri après une tâche particulière

J'utilise du céleri (pool solo avec concurrence = 1) et je veux pouvoir arrêter le travailleur après l'exécution d'une tâche particulière. Une mise en garde est que je veux éviter toute possibilité que le travailleur reprenne d'autres tâches après celle-ci.

Voici ma tentative dans le contour:

from __future__ import absolute_import, unicode_literals
from celery import Celery
from celery.exceptions import WorkerShutdown
from celery.signals import task_postrun

app = Celery()
app.config_from_object('celeryconfig')

@app.task
def add(x, y):
    return x + y

@task_postrun.connect(sender=add)
def shutdown(*args, **kwargs):
    raise WorkerShutdown()

Cependant, lorsque je lance le travailleur

celery -A celeryapp  worker --concurrency=1 --pool=solo

et exécutez la tâche

add.delay(1,4)

J'obtiens ce qui suit:

 -------------- celery@sam-APOLLO-2000 v4.0.2 (latentcall)
---- **** ----- 
--- * ***  * -- Linux-4.4.0-116-generic-x86_64-with-Ubuntu-16.04-xenial 2018-03-18 14:08:37
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         __main__:0x7f596896ce90
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     redis://localhost/
- *** --- * --- .> concurrency: 4 (solo)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[2018-03-18 14:08:39,892: WARNING/MainProcess] Restoring 1 unacknowledged message(s)

La tâche est re-mise en file d'attente et sera exécutée à nouveau sur un autre travailleur, conduisant à une boucle.

Cela se produit également lorsque je déplace l'exception WorkerShutdown dans la tâche elle-même.

@app.task
def add(x, y):
    print(x + y)
    raise WorkerShutdown()

Existe-t-il un moyen d'arrêter le travailleur après une tâche particulière, tout en évitant cet effet secondaire malheureux?

23
samfrances

Le processus recommandé pour arrêter un travailleur consiste à envoyer le signal TERM. Cela entraînera l'arrêt d'un travailleur de céleri après avoir terminé toutes les tâches en cours d'exécution. Si vous envoyez un signal QUIT au processus principal du travailleur, celui-ci s'arrêtera immédiatement.

Les documents de céleri, cependant, discutent généralement de cela en termes de gestion du céleri à partir d'une ligne de commande ou via systemd/initd, mais céleri fournit en outre une API de contrôle de travailleur distant via celery.app.control.
Vous pouvez révoquer une tâche pour empêcher les travailleurs d'exécuter la tâche. Cela devrait empêcher la boucle que vous rencontrez. En outre, le contrôle prend également en charge arrêt d'un travailleur.

J'imagine donc que ce qui suit vous donnera le comportement que vous désirez.

@app.task(bind=True)
def shutdown(self):
    app.control.revoke(self.id) # prevent this task from being executed again
    app.control.shutdown() # send shutdown signal to all workers

Puisqu'il n'est actuellement pas possible d'acquitter la tâche à partir de la tâche, puis de continuer à exécuter cette tâche, cette méthode d'utilisation de revoke contourne ce problème de sorte que, même si la tâche est à nouveau mise en file d'attente, le nouveau travailleur ignorera simplement il.

Alternativement, les éléments suivants empêcheraient également l'exécution d'une tâche redistribuée une deuxième fois ...

@app.task(bind=True)
def some_task(self):
    if self.request.delivery_info['redelivered']:
        raise Ignore() # ignore if this task was redelivered
    print('This should only execute on first receipt of task')

A noter également AsyncResult possède également une méthode revoke qui appelle self.app.control.revoke pour vous.

6
sytech

Si vous arrêtez le programme de travail, une fois la tâche terminée, il ne sera plus mis en file d'attente.

@task_postrun.connect(sender=add)
def shutdown(*args, **kwargs):
    app.control.broadcast('shutdown')

Cela arrêtera gracieusement le travailleur une fois les tâches terminées.

[2018-04-01 18:44:14,627: INFO/MainProcess] Connected to redis://localhost:6379/0
[2018-04-01 18:44:14,656: INFO/MainProcess] mingle: searching for neighbors
[2018-04-01 18:44:15,719: INFO/MainProcess] mingle: all alone
[2018-04-01 18:44:15,742: INFO/MainProcess] celery@foo ready.
[2018-04-01 18:46:28,572: INFO/MainProcess] Received task: celery_worker_stop.add[ac8a65ff-5aad-41a6-a2d6-a659d021fb9b]
[2018-04-01 18:46:28,585: INFO/ForkPoolWorker-4] Task celery_worker_stop.add[ac8a65ff-5aad-41a6-a2d6-a659d021fb9b] succeeded in 0.005628278013318777s: 3   
[2018-04-01 18:46:28,665: WARNING/MainProcess] Got shutdown from remote

Remarque: la diffusion arrêtera tous les employés. Si vous souhaitez fermer un employé spécifique, démarrez-le avec un nom

celery -A celeryapp  worker -n self_killing --concurrency=1 --pool=solo

Vous pouvez maintenant l'arrêter avec le paramètre de destination.

app.control.broadcast('shutdown', destination=['celery@self_killing'])
2
ChillarAnand

Si vous devez arrêter un travailleur spécifique et que vous ne connaissez pas son nom à l'avance, vous pouvez l'obtenir à partir des propriétés de la tâche. Sur la base des réponses ci-dessus, vous pouvez utiliser:

app.control.shutdown(destination=[self.request.hostname])

ou

app.control.broadcast('shutdown', destination=[self.request.hostname])

Remarque:

  • Un travailleur doit être démarré avec un nom (option '-n');
  • La tâche doit être définie avec bind=True paramètre.
0
desergik