Skip to content

Commit c966090

Browse files
committed
Protocol.load: reload objects if our copy is over 30d old
fixes #628 no clue how much this will impact our outbound request load. we'll see!
1 parent c8c42ed commit c966090

File tree

3 files changed

+50
-27
lines changed

3 files changed

+50
-27
lines changed

protocol.py

+29-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Base protocol class and common code."""
22
import copy
3+
from datetime import timedelta
34
import logging
45
from threading import Lock
56
from urllib.parse import urljoin
@@ -38,6 +39,8 @@
3839
'video',
3940
)
4041

42+
OBJECT_REFRESH_AGE = timedelta(days=30)
43+
4144
# activity ids that we've already handled and can now ignore.
4245
# used in Protocol.receive
4346
seen_ids = LRUCache(100000)
@@ -1099,44 +1102,48 @@ def load(cls, id, remote=None, local=True, **kwargs):
10991102
requests.HTTPError: anything that :meth:`fetch` raises
11001103
"""
11011104
assert local or remote is not False
1102-
1103-
if remote is not True:
1104-
with objects_cache_lock:
1105-
cached = objects_cache.get(id)
1106-
if cached:
1107-
# make a copy so that if the client modifies this entity in
1108-
# memory, those modifications aren't applied to the cache
1109-
# until they explicitly put() the modified entity.
1110-
# NOTE: keep in sync with Object._post_put_hook!
1111-
return Object(id=cached.key.id(), **cached.to_dict(
1112-
# computed properties
1113-
exclude=['as1', 'expire', 'object_ids', 'type']))
1105+
logger.debug(f'Loading Object {id} local={local} remote={remote}')
11141106

11151107
obj = orig_as1 = None
1116-
if local:
1108+
with objects_cache_lock:
1109+
cached = objects_cache.get(id)
1110+
if cached:
1111+
# make a copy so that if the client modifies this entity in
1112+
# memory, those modifications aren't applied to the cache
1113+
# until they explicitly put() the modified entity.
1114+
# NOTE: keep in sync with Object._post_put_hook!
1115+
logger.debug(' got from cache')
1116+
obj = Object(id=cached.key.id(), **cached.to_dict(
1117+
# computed properties
1118+
exclude=['as1', 'expire', 'object_ids', 'type']))
1119+
1120+
if local and not obj:
11171121
obj = Object.get_by_id(id)
1118-
if obj and (obj.as1 or obj.raw or obj.deleted):
1119-
logger.info(' got from datastore')
1122+
if not obj:
1123+
logger.debug(f' not in datastore')
1124+
elif obj.as1 or obj.raw or obj.deleted:
1125+
logger.debug(' got from datastore')
11201126
obj.new = False
1121-
orig_as1 = obj.as1
11221127
if remote is not True:
11231128
with objects_cache_lock:
11241129
objects_cache[id] = obj
1125-
return obj
11261130

1127-
if remote is True:
1128-
logger.debug(f'Loading Object {id} local={local} remote={remote}, forced refresh requested')
1129-
elif remote is False:
1130-
logger.debug(f'Loading Object {id} local={local} remote={remote} {"empty" if obj else "not"} in datastore')
1131+
if remote is False:
11311132
return obj
1133+
elif remote is None and obj:
1134+
if obj.updated < util.as_utc(util.now() - OBJECT_REFRESH_AGE):
1135+
logger.debug(f' last updated {obj.updated}, refreshing')
1136+
else:
1137+
return obj
11321138

11331139
if obj:
1140+
orig_as1 = obj.as1
11341141
obj.clear()
11351142
obj.new = False
11361143
else:
11371144
obj = Object(id=id)
11381145
if local:
1139-
logger.info(' not in datastore')
1146+
logger.debug(' not in datastore')
11401147
obj.new = True
11411148
obj.changed = False
11421149

tests/test_models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def test_get_by_id(self):
414414
self.assert_entities_equal(obj, Object.get_by_id('ab^^c'))
415415

416416
def test_get_by_id_uses_cache(self):
417-
obj = Object(id='foo', our_as1={'x': 'y'})
417+
obj = Object(id='foo', our_as1={'x': 'y'}, updated=util.as_utc(NOW))
418418
protocol.objects_cache['foo'] = obj
419419
loaded = Fake.load('foo')
420420
self.assert_entities_equal(obj, loaded)
@@ -439,7 +439,7 @@ def test_put_cached_makes_copy(self):
439439
}, Fake.load('foo').our_as1)
440440

441441
def test_get_by_id_cached_makes_copy(self):
442-
obj = Object(id='foo', our_as1={'x': 'y'})
442+
obj = Object(id='foo', our_as1={'x': 'y'}, updated=util.as_utc(NOW))
443443
protocol.objects_cache['foo'] = obj
444444
loaded = Fake.load('foo')
445445
self.assert_entities_equal(obj, loaded)

tests/test_protocol.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Unit tests for protocol.py."""
22
import copy
3+
from datetime import timedelta
34
import logging
45
from unittest import skip
56
from unittest.mock import patch
@@ -8,9 +9,9 @@
89
from flask import g
910
from google.cloud import ndb
1011
from granary import as2
11-
from oauth_dropins.webutil import appengine_info
12+
from oauth_dropins.webutil import appengine_info, util
1213
from oauth_dropins.webutil.flask_util import CLOUD_TASKS_QUEUE_HEADER, NoContent
13-
from oauth_dropins.webutil.testutil import requests_response
14+
from oauth_dropins.webutil.testutil import NOW, requests_response
1415
import requests
1516
from werkzeug.exceptions import BadRequest
1617

@@ -200,7 +201,7 @@ def test_load_existing_empty_deleted(self):
200201
self.assertEqual([], Fake.fetched)
201202

202203
def test_load_cached(self):
203-
obj = Object(id='foo', our_as1={'x': 'y'})
204+
obj = Object(id='foo', our_as1={'x': 'y'}, updated=util.as_utc(NOW))
204205
protocol.objects_cache['foo'] = obj
205206
loaded = Fake.load('foo')
206207
self.assert_entities_equal(obj, loaded)
@@ -346,6 +347,21 @@ def test_load_preserves_fragment(self):
346347
self.assert_entities_equal(stored, got)
347348
self.assertEqual([], Fake.fetched)
348349

350+
def test_load_refresh(self):
351+
Fake.fetchable['foo'] = {'fetched': 'x'}
352+
353+
too_old = (NOW.replace(tzinfo=None)
354+
- protocol.OBJECT_REFRESH_AGE
355+
- timedelta(days=1))
356+
with patch('models.Object.updated._now', return_value=too_old):
357+
obj = Object(id='foo', our_as1={'orig': 'y'}, status='in progress')
358+
obj.put()
359+
360+
protocol.objects_cache['foo'] = obj
361+
362+
loaded = Fake.load('foo')
363+
self.assertEqual({'fetched': 'x', 'id': 'foo'}, loaded.our_as1)
364+
349365
def test_actor_key(self):
350366
user = self.make_user(id='fake:a', cls=Fake)
351367
a_key = user.key

0 commit comments

Comments
 (0)