Celery: Delay dan apply_async menunggu selamanya ketika broker sedang down.

Dibuat pada 28 Sep 2017  ·  44Komentar  ·  Sumber: celery/celery

Daftar Periksa

  • [X] Saya telah memasukkan keluaran dari celery -A proj report dalam terbitan.
    (jika Anda tidak dapat melakukan ini, setidaknya tentukan Celery
    versi terpengaruh).
  • [X] Saya telah memverifikasi bahwa masalah tersebut ada pada cabang Celery master .

Langkah-langkah untuk mereproduksi

# tasks.py
from celery import Celery

app = Celery('tasks', broker='pyamqp://guest@localhost//')

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

@app.task
def mul(x, y):
    return x * y

Saya menguji kasus saat rabbitmq turun. Jadi saya tidak memulai rabbitmq sama sekali. Kemudian, saya menjalankan perintah berikut di ipython:

from tasks import add
add.delay()

add.delay() selalu diblokir. Saya mencoba menyetel retry ke False, tetapi tidak berhasil.

add.apply_async((2, 2), retry=False)

Saya juga mencoba memberikan retry_policy dan itu juga tidak berhasil.

add.apply_async((2, 2), retry=True, retry_policy={
    'max_retries': 3,
    'interval_start': 0, 
    'interval_step': 0.2,
    'interval_max': 0.2,
})

Perilaku yang diharapkan

Saya berharap untuk melihat pengecualian saat saya menyetel coba lagi ke False. Ketika saya menyetel coba lagi ke True, itu harus menjalankan jumlah default max_retries dan jika tidak berfungsi, itu harus mengeluarkan pengecualian.

Perilaku sebenarnya

Fungsi tersebut berjalan selamanya dan memblokir proses tersebut.

Error Stack Trace

Saya menekan ctrl+c untuk mendapatkan informasi kesalahan berikut:

In [1]: from tasks import add, mul

In [2]: add.delay(2,3)
^C---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/github/celery-demo/.venv/src/kombu/kombu/utils/functional.py in __call__(self)
     35         try:
---> 36             return self.__value__
     37         except AttributeError:

AttributeError: 'ChannelPromise' object has no attribute '__value__'

During handling of the above exception, another exception occurred:

ConnectionRefusedError                    Traceback (most recent call last)
~/github/celery-demo/.venv/src/kombu/kombu/utils/functional.py in retry_over_time(fun, catch, args, kwargs, errback, max_retries, interval_start, interval_step, interval_max, callback)
    337         try:
--> 338             return fun(*args, **kwargs)
    339         except catch as exc:

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in connect(self)
    260         self._closed = False
--> 261         return self.connection
    262

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in connection(self)
    801                 self._default_channel = None
--> 802                 self._connection = self._establish_connection()
    803                 self._closed = False

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in _establish_connection(self)
    756         self._debug('establishing connection...')
--> 757         conn = self.transport.establish_connection()
    758         self._debug('connection established: %r', self)

~/github/celery-demo/.venv/src/kombu/kombu/transport/pyamqp.py in establish_connection(self)
    129         conn.client = self.client
--> 130         conn.connect()
    131         return conn

~/github/celery-demo/.venv/lib/python3.6/site-packages/amqp/connection.py in connect(self, callback)
    281         )
--> 282         self.transport.connect()
    283         self.on_inbound_frame = self.frame_handler_cls(

~/github/celery-demo/.venv/lib/python3.6/site-packages/amqp/transport.py in connect(self)
    108     def connect(self):
--> 109         self._connect(self.host, self.port, self.connect_timeout)
    110         self._init_socket(

~/github/celery-demo/.venv/lib/python3.6/site-packages/amqp/transport.py in _connect(self, host, port, timeout)
    149                 self.sock.settimeout(timeout)
--> 150                 self.sock.connect(sa)
    151             except socket.error:

ConnectionRefusedError: [Errno 61] Connection refused

During handling of the above exception, another exception occurred:

KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-2-e2f2f196d9be> in <module>()
----> 1 add.delay(2,3)

~/github/celery-demo/.venv/src/celery/celery/app/task.py in delay(self, *args, **kwargs)
    414             celery.result.AsyncResult: Future promise.
    415         """
--> 416         return self.apply_async(args, kwargs)
    417
    418     def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,

~/github/celery-demo/.venv/src/celery/celery/app/task.py in apply_async(self, args, kwargs, task_id, producer, link, link_error, shadow, **options)
    537             link=link, link_error=link_error, result_cls=self.AsyncResult,
    538             shadow=shadow, task_type=self,
--> 539             **options
    540         )
    541

~/github/celery-demo/.venv/src/celery/celery/app/base.py in send_task(self, name, args, kwargs, countdown, eta, task_id, producer, connection, router, result_cls, expires, publisher, link, link_error, add_to_parent, group_id, retries, chord, reply_to, time_limit, soft_time_limit, root_id, parent_id, route_name, shadow, chain, task_type, **options)
    747             with P.connection._reraise_as_library_errors():
    748                 self.backend.on_task_call(P, task_id)
--> 749                 amqp.send_task_message(P, name, message, **options)
    750         result = (result_cls or self.AsyncResult)(task_id)
    751         if add_to_parent:

~/github/celery-demo/.venv/src/celery/celery/app/amqp.py in send_task_message(producer, name, message, exchange, routing_key, queue, event_dispatcher, retry, retry_policy, serializer, delivery_mode, compression, declare, headers, exchange_type, **kwargs)
    552                 delivery_mode=delivery_mode, declare=declare,
    553                 headers=headers2,
--> 554                 **properties
    555             )
    556             if after_receivers:

~/github/celery-demo/.venv/src/kombu/kombu/messaging.py in publish(self, body, routing_key, delivery_mode, mandatory, immediate, priority, content_type, content_encoding, serializer, headers, compression, exchange, retry, retry_policy, declare, expiration, **properties)
    179             body, priority, content_type, content_encoding,
    180             headers, properties, routing_key, mandatory, immediate,
--> 181             exchange_name, declare,
    182         )
    183

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in _ensured(*args, **kwargs)
    492                 for retries in count(0):  # for infinity
    493                     try:
--> 494                         return fun(*args, **kwargs)
    495                     except conn_errors as exc:
    496                         if got_connection and not has_modern_errors:

~/github/celery-demo/.venv/src/kombu/kombu/messaging.py in _publish(self, body, priority, content_type, content_encoding, headers, properties, routing_key, mandatory, immediate, exchange, declare)
    185                  headers, properties, routing_key, mandatory,
    186                  immediate, exchange, declare):
--> 187         channel = self.channel
    188         message = channel.prepare_message(
    189             body, priority, content_type,

~/github/celery-demo/.venv/src/kombu/kombu/messaging.py in _get_channel(self)
    207         channel = self._channel
    208         if isinstance(channel, ChannelPromise):
--> 209             channel = self._channel = channel()
    210             self.exchange.revive(channel)
    211             if self.on_return:

~/github/celery-demo/.venv/src/kombu/kombu/utils/functional.py in __call__(self)
     36             return self.__value__
     37         except AttributeError:
---> 38             value = self.__value__ = self.__contract__()
     39             return value
     40

~/github/celery-demo/.venv/src/kombu/kombu/messaging.py in <lambda>()
    222             connection = channel
    223             self.__connection__ = connection
--> 224             channel = ChannelPromise(lambda: connection.default_channel)
    225         if isinstance(channel, ChannelPromise):
    226             self._channel = channel

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in default_channel(self)
    829
    830         # make sure we're still connected, and if not refresh.
--> 831         self.ensure_connection(**conn_opts)
    832         if self._default_channel is None:
    833             self._default_channel = self.channel()

~/github/celery-demo/.venv/src/kombu/kombu/connection.py in ensure_connection(self, errback, max_retries, interval_start, interval_step, interval_max, callback, reraise_as_library_errors)
    403                             (), {}, on_error, max_retries,
    404                             interval_start, interval_step, interval_max,
--> 405                             callback)
    406         return self
    407

~/github/celery-demo/.venv/src/kombu/kombu/utils/functional.py in retry_over_time(fun, catch, args, kwargs, errback, max_retries, interval_start, interval_step, interval_max, callback)
    348                     if callback:
    349                         callback()
--> 350                     sleep(1.0)
    351                 # sleep remainder after int truncation above.
    352                 sleep(abs(int(tts) - tts))

KeyboardInterrupt:

Saya menemukan masalah utama terjadi pada kombu/kombu/messaging.py::_publish , yang akhirnya mencapai kombu/kombu/connection.py::default_channel . default_channel memanggil kombu/kombu/connection.py::ensure_connection sesuai dengan max_retries , interval_start , interval_step , interval_max dalam transport_options .

Ketika transport_options adalah {} dan broker jatuh, self.ensure_connection(**conn_opts) dalam default_channel berjalan selamanya.

Tampaknya ada dua pembungkus yang mencoba lagi di kombu/messaging.py::Producer.publish . Satu didasarkan pada retry dan retry_policy disediakan oleh seledri amqp dan yang lainnya didasarkan pada transport_options .

Solusi saya saat ini adalah menambahkan konfigurasi percobaan ulang ke transport_options juga.

app.conf.broker_transport_options = {
    'max_retries': 3,
    'interval_start': 0,
    'interval_step': 0.2,
    'interval_max': 0.2,
}
Kombu Bug Report

Komentar yang paling membantu

Saya mendapat masalah serupa. Masalahnya adalah saya lupa menambahkan ini.

Kemudian Anda perlu mengimpor aplikasi ini ke modul proj / proj / __ init__.py Anda. Ini memastikan bahwa aplikasi dimuat ketika Django mulai sehingga dekorator @shared_task (disebutkan nanti) akan menggunakannya:

proj / proj / __ init__.py:

from __future__ import absolute_import, unicode_literals

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ('celery_app',)

Semua 44 komentar

Hai,
Saya mengalami masalah yang kedengarannya mirip dengan masalah Anda:
https://github.com/celery/kombu/issues/795

Bagi saya sebagai solusinya, menggunakan Kombu 4.0.2 telah berhasil.

@HengfengLi versi apa yang Anda gunakan?

@markddavidoff Ini celery (4.1.0) .

Saya mengalami masalah ini juga ketika mencoba memperbarui Jobtastic untuk mendukung seledri 4. Saya perlu mematok versi Kombu agar tes tidak hang selamanya.

Jadi, dalam versi kombu / seledri yang lebih rendah, waktu tunggu tidak semuanya diteruskan ke redis-py dengan benar, jadi dalam kasus saya, saya akhirnya membuat versi bercabang dari redis-py dan mengganti batas waktu secara manual

https://github.com/celery/kombu/pull/769 ini seharusnya memperbaiki masalah

Saya tidak memiliki masalah terselesaikan. Saya menggunakan seledri 4.1 dan kombu 4.1. Haruskah saya menggunakan versi kombu yang lebih baru?

Ketika ada masalah dengan pekerja, program yang mengatur tugas di beberapa titik mengirimkan tugas. Kode itu adalah app.send_task('module.name', []) dan frezzes di sana. Ini harus memberikan kesalahan koneksi atau sesuatu. Tidak diblokir di sana sepanjang hari. Mungkin saya melewatkan konfigurasi? Saya menggunakan rabbitMQ dan database backend, karena rpc memberi saya banyak masalah.

tunggu rilis 4.2

Mengapa? Apakah ini bug dari rilis 4.1.0? Sementara itu, apakah saya harus menurunkan versi ke 4.0.x? Terima kasih

sementara itu coba instal dari github master

Kami juga melihat masalah serupa menggunakan seledri & kombu dari cabang induknya. Saat broker redis turun, apply_async() akan diblokir hingga redis tersedia lagi. Saya telah bereksperimen dengan broker_transport_options yang berbeda, tetapi tidak berhasil.

cara mengatur penundaan 5 menit dalam kode di bawah ini
dari flask import Flask, Blueprint, abort, jsonify, request, session
pengaturan impor
dari seledri import Seledri

app = Flask (__ name__)
app.config.from_object (pengaturan)

def make_celery (app):
seledri = Seledri (app.import_name, broker = app.config ['CELERY_BROKER_URL'])
celery.conf.update (app.config)
TaskBase = celery.Task
kelas ContextTask (TaskBase):
abstrak = Benar
def __call __ (self, args, * kwargs):
dengan app.app_context ():
return TaskBase .__ call __ (self, args, * kwargs)
celery.Task = ContextTask
kembalikan seledri

seledri = make_celery (aplikasi)

@ celery.task (nama = "tugas.add")
def tambahkan (x, y):
return x + y

@ app.route ("/ test")
def hello_world (x = 16, y = 16):
x = int (request.args.get ("x", x))
y = int (request.args.get ("y", y))
res = add.apply_async ((x, y))
konteks = {"id": res.task_id, "x": x, "y": y}
hasil = "tambah ((x) {}, (y) {})". format (konteks ['x'], konteks ['y'])
goto = "{}". format (konteks ['id'])
return jsonify (result = result, goto = goto)

@ app.route ("/ tes / hasil /")
def show_result (task_id):
retval = add.AsyncResult (task_id) .get (waktu tunggu = 5.0)
kembali repr (retval + "mmk")

jika __name__ == "__main__":
port = int (environment.get ("PORT", 5000))
app.run (host = '0.0.0.0', port = port, debug = True)

Bahkan dengan seledri 4.1.1 saya masih mengalami hang saat broker (RabbitMQ) turun.

apa yang Anda hadapi di 4.2rc4? ini bukan di cabang 4.1.x.

Ini terdengar sangat mirip # 4627, yang masih menjadi masalah bahkan pada master.

terima kasih @clok

Jadi 4,2rc4 tidak akan berhasil? Atau bagaimana cara mendapatkan versi itu?

silakan coba versi itu dan laporkan

Saya melihat apa yang tampak seperti masalah yang sama. Saya menggunakan seledri 4.1.1 dan kombu 4.2.1.

Nanti: Sebenarnya dalam kasus saya, server - redis - harus aktif dan merespons. ketukan seledri tampaknya memicu kejadian dengan baik, tetapi metode penundaan panggilan hang selamanya. Akan terus menyelidiki mulai minggu depan kalau-kalau saya telah melakukan kesalahan lain.

ini harus diperbaiki di 4.2rc4, mau mencobanya? kami ingin tahu statusnya saat ini

Asif Saifuddin Auvi [email protected] menulis:

ini harus diperbaiki di 4.2rc4, mau mencobanya?

Akan melakukannya pada hari Selasa (Senin adalah hari libur umum di sini dan sudah

selesai untuk hari Jumat). Terima kasih atas tanggapan yang cepat.

Brian May [email protected]
https://linuxpenguins.xyz/brian/

Ok terima kasih

Lupakan, ternyata masalah saya berbeda. Saya tidak menginisialisasi Celery dengan benar dari Django, jadi secara diam-diam menggunakan amqp daripada redis, di mana ia diam-diam mencoba untuk menghubungkan ke soket AMQP yang sebenarnya tidak mendengarkan.

tolong buka masalah baru setelah mencoba seledri 4.2

Saya telah mencoba dengan:

celery==4.2.0
kombu==4.2.1

dan juga perlu menentukan broker_transport_options agar apply_async tidak diblokir selamanya saat Redis mati.

Masih mengalami masalah ini:

  • seledri: 4.2.1
  • kombu: 4.2.1
  • broker: rabbitmq

Proses utama ditutup dan tidak ada pengecualian yang dimunculkan.

Memperbarui:

diselesaikan dengan:

app.conf.broker_transport_options = {
'max_retries': 3,
'interval_start': 0,
'interval_step': 0,2,
'interval_max': 0,2,
}

Masih menjadi masalah jika broker berfungsi, lalu mati dan kami mengirimkan beberapa tugas tepat setelah saat ini:

seledri: 4.2.1
kombu: 4.2.1
broker: rabbitmq

Langkah-langkah untuk mereproduksi:

  1. Jalankan rabbitmq dan kirim tugas ke sana. Objek AsyncResult dikembalikan.
  2. Hentikan rabbitmq dan coba kirim tugas lagi. .delay () hang selamanya

Saya mencoba melakukan penggalian. Kesimpulan saya:
- Seledri meminta properti Connection default_channel .
- default_channel tahu tentang broker_transport_options dan panggilan -> ensure_connection() -> retry_over_time() dengan broker_transport_options.
Fungsi ini mencoba melakukan return fun(*args, **kwargs) dimana kesenangan bound method Connection.connect. Dan fungsi ini berhasil mengembalikan objek Connection /.
jadi default_channel mengembalikan self._default_channel dan seledri mencoba sesuatu dengannya.

Tetapi setelah serangkaian langkah pemrosesan on_task_call -> maybe_declare -> _imaybe_declare dari kombu.common dipanggil. Itu mengambil objek Connection dari Queue dan memanggil metode sure () nya. Tetapi tidak tahu tentang broker_transport_options jadi metode ensure dipanggil tanpa broker_transport_options dan menggunakan nilai default - max_retries=None yang berarti percobaan ulang tak terbatas :(
Saya telah menambahkan pemrosesan broker_transport_options di kombu.connection.Connection ensure() method sehingga menggunakan broker_transport_options setiap kali memeriksa koneksi ke broker tetapi saya tidak tahu apakah ini perbaikan yang tepat untuk masalah ini atau tidak.

seledri: 4.2.1
kombu: 4.2.1

Contoh minimal mereproduksi masalah:

from celery import Celery

celery_app = Celery('lol', broker='LOL_NOT_VALID')
celery_app.send_task('test')

Saya mendapat masalah serupa. Masalahnya adalah saya lupa menambahkan ini.

Kemudian Anda perlu mengimpor aplikasi ini ke modul proj / proj / __ init__.py Anda. Ini memastikan bahwa aplikasi dimuat ketika Django mulai sehingga dekorator @shared_task (disebutkan nanti) akan menggunakannya:

proj / proj / __ init__.py:

from __future__ import absolute_import, unicode_literals

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ('celery_app',)

Adakah yang bisa datang dengan solusi yang sesuai?

Saya pikir kami mengatasi ini dengan menyediakan opsi transportasi yang hanya mencoba ulang beberapa kali (secara default Celery mencoba selamanya):

# Try 5 times. Initially try again immediately, then add 0.5 seconds for each
# subsequent try (with a maximum of 3 seconds). This comes out to roughly 3
# seconds of total delay (0, 0.5, 1, 1.5).
CELERY_BROKER_TRANSPORT_OPTIONS = {
    'max_retries': 4,
    'interval_start': 0,
    'interval_step': 0.5,
    'interval_max': 3,
}

Saya dapat mencoba mencari informasi lebih lanjut jika tidak jelas.

BROKER_TRANSPORT_OPTIONS memperbaikinya untuk saya, bukan CELERY_BROKER_TRANSPORT_OPTIONS. Menggunakan seledri 4.2.1, kombu 4.2.1 dan django 1.8.

Apakah Anda menggunakan BROKER_TRANSPORT_OPTIONS vs. CELERY_BROKER_TRANSPORT_OPTIONS vs. broker_transport_options dll. Tergantung pada bagaimana Anda mengkonfigurasi aplikasi Celery Anda. Senang itu berhasil untuk Anda! 👍

Ini sangat bodoh, tetapi bagi saya yang menggunakan Flask, saya telah melakukan ini:

app.py

    ...
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
    celery.conf.update(app.config)
    ...

task.py:

import celery

@celery.task(bind=True)
def my_task():
    ...

Saya perlu melakukan:
app.py

    ...
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
    celery.conf.update(app.config)

    app.celery = celery
    ...

task.py

from flask import current_app

celery = current_app.celery

@celery.task(bind=True)
def my_task():
    ...

Ini memecahkan masalah saya tentang gantung tanpa batas, tetapi sejujurnya saya berpikir bahwa mengingat pengaturan saya kacau, akan lebih baik seledri memberi tahu saya itu.

Solusi sebenarnya ada di sini: https://github.com/celery/celery/issues/4627#issuecomment -396907957

Sekadar memberi tahu Anda bahwa saya memiliki perilaku default yang sama dengan:

  • Seledri 4.3.0
  • Kombu 4.6.4

Pengaturan CELERY_BROKER_TRANSPORT_OPTIONS berfungsi dengan baik.

Sama di sini saya menggunakan:

  • Seledri 4.4.2
  • Kombu 4.6.8

Apakah masalahnya muncul kembali?

@ jreed1701 Saya melihat masalah yang sama dengan Anda, dan kami mendapatkan traceback yang sama seperti yang disebutkan di utas ini, tetapi menurut saya bukan karena alasan yang sama. Bagi saya, apply_async dan delay menunjukkan perilaku ini untuk fungsi @shared_task tetapi tidak untuk fungsi @app.task . Traceback yang sama seperti yang dibahas di utas ini, tetapi tidak ada hubungannya dengan status broker.

Hai @chrisconlan , lucu Anda menunjukkan ini. Saya sampai pada kesimpulan ini kemarin ketika saya memodifikasi perangkat lunak saya untuk menggunakan pola pabrik / ekstensi. Saya menggunakan metode @shared_task yang Anda tunjukkan, tetapi pindah ke metode @ app.task ketika saya mengubah ke pola baru. Perubahan dalam implementasi di pihak saya bergerak, saya membuat instance seledri tanpa konteks aplikasi / tugas, dan menggunakannya untuk menghiasi fungsi tugas saya pada waktu impor. Kemudian pada saat runtime saya mengganti konteks tugas objek / instance seledri yang sama dengan konteks aplikasi yang sesuai, memperbaruinya dengan input konfigurasi, dan kemudian menjalankannya. Perilaku hangup yang tidak terbatas berhenti dan apply_async dijalankan meskipun saya tidak memiliki pialang yang terhubung. Saya bertanya-tanya bagaimana dengan mengubah konteks membebaskan semua ini?

@ jreed1701 Tidak yakin tentang Anda, tapi saya menggunakan celery sampai django . Saya tidak menyimpang terlalu jauh dari penggunaan yang terdokumentasi di bagian depan itu karena saya tidak ingin merusak penerapan. Seperti yang Anda katakan, saya tidak yakin ada sesuatu yang pada dasarnya rusak tentang seledri di sini, tapi menurut saya dokumen django-seledri memerlukan pembaruan untuk memastikan semua orang mengikuti praktik terbaik.

@ jreed1701 Anda memberi saya petunjuk bagus tentang konteks seledri. Saya memperbaikinya dengan yang berikut ...

Dari terminal, jalankan python manage.py shell untuk menginisialisasi shell Django, lalu ...

# Make sure to initialize celery context
from django_project.celery import app

# Now you can import shared tasks
from other_app.tasks import the_shared_task

# Now the shared task won't hang
the_shared_task.delay()

Catatan untuk pengelola saya dan @ jreed1701 sampai di sini karena .delay atau .apply_async dicoba pada fungsi yang didekorasi tanpa konteks seledri, tetapi itu berada di luar cakupan masalah ini.

Saya akan membuat masalah tentang ini.

@chrisconlan Saya menggunakan Flask. Belum sempat ke Django.
Lihat Anda berbasis di MD! Saya dekat :)
Senang Anda melakukannya! Setuju, ini adalah perbaikan dokumentasi, tetapi juga periksa log masalah Kombu. Saya bingung membaca apakah itu masalah komunikasi Kombu dengan Redis atau tidak selain apa yang kami alami dengan Celery.

@ jreed701 Pria yang mengagumkan. Kirimi saya email dan tetap berhubungan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat