D
Daniel Pitts
Just wanted to share some experience with the group.
I've come across a very strange situation, and I think I have the
solution (still needs to be verified in production, but looks promising).
I've inherited some code which basically does the following. Obfuscated
and simplified for demonstration purposes.
void logResults(Map<String, Result> resultsMap) {
StringBuilder logLine = new StringBuilder("Some stuff");
Set<String> keySet = resultsMap.keySet();
for (String key: keySet) {
logLine.append(key).append(": ").append(resultsMap.get(key));
}
logger.info(logLine.toString());
}
One day, we upgraded our application container, and this (which had been
working fine), started to cause OOM exceptions. Digging through an
HPROF, I found the "Some stuff" string in a StringBuilder which was
600MB is size, and had the same values of a couple of keys repeated over
and over again.
I found a newer version of this library function which someone had added
a simple counter to break out of the loop if the count > keySet.size();
Fixes the catastrophic symptom, but not the cause.
My belief is that this is an unusual manifestation of a concurrency
issue. the resultsMap is actually written to in this manor, by multiple
threads.
void addResult(ResultFactory factory, Map<String, Result> resultsMap) {
synchronize(factory) {
Result result;
result = resultsMap.get(factory.getName());
if (result == null) {
result = new Result();
resultsMap.put(factory.getName(), result);
}
result.recordResult(factory.getResult());
}
}
This may look "safe", but really the resultsMap internal data structure
can become inconsistent because it is being accessed by two threads at
the same time.
Since this is legacy code and I don't have time to do a full analysis
and repair the fundamental design flaws, my solution was to make
resultsMap a Collections.synchronizedMap(new HashMap<String, Result>()),
instead of a straight up HashMap.
From what I could tell, addResult is the only method which writes to
that map (but since its a Map being sent around all over the place,
that's hard to tell for sure). addResult's synchronization should be
enough to handle the "atomic" operation of "add if absent". Given that
factory.getName() is unique to that factory instance.
Anyway, just thought I'd share this interesting case-study with the
group. I just wish my predecessor had read /Java Concurrency in
Practice/ before trying to write a threaded framework. This is only
*one* of the issues that's come out of this framework.
Sincerely,
Daniel.
I've come across a very strange situation, and I think I have the
solution (still needs to be verified in production, but looks promising).
I've inherited some code which basically does the following. Obfuscated
and simplified for demonstration purposes.
void logResults(Map<String, Result> resultsMap) {
StringBuilder logLine = new StringBuilder("Some stuff");
Set<String> keySet = resultsMap.keySet();
for (String key: keySet) {
logLine.append(key).append(": ").append(resultsMap.get(key));
}
logger.info(logLine.toString());
}
One day, we upgraded our application container, and this (which had been
working fine), started to cause OOM exceptions. Digging through an
HPROF, I found the "Some stuff" string in a StringBuilder which was
600MB is size, and had the same values of a couple of keys repeated over
and over again.
I found a newer version of this library function which someone had added
a simple counter to break out of the loop if the count > keySet.size();
Fixes the catastrophic symptom, but not the cause.
My belief is that this is an unusual manifestation of a concurrency
issue. the resultsMap is actually written to in this manor, by multiple
threads.
void addResult(ResultFactory factory, Map<String, Result> resultsMap) {
synchronize(factory) {
Result result;
result = resultsMap.get(factory.getName());
if (result == null) {
result = new Result();
resultsMap.put(factory.getName(), result);
}
result.recordResult(factory.getResult());
}
}
This may look "safe", but really the resultsMap internal data structure
can become inconsistent because it is being accessed by two threads at
the same time.
Since this is legacy code and I don't have time to do a full analysis
and repair the fundamental design flaws, my solution was to make
resultsMap a Collections.synchronizedMap(new HashMap<String, Result>()),
instead of a straight up HashMap.
From what I could tell, addResult is the only method which writes to
that map (but since its a Map being sent around all over the place,
that's hard to tell for sure). addResult's synchronization should be
enough to handle the "atomic" operation of "add if absent". Given that
factory.getName() is unique to that factory instance.
Anyway, just thought I'd share this interesting case-study with the
group. I just wish my predecessor had read /Java Concurrency in
Practice/ before trying to write a threaded framework. This is only
*one* of the issues that's come out of this framework.
Sincerely,
Daniel.