Skip to content
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

Add setter to TransactionManager.DefaultTimeout and MaxTimeout. #71703

Merged
merged 10 commits into from
Aug 2, 2022

Conversation

imcarolwang
Copy link
Contributor

@imcarolwang imcarolwang commented Jul 6, 2022

@HongGit , @mconnew , @stephentoub, this is for #71025 and #59282.

Test scenario:

  1. set DefaultTimeout and MaxTimeout with value in normal/supported range, get expected value being set
  2. set DefaultTimeout with value greater than MaxTimeout, get the MaxTimeout value for DefaultTimeout
  3. set DefaultTimeout and MaxTimeout with negative timespan, throws ArgumentOutOfRangeException

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

}
}

s_defaultTimeoutValidated = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should DefaultTimeout always be less than MaxTimeout? If so, when setting either of the two property values, the other property value needs to be validated and adjusted accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

My intuition says yes, default should always be less than max. What did the config system do in 4.8?

It appears that ValidateTimeout() already handles this case here. But a similar validtation would need to be added in the setter for MaxTimeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In 4.8 it did one-time loading from config settings and adjust, test result shows default always less than max. I have added ValidateTimeout for default timeout value in the MaxTimeout setter.

}

s_cachedMaxTimeout = false;
LazyInitializer.EnsureInitialized(ref s_maximumTimeout, ref s_cachedMaxTimeout, ref s_classSyncObject, () => value);
Copy link
Member

Choose a reason for hiding this comment

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

Is there value to using the LazyInitializer pattern here? We already have the value. It's just a simple assignment at this point. Why don't we set s_cachedMaxTimeout to true and just assign the value here?

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking the same thing. The LazyInitializer handles lazily assigning an object to s_classSyncObject for locking so that might be null. I don't see any reason to need a lock here though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the explanation! It's updated to set s_cachedMaxTimeout to true and assign the value directly.

@stephentoub
Copy link
Member

Thanks for your interest in contributing here. The API in #59282 hasn't been approved yet. We generally discourage PRs for new APIs that haven't yet been approved, but regardless we shouldn't merge this until it is.

s_cachedMaxTimeout = true;
s_maximumTimeout = value;
s_defaultTimeout = ValidateTimeout(s_defaultTimeout);
s_defaultTimeoutValidated = true;
Copy link
Member

Choose a reason for hiding this comment

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

We should log the same event if the default timeout gets reduced. Save a local copy of s_defaultTimeout before the call to ValidateTimeout, then do a comparison and emit an event similar to the DefaultTimeout setter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Matt! Updated.

TimeSpan tsMax = TimeSpan.Parse("00:30:00");

TransactionManager.DefaultTimeout = tsDefault;
Assert.Equal(tsDefault, TransactionManager.DefaultTimeout);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the pipeline run, test sometimes fails (expected 00:02:00, actual 00:01:00) at this line on mono platform. I think the error reproduces more than 80% of the time, I initially thought running the cases in parallel caused the problem, but after writing all the settings in one method, the problem persists. I have no idea how this happened, @StephenMolloy, @mconnew, any thoughts?

@mconnew
Copy link
Member

mconnew commented Jul 13, 2022

@imcarolwang, I just reread the original API proposal and there was a comment by Levi pointing out the potential for data tearing problems in the implementation and to be aware of it. Basically a long is 64bits and on a 32bit platform the two halves are set in two different machine instructions. If you have two different threads setting 2 different values at the same time and you are running on a 32bit platform, you can get the low 32bits from Thread 1 and the High 32bits from Thread 2 and end up with a value which is different from what either thread is trying to set.
There are a few possible solutions. One is to use the LazyInitializer, but I think that probably won't work after setting it the first time. The second is to set it using Interlocked.Exchange. The third is to set it inside of a lock statement. I think the interlocked solution is probably the best as you don't need to worry about lazily initializing the lock, and on 64bit platforms it gets converted by the jit to a simple memory store as that's atomic on 64bit platforms.

@imcarolwang
Copy link
Contributor Author

imcarolwang commented Jul 14, 2022

Thank you @mconnew for the comments! This morning I tried the Interlocked.Exchange approach in a different test PR, the same failure happened on "mono OSX x64 Debug" (for current PR it failed initially and passed in a latter re-ran). As Interlocked.Exchange doesn't have overload with TimeSpan so I convert it to ticks with type long, does it look right to you? I was thinking that if the data tearing thing doesn't happen on x64 platform, then the failure is caused by something else.


long defaultTimeoutTicks = Interlocked.Read(ref s_defaultTimeoutTicks);
Interlocked.Exchange(ref s_defaultTimeoutTicks, ValidateTimeout(new TimeSpan(Interlocked.Read(ref s_defaultTimeoutTicks))).Ticks);
if (Interlocked.Read(ref s_defaultTimeoutTicks) != defaultTimeoutTicks)
Copy link
Member

Choose a reason for hiding this comment

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

You are using Interlocked.Read to read s_defaultTimeoutTicks, but you just did this on the previous line. You can reuse the local defaultTimeoutTicks here instead of doing a second Interlocked.Read

@mconnew
Copy link
Member

mconnew commented Aug 1, 2022

@imcarolwang, once you remove the additional Interlocked, this is good to check in.

@imcarolwang imcarolwang merged commit 8fc905e into dotnet:main Aug 2, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants