Skip to content

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

Merged

Conversation

fredericDelaporte
Copy link
Member

@fredericDelaporte fredericDelaporte commented Apr 26, 2018

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


public SqlString Render(IList args, ISessionFactoryImplementor factory)
{
var filteredArgs = new Queue();
Copy link
Member

@hazzik hazzik Apr 26, 2018

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?

Copy link
Member Author

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.

Copy link
Member

@hazzik hazzik Apr 28, 2018

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.

Copy link
Member Author

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

@hazzik
Copy link
Member

hazzik commented Apr 26, 2018

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)

@fredericDelaporte
Copy link
Member Author

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.)

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Apr 27, 2018

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.)
I have also split the file move in its own commit, and added a commit for fixing PostgreSQL failing for xor (#1673).

@@ -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)}},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hazzik
Copy link
Member

hazzik commented Apr 28, 2018

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()))
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

@fredericDelaporte fredericDelaporte Apr 28, 2018

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);
Copy link
Member

@hazzik hazzik Apr 28, 2018

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(" ");

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Apr 28, 2018

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:

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?

@fredericDelaporte
Copy link
Member Author

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?
I am more for the later, at least 5.1.x. This issue could bite anyone using a binary operator in a query in a multi-threaded application (so potentially many web applications), and that would likely cause infrequent failures, causing the application to lack reliability.

@hazzik
Copy link
Member

hazzik commented Apr 30, 2018

Let's do 1st and 4th commits (squashed) into 5.1.x (we would need to exclude PostgreSQL from some tests as I understand).

@fredericDelaporte fredericDelaporte force-pushed the NonTHreadSafeFunction branch from 4716635 to 5c5b87e Compare May 1, 2018 11:52
@fredericDelaporte fredericDelaporte changed the base branch from master to 5.1.x May 1, 2018 11:53
Functions instances are hold in the dialect, shared among sessions
spawned from the same factory. They must be thread safe.
@fredericDelaporte fredericDelaporte force-pushed the NonTHreadSafeFunction branch from 5c5b87e to f6270ec Compare May 1, 2018 11:59
@fredericDelaporte
Copy link
Member Author

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;
Copy link
Member Author

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.

@hazzik hazzik modified the milestones: 5.2, 5.1.2 May 1, 2018
@fredericDelaporte fredericDelaporte merged commit 231b080 into nhibernate:5.1.x May 1, 2018
@fredericDelaporte fredericDelaporte deleted the NonTHreadSafeFunction branch May 1, 2018 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants