chen870647924 / guava-libraries

Automatically exported from code.google.com/p/guava-libraries
Apache License 2.0
0 stars 0 forks source link

Single-element cache #872

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
First off, Guava's caching API is great stuff. It's made my code more clean, 
more efficient, and more robust. 

I do, however, have a confession... I sometimes abuse it.

Clearly, the cache is intended for key-based lookup, but occasionally, I have a 
single item I need to cache. It's something that is too expensive to fetch all 
the time, and the "memoizeWithExpiration" Supplier is a little thin (for 
example, I can't force eviction/invalidation).

So, what I've done is have some dummy key that I used to look up this single 
item. I'm sure you're cringing as your read this.

It would be nice to have a way of supporting this functionality that doesn't 
feel quite so dirty. Whether it would be some extension to the Cache API (don't 
know how...) or perhaps a beefed-up supplier that pulls in some functionality 
from Cache.

Original issue reported on code.google.com by raymond....@gmail.com on 18 Jan 2012 at 2:55

GoogleCodeExporter commented 9 years ago
I'm...in favor of this, and tentatively endorse a beefed-up Supplier.

Original comment by wasserman.louis on 18 Jan 2012 at 6:43

GoogleCodeExporter commented 9 years ago
A proposed interface:

interface BeefySupplier<T> extends Supplier<T> {
    void invalidate(); // or, perhaps, clear()
    void refresh();
    void put(T object);
}

It would also be nice if the "memoizeWithExpiration" static factory could 
become a builder, so I could do something like:

BeefySupplier<Steer> steerSupplier = Suppliers.builder()
    .memoize()
    .expireAfterAccess(100)
    .refreshAfterWrite(200)
    .build(
        new Supplier<Steer>() {
            public Steer get() {
                return raiseASteer(); // it takes a long time to raise one, hence why I want to cache it
            }
        }
    );

Original comment by raymond....@gmail.com on 18 Jan 2012 at 8:42

GoogleCodeExporter commented 9 years ago
Hmm, so I just noticed that there is a CacheLoader.from(Supplier<V> supplier). 
Is this intended to be the way to go about making a single-element cache? If 
so, this issue can be closed.

Original comment by raymond....@gmail.com on 19 Jan 2012 at 1:27

GoogleCodeExporter commented 9 years ago
I'm not satisfied with that, exactly?  I'd like to eliminate the key from the 
picture, for the single-element cache.

Original comment by wasserman.louis on 19 Jan 2012 at 1:30

GoogleCodeExporter commented 9 years ago
Raymond, that's actually completely unrelated.  It's just like any other cache, 
just that the CacheLoader doesn't actually depend on the key.  

Original comment by kevinb@google.com on 19 Jan 2012 at 3:30

GoogleCodeExporter commented 9 years ago
In general, I think it's probably a good idea to explore the idea of a 
singleton-cache type that's the no-key,one-value analogue of 
CacheBuilder/LoadingCache, then whittle the set of methods suggested by that 
analogy down to the ones we know we have use cases for Out There, and see what 
we arrive at.

To me, the fact it'll implement Supplier is just incidental, as with the case 
of LoadingCache implementing Function, and so I don't think Supplier would 
feature in the name.

Original comment by kevinb@google.com on 19 Jan 2012 at 3:32

GoogleCodeExporter commented 9 years ago
I have an implementation of this that I called ReferenceMaker, with options 
similar to MapMaker/CacheLoader for expiry etc, but only storing the single 
reference. There are plenty of things this could support that aren't applicable 
to or done in CacheLoader, in particular timed async refresh as opposed to 
expiry with on-demand reinitialisation.

I considered this to be more like a Reference with some extra options (e.g. 
self-refreshable) than like a cache, though maybe these are close to the same 
thing.

Original comment by joe.j.kearney on 19 Jan 2012 at 9:54

GoogleCodeExporter commented 9 years ago

Original comment by fry@google.com on 16 Feb 2012 at 7:18

GoogleCodeExporter commented 9 years ago
Not sure if OP had a similar scenario in mind, but I needed a memoizing 
supplier which is calculated from non-static context (pseudo code):

if ((t = not_calculated_yet) != null) {
  block_all_other_threads();
  t = calculate(f);
  memoize(t);
}
return t;

My initial response to this was to use Guava's cache (like OP) but I think 
having an abstract class with double check idiom (to hide the inglorious bits) 
is a better solution.

There are numerous implementations on the web, and this is one of them: 
http://weblogs.java.net/blog/mason/archive/2006/09/rechecking_doub.html

Original comment by min...@gmail.com on 26 Apr 2012 at 3:21

GoogleCodeExporter commented 9 years ago
mindas, have you seen Suppliers.memoize?

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Su
ppliers.html#memoize%28com.google.common.base.Supplier%29

Original comment by cpov...@google.com on 26 Apr 2012 at 3:34

GoogleCodeExporter commented 9 years ago
Don't think Suppliers.memoize would be applicable for cases where supplier is 
static and the data to build the supplier from ("f" in pseudo code) is coming, 
say, from a method parameter. Consider something like

class Foo {
  private static final Supplier<Bar> BAR_SUPPLIER = Suppliers.memoize(...);

  private Foo() {}

  public static Bar get(Baz baz) {
     BAR_SUPPLIER.get(baz);  // there's no .get(key, Callable) like in Cache
  }
}

Original comment by min...@gmail.com on 26 Apr 2012 at 3:50

GoogleCodeExporter commented 9 years ago
Ah, so you know that get(Baz) will be called with the same Baz every time, but 
you don't know the value ahead of time?

Original comment by cpov...@google.com on 26 Apr 2012 at 9:01

GoogleCodeExporter commented 9 years ago
Yes, exactly.

Original comment by min...@gmail.com on 27 Apr 2012 at 8:29

GoogleCodeExporter commented 9 years ago
+1
I had the exact same experience as the OP.
I like the pattern that Raymond proposed (the Supplier builder). However we 
could start smaller by offering a supplier that behaves exactly like 
memoizeWithExpiration, except that it loads the value asynchronously (using an 
Executor passed at construction, for instance) automatically upon expiration or 
upon the first request that follows expiration, and continues to supply the old 
value until the new value is returned.

Original comment by shi...@gmail.com on 25 May 2012 at 5:08

GoogleCodeExporter commented 9 years ago
I must admit that my recent abuse of the glorious CacheLoader is even dirtier:
not only do I need just one value, so I use a dummy key,
but I also want my data to be set under a lock, so I also use a dummy value, 
and just implement the get() method as:

public Object get(Object ignoredKey) {
  mutex.lock();
  try {
    outerClass.this.value = calculateValue();
  } finally {
    mutex.unlock();
  }
  return DUMMY;  // Cannot return null
}

So Ugly, I know. I cynically use its "should get() be run now?" logic without 
anything else that has to do with a cache.
If memoizeWithExpiration is adapted to allow all the above, can it also please 
accept an optional lock to make writes under?

Original comment by yoa...@google.com on 28 May 2012 at 5:49

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
(sorry, OuterClass should be capitalized... and this is the load() method...)

Original comment by yoa...@google.com on 28 May 2012 at 5:51

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 30 May 2012 at 7:43

GoogleCodeExporter commented 9 years ago
I feel like this would be solved by just adding a reset method to the memoizing 
suppliers available today. At least thats what I've done in my code.

I found a need to memoize values for a long period of time, but every once in a 
while, usually triggered by users, I had to flush the value stored in the 
supplier.

Original comment by emily@soldal.org on 1 Jun 2012 at 2:16

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 22 Jun 2012 at 6:16

GoogleCodeExporter commented 9 years ago
Another useful feature (that I have an immediate use for) that this singleton 
cache could provide is memoise-with-soft-reference. Just to add to kevinb's 
list for Comment 6.

Original comment by cky944 on 1 Jul 2012 at 6:13

GoogleCodeExporter commented 9 years ago
Not sure if this should be its own issue...but... I just recalled that one of 
my earliest "guava-like" classes was resettable variants of MemoizingSupplier 
and ExpiringMemoizingSupplier. 

The reason was simpy because we had a config object which rarely changed, so 
holding it in a Memoizing supplier once it had been loaded was a good idea.... 
until it changed... then you had to reboot the server to get the changed to 
propogate.

Perhaps cache is the wrong idea here, just adding an interface like Resettable 
or Clearable akin to (Auto)Closable and attaching that to a tertiary interface 
which joins both Supplier and Resettable and returning that so that we can 
expose the reset method and supplier method but not the implementations.

Arguably these should be added to the current memoizing suppliers.

just my 2 cents

Original comment by emily@soldal.org on 3 Jul 2012 at 1:22

GoogleCodeExporter commented 9 years ago
I had a similar need. My solution was to ask an extended Predicate if the 
memoized value should get updated. The memoizing Supplier calls a done() method 
on the 'UpdateRequest' after the value is updated, so that it can change it's 
state.

public interface UpdateRequest<T> extends Predicate<T> {
    void done();
}
public static <T> Supplier<T> memoize(Supplier<T> delegate, UpdateRequest<? 
super T> updateRequest) {...}

Original comment by christop...@gmail.com on 30 Jul 2012 at 2:57

GoogleCodeExporter commented 9 years ago
Louis, if you're looking for work, this would be good to look into, at least as 
far as converging on an API/feature set we can all feel good about.

Original comment by kevinb@google.com on 24 Aug 2012 at 3:57

GoogleCodeExporter commented 9 years ago
I can do that; I'll do some experimentation over the next week or two.

Original comment by wasserman.louis on 24 Aug 2012 at 7:21

GoogleCodeExporter commented 9 years ago
There's another functionality aspect which can possibly be considered for this 
feature.

Let's say there's some expensiveFunction() which takes a long time to 
calculate. Data that makes the input of this function may change and might be a 
need for an idiom which would invalidate the current process of expensive 
calculation and start anew, making all waiting threads to wait for longer until 
the calculation is complete and no more invalidations are done in the due 
course.

Current LoadingCache does not support this feature (this is not too obvious 
from the javadoc). In other words, call to cache.refresh(key) or 
cache.invalidate(key) or cache.invalidateAll() is ignored if the calculation is 
in process and only makes any difference if invalidation happens *after* the 
calculation.

I needed this feature and wrote a quick hack myself. The source code is 
available at 
http://codereview.stackexchange.com/questions/18056/ability-to-forget-the-memoiz
ed-supplier-value -- I'd be more than happy to hear any 
suggestions/improvements.

Original comment by min...@gmail.com on 31 Oct 2012 at 10:56

GoogleCodeExporter commented 9 years ago
Mindas, what you describe *seems* like something that should be filed 
separately.

Original comment by kevinb@google.com on 9 Nov 2012 at 11:07

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 12 Mar 2013 at 6:43

GoogleCodeExporter commented 9 years ago
I'm also using a LoadingCache the same way as the OP; my own reasons are:
- expireAfterWrite
- thread-safe loading.

In general I can see most of the other features of CacheBuilder being useful 
for a singleton holder (various expiration policies, etc).

Original comment by pern...@google.com on 18 Jun 2013 at 12:55

GoogleCodeExporter commented 9 years ago
Issue 1466 has been merged into this issue.

Original comment by cgdecker@google.com on 3 Jul 2013 at 7:24

GoogleCodeExporter commented 9 years ago
Issue 1466 has been merged into this issue.

Original comment by cgdecker@google.com on 3 Jul 2013 at 7:30

GoogleCodeExporter commented 9 years ago
Issue 1773 has been merged into this issue.

Original comment by lowas...@google.com on 2 Jun 2014 at 7:24

GoogleCodeExporter commented 9 years ago
Issue 1834 has been merged into this issue.

Original comment by cpov...@google.com on 19 Aug 2014 at 1:41

GoogleCodeExporter commented 9 years ago
This issue has been migrated to GitHub.

It can be found at https://github.com/google/guava/issues/<id>

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:14

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:18

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 3 Nov 2014 at 9:09