-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Fix a race condition caused by other threads calling mapper methods while mappedStatements are being constructed #2709
Conversation
…hile mappedStatements are being constructed
Hello @tianshuang , It seems like the same issue as mybatis/spring#696 which may not be resolved by this change.
I understand why people do this, but the current MyBatis design may not allow this approach (see my comments on the linked issue for why). So, let me clarify. Did you verify that this change actually fixes your problem? |
Yes, this PR fixed my problem, my problem is similar to mybatis/spring#696, but the problem I am facing has no dependencies between mappers, just because As you say in mybatis/spring#696 (comment):
Since we can't prevent concurrent calls, why don't we make MyBatis more robust? This PR fixed the occasional exception:
|
Any ideas? |
Thank you for the detailed explanation! I could verify the effect. |
In projects using the Spring framework, we often use
@PostConstruct
to perform some operations after bean initialization. In some scenarios, we will create asynchronous thread to perform some time-consuming SQL operations to avoid blocking the application startup process. We observed that the use of mappers in asynchronous thread may broken due to theresize
of the underlyingStrictMap
ofmappedStatements
.Example code for using
@PostConstruct
with asynchronous thread, MybatisRaceConditionApplication.java:In this scenario, the main thread is still building the mapper of other beans, that is, it keeps putting
mappedStatement
tomappedStatements
, and in this asynchronous thread, it keeps gettingmappedStatement
frommappedStatements
, becauseStrictMap
inherits fromHashMap
, when the underlyingHashMap
performs theresize
operation, before the old array element migration is completed and the asynchronous thread obtains the latesttable
reference, At this time, themappedStatement
that has been put before cannot be obtained frommappedStatements
.This is the source code of
HashMap#resize
in JDK8:In the source code, a new array
newTab
is allocated first, and the empty arraynewTab
reference is assigned totable
. Before the element migration is completed, the asynchronous thread callingmappedStatements.get(id)
may not be able to get itmappedStatement
(depends on seeing the latesttable
reference, note thattable
does not contain thevolatile
modifier. however, it can be obtained normally before and after resize), instead throws the following exception:The root cause of this problem is that multiple threads access
mappedStatements
concurrently, and the main thread has made structural modifications tomappedStatements
, which does not conform to the usage specification ofHashMap
, so in order to solve this problem, we should makeStrictMap
inherited fromConcurrentHashMap
, at the same time, in order to be compatible with the changes of thecontainsKey
method and adapt to the NULL key, I rewrote thecontainsKey
method.This project can reproduce the race condition: GitHub - tianshuang/mybatis-race-condition
Reference:
java - HashMap resize() while single thread writing and multiple threads reading - Stack Overflow
Unit Test
All unit tests pass.