Skip to content

Commit b9c307c

Browse files
committed
Introduced renew message lock
This PR contains following changes - - Introduced renew message lock, which ensures that message is alive while processing and not returned to the queue to be reprocessed. - Added max concurrent messages login which can handle multiple requests at once - Added version.py file to maintain the package version - Updated readme
1 parent 5ff74f8 commit b9c307c

8 files changed

Lines changed: 75 additions & 24 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ ipython_config.py
9393

9494
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
9595
__pypackages__/
96-
version.py
9796
.build/
9897
.env
9998
service_details.json

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change log
22

3+
# 0.0.21
4+
- Introduced renew message lock, which ensures that message is alive while processing and not returned to the queue to be reprocessed.
5+
- Added max concurrent messages login which can handle multiple requests at once
6+
- Added version.py file to maintain the package version
7+
- Updated readme
8+
39
# 0.0.18
410
- Adds extra checks in service bus for retries
511
- Additional logging done if there is no network and service bus crashes

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Eg.
2525
```python
2626
from python_ms_core import Core
2727
core = Core() or Core(config='Local')
28+
29+
# To check the package version
30+
Core.__version__ # 0.0.21
2831
```
2932
The method analyzes the `.env` variables and does a health check on what components are available
3033

@@ -293,7 +296,7 @@ The project is configured with `python` to figure out the coverage of the unit t
293296
- The terminal will show the output of coverage like this
294297
```shell
295298

296-
> coverage run --source=src/python_ms_core -m unittest discover -v tests/unit_tests
299+
> python -m coverage run --source=src/python_ms_core -m unittest discover -v tests/unit_tests
297300
test_has_permission (test_auth.abstract.test_authorizer_abstraction.TestAuthorizerAbstract) ... ok
298301
test_get_search_params (test_auth.models.test_permission_request.TestPermissionRequest) ... ok
299302
test_has_permission_with_invalid_permissions (test_auth.provider.test_hosted_authorizer.TestHostedAuthorizer) ... ok

freeze_version.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import git
44
from datetime import date
5+
from src.python_ms_core import Core
56

67
project_path = os.path.dirname(os.path.abspath(__file__))
78
version_file_path = '{}/version.py'.format(project_path)
@@ -11,7 +12,7 @@
1112

1213
build_date = date.today().strftime('%Y-%m-%d')
1314

14-
version = '0.0.20'
15+
version = Core.__version__
1516

1617
with open(version_file_path, 'w+') as version_file:
1718
version_file.write("version = '{}'\n".format(version))

src/example.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
from python_ms_core.core.queue.models.queue_message import QueueMessage
1212
from python_ms_core.core.auth.models.permission_request import PermissionRequest
1313

14-
core = Core()
15-
print('Hello')
1614

15+
16+
core = Core()
17+
print(f'Core version: {Core.__version__}')
1718
topic = 'gtfs-pathways-upload'
1819
subscription = 'log'
1920
some_other_sub = 'usdufs'
@@ -42,7 +43,7 @@ def process(message):
4243
print(e)
4344

4445

45-
subscribe(topic, subscription)
46+
# subscribe(topic, subscription)
4647

4748
# azure_client = core.get_storage_client()
4849

src/python_ms_core/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .core.auth.provider.hosted.hosted_authorizer import HostedAuthorizer
99
from .core.auth.provider.simulated.simulated_authorizer import SimulatedAuthorizer
1010
from .core.config.config import CoreConfig, LocalConfig, AuthConfig, UnknownConfig
11+
from .version import __version__
1112

1213
LOCAL_ENV = 'LOCAL'
1314
AZURE_ENV = 'AZURE'
@@ -30,6 +31,9 @@ def __init__(self, config=None):
3031
self.config = CoreConfig()
3132
self.__check_health()
3233

34+
def __version__(self):
35+
return
36+
3337
def get_logger(self):
3438
logger_config = self.config.logger()
3539
if logger_config.provider.upper() == LOCAL_ENV:
@@ -39,12 +43,12 @@ def get_logger(self):
3943
else:
4044
logging.error(f'Failed to initialize core.get_logger for provider: {logger_config.provider}')
4145

42-
def get_topic(self, topic_name: str):
46+
def get_topic(self, topic_name: str, max_concurrent_messages=1):
4347
topic_config = self.config.topic()
4448
if topic_config.provider.upper() == LOCAL_ENV:
4549
return LocalTopic(config=topic_config, topic_name=topic_name)
4650
elif topic_config.provider.upper() == AZURE_ENV:
47-
return Topic(config=topic_config, topic_name=topic_name)
51+
return Topic(config=topic_config, topic_name=topic_name, max_concurrent_messages=max_concurrent_messages)
4852
else:
4953
logging.error(f'Failed to initialize core.get_topic for provider: {topic_config.provider}')
5054

@@ -114,3 +118,6 @@ def __check_health(self):
114118
print('\x1b[32m Logger configured \x1b[0m')
115119
print('\x1b[32m ------------------------- \x1b[0m')
116120
return True
121+
122+
123+
Core.__version__ = __version__

src/python_ms_core/core/topic/topic.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,26 @@
77
from ..resource_errors import ExceptionHandler
88
from ..queue.models.queue_message import QueueMessage
99

10-
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',datefmt='%Y-%m-%d %H:%M:%S')
10+
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
1111
logger = logging.getLogger('Topic')
1212
logger.setLevel(logging.INFO)
1313

1414

1515
class Callback:
16-
def __init__(self, fn=None):
16+
def __init__(self, fn=None, max_concurrent_messages=1):
1717
self._function_to_call = fn
18+
self._max_concurrent_messages = max_concurrent_messages
19+
self._stop_event = threading.Event()
20+
self._renewal_interval = 10 # seconds
21+
22+
def _renew_message_lock(self, message, receiver):
23+
while not self._stop_event.is_set():
24+
try:
25+
time.sleep(self._renewal_interval)
26+
if not message._lock_expired:
27+
receiver.renew_message_lock(message)
28+
except Exception as e:
29+
break
1830

1931
# old method to fetch messages. Not used anymore
2032
def messages(self, provider, topic, subscription):
@@ -33,41 +45,62 @@ def messages(self, provider, topic, subscription):
3345
logger.info('Completed topic receiver')
3446

3547
# Sends data to the callback function
36-
def process_message(self, message:str):
37-
queue_message = QueueMessage.data_from(message)
48+
def process_message(self, message, receiver):
49+
queue_message = QueueMessage.data_from(str(message))
3850
self._function_to_call(queue_message)
39-
51+
receiver.complete_message(message)
52+
4053
# Starts listening to the messages
4154
def start_listening(self, provider, topic, subscription):
42-
with provider.client: # service bus client
55+
with provider.client: # service bus client
4356
logger.info('Initiating receiver')
44-
topic_receiver = provider.client.get_subscription_receiver(topic, subscription_name=subscription) # servicebusclientsubscriptionreceiver
45-
logger.info('Done')
57+
topic_receiver = provider.client.get_subscription_receiver(
58+
topic_name=topic,
59+
subscription_name=subscription
60+
)
61+
logger.info('Receiver started')
4662
with topic_receiver:
4763
while True:
4864
try:
49-
for message in topic_receiver:
65+
messages = topic_receiver.receive_messages(
66+
max_message_count=self._max_concurrent_messages,
67+
max_wait_time=5
68+
)
69+
if not messages:
70+
continue
71+
72+
for message in messages:
73+
stop_event = threading.Event()
74+
lock_renewal_thread = threading.Thread(
75+
target=self._renew_message_lock,
76+
args=(message, topic_receiver)
77+
)
78+
lock_renewal_thread.start()
5079
try:
51-
self.process_message(message=str(message)) # sync call. [By default 1minute ] -> lock renewal for 300 seconds
80+
self.process_message(message, topic_receiver)
5281
except Exception as e:
53-
print(f'Error : {e}, Invalid message received : {message}')
82+
logger.error(f'Error processing message: {message}. Error: {e}')
83+
topic_receiver.abandon_message(message)
84+
logger.info(f'Message {message.message_id} abandoned')
5485
finally:
55-
topic_receiver.complete_message(message)
86+
stop_event.set() # Signal the lock renewal thread to stop
87+
lock_renewal_thread.join()
5688
except Exception as et:
57-
print(f'Error in service bus connection : {et}')
89+
logger.error(f'Error in service bus connection: {et}')
5890
# Change mode from PEEK_LOCK to RECEIVE_AND_DELETE
59-
logger.info('Topic receiver invalidated')
91+
logger.info('Receiver stopped')
6092

6193

6294
class Topic(TopicAbstract):
63-
def __init__(self, config=None, topic_name=None):
95+
def __init__(self, config=None, topic_name=None, max_concurrent_messages=1):
6496
self.topic = topic_name
6597
self.provider = Config(config=config, topic_name=topic_name)
98+
self.max_concurrent_messages = max_concurrent_messages
6699

67100
@ExceptionHandler.decorated
68101
def subscribe(self, subscription=None, callback=None):
69102
if subscription is not None:
70-
cb = Callback(callback)
103+
cb = Callback(callback, max_concurrent_messages=self.max_concurrent_messages)
71104
thread = threading.Thread(target=cb.start_listening, args=(self.provider, self.topic, subscription))
72105
thread.start()
73106
time.sleep(5)

src/python_ms_core/version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '0.0.21'

0 commit comments

Comments
 (0)