-
Notifications
You must be signed in to change notification settings - Fork 933
Fix bitwise functions thread safety #1670
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 bitwise functions thread safety #1670
Conversation
|
||
public SqlString Render(IList args, ISessionFactoryImplementor factory) | ||
{ | ||
var filteredArgs = new Queue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would you need a queue here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done the fix in a "minimal implementation changes" way, mainly fixing the fact that putting some state without any locks in an instance shared by many threads cannot be thread safe. So I have kept the previous logic using a queue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please remove it? Adding something to the queue which later is read from it just in the next block of code is an useless operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not using it forces to add one more comma to the buffer then remove it. We need a filtered list if you wish to reliably avoid adding a comma in excess.
public SqlString Render(IList args, ISessionFactoryImplementor factory) | ||
{ | ||
var sqlBuffer = new SqlStringBuilder(); | ||
var arguments = new Queue(args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
I understand that this is just a refactoring of a previous work. However, for me it looks incorrect. Could this be reworked? (Preferably with unit tests) |
I do not call a fix a refactoring. The fact that current implementation is not thread safe seems obvious to me, as seems removing all mutable state to be a proper fix for achieving thread safety of instances. Now am I wrong considering function instances must be thread safe? Looking at the code, I do not think so. Conceiving a test reliably demonstrating the thread safety failure while using sessions (so proving that thread safety is an issue) may be no easy task. The call to function rendering is just a very small part of the query creation processing, so having them occurring concurrently may be hard. There is also the query plan caching which may mitigate the trouble. These functions are otherwise covered by some tests (Linq.WhereTests.BitwiseQuery/2/3, NH1192, NH1775, NH1831.) |
7f27122
to
a7a9fb1
Compare
Anyway, I have added more tests and made one which demonstrates the thread safety issue. (At least on my machine, it succeeds seldom without the fix.) |
src/NHibernate.Test/TestCase.cs
Outdated
@@ -447,6 +447,7 @@ protected DateTime RoundForDialect(DateTime value) | |||
{"locate", new HashSet<System.Type> {typeof (SQLiteDialect)}}, | |||
{"bit_length", new HashSet<System.Type> {typeof (SQLiteDialect)}}, | |||
{"extract", new HashSet<System.Type> {typeof (SQLiteDialect)}}, | |||
{"bxor", new HashSet<System.Type> {typeof (SQLiteDialect)}}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not supported by SQLite. http://sqlite.1065341.n5.nabble.com/XOR-operator-td98004.html
I did not mean that your fix of thread safety is incorrect or there is no such issue. I called a "refactoring" the "minimal implementation changes". I was trying to say that there are some cases were and remain broken in the functions. |
var filteredArgs = new Queue(); | ||
foreach (var arg in args) | ||
{ | ||
if (!IsParens(arg.ToString())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear for me why this class worries about parens, and BitwiseNativeOperation
does not? Where these assumptions came from? What would happen if some parens would end up as an arguments to BitwiseNativeOperation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it, it might be just an optimization, for removing parens which would be redundant otherwise, because the function call forcibly adds its own parens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears to be a hack for the BitwiseQuery3
test. The args contains four elements instead of two, the first being actually an element, the second an opening parenthesis and the fouth a closing parenthesis. Outputing them all comma-separated could only fail. For this case ignoring the parens yield a good result. Could more elaborated queries yield more complex cases requiring more subtle handling? I do not know for sure. But fiddling with the query, I cannot get such a case.
The native case does not bother with it, because parens seem to appears only as second and fourth argument, while the operator is put only after the first argument. So it get suitably placed, and parens do not creates an issue there.
That seems a bit brittle, but it is needed and it seems there are no known failures with that.
if (_isNot == false) | ||
AddToBuffer(arguments.Dequeue(), sqlBuffer); | ||
|
||
AddToBuffer(string.Format(" {0} ", _sqlOpToken), sqlBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be just sqlBuffer.Add(string.Format(" {0} ", _sqlOpToken))
, or better
sqlBuffer.Add(" ").Add(_sqlOpToken).Add(" ");
Well, I did want to fix the direst trouble, thread safety, which can result in hard to debug troubles for users (rare intermittent failures). For the other shortcomings of the implementation, I have not considered them. If they cause issues, it is likely they are far easier to diagnose than a thread safety one. Or are you saying my changes introduce new issues? This code comes from #280. @amroel, any remembering on the subject? Like about:
|
d96a282
to
4716635
Compare
Thanks for the removal of the cherry-pick in 5.1.x. Now, do we simply merge this in 5.2, or should we fix the thread safety issue in previous versions? |
Let's do 1st and 4th commits (squashed) into 5.1.x (we would need to exclude PostgreSQL from some tests as I understand). |
4716635
to
5c5b87e
Compare
Functions instances are hold in the dialect, shared among sessions spawned from the same factory. They must be thread safe.
5c5b87e
to
f6270ec
Compare
Base branch of PR changed, commit 1 & 4 squashed (and adjusted for ignoring PostgreSQL in bitwise xor tests), other commits dropped. |
@@ -15,28 +15,26 @@ public class BitwiseNativeOperation : ISQLFunction | |||
{ | |||
private readonly string _sqlOpToken; | |||
private readonly bool _isNot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not kept the renaming to _isUnary
in order to avoid a grey area breaking change (serialization) in a release version (5.1.x). Same for constructor argument name, kept as isNot
instead of isUnary
.
Functions instances are hold in the dialect, shared among sessions spawned from the same factory. They must be thread safe.
Implementation moved to its correct directory by the way.
Fixes #1355