问题
Our setup is: Asp.NET + MVC5 using AutoFac for DI.
We have a class (which is a singleton) which is managing the access tokens for a variety of services. Every now and then, these tokens get too close to expiry (less then 10 minutes) and we request new tokens, refresh them. My current implementation looks like this:
// member int used for interlocking
int m_inter = 0;
private string Token { get; set; }
private DateTimeOffset TokenExpiry { get; set; }
public SingletonClassConstructor()
{
// Make sure the Token has some value.
RefreshToken();
}
public string GetCredentials()
{
if ((TokenExpiry - DateTimeOffset.UTCNow).TotalMinutes < 10)
{
if (Interlocked.CompareExchange(ref m_inter, 1, 0) == 0)
{
RefreshToken();
m_inter = 0;
}
}
return Token;
}
private void RefreshToken()
{
// Call some stuff
Token = X.Result().Token;
TokenExpiry = X.Result().Expiry;
}
As you can see the Interlocked makes sure only one thread goes through and the rest gets the old Token. What I'm wondering is - can we end up in a weird situation where when the Token is being overwritten, another thread tries to read and instead of the old token, gets a partial screwed up result? Any problems with this implementation?
Thanks!
回答1:
To me, the biggest issue with this implementation is that you may refresh the token two or more times for a single expiration period. If a thread is suspended just after checking the expiration condition but before the CompareExchange()
, then another thread could get all the way through the refresh operation, including resetting m_inter
, before that first thread is resumed. This can theoretically happen to arbitrarily many threads.
The remainder of your code isn't specific enough to comment on. There's no declaration of the Token
type, so it's not clear whether that's a struct
or class
. And your GetCredentials()
method is declared as returning a Credentials
value, but instead returns a Token
value, so that code obviously isn't even real code.
If the Token
type is a class
, then the rest of the implementation is probably fine. Reference type variables can be assigned atomically, even on x64 platforms, so code retrieving the Token
property value will see either the old token or the new one, not some corrupted intermediate state. (I'm assuming, of course, that the Token
object itself is thread-safe, preferably by virtue of being immutable.)
Personally, I would not bother with CompareExchange()
. Just use a full-blown C# lock
statement and be done with it. Contain the entire operation in the synchronized block: check the expiration time, replace the token if necessary, and return the token value, all from within the lock
.
Based on the code you showed, I would think it would make more sense to encapsulate the whole thing in the property itself and make that public
. But one way or the other, as long as code retrieving the token value can only get it through this section of synchronized code, the easiest and most reliably way to prove the code is correct is to use lock
. In the unlikely event that you see performance problems with that, then you can consider alternative implementations that are harder to get right.
来源:https://stackoverflow.com/questions/42515426/updating-property-of-a-singleton-with-thread-safety