|
33 | 33 | class HasInUse(object):
|
34 | 34 | """NeutronBaseV2 mixin, to add the flag "in_use" to a DB model.
|
35 | 35 |
|
36 |
| - The content of this flag (boolean) parameter is not relevant. The goal of |
37 |
| - this field is to be used in a write transaction to mark a DB register as |
38 |
| - "in_use". Writing any value on this DB parameter will lock the container |
39 |
| - register. At the end of the DB transaction, the DB engine will check if |
40 |
| - this register was modified or deleted. In such case, the transaction will |
41 |
| - fail and won't be commited. |
42 |
| -
|
43 |
| - "lock_register" is the method to write the register "in_use" column. |
44 |
| - Because the lifespan of this DB lock is the DB transaction, there isn't an |
45 |
| - unlock method. The lock will finish once the transaction ends. |
| 36 | + The goal of this class is to allow users lock specific database rows with |
| 37 | + a shared or exclusive lock (without necessarily introducing a change in |
| 38 | + the table itself). Having these locks allows the DB engine to prevent |
| 39 | + concurrent modifications (e.g. the deletion of a resource while we are |
| 40 | + currently adding a new dependency on the resource). |
| 41 | +
|
| 42 | + "read_lock_register" takes a shared DB lock on the row specified by the |
| 43 | + filters. The lock is automatically released once the transaction ends. |
| 44 | + You can have any number of parallel read locks on the same DB row. But |
| 45 | + you can not have any write lock in parallel. |
| 46 | +
|
| 47 | + "write_lock_register" takes an exclusive DB lock on the row specified by |
| 48 | + the filters. The lock is automatically released on transaction commit. |
| 49 | + You may only have one write lock on each row at a time. It therefor |
| 50 | + blocks all other read and write locks to this row. |
46 | 51 | """
|
| 52 | + # keep this value to not need to update the database schema |
| 53 | + # only at backport |
47 | 54 | in_use = sa.Column(sa.Boolean(), nullable=False,
|
48 | 55 | server_default=sql.false(), default=False)
|
49 | 56 |
|
50 | 57 | @classmethod
|
51 |
| - def lock_register(cls, context, exception, **filters): |
| 58 | + def write_lock_register(cls, context, exception, **filters): |
| 59 | + # we use `with_for_update()` to include `FOR UPDATE` in the sql |
| 60 | + # statement. |
| 61 | + # we need to set `enable_eagerloads(False)` so that we do not try to |
| 62 | + # load attached resources (e.g. standardattributes) as this breaks the |
| 63 | + # `FOR UPDATE` statement. |
52 | 64 | num_reg = context.session.query(
|
53 |
| - cls).filter_by(**filters).update({'in_use': True}) |
54 |
| - if num_reg != 1: |
| 65 | + cls).filter_by(**filters).enable_eagerloads( |
| 66 | + False).with_for_update().first() |
| 67 | + if num_reg is None: |
| 68 | + raise exception |
| 69 | + |
| 70 | + @classmethod |
| 71 | + def read_lock_register(cls, context, exception, **filters): |
| 72 | + # we use `with_for_update(read=True)` to include `LOCK IN SHARE MODE` |
| 73 | + # in the sql statement. |
| 74 | + # we need to set `enable_eagerloads(False)` so that we do not try to |
| 75 | + # load attached resources (e.g. standardattributes) as this breaks the |
| 76 | + # `LOCK IN SHARE MODE` statement. |
| 77 | + num_reg = context.session.query( |
| 78 | + cls).filter_by(**filters).enable_eagerloads( |
| 79 | + False).with_for_update(read=True).first() |
| 80 | + if num_reg is None: |
55 | 81 | raise exception
|
56 | 82 |
|
57 | 83 |
|
|
0 commit comments