本文目前仅提供英文版本。翻译即将推出。
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:
- 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_progressrow is never updated. - Network blip between Odoo and PostgreSQL. The connection drops mid-cron. Same outcome — DB-level lock auto-released, app-level state stale.
- Manual
kill -9on the cron worker. Same. - Truly long-running cron. The job is doing legitimate work for 6 hours. Not stuck — just slow. Distinguish via
pg_stat_activity. - 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: anystate='running'row older than 2x its expected duration. Catches stuck states immediately. - Memory limits with auto-restart.
limit_memory_hardtriggers 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=Trueflags or processed-since-X queries. - Watchdog cron. Self-heal stuck states automatically. Costs nothing, prevents permanent stalls.
Related Errors
- Slow list view on > 1M rows — sibling performance issue.
- Database locked during import — adjacent long-running write problem.
- Memory leak in cron worker — different cause of the same OOM symptom.
- Too many PostgreSQL connections — what stuck crons can lead to.
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.
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
BMF Programmablaufplan Lohnsteuer 2026:实施德国官方工资税计算(XML、API、Odoo)
BMF Programmablaufplan Lohnsteuer 2026 开发人员指南:PAP 是什么、XML 伪代码格式、官方测试服务以及到 Odoo 工资单的映射。
2026 年 CRM 系统的成本是多少? 40 多个实施的实际定价
来自 40 多个实施的真实 CRM 定价:每个用户的许可成本、实施费用、隐藏成本以及 Odoo、HubSpot、Salesforce 等的 3 年 TCO。
eMAG Odoo 集成:将罗马尼亚最大的市场连接到您的 ERP(订单、库存、e-Factura)
将 eMAG Marketplace 连接到 Odoo ERP:报价和订单同步、AWB 运输、退货、库存和价格更新,以及卖家的罗马尼亚 e-Factura 合规性。