Как сделать потокобезопасный вызов стороннего API (OAuth)? - PullRequest
1 голос
/ 01 июня 2011

Я звоню стороннему API, который использует OAuth для аутентификации, и мне интересно, как сделать этот потокобезопасным:

var token = _tokenService.GetCurrentToken(); // eg token could be "ABCDEF"
var newToken = oauth.RenewAccessToken(token); // eg newToken could be "123456"
_tokenService.UpdateCurrentToken(newToken); // save newToken to the database

Для этого нужно использовать предыдущий токен каждый раз, когда вызывается RenewAccessToken(). Но есть проблема, если два пользователя инициируют это одновременно (два разных потока будут запускать код в одно и то же время), и мы получим этот код, выполняемый в следующем порядке:

[Thread 1] var token = _tokenService.GetCurrentToken(); // returns "ABCDEF"
[Thread 2] var token = _tokenService.GetCurrentToken(); // returns "ABCDEF"
[Thread 1] var newToken = oauth.RenewAccessToken("ABCDEF"); // returns "123456"
[Thread 2] var newToken = oauth.RenewAccessToken("ABCDEF"); 
           // throws an invalid token exception

Произошло то, что в потоке 2 он на самом деле должен вызывать oauth.RenewAccessToken("123456"); (потому что это последнее значение токена. Но последний токен еще даже не сохранен в базе данных, поэтому поток 2 всегда имеет неправильное значение для текущего токена.

Что я могу сделать, чтобы это исправить?

Редактировать: было предложено использовать блокировку следующим образом:

private object tokenLock = new object();
lock(tokenLock)
{
    var token = _tokenService.GetCurrentToken(); 
    var newToken = oauth.RenewAccessToken(token); 
    _tokenService.UpdateCurrentToken(newToken);
}

Редактировать 2: Блокировка на самом деле не работает, это из моих журналов:

[43 22:38:26:9963] Renewing now using token JHCBTW1ZI96FF 
[36 22:38:26:9963] Renewing now using token JHCBTW1ZI96FF 
[36 22:38:29:1790] OAuthException exception

Первое число - это идентификатор потока, а второе - метка времени. Оба потока выполняются в одно и то же время с точностью до миллисекунд. Я не знаю, почему блокировка не смогла остановить поток 36, пока не закончится поток 43.

Редактировать 3: И снова, на этот раз после изменения object tokenLock на переменную класса вместо локальной переменной блокировка не сработала.

[25 10:53:58:3870] Renewing now using token N95984XVORY
[9 10:53:58:3948] Renewing now using token N95984XVORY
[9 10:54:55:7981] OAuthException exception

1 Ответ

4 голосов
/ 01 июня 2011

РЕДАКТИРОВАТЬ

Учитывая, что это приложение ASP.NET, простой маршрут (блокировка Monitor с использованием блока lock { }) не подходит. Вам нужно использовать именованный Mutex для решения этой проблемы.

Учитывая ваш пример кода, что-то вроде этого будет работать:

using(var m = new Mutex("OAuthToken"))
{
    m.WaitOne();

    try
    {
        var token = _tokenService.GetCurrentToken(); 
        var newToken = oauth.RenewAccessToken(token); 
        _tokenService.UpdateCurrentToken(newToken);
    }
    finally
    {
        m.ReleaseMutex();
    }
}

Обратите внимание на предложение finally; очень важно, чтобы вы освободили мьютекс. Поскольку это общесистемный объект, его состояние будет сохраняться за пределами вашего приложения. Если вы столкнетесь с исключением в указанном выше коде OAuth, вы не сможете повторно ввести код, пока система не будет перезапущена.

Кроме того, если у вас есть некоторый долговременный идентификатор для сеансов, которые используют один и тот же токен OAuth (что-то, что не будет изменено в результате этого процесса), вы можете использовать этот токен в качестве имени мьютекса вместо "OAuth" как у меня выше. Это сделало бы синхронизацию специфичной для данного токена, поэтому вам не придется беспокоиться о том, что операциям придется ждать обновления несвязанных токенов. Это должно компенсировать увеличение стоимости мьютекса по сравнению с Monitor замком.

Ради помощи тем, кто может найти этот вопрос, я оставил свой первоначальный ответ ниже:

Оригинальный ответ

Вам просто нужен простой замок вокруг вашей операции.

Создать экземпляр (или статическую, если эти функции статические) переменную типа object:

private object tokenLock = new object();

В своем коде заключите шаги, которые должны быть атомарными, в блок lock(tokenLock):

lock(tokenLock)
{
    var token = _tokenService.GetCurrentToken(); 
    var newToken = oauth.RenewAccessToken(token); 
    _tokenService.UpdateCurrentToken(newToken);
}

Это не позволит одному потоку запустить этот процесс, пока другой выполняет его.

...