Skip to content

Commit

Permalink
Fix sharing cache when parsing in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
ThiefMaster committed Mar 11, 2019
1 parent 0e96f78 commit a4a228b
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 24 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Changelog
Bug fixes:

* Fix race condition between parallel requests when the cache is used
(:issue:`371`). Thanks :user:`ThiefMaster` for reporting.
(:issue:`371`). Thanks :user:`ThiefMaster` for reporting and fixing.

5.1.2 (2019-02-03)
******************
Expand Down
3 changes: 1 addition & 2 deletions src/webargs/asyncparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ async def parse(
Receives the same arguments as `webargs.core.Parser.parse`.
"""
self.clear_cache() # in case someone used `parse_*()`
req = req if req is not None else self.get_default_request()
assert req is not None, "Must pass req object"
data = None
Expand All @@ -93,8 +94,6 @@ async def parse(
await self._on_validation_error(
error, req, schema, error_status_code, error_headers
)
finally:
self.clear_cache()
return data

async def _on_validation_error(
Expand Down
24 changes: 18 additions & 6 deletions src/webargs/core.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import inspect
import logging
import warnings
from copy import copy
from distutils.version import LooseVersion

try:
Expand Down Expand Up @@ -317,6 +318,11 @@ def _get_schema(self, argmap, req):
)
return schema

def _clone(self):
clone = copy(self)
clone.clear_cache()
return clone

def parse(
self,
argmap,
Expand Down Expand Up @@ -345,28 +351,34 @@ def parse(
:return: A dictionary of parsed arguments
"""
self.clear_cache() # in case someone used `parse_*()`
req = req if req is not None else self.get_default_request()
assert req is not None, "Must pass req object"
data = None
validators = _ensure_list_of_callables(validate)
parser = self._clone()
schema = self._get_schema(argmap, req)
try:
parsed = self._parse_request(
parsed = parser._parse_request(
schema=schema, req=req, locations=locations or self.locations
)
result = schema.load(parsed)
data = result.data if MARSHMALLOW_VERSION_INFO[0] < 3 else result
self._validate_arguments(data, validators)
parser._validate_arguments(data, validators)
except ma.exceptions.ValidationError as error:
self._on_validation_error(
parser._on_validation_error(
error, req, schema, error_status_code, error_headers
)
finally:
self.clear_cache()
return data

def clear_cache(self):
"""Invalidate the parser's cache."""
"""Invalidate the parser's cache.
This is usually a no-op now since the Parser clone used for parsing a
request is discarded afterwards. It can still be used when manually
calling ``parse_*`` methods which would populate the cache on the main
Parser instance.
"""
self._cache = {}
return None

Expand Down
4 changes: 0 additions & 4 deletions src/webargs/tornadoparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ def get_value(d, name, field):
class TornadoParser(core.Parser):
"""Tornado request argument parser."""

def __init__(self, *args, **kwargs):
super(TornadoParser, self).__init__(*args, **kwargs)
self.json = None

def parse_json(self, req, name, field):
"""Pull a json value from the request."""
json_data = self._cache.get("json")
Expand Down
11 changes: 0 additions & 11 deletions tests/test_tornadoparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,6 @@ def test_it_should_parse_query_arguments(self):
assert parsed["integer"] == [1, 2]
assert parsed["string"] == value

def test_parsing_clears_cache(self):
request = make_json_request({"string": "value", "integer": [1, 2]})
string_result = parser.parse_json(request, "string", fields.Str())
assert string_result == "value"
assert "json" in parser._cache
assert "string" in parser._cache["json"]
assert "integer" in parser._cache["json"]
attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())}
parser.parse(attrs, request)
assert parser._cache == {}

def test_it_should_parse_form_arguments(self):
attrs = {"string": fields.Field(), "integer": fields.List(fields.Int())}

Expand Down

0 comments on commit a4a228b

Please sign in to comment.