-
Notifications
You must be signed in to change notification settings - Fork 38.5k
MimeTypeUtils.parseMimeType returns null MIME type in case of high concurrency #23211
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
Comments
Reading your analysis, it seems we could fix that problem and even improve the performance by doing this: public V get(K key) {
this.lock.readLock().lock();
try {
if (this.queue.remove(key)) {
this.queue.add(key);
return this.cache.get(key);
}
}
finally {
this.lock.readLock().unlock();
}
this.lock.writeLock().lock();
try {
// retrying in case of concurrent reads on the same key
if (this.queue.remove(key)) {
this.queue.add(key);
return this.cache.get(key);
}
if (this.queue.size() == this.maxSize) {
K leastUsed = this.queue.poll();
if (leastUsed != null) {
this.cache.remove(leastUsed);
}
}
V value = this.generator.apply(key);
this.queue.add(key);
this.cache.put(key, value);
return value;
}
finally {
this.lock.writeLock().unlock();
}
} In this case, we avoid adding again the same key and thus returning Thanks for the analysis, it's really useful - more importantly, thanks for using our milestones in a traffic-heavy environment, this is really helping the Spring community, especially for that type of issues. |
This commit improves the cache implementation by skipping the ordering of most recently used cached keys when the cache is at least half empty. See gh-23211
I've reproduced the issue in a JMH benchmark (increasing the thread count is essential). In the various web benchmarks that we previously ran, the cached version had a positive impact on the performance (throughput and especially latency); the contention and concurrency on this specific part is probably not as high as the JMH benchmark. We could improve this even more by switching to a different parser implementation. I've tested a different parser implementation that's approx. +50% faster. When combined with a cache, there's not much difference anymore. Since the LRU cache implementation is internal, we can still reconsider using a LFU cache instead. |
Update `MimeTypeUtils` so that the StringUtils.hasLength check is performed immediately on the incoming argument, rather than in `parseMimeTypeInternal`. This restores the `IllegalArgumentException` rather than the `NullPointerException` which is thrown by the `ConcurrentHashMap`. Closes gh-23215 See gh-23211
Pull in fix for thread safety bug in mimetype caching (until next tagged release) spring-projects/spring-framework#23211
Affects: 5.5.0.M2 + (Problem observed in 5.5.0.M2, probably in any release after 2/5/2019)
I am getting an NPE on line MediaType.java:550 of spring-web-5.2.0.M2.jar (currently line 563 in master) because the cache is apparently returning a null type from MimeTypeUtils.parseMimeType().
and the method below doesn't account for a null return from the LRU cache
cachedMimeTypes
The ConcurrentLruCache in MimeTypeUtils must have a bug b/c that is the only way null type could be getting returned. The ConcurrentLruCache.get(key) method does returns in two places and one of them is returning a null. The return in the write lock block looks safe but the return in the read lock block could return null if the internal queue has the same key in it twice at which point the internal cache map wouldn't have the value anymore and then a null could be returned.
Assume two threads go through read lock block at same time, one of them will remove the key and add it back to front of queue, the other thread will fall through and wait for the write lock. Once the thread gets the write lock it will also add the key to the queue and now the key will be in the queue twice. Eventually the duplicate key might work its way to least used and get removed from the cache. At that point, all subsequent requests for that key will return null because the queue still has the key but the hash map doesn't have the value.
This is pretty serious b/c once it starts happening for a particular mime type, I think the application needs to be restarted.
The text was updated successfully, but these errors were encountered: