(preliminary note: maybe this is better suited for codereview?)
EDIT Answer to self; I believe this answer covers all my needs/problems, but of course, comments are welcome. Original question left below for reference.
Hello,
Of interest here is the .getSources()
method. This method is meant to return a list of message sources for a given Locale
.
The two central data structures to this method are sources
and failedLookups
, see the code for comments.
This particular implementation of .getSources()
can only ever return either an empty list or a single-element list, depending on the tryAndLookup()
method which prototype is:
protected abstract MessageSource tryAndLookup(final Locale locale)
throws IOException;
Right now, the logic of the code is as follows:
- if the message source for that locale has already been successfully looked up, it is returned;
- from this point on, no lookup has been done; it is however unknown whether this means that a previous lookup attempt has been done: check the set of failed lookups, if the locale to lookup is in there, it is a known failure, return the empty list;
- now, the known situation is that this locale lookup has actually never been performed: do perform it; depending on the result of the
tryAndLookup
method, record either a success or a failure.
Now, why I go to such lengths: I have no control over tryAndLookup()
; it may take an inordinate amount of time to execute before returning a valid source or failing. As a result, I am reluctant to using a coarse lock or synchronized
block.
/**
* Set of locales known to have failed lookup.
*
* <p>When a locale is in this set, it will not attempt to be reloaded.</p>
*/
private final Set<Locale> lookupFailures
= new CopyOnWriteArraySet<Locale>();
/**
* Set of message sources successfully looked up
*
* <p>When a source is in there, it is there permanently for now.</p>
*/
private final ConcurrentMap<Locale, MessageSource> sources
= new ConcurrentHashMap<Locale, MessageSource>();
@Override
protected final List<MessageSource> getSources(final Locale locale)
{
MessageSource source = sources.get(locale);
/*
* If found, return it
*/
if (source != null)
return Arrays.asList(source);
/*
* If it is a registered failure, return the empty list
*/
if (lookupFailures.contains(locale))
return Collections.emptyList();
/*
* OK, try and look it up. On success, register it in the sources map.
* On failure, record the failure an return the empty list.
*/
try {
source = tryAndLookup(locale);
sources.putIfAbsent(locale, source);
// EDIT: fix for bug pinpointed by JBNizet
// was:
// return Arrays.asList(source);
// now is:
return Arrays.asList(sources.get(locale));
} catch (IOException ignored) {
lookupFailures.add(locale);
return Collections.emptyList();
}
}
My question here is threefold:
- I purposefully limit myself to classes only available to the JDK; I chose
ConcurrentHashMap
as aConcurrentMap
implementation, andCopyOnWriteArraySet
as a thread-safeSet
implementation; from the javadoc, these are the best I could find. But was I misled somewhere? - I think this code is eventually thread safe; some corner cases may lead to lookups being done more than once for instance, but then this is why I do
.putIfAbsent()
; right now I have always used, and trusted, Guava'sLoadingCache
for caching purposes, and this is my first foray out of this territory; is this code actually thread safe? - this code has a fatal defect: more than one thread may be executing
tryAndLookup()
at the same time... What solutions exist to have this method executed only once per lookup?