У меня есть веб-приложение, которое контролирует, какие веб-приложения получают трафик от нашего балансировщика нагрузки. Веб-приложение работает на каждом отдельном сервере.
Он отслеживает состояние «включено или выключено» для каждого приложения в объекте в состоянии приложения ASP.NET, и объект сериализуется в файл на диске при каждом изменении состояния. Состояние десериализуется из файла при запуске веб-приложения.
Хотя сам сайт получает только пару запросов на получение второй вершины, и файл, к которому он редко обращается, я обнаружил, что по какой-то причине было очень легко получить коллизии при попытке чтения или записи в файл. Этот механизм должен быть чрезвычайно надежным, потому что у нас есть автоматизированная система, которая регулярно выполняет развертывание на сервере.
Прежде чем кто-либо сделает какие-либо комментарии, ставящие под сомнение благоразумие любого из вышеперечисленного, позвольте мне просто сказать, что объяснение причин этого сделало бы этот пост намного длиннее, чем он уже есть, поэтому я бы хотел избежать перемещения гор.
Тем не менее код, который я использую для контроля доступа к файлу, выглядит следующим образом:
internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no
/// locking collisions caused by attempting to save and load the file simultaneously
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />.
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
var l = new Logger();
if (ServerState._lock.WaitOne(1500, false))
{
l.LogInformation( "Got lock to read/write file-based server state."
, (Int32)VipEvent.GotStateLock);
var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
, FileAccess.ReadWrite, FileShare.None);
result = func.Invoke(fileStream);
fileStream.Close();
fileStream.Dispose();
fileStream = null;
ServerState._lock.ReleaseMutex();
l.LogInformation( "Released state file lock."
, (Int32)VipEvent.ReleasedStateLock);
return true;
}
else
{
l.LogWarning( "Could not get a lock to access the file-based server state."
, (Int32)VipEvent.CouldNotGetStateLock);
result = null;
return false;
}
}
Этот обычно работает, но иногда я не могу получить доступ к мьютексу (я вижу событие "Не удалось получить блокировку" в журнале). Я не могу воспроизвести это локально - это происходит только на моих производственных серверах (Win Server 2k3 / IIS 6). Если я удаляю тайм-аут, приложение зависает на неопределенное время (состояние гонки ??), в том числе при последующих запросах.
Когда я получаю ошибки, просмотр журнала событий показывает, что блокировка мьютекса была достигнута и снята предыдущим запросом до , когда ошибка была зарегистрирована.
Мьютекс создается в событии Application_Start. Я получаю те же результаты, когда он статически создается в объявлении.
Извините, извините: многопоточность / блокировка - это не моя удача, так как мне, как правило, об этом не нужно беспокоиться.
Какие-либо предложения относительно того, почему он случайно не сможет получить сигнал?
Обновление:
Я добавил правильную обработку ошибок (как неловко!), Но я все еще получаю те же ошибки - и для записи, необработанные исключения никогда не были проблемой.
Только один процесс будет когда-либо получать доступ к файлу - я не использую веб-сад для веб-пула этого приложения, и никакие другие приложения не используют этот файл. Единственное исключение, о котором я могу подумать, - это когда пул приложений перезагружается, а старый WP все еще открыт при создании нового, но по наблюдению за диспетчером задач я могу сказать, что проблема возникает, когда существует только один рабочий процесс.
@ mmr: Чем отличается использование Monitor от Mutex? Основываясь на документации MSDN, похоже, что он фактически делает то же самое - если и я не могу получить блокировку с помощью моего Mutex, он действительно терпит неудачу, просто возвращая false.
Еще один момент, на который следует обратить внимание: проблемы, которые у меня возникают, кажутся совершенно случайными - если при одном запросе происходит сбой, то при следующем может нормально работать. Кажется, что здесь тоже нет паттерна (по крайней мере, ни одного другого).
Обновление 2:
Эта блокировка не используется для любых других вызовов. Единственный раз, когда на _lock ссылаются вне метода InvokeOnFile, - это когда он создается.
Вызываемый Func либо читает файл и десериализует его в объект, либо сериализует объект и записывает его в файл. Ни одна операция не выполняется в отдельном потоке.
ServerState.PATH - это статическое поле только для чтения, которое, как я ожидаю, не вызовет проблем с параллелизмом.
Я также хотел бы повторить мою прежнюю мысль о том, что я не могу воспроизвести это локально (в Кассини).
Извлеченные уроки:
- Используйте правильную обработку ошибок (дух!)
- Используйте правильный инструмент для работы (и имейте общее представление о том, что / как этот инструмент делает). Как указывает самбо, использование Mutex, по-видимому, связано с большими накладными расходами, что вызывало проблемы в моем приложении, тогда как Monitor разработан специально для .NET.