Skip to main content
added 20 characters in body
Source Link
Bob
  • 587
  • 2
  • 8

For clarity, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.


// bad!
async Task<HttpResponseMessage> MakeRequest(...params...)
{
    await LoginCheck();
    return await RequestWithGlobalState(...params...);
}
private Token storedToken = Token.ExpiredToken;

// better!
async Task<HttpResponseMessage> MakeRequest(...params...)
{
    var authToken = await GetAuthToken();
    return await RequestWithoutGlobalState(authToken, ...params...);
}

Tokenasync Task<Token> GetAuthToken()
{
    lock(tokenLock)
    {
        if (storedToken.expiryTimeExpiryTime >= currentTime)
        {
            storedToken = await FetchNewToken();
        }
        
        return storedToken;
    }
}
private Token storedToken = Token.ExpiredToken;

async Task<HttpResponseMessage> MakeRequest(...params...)
{
    var authToken = await GetAuthToken();
    return await RequestWithoutGlobalState(authToken, ...params...);
}

Tokenasync Task<Token> GetAuthToken()
{
    IDisposable rLock = await rwLock.ReaderLockAsync();
    IDisposable wLock = null;

    var currentTime = DateTime.UtcNow;

    try
    {
        // note: DateTime comparisons are not atomic so don't do this when not locked
        // we're in a reader lock here so it's okay
        if (storedToken.expiryTimeExpiryTime >= currentTime)
        {
            // need to release reader lock before switching to write mode
            rLock.Dispose();

            // only one thread at a time can enter the writer lock, and all threads must be out of the reader locks
            wLock = await rwLock.WriterLockAsync();
            // check again here in case another thread has already refreshed while we were waiting on a writer lock
            if (storedToken.expiryTimeExpiryTime >= currentTime)
            {
                storedToken = await FetchNewToken();
            }
            wLock.Dispose();

            // re-acquire reader lock
            rLock = await rwLock.ReaderLockAsync();
        }

        return storedToken;
    }
    finally
    {
        rLock?.Dispose();
        wLock?.Dispose();
    }
}

For clarity, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.


// bad!
MakeRequest()
{
    await LoginCheck();
    await RequestWithGlobalState();
}
// better!
MakeRequest()
{
    var authToken = await GetAuthToken();
    await RequestWithoutGlobalState(authToken);
}

Token GetAuthToken()
{
    lock(tokenLock)
    {
        if (storedToken.expiryTime >= currentTime)
        {
            storedToken = await FetchNewToken();
        }
        
        return storedToken;
    }
}
MakeRequest()
{
    var authToken = await GetAuthToken();
    await RequestWithoutGlobalState(authToken);
}

Token GetAuthToken()
{
    IDisposable rLock = await rwLock.ReaderLockAsync();
    IDisposable wLock = null;

    try
    {
        // note: DateTime comparisons are not atomic so don't do this when not locked
        // we're in a reader lock here so it's okay
        if (storedToken.expiryTime >= currentTime)
        {
            // need to release reader lock before switching to write mode
            rLock.Dispose();

            // only one thread at a time can enter the writer lock, and all threads must be out of the reader locks
            wLock = await rwLock.WriterLockAsync();
            // check again here in case another thread has already refreshed while we were waiting on a writer lock
            if (storedToken.expiryTime >= currentTime)
            {
                storedToken = await FetchNewToken();
            }
            wLock.Dispose();

            // re-acquire reader lock
            rLock = await rwLock.ReaderLockAsync();
        }

        return storedToken;
    }
    finally
    {
        rLock?.Dispose();
        wLock?.Dispose();
    }
}
// bad!
async Task<HttpResponseMessage> MakeRequest(...params...)
{
    await LoginCheck();
    return await RequestWithGlobalState(...params...);
}
private Token storedToken = Token.ExpiredToken;

// better!
async Task<HttpResponseMessage> MakeRequest(...params...)
{
    var authToken = await GetAuthToken();
    return await RequestWithoutGlobalState(authToken, ...params...);
}

async Task<Token> GetAuthToken()
{
    lock(tokenLock)
    {
        if (storedToken.ExpiryTime >= currentTime)
        {
            storedToken = await FetchNewToken();
        }
        
        return storedToken;
    }
}
private Token storedToken = Token.ExpiredToken;

async Task<HttpResponseMessage> MakeRequest(...params...)
{
    var authToken = await GetAuthToken();
    return await RequestWithoutGlobalState(authToken, ...params...);
}

async Task<Token> GetAuthToken()
{
    IDisposable rLock = await rwLock.ReaderLockAsync();
    IDisposable wLock = null;

    var currentTime = DateTime.UtcNow;

    try
    {
        // note: DateTime comparisons are not atomic so don't do this when not locked
        // we're in a reader lock here so it's okay
        if (storedToken.ExpiryTime >= currentTime)
        {
            // need to release reader lock before switching to write mode
            rLock.Dispose();

            // only one thread at a time can enter the writer lock, and all threads must be out of the reader locks
            wLock = await rwLock.WriterLockAsync();
            // check again here in case another thread has already refreshed while we were waiting on a writer lock
            if (storedToken.ExpiryTime >= currentTime)
            {
                storedToken = await FetchNewToken();
            }
            wLock.Dispose();

            // re-acquire reader lock
            rLock = await rwLock.ReaderLockAsync();
        }

        return storedToken;
    }
    finally
    {
        rLock?.Dispose();
        wLock?.Dispose();
    }
}
deleted 50 characters in body
Source Link
Bob
  • 587
  • 2
  • 8

For clarity, because apparently it needs to be said, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; obviously make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.

For clarity, because apparently it needs to be said, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; obviously make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.

For clarity, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.

Be clear that the answer contains pseudocode, because apparently that's necessary now
Source Link
Bob
  • 587
  • 2
  • 8

For clarity, because apparently it needs to be said, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; obviously make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.


For clarity, because apparently it needs to be said, the code examples in this answer are pseudocode intended to demonstrate my recommendations for login flow and general structure. They are not intended to be syntactically-correct directly runnable examples; obviously make sure return types etc. are valid and name the methods in accordance to whichever style guide you use.


Source Link
Bob
  • 587
  • 2
  • 8
Loading