-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Using cucumber-spring does not share Spring context when using parallel (methods) surefire settings #1846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Im not sure I understand why its not trivial. Adding a synchronized block seems to have fixed it for me.... (im using 4.8.0) |
Unfortunately The step definition instances contain the current state of a scenario. So swapping these out concurrently may result in leaking state between scenarios. |
But looking at the code it is intending to put them in as "cucumber-scope" beans, which are ThreadLocal scoped, which it does do. So the instance of the bean is only ever in GlueCodeScope. The method returns a bean and stores the bean in a thereadlocal context. @Override
public Object get(String name, ObjectFactory<?> objectFactory) {
GlueCodeContext context = GlueCodeContext.getInstance();
Object obj = context.get(name);
if (obj == null) {
obj = objectFactory.getObject();
context.put(name, obj);
}
return obj;
} |
(Im just trying to get to the bottom of why we think this is non trivial as the code looks to be doing everything required! Except for one or two lines of non-thread safe stuff in SpringFactory) |
So you're saying that |
Please feel free to send a PR! |
I've been working with the assumption that there was only one |
Yes, getInstance does pull out of current glue scope and current glue scope (of which there is one defined but delegates to a ThreadLocal) . There is definitely a race condition but not sure where...ill carry on trying to diagnose, but I feel like it will be a small fix |
Yes so I found the issue. I would say its more of a spring bug. If two threads call (in DefaultListableBeanFactory) synchronized (this.beanDefinitionMap) { //synchronized, but doesnt check if another thread already added it as the "exists" check is NOT synchronized
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
**updatedDefinitions.addAll(this.beanDefinitionNames);**
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions; So |
Shouldn't be too much hardship to synchronize |
Ah. No that won't work. It's the underlying application context that is shared. |
Away over Xmas so raised a quick PR which I believe will fix the issue0. Please note it doesnt change the fact that you still get a context per thread by default (but this is easy to override see below) public class MultiThreadedAwareSpringBootstrapper extends org.springframework.test.context.web.WebTestContextBootstrapper {
@Override
public TestContext buildTestContext() {
return new DefaultTestContext(getBootstrapContext().getTestClass(), buildMergedContextConfiguration(),
new DefaultCacheAwareContextLoaderDelegate());
} |
If I am not mistaken that is caused by: https://github.com/cucumber/cucumber-jvm/blob/master/spring/src/main/java/io/cucumber/spring/FixBootstrapUtils.java#L47 Which is a class I'll enjoy deleting. Anyway. Enjoy the holidays! |
Yes you are right, but it might be desirable to have a context per feature , I am not sure really.. need to think about it. Per thread is not very useful, per scenario might be useful... but if we are happy to get rid of "context per thread" as FixBootStrapUtils is doing, then yes we dont need that class anymore |
There is no concept of features when executing cucumber. We only deal with pickles (compiled scenarios, and examples). Each of these is assumed to be independent from the others. Ofc. with the spring context that doesn't really work out, but at this point it is up to the test author to ensure everything can be executed in parallel (much like JUnit). |
And it is s a good thing we're working on major release now. So we can break everything - it just means writing more release notes. 😆 |
ok - updated the PR to default the behaviour using a single context (rather than one per thread) |
I think its a bit tricky to have a deterministic test, what do you think? |
When executing Cucumber in parallel in combination with `cucumber-spring` users would often encounter a cryptic error message: "No qualifying bean of type 'Steps' available: expected single matching bean but found 1" (#1823). This was initially fixed by ensuring that the Spring application context was not shared between threads (#1153, #1148). This however has the downside that the application context is not shared with JUnit tests either (#1583) and comes at significant performance penalty (#1846). The root cause however was a race condition in `DefaultListableBeanFactory.registerBeanDefinition(String beanName, BeanDefinition beanDefinition)`. There is a check to see if the definition already exists. If it does not exist, it replaces the bean definition in a ConcurrentHashMap (in a synchronized block as well) but then adds the bean name to a list without checking if it already exists. By synchronizing inside `SpringFactory.start` we prevent concurrent modification of the application context by `cucumber-spring` This in turn makes it possible to share the application context between threads and with JUnit tests. Fixes: #1846 Fixes: #1583 Closes: #1848 Closes: #1582
Summary
I have 3 feature files, with a single StepDefinition. When running in single threaded mode, everything is perfect. When running with the maven surefire plugin, using parallel as methods, I get a spring context created per thread, which I cannot have (startup time of the app would mean no benefit to threading)
I am working on a test case to share
Expected Behavior
The problem is the code uses a "TestContext" which binds a context to ThreadLocal.
So I worked around this by putting my own bootstrapper
Current Behavior
This works, except once in a while it blows up (SpringFactory seems not to be thread safe)
Possible Solution
Make SpringFactory threadsafe...still investigating.
In particular this line is not thread safe (if the context is shared)
Steps to Reproduce (for bugs)
Working on it...
The text was updated successfully, but these errors were encountered: