Celery: Рабочий Celery использует 100% ЦП вокруг epoll с prefork + SQS, но все еще использует задачи

Созданный на 22 янв. 2019  ·  45Комментарии  ·  Источник: celery/celery

Окружающая среда и настройки

Версия сельдерея :

Отчет :

software -> celery:4.2.0 (windowlicker) kombu:4.2.2-post1 py:3.6.6
            billiard:3.5.0.5 sqs:N/A
platform -> system:Linux arch:64bit, ELF
            kernel version:3.13.0-139-generic imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:sqs results:disabled

broker_url: 'sqs://localhost//'
include: [...]
worker_hijack_root_logger: False
task_serializer: 'json'
result_expires: 3600
accept_content: ['json']
result_serializer: 'json'
timezone: 'Europe/Berlin'
enable_utc: True
broker_transport_options: {
    'polling_interval': 1,
    'region': 'eu-west-1',
    'visibility_timeout': 10860}
task_ignore_result: True
task_acks_late: True
worker_prefetch_multiplier: 1
worker_max_tasks_per_child: 10
worker_pool: 'celery.concurrency.prefork:TaskPool'
task_time_limit: 10800
worker_enable_remote_control: False
worker_send_task_events: False
task_default_queue: 'celery'

Действия по воспроизведению

Обязательные зависимости

  • Минимальная версия Python : 3.6
  • Минимальная версия брокера : н / д или неизвестно
  • Версия серверной части с минимальным результатом : N / A или Unknown
  • Минимальная версия ОС и / или ядра :: N / A или Unknown

Минимально воспроизводимый тестовый пример

  • отредактируйте kombu/asynchronous/hub.py чтобы раскомментировать операторы печати в create_loop
  • запустить ~ 500 очень коротких задач для выполнения (поэтому основной рабочий процесс должен выйти и запустить новых рабочих из-за worker_max_tasks_per_child: 10 )

Ожидаемое поведение

После выполнения 500 задач основной рабочий процесс должен довести использование ЦП до ~ 1%. Операторы печати следует запускать не так часто из-за вызова sleep в конце метода create_loop .

Фактическое поведение

Основной рабочий загружает ЦП на постоянные 100%, а вывод консоли (из-за раскомментированного оператора печати) заполняется большим количеством (почти каждую микросекунду) из них (никогда не останавливается):

WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="12">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="13">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="14">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="15">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="16">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,502; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,503; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="17">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="18">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="19">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="20">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="21">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,505; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,506; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="22">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="23">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="24">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="25">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="26">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,507; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,508; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="27">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="28">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="29">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="30">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="31">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,509; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,510; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="32">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="33">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="34">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="35">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="36">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,511; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,513; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)

кроме того, strace полон этих:

epoll_ctl(9, EPOLL_CTL_DEL, 43, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRNORM|EPOLLERR|0x36f80000, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)
epoll_ctl(9, EPOLL_CTL_DEL, 46, {EPOLLERR|0x36ca1800, {u32=32711, u64=4295000007}}) = -1 EBADF (Bad file descriptor)
epoll_ctl(9, EPOLL_CTL_DEL, 44, {EPOLLERR|0x36ca1800, {u32=32711, u64=4295000007}}) = -1 EBADF (Bad file descriptor)
epoll_ctl(9, EPOLL_CTL_DEL, 19, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRBAND|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT|0x1a0fd820, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)
epoll_ctl(9, EPOLL_CTL_DEL, 23, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRBAND|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT|0x1a0fd820, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)
epoll_ctl(9, EPOLL_CTL_DEL, 27, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRBAND|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT|0x1a0fd820, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)
epoll_ctl(9, EPOLL_CTL_DEL, 11, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRBAND|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT|0x1a0fd820, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)
epoll_ctl(9, EPOLL_CTL_DEL, 15, {EPOLLRDNORM|EPOLLRDBAND|EPOLLWRBAND|EPOLLERR|EPOLLRDHUP|EPOLLONESHOT|0x1a0fd820, {u32=32711, u64=4295000007}}) = -1 ENOENT (No such file or directory)

Были похожие проблемы, но в большинстве случаев они были связаны с redis . Еще одно отличие от прежних задач состоит в том, что рабочий по-прежнему распределяет / потребляет новые задачи. В общем, все работает, но ЦП не работает на холостом ходу, а вместо этого забивает цикл. Я могу постоянно воспроизводить это с помощью моего SQS worker (concurrency 5 btw), поэтому, пожалуйста, дайте мне знать, какую еще информацию я могу собрать, чтобы выследить эту проблему. СПАСИБО!

Amazon SQS Broker Prefork Workers Pool Bug Report Critical

Самый полезный комментарий

Итак, on_inqueue_close вызывается успешно, но каждый раз, хотя бы один раз после этого, вызывается on_process_alive и строка https://github.com/celery/celery/blob/e7ae4290ef044de4ead45314d8fe2b190e497322/celery /concurrency/asynpool.py#L1083 добавляет 8 .

Все 45 Комментарий

И снова здравствуйте. Последние обновления, похоже, не помогают. В настоящее время мы находимся на

billiard==3.6.0.0
celery==4.3.0
kombu==4.5.0
django-celery-beat==1.4.0

Вся проблема очень похожа на https://github.com/celery/celery/issues/1845, только мы используем SQS в качестве брокера. Проблема, по-видимому, вызвана worker_max_tasks_per_child , потому что основной процесс переходит на 100% ЦП, как только ему приходится перезапускать свои дочерние элементы из-за этого ограничения задачи.

Я провел дополнительное расследование с python gdb . Вот обратная трассировка Python через py-bt :

Traceback (most recent call first):
  <built-in method unregister of select.epoll object at remote 0x7fadac9b8600>
  File "$ENV/lib/python3.6/site-packages/kombu/utils/eventio.py", line 75, in unregister
    self._epoll.unregister(fd)
  File "$ENV/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 243, in _unregister
    self.poller.unregister(fd)
  File "$ENV/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 160, in _remove_from_loop
    self._unregister(fd)
  File "$ENV/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 181, in remove
    self._remove_from_loop(fd)
  File "$ENV/lib/python3.6/site-packages/celery/concurrency/asynpool.py", line 721, in <listcomp>
    [hub_remove(fd) for fd in diff(active_writes)]
  File "$ENV/lib/python3.6/site-packages/celery/concurrency/asynpool.py", line 721, in on_poll_start
    [hub_remove(fd) for fd in diff(active_writes)]
  File "$ENV/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 295, in create_loop
    tick_callback()
  <built-in method next of module object at remote 0x7fadcaf37638>
  File "$ENV/lib/python3.6/site-packages/celery/worker/loops.py", line 91, in asynloop
    next(loop)
  File "$ENV/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 596, in start
    c.loop(*c.loop_args())
  File "$ENV/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
    step.start(parent)
  File "$ENV/lib/python3.6/site-packages/celery/worker/consumer/consumer.py", line 318, in start
    blueprint.start(self)
  File "$ENV/lib/python3.6/site-packages/celery/bootsteps.py", line 369, in start
    return self.obj.start()
  File "$ENV/lib/python3.6/site-packages/celery/bootsteps.py", line 119, in start
    step.start(parent)
  File "$ENV/lib/python3.6/site-packages/celery/worker/worker.py", line 205, in start
    self.blueprint.start(self)
  File "$ENV/lib/python3.6/site-packages/celery/bin/worker.py", line 258, in run
    worker.start()
  File "$ENV/lib/python3.6/site-packages/celery/bin/base.py", line 252, in __call__
    ret = self.run(*args, **kwargs)
  File "$ENV/lib/python3.6/site-packages/celery/bin/worker.py", line 223, in run_from_argv
    return self(*args, **options)
  File "$ENV/lib/python3.6/site-packages/celery/bin/celery.py", line 420, in execute
    ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
  File "$ENV/lib/python3.6/site-packages/celery/bin/celery.py", line 488, in handle_argv
    return self.execute(command, argv)
  File "$ENV/lib/python3.6/site-packages/celery/bin/base.py", line 298, in execute_from_commandline
    return self.handle_argv(self.prog_name, argv[1:])
  File "$ENV/lib/python3.6/site-packages/celery/bin/celery.py", line 496, in execute_from_commandline
    super(CeleryCommand, self).execute_from_commandline(argv)))
  File "$ENV/lib/python3.6/site-packages/celery/bin/celery.py", line 322, in main
    cmd.execute_from_commandline(argv)
  File "$ENV/lib/python3.6/site-packages/celery/__main__.py", line 16, in main
    _main()
  File "$ENV/lib/python3.6/site-packages/celery/__main__.py", line 20, in <module>
    main()
  <built-in method exec of module object at remote 0x7fadcaf37638>
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)

Он не может выполнить self._epoll.unregister(fd) который хорошо соотносится с выходными данными strace я изначально опубликовал. Пройдя какое-то время, я попадаю в

 241        def _unregister(self, fd):
 242            try:
 243                self.poller.unregister(fd)
 244            except (AttributeError, KeyError, OSError):
>245                pass
 246    
 247        def close(self, *args):
 248            [self._unregister(fd) for fd in self.readers]
 249            self.readers.clear()
 250            [self._unregister(fd) for fd in self.writers]

поэтому unregister явно вызывает исключение.

Такие FD, как 27 , 11 , 15 являются каналами согласно /proc/$PID/fd . Там я могу получить FD каналов, и, согласно lsof | grep $PIPE_FD они представляют собой каналы между основным процессом и дочерними процессами:

15614 - это основной процесс, а 23179 например, дочерний процесс:

python3.6 15614              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 15614              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18344              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18344              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18344 18346        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18344 18346        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18368              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18368              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18368 18406        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18368 18406        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18369              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18369              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18369 18382        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18369 18382        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18370              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18370              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18370 18389        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18370 18389        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18371              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18371              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18371 18391        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18371 18391        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18372              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18372              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18372 18390        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18372 18390        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18373              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18373              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18373 18393        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18373 18393        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18375              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18375              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18375 18405        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18375 18405        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18376              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18376              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18376 18395        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18376 18395        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18378              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18378              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18378 18403        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18378 18403        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18380              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18380 18400        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18383              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18383              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18383 18404        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18383 18404        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18384              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18384              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18384 18424        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18384 18424        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18386              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18386              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18386 18420        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18386 18420        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18387              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18387              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18387 18423        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18387 18423        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18396              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18396              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18396 18426        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18396 18426        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18397              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18397              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18397 18422        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18397 18422        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18399              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18399              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 18399 18425        ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 18399 18425        ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 23122              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 23122              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe
python3.6 23179              ubuntu   39r     FIFO                0,8      0t0   49209822 pipe
python3.6 23179              ubuntu   40w     FIFO                0,8      0t0   49209822 pipe

Не зная кода kombu / billiard мне кажется, что дочерние процессы были успешно перезапущены, но каким-то образом основной процесс не осознает этого и продолжает попытки избавиться от старых, которых больше не существует.

Теперь вы подводите нас к выводу, который мы можем использовать для дальнейшей отладки.
Не могли бы вы изменить код, чтобы возникло исключение?

Если исключение жалуется на дескриптор файла, которого не существует, возможно, мы могли бы просто удалить его из списка читателей / авторов.

Так что, возможно, это исключение не следует молча пропускать и игнорировать.
Я собираюсь заставить Celery создавать журнал для этих сбоев.

Спасибо за вашу поддержку.

В комбу eventio.py мне пришлось изменить _epoll чтобы не проглатывать исключения (так что выше я, вероятно, действительно не столкнулся с исключением. Отладчик gnome, вероятно, просто ошибочно подумал, что он был на этом пути). с этим я мог бы сделать видимыми все повторяющиеся ошибки отмены регистрации:

    def unregister(self, fd):
        try:
            self._epoll.unregister(fd)
        except (socket.error, ValueError, KeyError, TypeError):
            raise
        except (IOError, OSError) as exc:
            raise
            if getattr(exc, 'errno', None) not in (errno.ENOENT, errno.EPERM):
                raise

чтобы в hub.py я мог сделать

    def _unregister(self, fd):
        try:
            self.poller.unregister(fd)
        except (AttributeError, KeyError, OSError):
            logger.exception("pls debug me, fd = %s", fd, extra={'fd_debug': fd})

со многими числами fd повторяющимися тысячи раз за секунды.

например, сообщение было pls debug me, fd = 56 с отслеживанием

Traceback (most recent call last):
  File ".../kombu/asynchronous/hub.py", line 243, in _unregister
    self.poller.unregister(fd)
  File ".../kombu/utils/eventio.py", line 78, in unregister
    self._epoll.unregister(fd)
FileNotFoundError: [Errno 2] No such file or directory

Модуль eventio специально игнорирует эти исключения и не вызывает их.

Мы можем либо:

  • Поднимите ошибку и убедитесь, что она также вызывается методом _unregister в модуле hub . В этом случае нам придется обрабатывать исключение всякий раз, когда вызывается _unregistered .
  • Проверяйте на каждой итерации цикла, какие файловые дескрипторы все еще живы, и удаляйте мертвые.

Не могли бы вы проверить, помогает ли # 5499 каким-либо образом?

Я это попробую.

К сожалению, это не помогает. В зависимости от настройки ( -Ofair vs. -Odefault ) рабочий по-прежнему сходит с ума от этих строк:

https://github.com/celery/celery/blob/master/celery/concurrency/asynpool.py#L735
https://github.com/celery/celery/blob/master/celery/concurrency/asynpool.py#L742

редактировать:
И в журналах не нахожу вхождений Encountered OSError while trying .

Не понимаю почему.
Если мы звоним hub.remove мы в конечном итоге звоним
hub._discard должен удалить дескриптор файла как для читателей, так и для писателей.
Если вы добавите туда точку останова, вы увидите, что fd удаляется?
Это опять где-то добавлено?

Итак, для случая -Ofair я изменил on_poll_start следующим образом:

            def on_poll_start():
                if outbound and len(busy_workers) < len(all_inqueues):
                    #  print('ALL: %r ACTIVE: %r' % (len(all_inqueues),
                    #                                len(active_writes)))
                    inactive = diff(active_writes)
                    [hub_add(fd, None, WRITE | ERR, consolidate=True)
                     for fd in inactive]
                else:
                    for fd in diff(active_writes):
                        aw = list(self._active_writes)
                        ai = list(self._all_inqueues)
                        result = hub_remove(fd)
                        extra = {
                            'fd': fd,
                            'result': repr(result),
                            'active_writes': aw,
                            'all_inqueues': ai,
                        }
                        logger.warn('removed fd %r result %r aw %r ai %r', fd, result, aw, ai, extra=extra)

Я регистрирую self._active_writes и self._all_inqueues , потому что diff(active_writes) кажется эквивалентным self._all_inqueues - self._active_writes .

Затем я запустил worker с --concurrency 1 и worker_max_tasks_per_child=10 и delay ed несколько задач, поэтому перезапуск должен начаться быстро.

Еще до перезапуска мои журналы были полны этого

removed fd 8 result None aw [] ai [8]
removed fd 8 result None aw [] ai [8]
removed fd 8 result None aw [] ai [8]

Частота и процессор не высоки пока. Но как только происходит перезапуск и больше не приходят задачи, это сходит с ума до 7000 логов за 5 секунд. От начала до конца fd 8 никогда не меняется. Мне кажется, что AsynPool._all_inqueues не очищается должным образом. Отладить on_inqueue_close сейчас.

Итак, on_inqueue_close вызывается успешно, но каждый раз, хотя бы один раз после этого, вызывается on_process_alive и строка https://github.com/celery/celery/blob/e7ae4290ef044de4ead45314d8fe2b190e497322/celery /concurrency/asynpool.py#L1083 добавляет 8 .

Значит, мы не удаляем процесс из пула после того, как воркер перезапустится?

Каждый раз, когда мы запускаем процесс, мы добавляем воркера в пул:

https://github.com/celery/billiard/blob/265f119ec8b2944e36e3b0810578b5d615e6f987/billiard/pool.py#L1102 -L1123

Я не вижу, чтобы кто-нибудь требовал _join_exited_workers() который должен очистить их из пула.

Не в этом ли проблема?
Не могли бы вы проверить?

Я больше в этом не уверен, если это главный виновник. Я имею в виду, в конце концов, какой-то код должен отвечать за частый вызов очистки. Итак, я переместил трассировку вверх, чтобы разместить некоторый вход в цикл хаба в https://github.com/celery/kombu/blob/master/kombu/asynchronous/hub.py#L301 :

…
poll_timeout = fire_timers(propagate=propagate) if scheduled else 1
logger.info('new loop run with poll_timeout %r', poll_timeout)
if readers or writers:
    …

Позвольте мне показать вам, как я сейчас анализирую это сообщение журнала с помощью кибаны:

Screenshot_20190521_102310

  1. Я запускаю 20 задач по 10 максимум задач на ребенка
  2. Рабочий работает над первыми 10 задачами
  3. Рабочий перезапускается с коротким периодом 100% ЦП и большим количеством циклов.
  4. Рабочий работает над 10 последними задачами
  5. Рабочий перезапустился снова из-за максимального количества задач на ребенка. петля сходит с ума навсегда
  6. Вручную перезапустил рабочий. новые задачи не поступают, и цикл работает с разумной скоростью

Мне кажется, что что-то закорачивает цикл, и поэтому процессор переходит на 100%. Однако я понятия не имею, как эффективно отлаживать это дальше. Внутри так много предложений except и continue .

Не могли бы вы проверить?

Я не уверен, я понимаю. Вы хотите, чтобы я проверил, вызывается ли _join_exited_workers ?

Я проанализировал цикл с большим количеством операторов журнала, но все, что я могу сказать, это то, что большую часть времени он сталкивался с https://github.com/celery/kombu/blob/master/kombu/asynchronous/hub.py#L362 , что нормально, я думаю. Так что есть путь ко многим событиям, которые, кажется, не исчезнут. Как и в моем исходном посте:

WARNING:celery.redirected: [[[HUB]]]: (31)_event_process_exit(<Hub<strong i="7">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-32, started daemon)>)->R!, (20)on_result_readable(20)->R!, (34)_event_process_exit(<Hub<strong i="8">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-33, started daemon)>)->R!, (16)on_result_readable(16)->R!, (42)_event_process_exit(<Hub<strong i="9">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-34, started daemon)>)->R!, (24)on_result_readable(24)->R!, (53)_event_process_exit(<Hub<strong i="10">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-35, started daemon)>)->R!, (28)on_result_readable(28)->R!, (38)_event_process_exit(<Hub<strong i="11">@0x7fa987a1dc18</strong>: R:11 W:1>, <ForkProcess(ForkPoolWorker-36, started daemon)>)->R!, (12)on_result_readable(12)->R!, (45)on_readable(45)->R!, (45)on_writable(45)->W (2019-01-22 17:09:04,502; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)
WARNING:celery.redirected: [EVENTS]: (GONE)(48)->R, (GONE)(46)->R, (GONE)(44)->R! (2019-01-22 17:09:04,503; /home/ubuntu/.virtualenvs/unchained-worker3.6/src/celery/celery/utils/log.py:235)

Не могли бы вы проверить?

Я не уверен, я понимаю. Вы хотите, чтобы я проверил, вызывается ли _join_exited_workers ?

Да.
Может быть, добавить его и в начало цикла ...

Возможно ли, что в коде сельдерея уже есть комментарий, описывающий, что это могло произойти?

https://github.com/celery/celery/pull/5499/files#diff -c80fd9891efbbe68275a133d83cd22a4L456

    def _track_child_process(self, proc, hub):
        try:
            fd = proc._sentinel_poll
        except AttributeError:
            # we need to duplicate the fd here to carefully
            # control when the fd is removed from the process table,
            # as once the original fd is closed we cannot unregister
            # the fd from epoll(7) anymore, causing a 100% CPU poll loop.
            fd = proc._sentinel_poll = os.dup(proc._popen.sentinel)
        hub.add_reader(fd, self._event_process_exit, hub, proc)

Можете ли вы поделиться командой, которую вы запускаете @tuky для запуска сельдерея для этого отчета об ошибке? Возможно ли, что вы используете сердцебиение и не ведете - без общения - без сплетен?

Это была оригинальная фиксация, когда Ask пытался решить эту проблему для Celery 3, и он связал ее с epoll https://github.com/celery/celery/commit/ca57e722b25f8fca817084ec7562be3698c7ee02
и в коммите, о котором он упоминает, он обнаружил, что:

1) epoll_wait всегда возвращал состояние ошибки для fd канала Popen.
2) рабочий пытался отменить регистрацию этого fd на epoll, но
3) epoll.unregister отказался сделать это, выдав IOError (ENOENT)
ошибка.

Оказывается, это причуда epoll, и решение состоит в том, чтобы продублировать канал fd.
чтобы мы могли тщательно контролировать, когда он удаляется из процесса
таблица дескрипторов файлов.

Можете ли вы поделиться командой, которую вы запускаете @tuky для запуска сельдерея для этого отчета об ошибке?

Спасибо за расследование. Держи @matteius

python3.6 -m celery worker --loglevel=INFO --app=foo.worker -Ofair --concurrency=1 --logfile=/var/log/celery/foo.log --pidfile=/var/run/celery/foo.pid --hostname=foo<strong i="9">@bar</strong>

Можете ли вы сказать по этому поводу, использую ли я сердцебиение? По крайней мере, я не использую опцию --without-heartbeat . Стоит ли пробовать работать со всеми тремя вариантами --without-gossip --without-mingle --without-heartbeat ?

@tuky Ради науки стоило бы попробовать, хотя я подозреваю, что теперь, прочитав больше кода вчера вечером, проблема связана с поддержкой SQS и / или с тем, как он использует epoll. Я начал работать над изменением кода, аналогичным моему другому PR, который был объединен и связан здесь ранее, который также попытался бы принять эти другие точки файловых дескрипторов в пуле Async - надеясь потратить на это больше времени позже сегодня, но это будет нужно время, чтобы понять все, что происходит, так как их много. Есть ли шанс, что вы сможете поработать над модульным тестом в стиле TDD, который каким-то образом воспроизводит проблему ENOENT?

Также обратите внимание (раз уж вы спросили): мой опыт работы с сердцебиением находится на брокере RabbitMQ AMQP, где я считаю, что брокер настроен на отправку тепловых импульсов, и в этом случае вы должны, чтобы рабочий ответил в течение интервала тепловых импульсов, или он разрывает соединение - мы работаем таким образом и с флагами -without-gossip --without-mingle, потому что эти протоколы были слишком болтливыми, что приводило к некоторым ошибкам производительности в Celery 3.

Я пробовал работать с --without-gossip --without-mingle --without-heartbeat , но безуспешно. @thedrow Я также наконец _join_exited_workers действительно время от времени вызывается.

Я попытаюсь провести тест, аналогичный тесту https://github.com/celery/celery/pull/5499/files , но, вероятно, потерплю неудачу, потому что мне известно слишком мало деталей:

@tuky Хорошо, я подумал, что дам вам возможность написать этот модульный тест и сосредоточиться больше на изменении кода и рефакторинге, поскольку он на вид странно похож на другой PR, который был связан. Итак, теперь у меня есть этот новый PR, специально для этот билет - возможно, вы можете попробовать эти изменения, чтобы увидеть, решит ли это ошибку или нет, и мы сможем продолжить работу над тестами. Я планирую не писать модульные тесты в эти выходные, так как это было сверх того, что я думал, что смогу даже добраться: https://github.com/celery/celery/pull/5604/files

Усилия очень ценятся. Однако в нашем случае это не помогает. В моих журналах не встречается Encountered OSError when accessing fd . Только когда я деактивирую проглатывание исключения в https://github.com/celery/kombu/blob/master/kombu/asynchronous/hub.py#L245 и https://github.com/celery/kombu/blob/master/ kombu / utils / eventio.py # L79 , я получаю это сразу после запуска

Traceback (most recent call last):
  File "venv/src/celery/celery/concurrency/asynpool.py", line 240, in iterate_file_descriptors_safely
    hub_method(fd, *hub_args, **hub_kwargs)
  File "venv/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 181, in remove
    self._remove_from_loop(fd)
  File "venv/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 160, in _remove_from_loop
    self._unregister(fd)
  File "venv/lib/python3.6/site-packages/kombu/asynchronous/hub.py", line 243, in _unregister
    self.poller.unregister(fd)
  File "venv/lib/python3.6/site-packages/kombu/utils/eventio.py", line 75, in unregister
    self._epoll.unregister(fd)
FileNotFoundError: [Errno 2] No such file or directory

Это устраняет 100% -ный цикл ЦП, но может быть использован только один, а затем он мертв.

@tuky Похоже, что kombu также отслеживает те же самые FD в асинхронном концентраторе, и метод envtio _epoll там может не улавливать все соответствующие исключения. Я могу попытаться сделать нечто подобное в комбу, чтобы сделать этот код более безопасным, или, по крайней мере, регистрировать предупреждения, а не просто передавать. В вашем случае, хотя вы говорите, что он потребил 1, а затем умер - ну, это ошибка FileNotFoundError, а соединение является дескриптором файла, поэтому что-то закрыло или удалило временный дескриптор файла после первого подключения. Это FD подключение к SQS? Есть ли способ указать постоянные соединения с этой службой aws?

Странно, что вы получаете FileNotFoundError, вызванный этим iterate_file_descriptors_safely, хотя на самом деле это одно из двух исключений, которые он пытается поймать. Если бы он поймал его, он бы напечатал «Encountered OSError при доступе к fd», который, как вы сказали, отсутствует, но тогда есть трассировка FileNotFoundError

Странно, что вы получаете FileNotFoundError, вызванный этим iterate_file_descriptors_safely, хотя на самом деле это одно из двух исключений, которые он пытается поймать. Если бы он поймал, он бы напечатал "Encountered OSError при доступе к fd"

Простите, я вас запутал. К опубликованной мной трассировке было прикреплено сообщение Encountered OSError when accessing fd . Я скопировал трассировку из кибаны, и сообщение о том, что есть поле, отделенное от трассировки.

Это FD подключение к SQS?

Не знаю, как я мог это узнать / отладить. У вас есть команда, которую я мог бы запустить в своей ОС или в оболочке Python или что-то в этом роде?

Есть ли способ указать постоянные соединения с этой службой aws?

глядя на https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#configuration, действительно есть вариант tcp_keepalive . я попробую это.

Это FD подключение к SQS?

Не знаю, как я мог это узнать / отладить. У вас есть команда, которую я мог бы запустить в своей ОС или в оболочке Python или что-то в этом роде?

через

ls -l /proc/4460/fd/

я получил

8 -> pipe:[163974]

а затем с

lsof | grep 163974

я получил

python3.6  4460             ubuntu    7r     FIFO   0,10      0t0 163974 pipe
python3.6  4460             ubuntu    8w     FIFO   0,10      0t0 163974 pipe
python3.6  4530             ubuntu    7r     FIFO   0,10      0t0 163974 pipe

где 4460 - это pid родительского процесса, а 4530 - дочерний. поэтому fd - это канал от родительского к дочернему процессу. Мне интересно, почему родитель вызывает unregister в самом начале, когда еще не было обработано задач, вообще: confused:

Читая https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/, я исследовал немного дальше и решил, что используемое ядро ​​Linux может быть слишком старым. У нас была версия 4.4, в которой еще нет функции EPOLLEXCLUSIVE . Но по умолчанию он все равно не используется. Я обновил ядро ​​до 4.5 и активировал флаг при вызове _epoll.register , но это не сработало. На данный момент мы используем poll вместо epoll путем взлома

import select
del select.epoll

С poll я вообще не могу воспроизвести проблему 100% CPU. С SQS в качестве службы очереди также кажется ненужной оптимизацией использование epoll over poll . Не могли бы вы добавить параметр, при котором _get_poller предпочитает poll epoll ?

Это настораживает, поскольку все реализации цикла событий, включая gevent, используют epoll.
@jamadden На этом этапе я хотел бы привлечь вас к обсуждению, поскольку вы являетесь основным сопровождающим gevent.
Есть ли ошибка в gevent, которая может вызвать это?

Читая https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/, я исследовал немного дальше и решил, что используемое ядро ​​Linux может быть слишком старым. У нас была версия 4.4, в которой еще нет функции EPOLLEXCLUSIVE . Но по умолчанию он все равно не используется. Я обновил ядро ​​до 4.5 и активировал флаг при вызове _epoll.register , но это не сработало. На данный момент мы используем poll вместо epoll путем взлома

import select
del select.epoll

С poll я вообще не могу воспроизвести проблему 100% CPU. С SQS в качестве службы очереди также кажется ненужной оптимизацией использование epoll over poll . Не могли бы вы добавить параметр, при котором _get_poller предпочитает poll epoll ?

Я бы предпочел не раскрывать эти детали пользователям. Нам нужно найти правильное решение и внедрить его.

@tuky Я чувствую, что мы действительно к приближаемся, и сегодня я снова вернулся к этому разговору и понял, что то, что вы сказали 20 мая, теперь стало более ясным. Таким образом, канал FD между основным процессом сельдерея и дочерними элементами в этой конфигурации прерывается, когда достигается максимальное количество задач для каждого дочернего элемента. Он удаляет дескриптор файла из списка, но создается новый исполнитель с тем же дескриптором файла, добавленным обратно в список. Я пытался больше прочитать код биллиарда, чтобы увидеть, как реализована функция maxtasks.

Итак, on_inqueue_close вызывается успешно, но каждый раз, хотя бы один раз после этого, вызывается on_process_alive и строка

https://github.com/celery/celery/blob/e7ae4290ef044de4ead45314d8fe2b190e497322/celery/concurrency/asynpool.py#L1083

добавляет туда 8 .

Ясно, что новый рабочий, который запускается для замены старого, вызывает on_process_alive (как вы заявили) и добавляет обратно старый файловый дескриптор (который больше не существует), а затем позже в цикле он не может общаться с новым рабочим и ничего не перезапускает его, потому что он больше не получает задач.

Это место в Billiard - это то место, где создается рабочий процесс с использованием ранее известных FD в _create_worker_process: https://github.com/celery/billiard/blob/master/billiard/pool.py#L1143

Я также нашел это в Billiard, где реализованы maxtasks: https://github.com/celery/billiard/blob/master/billiard/pool.py#L389

Я сомневаюсь в порядке методов, вызываемых в методе Worker __call__: https://github.com/celery/billiard/blob/master/billiard/pool.py#L288
Сначала он вызывает self._make_child_methods () с использованием self.inq и self.synq перед вызовом self.after_fork (), который может вызвать закрытие для авторов self.inq и self.synq ... не говоря, что это помешает ему добавить старый FD вернулся, но он заставляет меня чесать голову, хотя и является полностью спекулятивным.

Затем я вернулся к тому, что @thedrow упомянул ранее в вопросе о том, что вызывается _join_exited_workers, и обнаружил, что он вызывается из _maintain_pool в биллиарде, который называется двумя точками, один находится в методе тела обработчика рабочего. Он вызывается, когда процесс _state находится в состоянии RUN с периодическим сном на 0,8 секунды, но я подозреваю, что _state мог измениться, когда рабочий возвращает состояние EX_RECYCLE из __call__. Кроме того, единственное другое место, которое вызывает его, - это общедоступный метод maintain_pool (который вызывается из AsyncPool сельдерея на _event_process_exit), но если я прав насчет переменной _state в то время, он на самом деле не будет вызывать _maintain_pool, потому что keep_pool проверяет, является ли self ._worker_handler._state == RUN и self._state == RUN перед этим, так что, возможно, этот важный фрагмент кода не вызывается.

Таким образом, хотя точное исправление повторяющегося файлового дескриптора в списке неизвестно, ясно, что старый FD снова добавляется в тестовые выходные данные Tuky. Интересно, если код выхода называется EX_RECYCLE, если логика Celery / Billiard / Kombu намеревается повторно использовать каналы файловых дескрипторов или нет - возможно, они нарушают реализацию SQS при перезапуске рабочего? Возможно, это ошибка не только для SQS, как вы упомянули некоторые другие отчеты, в которых упоминается Redis, и я был бы готов в это поверить - за исключением того, что мы используем максимальное количество задач на ребенка с бэкэндом AsyncPool и amqp, и очень редко рабочие получают вот так зависает при перезапуске задачи, хотя я могу указать несколько раз, что могло быть именно тем, что происходило. Если дескриптор файла исчез, и он должен быть там, чтобы перезапуск работал, это могло бы объяснить это. Другой мой PR не делает ничего для воссоздания плохих FD, он просто добавляет некоторые проверки безопасности в местах, где мы идем, чтобы использовать те, о которых мы уже знаем.

Было бы здорово, если бы у меня был способ воспроизвести это локально. Даже если мне нужно настроить песочницу AWS SQS для работы, было бы полезно иметь некоторые образцы конфигурации и задач для запуска без особой настройки. Возможно, это не обязательно, но, возможно, это поможет мне задавать меньше вопросов.

Было бы здорово, если бы у меня был способ воспроизвести это локально. Даже если мне нужно настроить песочницу AWS SQS для работы, было бы полезно иметь некоторые образцы конфигурации и задач для запуска без особой настройки.

Это было бы здорово, спасибо за ваше дальнейшее расследование. Я рассмотрю создание репродуктивного репо и предоставлю вам некоторые учетные данные SQS для очереди.

Я пригласил вас @matteius @thedrow и @auvipy в частное репо, которое готово к использованию, включая учетные данные, для очереди SQS. Спасибо, надеюсь, вы сможете воспроизвести проблему с этим.

Это уже исправлено? Есть ли какое-то решение для этого?

Я думаю, что у меня такая же / аналогичная проблема без "worker_max_tasks_per_child". Кажется, что труба между рабочими пропала и застряла.

Я запускаю python3.7-alpine с:

celery[sqs]==4.4.0rc4 
kombu==4.6.6

Это простая задача, у которой есть единственный параметр - UUID. max_retries установлен на None и retry_backoff = 2 с task_acks_late = True и task_acks_on_failure_or_timeout = False.

В моем случае задача всегда вызывает исключение, чтобы оставить сообщение в очереди для целей тестирования.

/code # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 {start-celery.sh} /bin/sh /code/docker/start-celery.sh
    8 root      0:01 {celery} /usr/local/bin/python /usr/local/bin/celery -A my_module worker -l debug -c1
   11 root      0:00 {celery} /usr/local/bin/python /usr/local/bin/celery -A my_module worker -l debug -c1
$ strace -fp 11 -s 10000
strace: Process 11 attached
read(8, 
$ ls -l /proc/11/fd/8
lr-x------    1 root     root            64 Nov 30 04:10 /proc/11/fd/8 -> pipe:[23027111]
# (find /proc -type l | xargs ls -l | fgrep 'pipe:[23027111]') 2>/dev/null
lr-x------    1 root     root            64 Nov 30 04:10 /proc/11/fd/8 -> pipe:[23027111]
lr-x------    1 root     root            64 Nov 30 04:10 /proc/11/task/11/fd/8 -> pipe:[23027111]
lr-x------    1 root     root            64 Nov 30 04:10 /proc/8/fd/8 -> pipe:[23027111]
l-wx------    1 root     root            64 Nov 30 04:10 /proc/8/fd/9 -> pipe:[23027111]
lr-x------    1 root     root            64 Nov 30 04:10 /proc/8/task/8/fd/8 -> pipe:[23027111]
l-wx------    1 root     root            64 Nov 30 04:10 /proc/8/task/8/fd/9 -> pipe:[23027111]
$ strace -fp 8 -s 10000
# Normal messages
epoll_ctl(7, EPOLL_CTL_ADD, 10, {EPOLLIN|EPOLLERR|EPOLLHUP, {u32=10, u64=139878494896138}}) = -1 EEXIST (File exists)
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3815615000
epoll_ctl(7, EPOLL_CTL_DEL, 9, 0x7ffdfeee653c) = -1 ENOENT (No such file or directory)
epoll_pwait(7, [], 1023, 666, NULL, 8)  = 0
epoll_ctl(7, EPOLL_CTL_DEL, 9, 0x7ffdfeee653c) = -1 ENOENT (No such file or directory)
sysinfo({uptime=58265, loads=[112224, 126656, 139264], totalram=24973213696, freeram=880857088, sharedram=1203023872, bufferram=2054041600, totalswap=8258580480, freeswap=7548694528, procs=2268, totalhigh=0, freehigh=0, mem_unit=1}) = 0
epoll_pwait(7, [], 1023, 67, NULL, 8)   = 0
epoll_ctl(7, EPOLL_CTL_DEL, 9, 0x7ffdfeee653c) = -1 ENOENT (No such file or directory)
epoll_pwait(7, [], 1023, 898, NULL, 8)  = 0
epoll_ctl(7, EPOLL_CTL_DEL, 9, 0x7ffdfeee653c) = -1 ENOENT (No such file or directory)
wait4(11, 0x7ffdfeee5e34, WNOHANG, NULL) = 0
epoll_pwait(7, [], 1023, 100, NULL, 8)  = 0
epoll_ctl(7, EPOLL_CTL_DEL, 9, 0x7ffdfeee653c) = -1 ENOENT (No such file or directory)
... same message

(как вы можете видеть здесь, сообщение повторяется бесконечно.)

@tuky Я имел дело с аналогичной настройкой: сельдерей с SQS в качестве брокера. Мы замечаем, что когда воркер завершает работу, новый воркер подключается к сети, но загрузка ЦП увеличивается до 100%.

После множества проб и ошибок понижение версии python сработало, и скачки производительности ЦП исчезли. (Базовый образ докера изменен с python:3.6 (последний?) На python:3.6.8 ). Я понимаю, что это не исправление, просто выложу его здесь на случай, если это кому-то поможет, или в самом расследовании.

@tuky Я имел дело с аналогичной настройкой: сельдерей с SQS в качестве брокера. Мы замечаем, что когда воркер завершает работу, новый воркер подключается к сети, но загрузка ЦП увеличивается до 100%.

После множества проб и ошибок понижение версии python сработало, и скачки производительности ЦП исчезли. (Базовый образ докера изменен с python:3.6 (последний?) На python:3.6.8 ). Я понимаю, что это не исправление, просто выложу его здесь на случай, если это кому-то поможет, или в самом расследовании.
Спасибо @ code-haven.
Изменилось ли что-то между 3.6.8 и 3.6.10, что могло бы повлиять на это?
Это путь к исследованию.

Я пробовал использовать python: 3.6.8-slim (docker) и celery == 4.2.2, но сельдерей по-прежнему работает с той же проблемой, когда рабочий перезагружается после использования max_task_per_child.

Инфраструктура PyPI начала замечать такое поведение при обновлении с python:3.7.3-slim-stretch до python:3.8.2-slim-buster в https://github.com/pypa/warehouse/pull/7828/files.

Мы используем SQS с --max-tasks-per-child .

С python:3.8.2-slim-buster , kombu==4.6.8 и pycurl==7.43.0.2 проблема всегда воспроизводится следующим образом:

  • инициализировать сельдерей с 1 рабочим и максимальным количеством задач на ребенка = 1
  • передать ему одну задачу, чтобы он воссоздал рабочего
  • подождите несколько минут

После некоторого изучения я вижу, что в моем случае файл, который нарушает работу epoll, является не конвейером между процессами, а сокетом для SQS. Так что, возможно, причина не связана с той, которая была у автора изначально.

Как я вижу, epoll ломается сразу после закрытия сокета SQS (а kombu не удаляет сокет из epoll, и, как мы все уже знаем, это неправильно ). Когда мы запускаем наш основной цикл, Pycurl устанавливает соединение Keepalive HTTPS с SQS. Затем Комбу устанавливает периодическую процедуру, которая отправляет http-запрос give me some new messages через это соединение. Но спустя несколько минут после старта локон внезапно решает закрыть его. Поэтому, когда Комбу восстанавливает контроль внутри обратного вызова CurlClient._handle_socket() , у него уже есть закрытый файловый дескриптор, который нельзя удалить из epoll.

Без --max-tasks-per-child он все равно работал бы - epoll содержит своего рода слабые ссылки на файловые объекты, поэтому, когда все сильные ссылки уничтожаются, файл также удаляется из epoll. Но в нашем случае новые воркеры порождаются путем разветвления из основного процесса, что означает, что воркер получает все открытые файловые дескрипторы из основного.

Я столкнулся с этой проблемой около года назад, и мне удалось обойти ее чрезвычайно глупым, но чрезвычайно эффективным способом: просто перейдите к kombu.asynchronous.hub.Hub.create_loop() и вставьте sleep() после каждых N итераций 🙂

Я столкнулся с этой проблемой около года назад, и мне удалось обойти ее чрезвычайно глупым, но чрезвычайно эффективным способом: просто перейдите к kombu.asynchronous.hub.Hub.create_loop() и вставьте sleep() после каждых N итераций слегка_smiling_face

не могли бы вы отправить пиар о комбу?

Инфраструктура PyPI начала замечать такое поведение при обновлении с python:3.7.3-slim-stretch до python:3.8.2-slim-buster в https://github.com/pypa/warehouse/pull/7828/files.

Мы используем SQS с --max-tasks-per-child .

не могли бы вы проверить предлагаемое исправление в комбу? https://github.com/celery/kombu/pull/1189

это захватывающие новости. проверим это в следующем месяце, исправит ли он все и для нас.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги