Skip to content

Commit 7535627

Browse files
committed
use uuid/request mapping to persist distinct handles until cached service completely finishes computing the object (relates to #490 and bird-house/birdhouse-deploy#224 (comment))
1 parent 14e31f2 commit 7535627

File tree

2 files changed

+25
-9
lines changed

2 files changed

+25
-9
lines changed

CHANGES.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ Changes
77
`Unreleased <https://github.com/Ouranosinc/Magpie/tree/master>`_ (latest)
88
------------------------------------------------------------------------------------
99

10-
* Nothing new for the moment.
10+
Bug Fixes
11+
~~~~~~~~~~~~~~~~~~~~~
12+
* Fix initial request reference sometimes lost before cached service can finish its resolution in rare situations where
13+
another inbound request unsets the ``adapter`` request handle by hitting the same cached service key being computed
14+
(resolves issue detected with feature in PR `#490 <https://github.com/Ouranosinc/Magpie/pull/490>`_ and observed in
15+
`bird-house/birdhouse-deploy#224 <https://github.com/bird-house/birdhouse-deploy/pull/224#issuecomment-985668339>`_).
1116

1217
`3.19.0 <https://github.com/Ouranosinc/Magpie/tree/3.19.0>`_ (2021-12-02)
1318
------------------------------------------------------------------------------------

magpie/adapter/magpieowssecurity.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import uuid
23
from copy import copy
34
from distutils.version import LooseVersion
45
from typing import TYPE_CHECKING
@@ -49,7 +50,7 @@
4950

5051

5152
class MagpieOWSSecurity(OWSSecurityInterface):
52-
_cached_request = None
53+
_cached_request = {} # type: Dict[uuid.UUID, Request] # mapping to retrieve the request when caching is involved
5354

5455
def __init__(self, container):
5556
# type: (AnySettingsContainer) -> None
@@ -60,8 +61,8 @@ def __init__(self, container):
6061
self.twitcher_protected_path = self.settings.get("twitcher.ows_proxy_protected_path", "/ows")
6162

6263
@cache_region("service")
63-
def _get_service_cached(self, service_name):
64-
# type: (Str) -> Tuple[ServiceInterface, Dict[str, AnyValue]]
64+
def _get_service_cached(self, service_name, request_uuid):
65+
# type: (Str, uuid.UUID) -> Tuple[ServiceInterface, Dict[str, AnyValue]]
6566
"""
6667
Cache this method with :py:mod:`beaker` based on the provided caching key parameters.
6768
@@ -70,19 +71,22 @@ def _get_service_cached(self, service_name):
7071
7172
.. note::
7273
Function arguments are required to generate caching keys by which cached elements will be retrieved.
74+
Those arguments must be serializable to generate the cache key (i.e.: cannot pass a :class:`Request`
75+
object that contains session and other unserializable/circular references).
7376
7477
.. seealso::
7578
- :meth:`magpie.adapter.magpieowssecurity.MagpieOWSSecurity.get_service`
7679
- :meth:`magpie.adapter.magpieservice.MagpieServiceStore.fetch_by_name`
7780
"""
78-
session = get_connected_session(self._cached_request)
81+
request = self._cached_request.get(request_uuid)
82+
session = get_connected_session(request)
7983
service = evaluate_call(lambda: Service.by_service_name(service_name, db_session=session),
8084
http_error=HTTPForbidden, msg_on_fail="Service query by name refused by db.")
8185
verify_param(service, not_none=True, param_name="service_name",
8286
http_error=HTTPNotFound, msg_on_fail="Service name not found.")
8387

8488
# return a specific type of service (eg: ServiceWPS with all the ACL loaded according to the service impl.)
85-
service_impl = service_factory(service, self._cached_request)
89+
service_impl = service_factory(service, request)
8690
service_data = dict(service.get_appstruct())
8791
return service_impl, service_data
8892

@@ -104,10 +108,17 @@ def get_service(self, request):
104108
invalidate_service(service_name)
105109

106110
# retrieve the implementation and the service data contained in the database entry
111+
# Use mapping which temporarily holds a reference to the relevant request. Mapping is required in case another
112+
# inbound request needs to be processed while the cached function is already/still processing a previous one,
113+
# to make sure that we don't override nor clear the other request reference until it finished processing.
114+
# After cached service was completely processed, the request reference can be removed (not needed anymore)
115+
# because the service data will be retrieved from the cached result on future calls, until it must be
116+
# re-processed from scratch with a new request following cache reset.
107117
LOGGER.debug("Retrieving service [%s]", service_name)
108-
self._cached_request = request
109-
service_impl, service_data = self._get_service_cached(service_name)
110-
self._cached_request = None
118+
request_uuid = uuid.uuid4()
119+
self._cached_request[request_uuid] = request
120+
service_impl, service_data = self._get_service_cached(service_name, request_uuid)
121+
self._cached_request.pop(request_uuid, None)
111122

112123
# Because the database service *could* be linked to cached item, expired session creates unbound object
113124
# - rebuild the service from cached data such that following operations can retrieve details as needed

0 commit comments

Comments
 (0)