Сегодня мне попался весьма любопытный баг: Docker for Windows не запустится, если у вас запущена панель управления драйвером Razer Synapse.
Но интереснее всего то, почему так случилось…
Оба приложения написаны так, что можно одновременно запустить только один экземпляр. Для этого они создают глобальный мьютекс, используя в качестве ключа GUID сборки .NET, да?
Но увы, они делают это неправильно, и более того — одинаково неправильно. Ошибочный код выглядит примерно так:
string.Format("Global{0}", (object) Assembly.GetExecutingAssembly().GetType().GUID);
Идея заключается в том, чтобы получить GUID исполняемой сборки и создать на его основе мьютекс, который не даст запустить больше одного экземпляра.
Но должно быть не так. Там не должно быть вызова GetType()
.
В этом варианте возвращается не GUID конкретной сборки, а GUID встроенного в .NET типа, который описывает сборки как таковые — System.Reflection.RuntimeAssembly
.
Поэтому, когда они создают мьютексы, они используют не GUID из их собственного кода, а GUID из внутренностей .NET. И для обоих приложений этот GUID будет одинаковым.
Как же это случилось? Забавно, но мы в точности знаем, как. Всему виной Stackoverflow!
Еще в 2009 году пользователь c ником Nathan задал вопрос — "как получить GUID выполняемой сборки?". Через 12 минут ему ответил пользователь Cerebrus, но в его ответе была ошибка.
Спустя год и месяц, пользователь Yoopergeek указывает на ошибку. Cerebrus возвращается спустя еще три года и исправляет свой ответ — но удалить его уже нельзя, потому что ответ был помечен как «принятый».
Таким образом, ошибка в ответе на вопрос в 2009 году вызывала баг, просуществовавший как минимум до марта 2018.
Домашнее задание для всех программистов, читающих данный пост: подумайте, как бы вы обнаружили такой баг в своем приложении? Вы копипастите код, он вроде работает, но вы даже не догадываетесь, что на самом деле он сломан: вы же не запускаете две программы с одинаковой ошибкой одновременно. О проблеме вам станет известно, только когда пользователи начнут жаловаться.
Как можно было бы поменять процесс разработки, чтобы обнаруживать подобные ошибки до релиза?
Автор: Андрей