|
37 | 37 | DEFAULT_MAX_RETRIES = 5 |
38 | 38 | RETRY_INTERVAL = 10 * 60 # seconds |
39 | 39 |
|
| 40 | +PG_ADVISORY_EXECUTION_LOCK_ID = 965655780 |
| 41 | + |
40 | 42 | _logger = logging.getLogger(__name__) |
41 | 43 |
|
42 | 44 |
|
@@ -221,56 +223,31 @@ def load_many(cls, env, job_uuids): |
221 | 223 | recordset = cls.db_records_from_uuids(env, job_uuids) |
222 | 224 | return {cls._load_from_db_record(record) for record in recordset} |
223 | 225 |
|
224 | | - def add_lock_record(self) -> None: |
225 | | - """ |
226 | | - Create row in db to be locked while the job is being performed. |
227 | | - """ |
228 | | - self.env.cr.execute( |
229 | | - """ |
230 | | - INSERT INTO |
231 | | - queue_job_lock (id, queue_job_id) |
232 | | - SELECT |
233 | | - id, id |
234 | | - FROM |
235 | | - queue_job |
236 | | - WHERE |
237 | | - uuid = %s |
238 | | - ON CONFLICT(id) |
239 | | - DO NOTHING; |
240 | | - """, |
241 | | - [self.uuid], |
242 | | - ) |
243 | | - |
244 | 226 | def lock(self) -> bool: |
245 | | - """Lock row of job that is being performed. |
| 227 | + """Lock job that is being performed using a session-level advisory lock. |
246 | 228 |
|
247 | 229 | Return False if a job cannot be locked: it means that the job is not in |
248 | 230 | STARTED state or is already locked by another worker. |
249 | 231 | """ |
| 232 | + # 2147483647 is the max value for int, which is the max value accepted |
| 233 | + # by pg_try_advisory_lock when using two arguments, as the job id might |
| 234 | + # be higher than that, we use a modulo to be sure to never exceed the limit. |
| 235 | + # A collision is highly unlikely because ids are sequential so a modulo |
| 236 | + # should not cause two different jobs to have the same lock id at the |
| 237 | + # same time. Even then, they would run one after the other. |
250 | 238 | self.env.cr.execute( |
251 | | - """ |
252 | | - SELECT |
253 | | - * |
254 | | - FROM |
255 | | - queue_job_lock |
256 | | - WHERE |
257 | | - queue_job_id in ( |
258 | | - SELECT |
259 | | - id |
260 | | - FROM |
261 | | - queue_job |
262 | | - WHERE |
263 | | - uuid = %s |
264 | | - AND state = %s |
265 | | - ) |
266 | | - FOR NO KEY UPDATE SKIP LOCKED; |
267 | | - """, |
268 | | - [self.uuid, STARTED], |
| 239 | + "SELECT pg_try_advisory_lock(%s, (%s %% 2147483647)::integer)", |
| 240 | + (PG_ADVISORY_EXECUTION_LOCK_ID, self._record_id), |
269 | 241 | ) |
270 | | - |
271 | | - # 1 job should be locked |
272 | 242 | return bool(self.env.cr.fetchall()) |
273 | 243 |
|
| 244 | + def unlock(self) -> None: |
| 245 | + """Release the session-level advisory lock.""" |
| 246 | + self.env.cr.execute( |
| 247 | + "SELECT pg_advisory_unlock(%s, (%s %% 2147483647)::integer)", |
| 248 | + (PG_ADVISORY_EXECUTION_LOCK_ID, self._record_id), |
| 249 | + ) |
| 250 | + |
274 | 251 | @classmethod |
275 | 252 | def _load_from_db_record(cls, job_db_record): |
276 | 253 | stored = job_db_record |
@@ -298,6 +275,8 @@ def _load_from_db_record(cls, job_db_record): |
298 | 275 | identity_key=stored.identity_key, |
299 | 276 | ) |
300 | 277 |
|
| 278 | + job_._record_id = stored.id |
| 279 | + |
301 | 280 | if stored.date_created: |
302 | 281 | job_.date_created = stored.date_created |
303 | 282 |
|
@@ -424,6 +403,7 @@ def __init__( |
424 | 403 |
|
425 | 404 | self._uuid = job_uuid |
426 | 405 | self.graph_uuid = None |
| 406 | + self._record_id = None |
427 | 407 |
|
428 | 408 | self.args = args |
429 | 409 | self.kwargs = kwargs |
@@ -788,7 +768,6 @@ def set_started(self): |
788 | 768 | self.state = STARTED |
789 | 769 | self.date_started = datetime.now() |
790 | 770 | self.worker_pid = os.getpid() |
791 | | - self.add_lock_record() |
792 | 771 |
|
793 | 772 | def set_done(self, result=None): |
794 | 773 | self.state = DONE |
|
0 commit comments