Updating property of a singleton with Thread Safety

旧街凉风 提交于 2019-12-08 03:47:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!