انتقل إلى المحتوى الرئيسي

وظيفة Odoo Cron متوقفة عن العمل إلى الأبد — كيفية إصلاحها بسرعة

وظيفة Odoo cron عالقة في حالة التشغيل في 17/18/19؟ قم بإعادة تعيين الصفوف المقفلة، والعثور على العامل المعلق، ومنع التكرار من خلال هذا الدليل الكامل في الإنتاج اليوم.

E
ECOSIRE Research and Development Team
|4 مايو 20267 دقائق قراءة1.3k كلمات|

هذه المقالة متاحة حاليًا باللغة الإنجليزية فقط. الترجمة قريبا.

You open Settings → Technical → Scheduled Actions and find a cron job stuck in "Running" state for hours or days. Other crons that should run after it have not. The log shows:

You open Settings → Technical → Scheduled Actions and find a cron job stuck in "Running" state for hours or days. Other crons that should run after it have not. The log shows:

INFO: Starting cron 'Backfill Customer Statistics'
... (no further output for hours)

This is one of the most operationally annoying Odoo bugs because it stops other automation silently. It bites Odoo 17.0/18.0/19.0 with the same root causes.

Quick Fix

Find the stuck cron and forcibly reset it:

-- Find stuck crons
SELECT id, cron_name, state, lastcall, write_date
FROM   ir_cron c
JOIN   ir_cron_progress p ON p.cron_id = c.id
WHERE  p.state = 'running'
  AND  p.write_date < now() - interval '1 hour';

-- Reset the lock
UPDATE ir_cron_progress
SET    state = 'failed', done = 0
WHERE  cron_id = <id> AND state = 'running';

Then restart the Odoo workers:

sudo systemctl restart odoo

Why This Happens

Odoo crons are dispatched by cron worker threads. Each running cron acquires an advisory lock in PostgreSQL plus a row in ir_cron_progress with state='running'. If the worker process dies (OOM, crash, network kill) without releasing the lock and updating the row, the cron appears "running" forever even though no process is actually executing.

The five common causes:

  1. Worker OOM-killed. A long-running cron consuming memory hits the worker limit and gets terminated by the OS. The advisory lock is released by PostgreSQL on connection close, but the ir_cron_progress row is never updated.
  2. Network blip between Odoo and PostgreSQL. The connection drops mid-cron. Same outcome — DB-level lock auto-released, app-level state stale.
  3. Manual kill -9 on the cron worker. Same.
  4. Truly long-running cron. The job is doing legitimate work for 6 hours. Not stuck — just slow. Distinguish via pg_stat_activity.
  5. Deadlock between the cron and another transaction. PostgreSQL detects and aborts one, but if the abort path missed updating ir_cron_progress, the row stays "running".

Step-by-Step Diagnosis

1. Check ir_cron_progress.

SELECT c.cron_name, p.state, p.done, p.write_date, p.cron_id
FROM   ir_cron_progress p
JOIN   ir_cron c ON c.id = p.cron_id
WHERE  p.state = 'running';

Note the write_date. If older than an hour for a cron that should run quickly, it is stuck.

2. Confirm no actual worker is doing the work.

SELECT pid, application_name, state, query_start, query
FROM   pg_stat_activity
WHERE  application_name LIKE '%cron%' AND state = 'active';

Empty result for the stuck cron = no worker is running it. The "running" flag is a lie.

3. Check OS-level processes.

ps -ef | grep odoo-bin | grep -v grep

If the cron worker count matches your --max-cron-threads config, no worker is hung — they are idle. The DB state is stale.

4. Check the log around when the cron started.

grep "Backfill Customer Statistics" /var/log/odoo/odoo.log

Look for the line where it started, then any error or signal afterward. OOM-kills often leave a single "Killed" line.

5. Check OS-level OOM history:

sudo dmesg | grep -i "killed process" | tail
sudo journalctl -k | grep "Out of memory" | tail

A recent kill matches the cron start time → root cause is OOM.

Permanent Fix

For stuck crons right now, reset the row:

UPDATE ir_cron_progress
SET    state = 'failed', done = 0
WHERE  cron_id = <id> AND state = 'running';

Then restart Odoo so the cron worker re-checks and picks up pending crons fresh.

For OOM-killed crons, raise memory limits or split the work:

# odoo.conf — for the cron worker pool
limit_memory_soft = 4294967296    # 4 GB before recycling
limit_memory_hard = 6442450944    # 6 GB before kill
limit_time_real_cron = 14400      # 4 hours max wallclock per cron
limit_time_cpu = 7200             # 2 hours max CPU per cron

For genuinely long jobs, batch:

@api.model
def cron_backfill_statistics(self, batch_size=1000):
    pending = self.search([('stats_done', '=', False)], limit=batch_size)
    if not pending:
        return
    pending._compute_statistics()
    pending.write({'stats_done': True})
    # Schedule next batch immediately
    self.env['ir.cron'].sudo().search([('cron_name', '=', 'Backfill Statistics')]).write({
        'nextcall': fields.Datetime.now(),
    })

This pattern processes 1000 records per cron run; the cron schedules itself again until done. Each run is short, OOM-resistant, and resumable.

For network/connection issues, harden the cron's transaction:

def cron_backfill(self):
    try:
        self._do_work()
    except Exception:
        self.env.cr.rollback()
        # Update the cron progress row even on failure
        self.env['ir.cron.progress'].search([('cron_id', '=', self.id)]).write({
            'state': 'failed',
        })
        raise

Add a watchdog cron that resets stuck rows automatically:

@api.model
def cron_reset_stuck_progress(self):
    self.env.cr.execute("""
        UPDATE ir_cron_progress
        SET    state = 'failed'
        WHERE  state = 'running'
          AND  write_date < now() - interval '2 hours'
    """)

Schedule this watchdog every 30 minutes. It self-heals stuck states.

How to Prevent It

  • Always batch long-running crons. Anything that processes more than 10K records should batch. Eliminates OOM and timeout failure modes.
  • Set limit_time_real_cron. A hard wallclock limit forces termination before infinite hangs. Set to 2x the longest-expected legitimate run.
  • Monitor ir_cron_progress. A simple query alert: any state='running' row older than 2x its expected duration. Catches stuck states immediately.
  • Memory limits with auto-restart. limit_memory_hard triggers a clean restart of the worker before OS OOM-kill. Cleaner failure mode.
  • Idempotent cron design. Crons should be safely re-runnable. If a cron is killed mid-flight, the next run should pick up where it left off without duplicating work. Use done=True flags or processed-since-X queries.
  • Watchdog cron. Self-heal stuck states automatically. Costs nothing, prevents permanent stalls.

Frequently Asked Questions

Can I disable a stuck cron?

Yes. Set active=False in ir_cron. The watchdog will not fire it, and no further runs will pile up. Re-enable after fixing the root cause.

How long is too long for a cron?

Anything over an hour is suspect. Anything over 4 hours is a problem. Long crons block other automation and amplify the cost of any failure. Batch and shorten.

Why does Odoo not auto-detect stuck crons?

Because Odoo cannot distinguish "running for 8 hours legitimately" from "stuck". The progress row is the only signal. A watchdog cron with a sensible age threshold is the right answer — Odoo could ship one, but does not by default. Build it yourself or install OCA's cron_run_manually family of utilities.

Should I use Celery or another task queue instead?

For very heavy workloads, yes. Odoo's cron is fine for moderate scheduled work; for high-throughput async work (millions of small jobs), a dedicated queue scales better. The OCA queue_job module bridges this nicely — install it, decorate methods with @job, and the queue worker handles execution outside the main Odoo cron.

What is the right priority for ir_cron?

Lower numbers run first when multiple crons are due simultaneously. Set critical maintenance crons (data integrity checks, mail queue) to priority 1; bulk computes to priority 5; analytics rebuild to priority 10. Without explicit priorities, ties resolve by id, which is unpredictable.

Can I see the lock that's stuck?

Yes:

SELECT pid, mode, locktype, relation::regclass, granted
FROM   pg_locks
WHERE  locktype = 'advisory';

Advisory locks are what crons use. If you see one held forever, it points at the worker that died without cleanup. PostgreSQL releases on connection close, so once the worker is gone the lock should clear automatically — if it does not, you have a deeper bug.


Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.

مشاركة:
E

بقلم

ECOSIRE Research and Development Team

الكتابة التقنية

يغطي فريق الكتابة التقنية في ECOSIRE موضوعات Odoo ERP والتجارة الإلكترونية عبر Shopify ووكلاء الذكاء الاصطناعي وتحليلات Power BI والأتمتة عبر GoHighLevel وأفضل ممارسات برمجيات المؤسسات. تساعد أدلتنا الشركات على اتخاذ قرارات تقنية مدروسة.

ECOSIRE

قم بتحويل أعمالك باستخدام Odoo ERP

تنفيذ وتخصيص ودعم خبير Odoo لتبسيط عملياتك.