Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-request expired resources #3944

Merged
merged 15 commits into from
Jan 24, 2017
Merged

Re-request expired resources #3944

merged 15 commits into from
Jan 24, 2017

Conversation

lbud
Copy link
Contributor

@lbud lbud commented Jan 10, 2017

Closes #1947.

This PR modifies ajax#getArrayBuffer to request Cache-Control and Expires headers. SourceCache maintains timers for tiles with set expiry dates for both active and cached tiles:

  • when an active tile's timer expires, it re-requests the tile
  • when a cached tile's timer expires, it is removed from the cache

Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • manually test the debug page

@lbud lbud changed the title [not ready] Re-request expired resources Re-request expired resources Jan 24, 2017
@lbud lbud requested a review from lucaswoj January 24, 2017 00:18
@lbud
Copy link
Contributor Author

lbud commented Jan 24, 2017

ready for 👀 🚦


if (this._cacheTimers[coord.id]) {
clearTimeout(this._cacheTimers[coord.id]);
delete this._cacheTimers[coord.id];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though delete is clearer, this might be a case where it's better to use this._cacheTimers[coord.id] = undefined, because this method is (relatively) hot and, as I understand it, delete causes V8 to re-compute an object's hidden class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oo @anandthakker thank you — did not know this, googled a little hole for myself to understand further: googleapis/google-api-nodejs-client#375

@@ -396,14 +411,19 @@ class SourceCache extends Evented {
tile = this._cache.get(wrapped.id);
if (tile) {
tile.redoPlacement(this._source);
if (this._cacheTimers[wrapped.id]) {
clearTimeout(this._cacheTimers[wrapped.id]);
delete this._cacheTimers[wrapped.id];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above re: delete

if (tileExpires) {
this._cacheTimers[id] = setTimeout(() => {
this._cache.remove(id);
delete this._timers[id];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be this._cacheTimers[id]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should, yes 🙇‍♀️

remove(key: string) {
if (!this.has(key)) { return this; }

this.onRemove(this.data[key]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In add(), it looks like this.onRemove() gets called after the item is deleted from this.data, whereas here, it's getting called before; being inconsistent could cause problem at some point.

Also, a question: am I missing something, or does cache.get() actually also remove the item? If that's so, is remove() redundant? (Although, admittedly, I normally would not expect a method called get() to have a side effect...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache.get removes an item from the cache and returns it, so there is some repeated code here, and we could use cache.get to delete tiles from the cache, but then it's returning expiring tiles to nowhere and would be less intuitive in code (when cached tile's timer expires, retrieve it from the cache … vs when cached tile's timer expires, remove it from the cache). 😕

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair enough. I'd actually be okay with "returning expired tiles to nowhere" (i.e. the calling code need not actually store the return value), BUT because it's called get I agree it would look pretty weird:

// oh, we need to remove the cache entry now, so
cache.get(id)
// cool, moving on

I sort of wonder if we should just rename LRUCache#get to LRUCache#remove -- that way, whether the calling code actually uses the return value or not, it is very clear that you're removing the entry from the cache.

clearTimeout(this._cacheTimers[coord.id]);
delete this._cacheTimers[coord.id];
this._setTimers(coord.id, cachedTile);
}
Copy link
Contributor

@anandthakker anandthakker Jan 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like we always do addTile() with any nonempty return value from this method -- since that's the case, and since we have equivalent logic for removing cacheTimer / adding timer already in addTile, is it possible that we could avoid redundancy by only doing it in addTile?

@anandthakker
Copy link
Contributor

anandthakker commented Jan 24, 2017

@lbud changes LGTM!

All I have left are a couple nits on naming/clarity that I should have thought of earlier:

  • LRUCache get vs remove see comment here
  • In SourceCache, what do you think about renaming _setTimers to something like _setTileReloadTimer and _setCacheTimers to _setCacheRemovalTimer?
  • I'm less sure of this than the above, but: would it make sense to move the this._cacheTimers[id] = undefined into _setTileReloadTimer() (and similar for this._timers = undefined), so that the calling code could be cleaner (and so that each tile having <= 1 timer going is enforced by those functions)?

^ this is all motivated by the notion that caching logic in general and SourceCache in particular tend to be hard to read/reason about and easy to mess up later (for me, anyway 😕 )

Copy link
Contributor

@anandthakker anandthakker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lbud Marking as approved since my remaining comments are all opinion/judgment calls

@lbud
Copy link
Contributor Author

lbud commented Jan 24, 2017

LRUCache get vs remove see comment here

I'd be okay with renaming it, although I instinctively think it's fine to keep both: adding remove doesn't add much code complexity, and they exist to serve different purposes. I'm going to leave it for now and if we feel strongly we can revise in the future.

I'm less sure of this than the above, but: would it make sense to move the this._cacheTimers[id] = undefined into _setTileReloadTimer() (and similar for this._timers = undefined), so that the calling code could be cleaner (and so that each tile having <= 1 timer going is enforced by those functions)?

I started doing this, but hesitated because of this part of removeTile — to be honest I'm not quite sure in which cases a tile would land here and return early, but I'm not comfortable reordering things in this fn at the moment (which combining timer resetting with cacheTimer setting would necessitate). 🤔

aaronlidman and others added 15 commits January 24, 2017 13:41
* set cache timers in SourceCache + remove tiles from the cache when they expire
* handle for setting/resetting timers when moving from SourceCache <-> LRUCache
* Avoid 'delete's so as to not re-compute hidden classes
* Remove redundant cache timer clearing logic from 'findLoadedParent'
* Consistify lru_cache's order of operations re deleting
* _setTimers -> _setTileReloadTimer
* _setCacheTimer -> _setCacheInvalidationTimer
@anandthakker
Copy link
Contributor

I started doing this, but hesitated because of this part of removeTile — to be honest I'm not quite sure in which cases a tile would land here and return early, but I'm not comfortable reordering things in this fn at the moment (which combining timer resetting with cacheTimer setting would necessitate)

Ah, yeah I see what you mean. 👍

@lbud lbud deleted the maxage branch January 24, 2017 22:21
@cammanderson
Copy link
Contributor

Very cool stuff - excited about this. We have a use case for this right now, great to see it is in action!

@karlguillotte
Copy link
Contributor

karlguillotte commented Feb 7, 2017

Since I upgraded to the version that include that feature, I started getting bunch of errors Unable to get property 'state' of undefined or null reference.

It is coming from

if (tile.state !== 'loading') {
.

I am sorry, I can not point you out to any gist that shows the errors. I am still investigating. Wondering if someone has that same issue?

I am using the new smart setStyle().

Thanks for MapBox GL.

@anandthakker
Copy link
Contributor

@karlguillotte thank you for reporting this. Could you please open a new issue describing this bug?

I know you said that you're still investigating -- if you're able to provide a minimal example demonstrating the problem, that would greatly improve our chances of to diagnosing and solving it.

@AndyMoreland
Copy link
Contributor

I have this bug also. Working on tracking down exactttllyy what's going on.

@AndyMoreland
Copy link
Contributor

AndyMoreland commented Feb 8, 2017

The root cause appears to be this line: https://github.com/mapbox/mapbox-gl-js/blob/master/src/source/source_cache.js#L438

The issue occurs when tile.getExpiry() - (new Date()).getTime() is larger than a 32-bit signed int. In my case the delta is 2592000000 and the timeout in executes immediately which results in an infinite loop of reloading tiles and the errors that @karlguillotte reported if I pan.

(Problem seems to be because of the implementation of setTimeout in browser JS engines, and in node: nodejs/node-v0.x-archive#8656)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants