Skip to content

Commit 08eadd3

Browse files
committed
shopfloor_base: improve lock for update action
Add the skip locked option.
1 parent f8002bc commit 08eadd3

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

Diff for: shopfloor_base/actions/lock.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2022 Michael Tietz (MT Software) <[email protected]>
2+
# Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
23
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
34
import hashlib
45
import struct
@@ -26,7 +27,23 @@ def advisory(self, name):
2627
self.env.cr.execute("SELECT pg_advisory_xact_lock(%s);", (int_lock,))
2728
self.env.cr.fetchone()[0]
2829

29-
def for_update(self, records, log_exceptions=False):
30-
"""Lock a table FOR UPDATE"""
31-
sql = "SELECT id FROM %s WHERE ID IN %%s FOR UPDATE" % records._table
32-
self.env.cr.execute(sql, (tuple(records.ids),), log_exceptions=False)
30+
def for_update(self, records, log_exceptions=False, skip_locked=False):
31+
"""Lock rows for update on a specific table.
32+
33+
This function will try to obtain a lock on the rows (records parameter) and
34+
wait until they are available for update.
35+
36+
Using the SKIP LOCKED parameter (better used with only one record), it will
37+
not wait for the row to be available but return False if the lock could not
38+
be obtained.
39+
40+
"""
41+
query = "SELECT id FROM %s WHERE ID IN %%s FOR UPDATE"
42+
if skip_locked:
43+
query += " SKIP LOCKED"
44+
sql = query % records._table
45+
self.env.cr.execute(sql, (tuple(records.ids),), log_exceptions=log_exceptions)
46+
if skip_locked:
47+
rows = self.env.cr.fetchall()
48+
return len(rows) == len(records)
49+
return True

Diff for: shopfloor_base/tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from . import test_actions_data
2+
from . import test_actions_lock
23
from . import test_menu_service
34
from . import test_profile_service
45
from . import test_scan_anything_service

Diff for: shopfloor_base/tests/test_actions_lock.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
2+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
3+
from contextlib import closing
4+
5+
from .common import CommonCase
6+
7+
8+
class ActionsLockCase(CommonCase):
9+
@classmethod
10+
def setUpClassBaseData(cls):
11+
super().setUpClassBaseData()
12+
cls.partner = cls.env.ref("base.res_partner_12")
13+
with cls.work_on_actions(cls) as work:
14+
cls.lock = work.component(usage="lock")
15+
16+
def test_select_for_update_skip_locked_ok(self):
17+
"""Check the lock is obtained and True is returned."""
18+
result = self.lock.for_update(self.partner, skip_locked=True)
19+
self.assertTrue(result)
20+
21+
def test_select_for_update_skip_locked_not_ok(self):
22+
"""Check the lock is NOT obtained and False is returned."""
23+
with closing(self.registry.cursor()) as cr:
24+
# Simulate another user locked a row
25+
cr.execute(
26+
"SELECT id FROM res_partner WHERE id=%s FOR UPDATE", (self.partner.id,)
27+
)
28+
result = self.lock.for_update(self.partner, skip_locked=True)
29+
self.assertFalse(result)

0 commit comments

Comments
 (0)