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

时间:2017-05-24 18:47:45

标签: thread-safety cucumber testng guice executorservice

[长描述警告]

我正在运行一些必须执行插入已定义服务器的黄瓜测试 - 例如: a.feature - > JBoss Server 1 | b.feature - > JBoss Serv。 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类中,我将 instanceId 作为参数传递给TestNG(XmlTest类),如下所示:

@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;
}

然后,我在 扩展TestNgCucumberAdaptor 的类中得到了这个:

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

在@BeforeClass中我用这个instanceId填充Pojo,并在另一个类的threadLocal属性中设置Pojo。到目前为止,非常好。

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

现在问题真的开始了 - 我在第三类中使用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 个答案:

答案 0 :(得分:1)

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

  1. 创建一个类,该类维护带有标识符的Map(唯一且线程安全的)作为键,Guice Injector作为值;
  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);
            }
        }
    }
    
  4. 现在问题就是@ScenarioScoped要小心,代码将按预期工作。