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 在这种情况下不是线程安全的——这可能是一个远景,但如果有人能猜一猜,我将不胜感激。
此致
嗯,只是为了给别人提供一个解决方案,我的解决方案如下:
- 创建一个 class 维护一个以标识符(唯一且线程安全的标识符)作为键并以 Guice 注入器作为值的映射;
在我的 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();
}
综上所述,线程安全将在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 并且代码将按预期工作。
[长描述警告]
我是 运行 一些必须在定义的服务器中插入执行的黄瓜测试 - 例如: 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 在这种情况下不是线程安全的——这可能是一个远景,但如果有人能猜一猜,我将不胜感激。
此致
嗯,只是为了给别人提供一个解决方案,我的解决方案如下:
- 创建一个 class 维护一个以标识符(唯一且线程安全的标识符)作为键并以 Guice 注入器作为值的映射;
在我的 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(); }
综上所述,线程安全将在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 并且代码将按预期工作。