Skip to content

Commit 4be2d35

Browse files
dependabot[bot]AlexSCorey
authored andcommitted
Bump pandas from 2.2.3 to 2.3.3 (ansible#252)
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.3 to 2.3.3. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](pandas-dev/pandas@v2.2.3...v2.3.3) --- updated-dependencies: - dependency-name: pandas dependency-version: 2.3.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent f54aee4 commit 4be2d35

File tree

4 files changed

+178
-64
lines changed

4 files changed

+178
-64
lines changed

metrics_utility/automation_controller_billing/collector.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
1-
import contextlib
21
import json
32
import os
43

4+
from contextlib import contextmanager
5+
56
from django.conf import settings
67
from django.core.serializers.json import DjangoJSONEncoder
78
from django.db import connection
89

910
import metrics_utility.base as base
1011

12+
from metrics_utility.automation_controller_billing.db_lock_helpers import advisory_lock
1113
from metrics_utility.automation_controller_billing.helpers import get_last_entries_from_db
1214
from metrics_utility.automation_controller_billing.package.factory import Factory as PackageFactory
1315
from metrics_utility.logger import logger
1416

1517

16-
# work around https://github.com/ansible/awx/pull/15676
17-
try:
18-
# 2.4, early 2.5
19-
from awx.main.utils.pglock import advisory_lock
20-
except ImportError:
21-
# later 2.5, 2.6
22-
from ansible_base.lib.utils.db import advisory_lock
23-
24-
2518
class Collector(base.Collector):
2619
def __init__(self, collection_type=base.Collector.SCHEDULED_COLLECTION, collector_module=None, ship_target=None, billing_provider_params=None):
2720
if collector_module is None:
@@ -106,7 +99,7 @@ def registered_collectors(cls, module=None):
10699

107100
return base.Collector.registered_collectors(collectors)
108101

109-
@contextlib.contextmanager
102+
@contextmanager
110103
def _pg_advisory_lock(self, key, wait=False):
111104
"""Use awx specific implementation to pass tests with sqlite3"""
112105
with advisory_lock(key, wait=wait) as lock:
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from contextlib import contextmanager
2+
from zlib import crc32
3+
4+
from django.db import DEFAULT_DB_ALIAS, connection, connections
5+
6+
from metrics_utility.logger import logger
7+
8+
9+
@contextmanager
10+
def django_pglocks_advisory_lock(key, shared=False, wait=False, db_alias=DEFAULT_DB_ALIAS):
11+
function_name = 'pg_'
12+
release_function_name = 'pg_advisory_unlock'
13+
if not wait:
14+
function_name += 'try_'
15+
function_name += 'advisory_lock'
16+
if shared:
17+
function_name += '_shared'
18+
release_function_name += '_shared'
19+
tuple_format = False
20+
if isinstance(key, (list, tuple)):
21+
if len(key) != 2:
22+
raise ValueError('Tuples and lists as lock IDs must have exactly two entries.')
23+
if not isinstance(key[0], int) or not isinstance(key[1], int):
24+
raise ValueError('Both members of a tuple/list lock ID must be integers')
25+
tuple_format = True
26+
elif isinstance(key, str):
27+
pos = crc32(key.encode('utf-8'))
28+
key = (2**31 - 1) & pos
29+
if pos & 2**31:
30+
key -= 2**31
31+
elif not isinstance(key, int):
32+
raise ValueError('Cannot use %s as a lock id' % key)
33+
if tuple_format:
34+
base = 'SELECT %s(%d, %d)'
35+
params = (key[0], key[1])
36+
else:
37+
base = 'SELECT %s(%d)'
38+
params = (key,)
39+
acquire_params = (function_name,) + params
40+
command = base % acquire_params
41+
42+
with connections[db_alias].cursor() as cursor:
43+
cursor.execute(command)
44+
45+
if not wait:
46+
acquired = cursor.fetchone()[0]
47+
else:
48+
acquired = True
49+
try:
50+
yield acquired
51+
except Exception as e:
52+
if not acquired and not wait:
53+
raise e
54+
finally:
55+
if acquired:
56+
release_params = (release_function_name,) + params
57+
58+
command = base % release_params
59+
cursor.execute(command)
60+
61+
62+
@contextmanager
63+
def advisory_lock(*args, lock_session_timeout_milliseconds=0, **kwargs):
64+
if connection.vendor == 'postgresql':
65+
cur = None
66+
idle_in_transaction_session_timeout = None
67+
idle_session_timeout = None
68+
try:
69+
if lock_session_timeout_milliseconds > 0:
70+
"""Set the idle session timeout for the current transaction"""
71+
with connection.cursor() as cur:
72+
idle_in_transaction_session_timeout = cur.execute('SHOW idle_in_transaction_session_timeout').fetchone()[0]
73+
idle_session_timeout = cur.execute('SHOW idle_session_timeout').fetchone()[0]
74+
cur.execute('SET idle_in_transaction_session_timeout = %s', (lock_session_timeout_milliseconds,))
75+
cur.execute('SET idle_session_timeout = %s', (lock_session_timeout_milliseconds,))
76+
with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
77+
yield internal_lock
78+
if lock_session_timeout_milliseconds > 0:
79+
with connection.cursor() as cur:
80+
cur.execute('SET idle_in_transaction_session_timeout = %s', (idle_in_transaction_session_timeout,))
81+
cur.execute('SET idle_session_timeout = %s', (idle_session_timeout,))
82+
except Exception as e:
83+
logger.error(f'Error setting idle session timeout: {e}')
84+
exit(1)
85+
elif connection.vendor == 'sqlite':
86+
yield True
87+
else:
88+
raise RuntimeError(f'Advisory lock not implemented for database type {connection.vendor}')

metrics_utility/base/collector.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import contextlib
2-
import hashlib
32
import inspect
43
import logging
54
import os
@@ -9,6 +8,7 @@
98

109
from abc import abstractmethod
1110

11+
from django.db import DEFAULT_DB_ALIAS
1212
from django.utils.timezone import now, timedelta
1313

1414
from metrics_utility.logger import logger
@@ -332,29 +332,49 @@ def _add_collection_to_package(self, collection):
332332
if collection.ship_immediately():
333333
self._process_package(package)
334334

335+
@abstractmethod
335336
@contextlib.contextmanager
336-
def _pg_advisory_lock(self, key, wait=False):
337-
"""Postgres db lock"""
338-
connection = self.db_connection()
339-
340-
if connection is None:
341-
yield True
342-
else:
343-
# Build 64-bit integer out of the resource id
344-
resource_key = int(hashlib.sha512(key.encode()).hexdigest(), 16) % 2**63
345-
346-
cursor = connection.cursor()
347-
348-
try:
349-
if wait:
350-
cursor.execute('SELECT pg_advisory_lock(%s);', (resource_key,))
351-
else:
352-
cursor.execute('SELECT pg_try_advisory_lock(%s);', (resource_key,))
353-
acquired = cursor.fetchall()[0][0]
354-
yield acquired
355-
finally:
356-
cursor.execute('SELECT pg_advisory_unlock(%s);', (resource_key,))
357-
cursor.close()
337+
def _pg_advisory_lock(self, key, shared=False, wait=False, db_alias=DEFAULT_DB_ALIAS):
338+
"""PostgreSQL advisory lock context manager.
339+
340+
Subclasses must implement this method to provide database advisory locking.
341+
The implementation should:
342+
343+
Args:
344+
key (str): Lock identifier. Can be a string which will be converted to an integer
345+
using a hash function (CRC32, SHA512, etc.), or an integer, or a tuple
346+
of two integers for PostgreSQL's two-parameter lock functions.
347+
shared (bool): If True, acquire a shared lock (pg_advisory_lock_shared).
348+
If False, acquire an exclusive lock (pg_advisory_lock).
349+
Default is False (exclusive lock).
350+
wait (bool): If True, block until lock is acquired (pg_advisory_lock).
351+
If False, return immediately (pg_try_advisory_lock).
352+
When wait=False and lock cannot be acquired, should raise an exception.
353+
Default is False (non-blocking).
354+
db_alias (str): Django database alias to use for the connection.
355+
Default is DEFAULT_DB_ALIAS (usually 'default').
356+
357+
Yields:
358+
bool: True if lock was acquired, False if not acquired (only when wait=True).
359+
When wait=False, should either yield True or raise an exception.
360+
361+
The implementation should:
362+
- Support PostgreSQL advisory locks using pg_advisory_lock/pg_try_advisory_lock
363+
- Handle lock release automatically in finally block using pg_advisory_unlock
364+
- Support both shared and exclusive locks if needed
365+
- Handle different database vendors (PostgreSQL, SQLite, etc.)
366+
- Convert string keys to appropriate integer format for PostgreSQL
367+
- Raise exceptions when wait=False and lock cannot be acquired
368+
369+
Example PostgreSQL lock functions used:
370+
- pg_advisory_lock(key) - blocking exclusive lock
371+
- pg_try_advisory_lock(key) - non-blocking exclusive lock
372+
- pg_advisory_lock_shared(key) - blocking shared lock
373+
- pg_try_advisory_lock_shared(key) - non-blocking shared lock
374+
- pg_advisory_unlock(key) - release exclusive lock
375+
- pg_advisory_unlock_shared(key) - release shared lock
376+
"""
377+
pass
358378

359379
def _process_packages(self):
360380
for group, packages in self.packages.items():

0 commit comments

Comments
 (0)