Почему пользовательская область Guice успешно работает в Java 8, но не работает в Java 11 с ConcurrentModificationException - PullRequest
0 голосов
/ 11 февраля 2019

У меня был этот тест - который я обрезал для MVCE - некоторое время, и сегодня я попытался проверить его в Java 11. Он выдает ConcurrentModificationException, типично, когда коллекция изменяется одновременно.

Зная немного, как Guice работает внутри, я заподозрил проблему повторного входа в мой computeIfAbsent звонок, поэтому избавился от своего второго computeIfAbsent(), и это сработало.Итак, я как бы ожидаю, что сейчас это не получится, но почему это удалось на Java 8?

Кроме того, я подозреваю, что я написал что-то неправильно в том, как должны быть написаны области.Итак, чтобы избежать этой проблемы, как мой механизм видимости должен быть обновлен, чтобы быть готовым к будущему?

Используемые версии:

  • Guice: 4.2.2
  • Java 8: Oracle JDK 1.8.0_191
  • Java 11: OpenJDK 11.0.2

Код:

package scopetest;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.ScopeAnnotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;

public class ScopeTest {

  @Test void test() {
    Injector injector = Guice.createInjector(new FooModule());
    GuiceFooScope guiceScope = injector.getInstance(GuiceFooScope.class);

    Key<Bar<String>> barKey = new Key<Bar<String>>(){};

    FooScope fooScopeOne = new FooScope();
    guiceScope.enter(fooScopeOne);
    Foo fooOne = injector.getInstance(Foo.class);
    Bar<String> fooOneBar = injector.getInstance(barKey);
    guiceScope.leave();

    FooScope fooScopeTwo = new FooScope();
    guiceScope.enter(fooScopeTwo);
    Bar<String> fooTwoBar = injector.getInstance(barKey); // FAILS HERE!
    guiceScope.leave();

    assertThat(fooOneBar.foo).isSameAs(fooOne);
    assertThat(fooTwoBar).isNotSameAs(fooOneBar);
    assertThat(fooTwoBar.foo).isNotSameAs(fooOne);
  }

  static class FooScope { }

  @FooScoped static class Foo {
    @Inject Foo() { }
  }

  @FooScoped static class Bar<T> {
    private final Foo foo;
    @Inject Bar(Foo foo) {
      this.foo = foo;
    }
  }

  static class FooModule extends AbstractModule {
    @Override protected void configure() {
      GuiceFooScope scope = new GuiceFooScope();
      bindScope(FooScoped.class, scope);
      bind(GuiceFooScope.class).toInstance(scope);
    }
  }

  @Target({TYPE, METHOD}) @Retention(RUNTIME) @ScopeAnnotation
  public @interface FooScoped { }

  static class GuiceFooScope implements Scope {

    private final Map<FooScope, Map<Key<?>, Object>> allScopedObjects = new HashMap<>();
    private FooScope currentScope;

    @Override public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
      return () -> {
        FooScope scope = currentScope;
        if (scope == null) {
          throw new OutOfScopeException("");
        }
        return (T) allScopedObjects.computeIfAbsent(scope, x -> new HashMap<>())
            .computeIfAbsent(key, x -> unscoped.get());
      };
    }

    void enter(FooScope current) {
      currentScope = requireNonNull(current);
    }

    void leave() {
      currentScope = null;
    }
  }
}

Верная ошибка, которую я получаю под Java 11,следующие (строки правильны относительно исходного файла):

-------------------------------------------------------------------------------
Test set: scopetest.ScopeTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.001 s <<< FAILURE! - in scopetest.ScopeTest
test  Time elapsed: 0.001 s  <<< ERROR!
com.google.inject.ProvisionException: 
Unable to provision, see the following errors:

1) Error in custom provider, java.util.ConcurrentModificationException
  at scopetest.ScopeTest$Bar.class(ScopeTest.java:54)
  while locating scopetest.ScopeTest$Bar<java.lang.String>

1 error
    at scopetest.ScopeTest.test(ScopeTest.java:40)
Caused by: java.util.ConcurrentModificationException
    at scopetest.ScopeTest.test(ScopeTest.java:40)

Новый, рабочий метод scope следующий:

    @Override public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
      return () -> {
        FooScope scope = currentScope;
        if (scope == null) {
          throw new OutOfScopeException("");
        }
        Map<Key<?>, Object> scopedObject = allScopedObjects.computeIfAbsent(scope, x -> new HashMap<>());
        Object instance = scopedObject.get(key); // Cannot computeIfAbsent because of reentrance.
        if (instance == null && !scopedObject.containsKey(key)) {
          instance = unscoped.get();
          scopedObject.put(key, instance);
        }
        return (T)instance;
      };
    }
...