A scheduled action (ir.cron) runs in a background worker. A bad one holds a database lock and blocks users.
Define it
<record id="cron_sync_prices" model="ir.cron">
<field name="name">Sync prices</field>
<field name="model_id" ref="product.model_product_template"/>
<field name="state">code</field>
<field name="code">model._cron_sync_prices()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
Work in batches
@api.model
def _cron_sync_prices(self, batch=200):
todo = self.search([('price_dirty', '=', True)], limit=batch)
for rec in todo:
rec._sync_price()
if len(todo) == batch:
self.env.ref('mymodule.cron_sync_prices')._trigger()
self.env['ir.cron']._commit_progress(len(todo)) # Odoo 18+
_trigger() schedules another run right away, so one run never holds a huge transaction.
Rules
- Make the work idempotent. Running twice must not double-count.
- Never loop over every record without a
limit. - Log how many records you handled.
- Test it from Settings → Technical → Scheduled Actions → Run Manually.