Cucumber Guice / Injector 似乎不是线程安全的(并行执行 / ExecutorService)

Cucumber Guice / Injector seems not to be thread-safe (Parallel execution / ExecutorService)

[长描述警告]

我是 运行 一些必须在定义的服务器中插入执行的黄瓜测试 - 例如: a.feature -> JBoss 服务器 1 | b.feature -> JBoss 服务。 2 | c.feature -> JB1 |等等

为此,我创建了一个假设的 ExecutorService,如下所示:

    final ExecutorService executorService = Executors.newFixedThreadPool(2); //numberOfServers

    for (Runnable task : tasks) {
        executorService.execute(task);
    }
    executorService.shutdown();
    try {
        executorService.awaitTermination(1000, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        //doX();
    }

我管理如何选择可执行的服务器的方式是:

在为 executorService 创建的 Runnable class 内部,我将 instanceId 作为参数传递给 TestNG (XmlTest class),如下所示:

@Override
public void run() {
    setupTest().run();
}

private TestNG setupTest() {
    TestNG testNG = new TestNG();
    XmlSuite xmlSuite = new XmlSuite();
    XmlTest xmlTest = new XmlTest(xmlSuite);
    xmlTest.setName(//irrelevant);
    xmlTest.addParameter("instanceId", String.valueOf(instanceId));
    xmlTest.setXmlClasses(..........);
    testNG.setXmlSuites(..........);
    return testNG;
}

然后,我在 class 中得到这个很好 extends TestNgCucumberAdaptor:

@BeforeTest
@Parameters({"instanceId"})
public void setInstanceId(@Optional("") String instanceId) {
    if (!StringUtils.isEmpty(instanceId)) {
        this.instanceId = Integer.valueOf(instanceId);
    }
}

在@BeforeClass 中,我使用此 instanceId 填充 Pojo,并在另一个 class 的 threadLocal 属性中设置 Pojo。到目前为止,还不错。

public class CurrentPojoContext {
    private static final ThreadLocal<PojoContext> TEST_CONTEXT = new ThreadLocal<PojoContext>();
    ...
    public static PojoContext getContext(){
        TEST_CONTEXT.get();
    }

现在问题真正开始了——我在第 3 个 class 中使用 Guice(还有 Cucumber guice),注入这个包含 instanceId 的 pojo 对象。示例如下:

public class Environment {    
    protected final PojoContext pojoContext;    
    @Inject
    public Environment() {
        this.pojoContext = CurrentPojoContext.getContext();
    }    
    public void foo() {
        print(pojoContext.instanceId); // output: 1
        Another.doSomething(pojoContext);
    }

    class Another{
        public String doSomething(PojoContext p){
            print(p.instanceId); // output: 2
        }
    }
}

这里不是每次都这样输出(1 和 2),但我时不时地意识到不同线程的执行正在扰乱属性 pojoContext。我知道这有点令人困惑,但我的猜测是 Guice Injector 在这种情况下不是线程安全的——这可能是一个远景,但如果有人能猜一猜,我将不胜感激。

此致

嗯,只是为了给别人提供一个解决方案,我的解决方案如下:

  1. 创建一个 class 维护一个以标识符(唯一且线程安全的标识符)作为键并以 Guice 注入器作为值的映射;
  2. 在我的 Guice 注入器实例化中,我创建了自己的模块

    Guice.createInjector(Stage.PRODUCTION, MyOwnModules.SCENARIO, new RandomModule());
    

    对于此模块:

    public class MyOwnModules {
        public static final Module SCENARIO = new ScenarioModule(MyOwnCucumberScopes.SCENARIO);
    }
    

    此处定义的范围提供以下内容:

    public class MyOwnCucumberScopes {
        public static final ScenarioScope SCENARIO = new ParallelScenarioScope();
    }
    
  3. 综上所述,线程安全将在ParallelScenarioScope中:

    public class ParallelScenarioScope implements ScenarioScope {
    
        private static final Logger LOGGER = Logger.getLogger(ParallelScenarioScope.class);
    
        private final ThreadLocal<Map<Key<?>, Object>> threadLocalMap = new ThreadLocal<Map<Key<?>, Object>>();
    
        @Override
        public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
            return new Provider<T>() {
                public T get() {
                    Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key);
    
                    @SuppressWarnings("unchecked")
                    T current = (T) scopedObjects.get(key);
                    if (current == null && !scopedObjects.containsKey(key)) {
                        current = unscoped.get();
                        scopedObjects.put(key, current);
                    }
                    return current;
                }
            };
        }
    
        protected <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) {
            Map<Key<?>, Object> map = threadLocalMap.get();
            if (map == null) {
                throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block");
            }
            return map;
        }
    
        @Override
        public void enterScope() {
            checkState(threadLocalMap.get() == null, "A scoping block is already in progress");
            threadLocalMap.set(new ConcurrentHashMap<Key<?>, Object>());
        }
    
        @Override
        public void exitScope() {
            checkState(threadLocalMap.get() != null, "No scoping block in progress");
            threadLocalMap.remove();
        }
    
        private void checkState(boolean expression, String errorMessage) {
            if (!expression) {
                LOGGER.info("M=checkState, Will throw exception: " + errorMessage);
                throw new IllegalStateException(errorMessage);
            }
        }
    }
    

现在的问题是要小心 @ScenarioScoped 并且代码将按预期工作。