ConcurrentModificationException in captcha generation

Description

We found two different exceptions running a high-concurrency scenario.
Apparently the problem raises while trying to garbage-collect the generated captchas.
One of the problems seems to be that the "times" FastHashMap for AbstractManageableCaptchaService is not synchronized, which in some cases raise a ConcurrentModificationException or in another case one thread attempts to read a Captcha(time) that was already removed from another thread.

Scenario 1:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$EntryIterator.next(HashMap.java:834)
at java.util.HashMap$EntryIterator.next(HashMap.java:832)
at org.apache.commons.collections.FastHashMap$CollectionView$CollectionViewIterator.next(FastHashMap.java:646)
at java.util.AbstractCollection.addAll(AbstractCollection.java:305)
at java.util.HashSet.<init>(HashSet.java:100)
at com.octo.captcha.service.AbstractManageableCaptchaService.getGarbageCollectableCaptchaIds(AbstractManageableCaptchaService.java:291)
at com.octo.captcha.service.AbstractManageableCaptchaService.garbageCollectCaptchaStore(AbstractManageableCaptchaService.java:254)
at com.octo.captcha.service.AbstractManageableCaptchaService.generateAndStoreCaptcha(AbstractManageableCaptchaService.java:327)
at com.octo.captcha.service.AbstractCaptchaService.getChallengeForID(AbstractCaptchaService.java:64)
at com.octo.captcha.service.AbstractCaptchaService.getChallengeForID(AbstractCaptchaService.java:46)
at com.octo.captcha.service.image.AbstractManageableImageCaptchaService.getImageChallengeForID(AbstractManageableImageCaptchaService.java:47)
at Test$1.run(Test.java:81)

Scenario 2:
java.lang.NullPointerException
at com.octo.captcha.service.AbstractManageableCaptchaService.garbageCollectCaptchaStore(AbstractManageableCaptchaService.java:257)
at com.octo.captcha.service.AbstractManageableCaptchaService.generateAndStoreCaptcha(AbstractManageableCaptchaService.java:327)
at com.octo.captcha.service.AbstractCaptchaService.getChallengeForID(AbstractCaptchaService.java:64)
at com.octo.captcha.service.AbstractCaptchaService.getChallengeForID(AbstractCaptchaService.java:46)
at com.octo.captcha.service.image.AbstractManageableImageCaptchaService.getImageChallengeForID(AbstractManageableImageCaptchaService.java:47)
at Test$1.run(Test.java:81)

This test recreates the problem:

public class Test {

public static void main(String[] args) throws Exception {

final DefaultManageableImageCaptchaService service = buildService();

// The purpose of These settings are for failing faster
// otherwise with default values you would have to wait
// until store reaches the quota (75000 captchas) and some captcha is
// expired.
service.setCaptchaStoreSizeBeforeGarbageCollection(3500);
service.setCaptchaStoreMaxSize(5000);
service.setMinGuarantedStorageDelayInSeconds(0);

for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
int i = 0;
while (true) {
service.getImageChallengeForID(this.getName() + "-" + i);
i++;
}
}

}.start();
}

}

private static DefaultManageableImageCaptchaService buildService() {

DefaultManageableImageCaptchaService service = new DefaultManageableImageCaptchaService();

GenericCaptchaEngine genericCaptchaEngine = new GenericCaptchaEngine(new CaptchaFactory[] { new GimpyFactory(
new RandomWordGenerator("abcdefghijklmnopqrstuvwxyz"), new SimpleWordToImage()) });

service.setCaptchaEngine(genericCaptchaEngine);
return service;
}
}

Environment

None

Assignee

Unassigned

Reporter

Maximiliano Vazquez

Labels

None

Components

Affects versions

Priority

Major
Configure