1
1
import logging
2
+ import uuid
2
3
from copy import copy
3
4
from distutils .version import LooseVersion
4
5
from typing import TYPE_CHECKING
49
50
50
51
51
52
class MagpieOWSSecurity (OWSSecurityInterface ):
52
- _cached_request = None
53
+ _cached_request = {} # type: Dict[uuid.UUID, Request] # mapping to retrieve the request when caching is involved
53
54
54
55
def __init__ (self , container ):
55
56
# type: (AnySettingsContainer) -> None
@@ -60,8 +61,8 @@ def __init__(self, container):
60
61
self .twitcher_protected_path = self .settings .get ("twitcher.ows_proxy_protected_path" , "/ows" )
61
62
62
63
@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]]
65
66
"""
66
67
Cache this method with :py:mod:`beaker` based on the provided caching key parameters.
67
68
@@ -70,19 +71,22 @@ def _get_service_cached(self, service_name):
70
71
71
72
.. note::
72
73
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).
73
76
74
77
.. seealso::
75
78
- :meth:`magpie.adapter.magpieowssecurity.MagpieOWSSecurity.get_service`
76
79
- :meth:`magpie.adapter.magpieservice.MagpieServiceStore.fetch_by_name`
77
80
"""
78
- session = get_connected_session (self ._cached_request )
81
+ request = self ._cached_request .get (request_uuid )
82
+ session = get_connected_session (request )
79
83
service = evaluate_call (lambda : Service .by_service_name (service_name , db_session = session ),
80
84
http_error = HTTPForbidden , msg_on_fail = "Service query by name refused by db." )
81
85
verify_param (service , not_none = True , param_name = "service_name" ,
82
86
http_error = HTTPNotFound , msg_on_fail = "Service name not found." )
83
87
84
88
# 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 )
86
90
service_data = dict (service .get_appstruct ())
87
91
return service_impl , service_data
88
92
@@ -104,10 +108,17 @@ def get_service(self, request):
104
108
invalidate_service (service_name )
105
109
106
110
# 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.
107
117
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 )
111
122
112
123
# Because the database service *could* be linked to cached item, expired session creates unbound object
113
124
# - rebuild the service from cached data such that following operations can retrieve details as needed
0 commit comments