Что мешало найти? Как вы его отследили?
Недостаточно близко, чтобы закрыть, но также см.
https://stackoverflow.com/questions/175854/what-is-the-funniest-bug-youve-ever-experienced

Хотя я не могу вспомнить конкретный случай, самой сложной категорией являются те ошибки, которые проявляются только после того, как система проработала в течение нескольких часов или дней, и когда она выходит из строя, не оставляет почти никаких следов того, что вызвало сбой. Что делает их особенно плохими, так это то, что независимо от того, насколько хорошо вы думаете, что выяснили причину и применили соответствующее исправление для ее устранения, вам придется подождать еще несколько часов или дней, чтобы получить хоть какую-то уверенность в том, что вы я действительно прибил это.
И неизменно требуется как минимум 3 итерации этого, чтобы действительно исправить правильную ошибку ...
У меня есть ошибка, которая возникает случайно и примерно раз в месяц. Каждый раз, когда я думаю, что исправил это, это меня кусает. Эта единственная ошибка заставила меня исправить уже около 50 различных ошибок, но не эту ошибку.
Многопоточные приложения, в которых выполняется отладка, - это нормально, но как только вы запускаете их в выпуске, все идет не так из-за немного другого времени. Даже добавление вызовов Console.WriteLine к основной отладке продукта вызывало достаточное изменение времени, чтобы оно работало и не отображало проблему. Инструмент в неделю, чтобы найти и исправить пару строк кода, которые необходимо изменить.
В последнее время у меня было обратное. Прекрасно работает в режиме выпуска, но когда мне нужно перейти в режим отладки, чтобы что-то проверить, все идет не так.
@Grauenwolf - это значит, что рано или поздно он начнет лажать с выпуском!
Не уверен, что это самый сложный вариант, но несколько лет назад у меня была программа на Java, в которой использовался XMLEncoder для сохранения / загрузки определенного класса. По какой-то причине класс не работал должным образом. Я выполнил простой двоичный поиск ошибки и обнаружил, что ошибка возникает после одного вызова функции, но до другого вызова, что должно было быть невозможно. Через 2 часа я не понял этого, хотя в тот момент, когда я сделал перерыв (и уезжал), я понял проблему. Оказалось, что XMLEncoder создавал экземпляр класса, созданный по умолчанию, вместо того, чтобы класс и ссылка на класс ссылались на один и тот же объект. Итак, хотя я думал, что два вызова функций выполняются для членов одного и того же экземпляра определенного класса, на самом деле один был в копии, созданной по умолчанию.
Было сложно найти, поскольку я знал, они оба были ссылками на один и тот же класс.
У меня была ошибка в консольной игре, которая возникала только после того, как вы сражались и выиграли длительную битву с боссом, и то только примерно 1 раз из 5. Когда она срабатывала, она оставляла оборудование на 100% заблокированным и неспособным разговаривать с внешним миром совсем.
Это была самая застенчивая ошибка, с которой я когда-либо сталкивался; изменение, автоматизация, инструментирование или отладка битвы с боссом скрыли бы ошибку (и, конечно, мне пришлось бы сделать 10-20 прогонов, чтобы определить, что ошибка скрыта).
В конце концов, я обнаружил проблему (проблема с кешем / DMA / прерыванием), читая код снова и снова в течение 2-3 дней.
и после бесчисленных часов сражения с боссом! +1
Ошибка, при которой вы сталкиваетесь с некоторым кодом и после его изучения делаете вывод: «Это никак не могло сработать!» и вдруг он перестает работать, хотя раньше всегда работал.
Я все время это вижу, очень странно.
Также известен как Schroedinbug. Не уверен, что это можно назвать «жестким».
Я согласен - я видел это снова и снова за последние 20 лет.
такое случается часто, но обычно, как только вы это понимаете, вы более осторожны с шагами воспроизведения или входными данными.
Со мной обычно бывает наоборот. Код не работает, а затем я просматриваю его, просматриваю и прихожу к выводу, что он ДОЛЖЕН работать. И тогда это происходит.
@Nick: со мной уже дважды случалось (обратите внимание, что я очень молодой программист): первая проблема возникла из-за ленивого характера IEnumerable <T> (строго типизированная последовательность в .NET); второй - проблема многопоточности (я полагаю, эксперты назвали бы это «состоянием гонки», но я могу ошибаться).
Самую сложную ошибку, которую мне когда-либо приходилось исправлять, я поднял сам - я работал тестировщиком в крупной телекоммуникационной компании, тестируя продукт другой компании. Несколько лет спустя у меня был контракт с другой компанией, и первое, что они дали мне, были ошибки, которые я сам поднял.
Это было состояние гонки ядра во встроенной операционной системе, написанной на ассемблере 6809 и BCPL. Среда отладки состояла из специального printf, который записывал на последовательное устройство; в этой настройке нет причудливых вещей IDE.
На то, чтобы это исправить, потребовалось некоторое время, но когда я, наконец, остановился, это было огромным удовольствием.
Грязный сбой в приложении с графическим интерфейсом, написанном на Turbo Pascal. За три дня до того, как я обнаружил, с помощью одного шага в отладчике на уровне машинного кода, поверх простого и, очевидно, правильного кода, я помещал 16-битное целое число в стек вызовов для функции, ожидающей 32-битной (или некоторой такое несоответствие)
Теперь я поступил мудро, хотя современные компиляторы больше не допускают подобных проблем.
давным-давно я написал объектно-ориентированный язык с использованием C и (символьной) библиотеки форм; каждая форма была объектом, формы могли содержать подчиненные формы и так далее. Сложное приложение для выставления счетов, написанное с использованием этого, будет нормально работать около 20 минут, затем на экране то и дело будут появляться случайные символы мусора. Через несколько минут использования приложения компьютер перезагружался, зависал или что-то серьезное.
это оказалось плохим освобождением из-за неверно направленного делегирования в механизме обработки сообщений; неправильно перенаправленные сообщения делегировались вверх по дереву включения, когда у нас заканчивались суперклассы, и иногда родительские объекты имели методы с одинаковыми именами, поэтому казалось, что они работают большую часть времени. В остальное время он освобождает небольшой буфер (8 байтов или около того) в неправильном контексте. Неправильно освобожденный указатель на самом деле был мертвой памятью, используемой промежуточным счетчиком для другой операции, поэтому его значение со временем стремится к нулю.
да, плохой указатель будет пересекать область карты памяти экрана на своем пути к нулевой странице, где он в конечном итоге перезаписывает вектор прерывания и убивает ПК
это было задолго до появления современных средств отладки, поэтому выяснение того, что происходит, заняло пару недель ...
Наш сетевой интерфейс, ATM-карта с поддержкой DMA, очень иногда доставляет поврежденные данные в полученных пакетах. CRC AAL5 была проверена как правильная, когда пакет пришел по сети, но данные DMAd в память были неправильными. Контрольная сумма TCP обычно улавливает это, но в те бурные дни, когда люди работали с банкоматами, были в восторге от запуска собственных приложений непосредственно на AAL5, полностью отказавшись от TCP / IP. В конце концов мы заметили, что повреждение произошло только на некоторых моделях рабочих станций поставщика (которые останутся безымянными), а не на других.
Вычислив CRC в программном обеспечении драйвера, мы смогли обнаружить поврежденные пакеты за счет огромного снижения производительности. Пытаясь отладить, мы заметили, что если мы просто сохраним пакет какое-то время и вернемся, чтобы посмотреть на него позже, повреждение данных волшебным образом исчезнет. С содержимым пакета все будет в порядке, и если драйвер вычислит CRC во второй раз, он выдаст ошибку.
Мы обнаружили ошибку в кэше данных поставляемого ЦП. Кэш в этом процессоре не был согласован с DMA, что требовало, чтобы программное обеспечение явно очищало его в надлежащее время. Ошибка заключалась в том, что иногда кеш фактически не сбрасывал свое содержимое, когда его просили сделать это.
Возникает вопрос: что побудило вас хранить пакет какое-то время, а потом проверять CRC?
Сначала мы помещаем проверку CRC в обработчик прерывания, и он обнаружит поврежденные пакеты. Под нагрузкой это также приведет к сбросу системы из-за слишком большого количества времени на уровне прерывания, поэтому мы переместили его в асинхронный поток ядра ... и задержка в вычислении CRC имела значение.
Для этого нужно немного знать ассемблер Z-8000, который я объясню по ходу дела.
Я работал над встроенной системой (на ассемблере Z-8000). Другое подразделение компании создавало другую систему на той же платформе и написало библиотеку функций, которую я также использовал в своем проекте. Ошибка заключалась в том, что каждый раз, когда я вызывал одну функцию, программа падала. Я проверил все свои данные; они были в порядке. Это должно быть ошибка в библиотеке - за исключением того, что библиотека использовалась (и работала нормально) на тысячах POS-сайтов по всей стране.
Теперь процессоры Z-8000 имеют 16 16-битных регистров, R0, R1, R2 ... R15, которые также могут быть адресованы как 8 32-битных регистров с именами RR0, RR2, RR4..RR14 и т. д. Библиотека была написана с нуля, рефакторинг кучи старых библиотек. Он был очень чистым и соответствовал строгим стандартам программирования. В начале каждой функции каждый регистр, который будет использоваться в функции, помещается в стек для сохранения своего значения. Все было аккуратно и аккуратно - идеально.
Тем не менее, я изучил листинг ассемблера для библиотеки и заметил что-то странное в этой функции: в начале функции было PUSH RR0 / PUSH RR2, а в конце было POP RR2 / POP R0. Теперь, если вы этого не сделали, он поместил 4 значения в стек в начале, но удалил только 3 из них в конце. Это рецепт катастрофы. Неизвестное значение наверху стека, где должен быть адрес возврата. Функция не могла работать.
Кроме того, позвольте мне напомнить вам, что это БЫЛО работало. Его вызывали тысячи раз в день на тысячах машин. Это не могло НЕ работать.
После некоторого времени отладки (что было нелегко в ассемблере на встроенной системе с инструментами середины 1980-х), он всегда вылетал при возврате, потому что неправильное значение отправляло его на случайный адрес. Очевидно, мне пришлось отлаживать работающее приложение, чтобы понять, почему оно не дает сбоев.
Что ж, помните, что библиотека очень хорошо сохраняла значения в регистрах, поэтому, как только вы помещаете значение в регистр, оно остается там. В R1 было 0000. При вызове этой функции в нем всегда будет 0000. Таким образом, ошибка оставила 0000 в стеке. Поэтому, когда функция вернется, она перейдет к адресу 0000, который, как оказалось, был RET, который вытолкнет следующее значение (правильный адрес возврата) из стека и перейдет к нему. Данные отлично замаскировали ошибку.
Конечно, в моем приложении у меня было другое значение в R1, поэтому оно просто вылетало ....
Я должен спросить - является ли местоположение 0000 специальным на Z-80000 (как векторы прерывания i86) и как кодируется код операции RET? Какая отличная отладка.
Не помню, были ли у Z-8000 такие векторы прерываний. 8080 и Z-80 сделали, так что это возможно. Если да, то мы их не использовали.
Сколько времени вам понадобилось, чтобы понять это?
Со мной этого не случилось, но друг рассказал мне об этом.
Ему пришлось отлаживать приложение, которое очень редко давало сбой. Выходит из строя только по средам - в сентябре - после 9 числа. Да, 362 дня в году, это нормально, и три дня в году он сразу ломался.
Он отформатировал бы дату как «среда, 22 сентября 2008 г.», но буфер был на один символ слишком коротким, поэтому это могло вызвать проблему только тогда, когда у вас был двухзначный DOM в день с самым длинным именем в месяце с самое длинное имя.
Престижность! Это действительно крутая ошибка
Вау. Те, что идут в мой альбом для вырезок.
Вау, это чертовски ошибка! Как долго твой друг наткнулся на это?
У нас было нечто похожее. Буфер в стеке, длина которого не превышает одного байта. Дата и время форматировались начальными нулями без. Только для месяцев> 9, дней> 9, часов> 9, минут> 9, секунд> 9 конечный нулевой байт будет шагать по старшему байту (это было 68k) другого значения в стеке. И это имело значение только в том случае, если в этом значении еще не было 0 в и, если подпрограмма не завершилась раньше по другим причинам. Это было в собственной многопоточной среде. На его поиски ушло больше двух недель.
Моя команда унаследовала многопоточное веб-приложение на C++ на основе CGI. Основной платформой была Windows; отдаленной вторичной платформой был Solaris с потоками Posix. По какой-то причине стабильность на Solaris была катастрофой. У нас были разные люди, которые смотрели на проблему больше года, то и дело (в основном от случая к случаю), в то время как наши сотрудники отдела продаж успешно продвинули версию для Windows.
Симптомом была жалкая стабильность: широкий спектр системных сбоев без малейшей причины или рифмы. Приложение использовало как Corba, так и собственный протокол. Один разработчик зашел так далеко, что в качестве отчаянной меры удалил всю подсистему Corba: не повезло.
Наконец, один старший разработчик вслух задумался об идее. Мы изучили его и в конце концов обнаружили проблему: в Solaris был параметр времени компиляции (или времени выполнения?), Позволяющий регулировать размер стека для исполняемого файла. Он был выставлен неправильно: слишком маленький. Итак, приложение исчерпало стек и печатало трассировки стека, которые были отвлекающими маневрами.
Это был настоящий кошмар.
Уроки выучены:
Была ошибка на платформе с очень плохим отладчиком устройства. Мы получили бы крушение на устройстве если мы добавили printf к коду. Тогда он вылетел бы в другом месте, а не в том месте, где находится printf. Если мы переместили printf, авария переместится или исчезнет. Фактически, если бы мы изменили этот код, переупорядочив некоторые простые инструкции, сбой произошел бы там, где это не было связано с кодом, который мы действительно изменили.
Оказывается, был ошибка в релокаторе для нашей платформы., перестановщик не был нулевым, инициализируя секцию ZI, а скорее использовал таблицу перемещения для инициализации значений. Таким образом, каждый раз, когда таблица перемещения изменяется в двоичном файле, ошибка перемещается. Таким образом, простое добавление printf изменило бы таблицу перемещения для ошибки.
В старом TRS-80 память инициализировалась чередующимися блоками 0x00 и 0xff, каждый (IIRC) длиной 64 или 256 байтов. Одна программа перестала работать, когда автор добавил заставку. Он полагался на неинициализированную переменную, которая была помещена в область 0xFF трехбайтовой инструкцией CALL для вызова процедуры экрана-заставки.
У нас была такая же проблема с загрузчиком, неправильно рассчитывающим размеры и т. д., А также с проблемами печати из-за заказа и т. д.
Во-первых, в нашем выпущенном продукте была обнаружена ошибка, но когда я попытался ее отладить, она не возникла. Сначала я думал, что это «выпуск против отладки», но даже когда я скомпилировал код в режиме выпуска, я не смог воспроизвести проблему. Я пошел посмотреть, сможет ли другой разработчик воспроизвести проблему. Неа. После долгого исследования (создание смешанного ассемблерного кода / листинга кода C) вывода программы и пошагового прохождения ассемблерного кода выпущенного продукта (фу!) Я обнаружил некорректную строку. Но линия мне очень понравилась! Затем мне пришлось посмотреть, что делали инструкции по сборке - и, конечно же, в выпущенном исполняемом файле была неправильная инструкция по сборке. Затем я проверил исполняемый файл, созданный моей средой сборки - в нем была правильная инструкция по сборке. Оказалось, что машина сборки каким-то образом испортилась и выдала плохой ассемблерный код только для одной инструкции для этого приложения. Все остальное (включая предыдущие версии нашего продукта) производило идентичный код для других машин разработчиков. После того, как я показал свое исследование менеджеру программного обеспечения, мы быстро перестроили нашу машину сборки.
Надеюсь, он пригласил вас выпить и повысил зарплату ...
Престижность за обнаружение этой особенно неприятной ошибки. Может быть, ЕСС RAM могла бы помочь?
Это заслуживает того, чтобы быть намного выше, чем есть
Один из продуктов, который я помогал создавать в своей работе, работал на сайте клиента в течение нескольких месяцев, собирая и успешно записывая каждое полученное событие в базу данных SQL Server. Он работал очень хорошо около 6 месяцев, собрав около 35 миллионов записей.
Однажды наш клиент спросил нас, почему база данных не обновлялась почти две недели. При дальнейшем исследовании мы обнаружили, что соединение с базой данных, которое выполняло вставки, не смогло возвратиться из вызова ODBC. К счастью, поток, который выполняет запись, был отделен от остальных потоков, что позволило всему, кроме потока записи, продолжать правильно работать в течение почти двух недель!
Несколько недель подряд мы пытались воспроизвести проблему на любой другой машине, кроме этой. Мы никогда не могли воспроизвести проблему. К сожалению, несколько других наших продуктов затем начали выходить из строя примерно таким же образом, ни один из которых не отделил потоки базы данных от остальной части их функций, что привело к зависанию всего приложения, которое затем приходилось перезапускать вручную каждый раз, когда они разбился.
Недели расследования превратились в несколько месяцев, и у нас все еще были те же симптомы: полное зависание ODBC в любом приложении, которое использовало базу данных. К этому времени наши продукты изобилуют отладочной информацией и способами определить, что и где пошло не так, вплоть до того, что некоторые из продуктов обнаруживают тупик, собирают информацию, отправляют нам результаты по электронной почте и затем перезапускают себя.
Однажды работая на сервере, все еще собирая отладочную информацию из приложений, когда они падали, пытаясь понять, что происходит, сервер BSoD на мне. Когда сервер снова подключился к сети, я открыл минидамп в WinDbg, чтобы выяснить, что это за драйвер. Я получил имя файла и проследил его до самого файла. Изучив информацию о версии в файле, я понял, что это часть антивирусного пакета McAfee, установленного на компьютере.
Мы отключили антивирус, и с тех пор не было ни одной проблемы !!
Ура - продукты AV и серверы никогда не должны контактировать друг с другом
Макафи ужасен ... Я не знал, что это ТАКОЕ зло!
У нас здесь есть похожие, совершенно случайные проблемы почти всех видов, и все они восходят к антивирусному программному обеспечению.
Я просто хочу указать на довольно распространенную и неприятную ошибку, которая может произойти в этой области Google:
вставка кода и пресловутый минус
Это когда вы скопируйте и вставьте какой-нибудь код с минус в нем вместо обычного символа ASCII дефис-минус ('-').
![]()
Плюс, минус (U + 2212), дефис-минус (U + 002D)
Теперь, даже несмотря на то, что минус предположительно отображается как более длинный, чем дефис-минус, в некоторых редакторах (или в окнах оболочки DOS), в зависимости от используемой кодировки, он фактически отображается как обычный знак дефис-минус '-'.
И ... вы можете часами пытаться понять, почему этот код не компилируется, удаляя каждую строку одну за другой, пока не найдете истинную причину!
Возможно, это не самая сложная ошибка, но достаточно неприятная;)
(Спасибо ShreevatsaR за обнаружение инверсии в моем исходном посте - см. Комментарии)
Так, может быть, лучше написать код с нуля?
Что ж, когда вы копируете код, это как раз с намерением создать код нет с нуля;)
Я рекомендую бинарный, а не линейный поиск такого рода вещей.
Вместо того, чтобы удалять каждую строку по одной, выполните двоичный поиск. Это сэкономит вам время :-)
@Jasper Это означает, что вы уже хорошо представляете потенциального виновника. Но когда вы даже не подозреваете, что в вашем коде может быть двоичный символ (т.е. когда вы не знаете, что искать) ... вернемся к старомодному методу отладки: упрощение.
У меня было несколько непечатаемых или кажущихся нормальными символьных ошибок, которые не устранялись, пока я не набирал весь раздел вручную с распечатки.
Вау, у меня была очень похожая проблема: копирование текста из Adobe InDesign в (обычный текст) XML-файл для использования на веб-сайте. В половине случаев сообщалось, что XML искажен из-за дефисов и многоточий. В большинстве случаев недопустимые символы были очевидны ... кроме того, что это был какой-то символ другой тип пространства !! Оказывается, есть по крайней мере 5 разных пробелов, которые мне хорошо показала статья «A List Apart».
Этот же минус-символ нарушает постоянные ссылки wordpress, которые вставлялись людьми. Этот знак усвоен на собственном горьком опыте. Спасибо, что поделился,
Flash делает то же самое с ActionScript ... если вы случайно удерживаете модификатор и создаете пробел, это приведет к тому, что компилятор испугается синтаксической ошибкой ... но вы можете сидеть там весь день, пытаясь найти видимую синтаксическую ошибку.
Эта ошибка становится еще лучше, если вы случайно вставляете неразрывный пробел (U + 00A0, знаменитый символ ).
Тупик в моей первой многопоточной программе!
Найти его было очень сложно, потому что это происходило в пуле потоков. Иногда поток в пуле блокировался, но другие продолжали работать. Поскольку размер пула был намного больше необходимого, потребовалась неделя или две, чтобы заметить первый симптом: приложение полностью зависло.
Две самые серьезные ошибки, которые приходят на ум, были связаны с одним и тем же типом программного обеспечения, только одна была в веб-версии, а другая - в версии для Windows.
Этот продукт является программой просмотра / редактирования поэтажных планов. Веб-версия имеет интерфейс для флэш-памяти, который загружает данные в формате SVG. Теперь это работало нормально, только иногда браузер зависал. Только на нескольких рисунках, и только когда вы ненадолго наведете указатель мыши на рисунок. Я сузил проблему до одного слоя рисования, содержащего 1,5 МБ данных SVG. Если бы я взял только часть данных, любой подраздел, зависания не произошло. В конце концов меня осенило, что проблема, вероятно, заключалась в том, что в файле было несколько разных разделов, которые в совокупности вызывали ошибку. Разумеется, после случайного удаления разделов слоя и тестирования на наличие ошибки я обнаружил оскорбительную комбинацию операторов рисования. Я написал обходной путь в генераторе SVG, и ошибка была исправлена без изменения строки скрипта действий.
В том же продукте на стороне Windows, написанном на Delphi, у нас была сопоставимая проблема. Здесь продукт берет файлы AutoCAD DXF, импортирует их во внутренний формат чертежа и отображает их в пользовательском механизме рисования. Эта процедура импорта не особенно эффективна (в ней используется много копий подстрок), но она выполняет свою работу. Только в этом случае этого не было. Файл размером 5 мегабайт обычно импортируется за 20 секунд, но для одного файла потребовалось 20 минут, поскольку объем памяти увеличился до гигабайта или более. Сначала это выглядело как типичная утечка памяти, но инструменты утечки памяти сообщили, что она чистая, и ручная проверка кода тоже ничего не дала. Проблема оказалась ошибкой в распределителе памяти Delphi 5. В некоторых случаях, когда этот конкретный файл воссоздавался должным образом, он был бы подвержен серьезной фрагментации памяти. Система будет пытаться выделить большие строки и не найдет места для их размещения, кроме как выше самого высокого выделенного блока памяти. Интеграция новой библиотеки распределения памяти устранила ошибку без изменения строки кода импорта.
Оглядываясь назад, можно сказать, что самые сложные ошибки - это те, исправление которых связано с изменением части системы, отличной от той, в которой возникает проблема.
«Самые серьезные ошибки кажутся теми, исправление которых связано с изменением части системы, отличной от той, в которой возникает проблема». Как очень верно. Найти ошибку в вашем ПО труднее всего, если ошибка даже не в ПО ваш ...
Не один из моих, а мой коллега на предыдущем месте работы потратил 3 дня на отладку своего элемента управления всплывающего редактора JavaScript (это было довольно давно, до радостей фреймворков), только чтобы обнаружить, что на полпути отсутствует одна точка с запятой. один из его огромных файлов ядра.
Мы назвали это «самой дорогой точкой с запятой в мире», но я уверен, что на протяжении всей истории было намного хуже!
Есть история того периода, когда был сбит Mariner I, но это якобы городская легенда - ошибка реальна, но она была обнаружена во время тестирования и не была виновата в поломке. Другие источники утверждают, что отсутствующая верхняя планка была проблемой Mariner I - en.wikipedia.org/wiki/Mariner_1
Когда домашний кролик клиента частично обгрыз сетевой кабель. да. Это было плохо.
Неа. Честная правда. Дважды переустанавливал окна. IIRC. Проверял, плотно ли вставлен кабель и т. д. Только случайно мы обнаружили обглоданный участок кабеля - он был около дюйма шириной только с одной стороны, но прорезал некоторые внутренних проводов. Главный ужас заключался в том, что часть сетевого функционала работала ...
Нарушение памяти кучи в элементе управления редактированием текста, который я использовал. После многих месяцев (...) поиска я нашел решение, работающее с другим программистом, отлаживающим проблему одноранговым узлом. Именно этот случай убедил меня в ценности работы в командах и Agile в целом. Подробнее об этом на мой блог
Это было в Linux, но могло произойти практически в любой ОС. Теперь большинство из вас, вероятно, знакомы с API сокетов BSD. Мы с удовольствием пользуемся им из года в год, и он работает.
Мы работали над массово параллельным приложением, в котором было бы открыто много сокетов. Для проверки его работы у нас была команда тестировщиков, которая открывала сотни, а иногда и более тысячи соединений для передачи данных. При максимальном количестве каналов наше приложение начинает показывать странное поведение. Иногда просто вылетало. В другой раз мы получили ошибки, которые просто не могли быть правдой (например, accept () возвращал тот же файловый дескриптор при последующих вызовах, что, конечно, привело к хаосу.)
Мы могли видеть в файлах журнала, что что-то пошло не так, но это было безумно сложно определить. Тесты с Rational Purify показали, что все в порядке. Но что-то БЫЛО НЕ ТАК. Мы работали над этим несколько дней и разочаровывались все больше. Это был шоублокер, потому что уже согласованный тест вызовет хаос в приложении.
Поскольку ошибка возникала только в ситуациях с высокой нагрузкой, я перепроверил все, что мы делали с сокетами. Мы никогда не тестировали случаи высокой нагрузки в Purify, потому что это было невозможно в такой ситуации с интенсивным использованием памяти.
Наконец (и к счастью) я вспомнил, что огромное количество сокетов может быть проблемой с select (), который ожидает изменения состояния на сокетах (может читать / может писать / ошибаться). Разумеется, наше приложение начало сеять хаос в тот момент, когда оно достигло сокета с дескриптором 1025. Проблема в том, что select () работает с параметрами битового поля. Битовые поля заполняются макросами FD_SET () и друзьями, которые НЕ ПРОВЕРЯЮТ ИХ ПАРАМЕТРЫ НА ДЕЙСТВИТЕЛЬНОСТЬ.
Таким образом, каждый раз, когда мы получаем более 1024 дескрипторов (каждая ОС имеет свой предел, ядра Linux vanilla имеют 1024, фактическое значение определяется как FD_SETSIZE), макрос FD_SET с радостью перезаписывает свое битовое поле и записывает мусор в следующую структуру в памяти.
Я заменил все вызовы select () на poll (), который является хорошо продуманной альтернативой загадочному вызову select (), и ситуации с высокой нагрузкой никогда не были проблемой. Нам повезло, потому что вся обработка сокетов осуществлялась в одном классе фреймворка, и 15 минут работы могли решить проблему. Было бы намного хуже, если бы вызовы select () были разбросаны по всему коду.
Уроки выучены:
даже если функции API 25 лет, и все ее используют, у нее могут быть темные углы, о которых вы еще не знаете
запись в неконтролируемую память в макросах API - ЗЛО
инструмент отладки, такой как Purify, не может помочь во всех ситуациях, особенно когда используется много памяти
По возможности всегда имейте основу для своего приложения. Его использование не только увеличивает переносимость, но и помогает в случае ошибок API.
многие приложения используют select (), не думая об ограничении сокетов. Так что я почти уверен, что вы можете вызвать ошибки в БОЛЬШОМ популярном программном обеспечении, просто используя множество сокетов. К счастью, у большинства приложений никогда не будет больше 1024 сокетов.
Вместо того, чтобы иметь безопасный API, разработчики ОС любят винить разработчика. На странице руководства Linux select () говорится:
"The behavior of these macros is undefined if a descriptor value is less than zero or greater than or equal to FD_SETSIZE, which is normally at least equal to the maximum number of descriptors supported by the system."
Это заблуждение. Linux может открывать более 1024 сокетов. И поведение абсолютно четко определено: использование неожиданных значений приведет к нарушению работы приложения. Вместо того, чтобы делать макросы устойчивыми к недопустимым значениям, разработчики просто перезаписывают другие структуры. FD_SET реализован как встроенный ассемблер (!) В заголовках linux и будет оцениваться как одна инструкция записи ассемблера. Ни малейшей проверки границ нигде не происходит.
Чтобы протестировать собственное приложение, вы можете искусственно увеличить количество используемых дескрипторов, программно открыв файлы или сокеты FD_SETSIZE непосредственно после main (), а затем запустив приложение.
Thorsten79
Трудно поверить, что у меня не было голосов, пока я не пришел ... хороший урок!
В наши дни epoll - еще лучшее решение этой проблемы.
Я потратил часы или дни на отладку ряда вещей, которые в конечном итоге можно было исправить буквально с помощью пары символов.
Несколько различных примеров:
У ffmpeg есть эта неприятная привычка выдавать предупреждение о «обрезке мозгового перда» (относится к случаю, когда значения кадрирования в потоке> = 16), когда значения кадрирования в потоке действительно были совершенно допустимыми. Я исправил это, добавив три символа: «h->».
В x264 была ошибка, из-за которой в очень редких случаях (один из миллиона кадров) при определенных параметрах создавался случайный блок совершенно неправильного цвета. Я исправил ошибку, добавив букву «О» в двух местах кода. Оказалось, что я неправильно ввел имя #define в более ранней фиксации.
Это был сбой нарушения прав доступа.
из аварийного дампа я мог только выяснить, что параметр в стеке вызовов был поврежден.
Причина был этот код:
n = strlen(p->s) - 1;
if (p->s[n] == '\n')
p->s[n] = '\0';
если длина строки была 0, а параметр в стеке выше оказался по адресу 0x0Axxxxxxx
==> повреждение стека
К счастью, этот код был достаточно близко к месту сбоя, поэтому просмотр (уродливого) исходного кода был способом найти виновник
С FORTRAN на миникомпьютере Data General в 80-х у нас был случай, когда компилятор заставлял константу 1 (один) обрабатывать как 0 (ноль). Это произошло из-за того, что какой-то старый код передавал константу значения 1 функции, которая объявляла переменную как параметр FORTRAN, что означало, что она (должна была быть) неизменной. Из-за дефекта в коде мы выполнили присвоение переменной параметра, и компилятор радостно изменил данные в той области памяти, которую он использовал для константы 1, на 0.
Позже у нас был код для многих несвязанных функций, который сравнивал с буквальным значением 1, и тест не удался. Я помню, как долгое время смотрел на этот код в отладчике. Я бы распечатал значение переменной, было бы 1, но тест if (foo .EQ. 1) не прошел. Мне потребовалось много времени, прежде чем я решил попросить отладчик распечатать то, что, по его мнению, было значением 1. Затем потребовалось много усилий, чтобы проследить код и найти, когда константа 1 стала 0.
Мы называем это динамизмом типа плохой.
Я могу вспомнить парочку из них, большинство из них вызвано мной :). Почти каждый из них нуждался в частом почесывании головы.
Я был частью java-проекта (богатый клиент), java-код работал без проблем на ванильных сборках или на новых машинах, но при установке на презентационные ноутбуки он внезапно перестал работать и начал бросать stackdump. Дальнейшее расследование показало, что код основан на пользовательской dll, которая конфликтует с cygwin. Это еще не конец истории, мы должны были установить его на 5 других машинах и угадайте что, на одной из машин он снова разбился! На этот раз виновником была jvm, код, который мы дали, был создан с использованием Sun Microsystems jdk, а машина имела jvm от ibm.
Еще один пример, который я могу припомнить, связан с кодом пользовательского обработчика событий. Код был протестирован и проверен, наконец, когда я удалил операторы print (), БУМ !!. Когда мы отлаживали, код работал отлично, добавляя к нашим долгам. Мне пришлось прибегнуть к дзен-медитации (вздремнуть на столе), и оказалось, что может быть временная анамолия! Событие, которое мы делегировали, запускало функцию еще до того, как условие было установлено, операторы печати и режим отладки давали достаточно времени для установки условия и поэтому работали правильно. Вздох облегчения, и некоторый рефакторинг решил проблему.
В один прекрасный день я решил, что с некоторыми объектами домена, необходимыми для реализации интерфейса Clonable, все в порядке. Через несколько недель мы заметили, что приложение начало вести себя странно. Угадай, что? мы добавляли эти неглубокие копии в классы коллекций, и методы remove () на самом деле не очищали содержимое должным образом (из-за повторяющихся ссылок, указывающих на один и тот же объект). Это вызвало серьезный обзор модели и пару приподнятых бровей.
И еще раз доказательство того, что Clonable - зло ...
Где-то глубоко в недрах сетевого приложения была строчка (упрощенная):
if (socket = accept() == 0)
return false;
//code using the socket()
Что произошло, когда звонок был успешным? socket был установлен на 1. Что делает send(), когда ему дано 1? (например, в:
send(socket, "mystring", 7);
Он печатает на stdout ... это я обнаружил после 4 часов размышлений, почему, когда все мои printf() были вынуты, мое приложение печатало в окно терминала, а не отправляло данные по сети.
gcc -Wall поймал бы это
Смех над моей задницей с этим. Слишком круто.
Не очень круто, но я много смеялся, когда его раскрыли.
Когда я поддерживал круглосуточную систему обработки заказов для интернет-магазина, покупатель пожаловался, что его заказ «усечен». Он утверждал, что хотя размещенный им приказ фактически содержал N позиций, система принимала гораздо меньше позиций без какого-либо предупреждения.
После того, как мы проследили поток заказов в системе, были выявлены следующие факты. Была хранимая процедура, отвечающая за хранение позиций заказа в базе данных. Он принял список элементов заказа в виде строки, которая закодировала список троек (product-id, quantity, price) следующим образом:
"<12345, 3, 19.99><56452, 1, 8.99><26586, 2, 12.99>"
Итак, автор хранимой процедуры был слишком умен, чтобы прибегать к чему-то вроде обычного синтаксического анализа и циклов. Поэтому он напрямую преобразовал строку в оператор множественной вставки SQL, заменив "<" на "insert into ... values (" и ">" на ");". Все было прекрасно, если бы только он не сохранил результирующую строку в переменной varchar (8000)!
Произошло то, что его "insert ...; insert ...;" был усечен до 8000-го символа, и для этого конкретного порядка сокращение было достаточно удачным, чтобы произойти прямо между insert, так что усеченный SQL остался синтаксически правильным.
Позже я узнал, что автор sp был моим начальником.
Была ошибка на платформе с очень плохим отладчиком устройства. Если бы мы добавили в код printf, на устройстве произошел сбой. Тогда он вылетел бы в другом месте, а не в том месте, где находится printf. Если бы мы переместили printf, сбой переместится или исчезнет. Фактически, если бы мы изменили этот код, переупорядочив некоторые простые инструкции, сбой произошел бы там, где это не было связано с кодом, который мы действительно изменили.
Это похоже на классический Heisenbug. Как только вы это узнаете, вы сразу же начинаете искать неинициализированные переменные или стирать границы стека.
Или отключите отладочную кучу в MSVS, что заставит код вести себя по-другому в отладчике, даже при отладке в режиме Release.
Незадолго до того, как Интернет стал популярным, мы работали над домашним банковским приложением на базе модема (первое в Северной Америке).
За три дня до выпуска мы были (почти) по графику и планировали использовать оставшееся время для исчерпывающего тестирования системы. У нас был план тестирования, и следующей в списке была модемная связь.
Примерно тогда наш клиент поспешил с просьбой обновить функции в последнюю минуту. Конечно, я был категорически против, но меня отвергли. Мы сжигали полуночное масло в течение трех дней, добавляя эту глупость, и заставили его работать к дате выпуска. Мы уложились в срок и доставили заказчикам более 2000 дискет.
На следующий день после выпуска я вернулся к своему графику тестирования и возобновил тестирование модуля связи модема. К моему большому удивлению, я обнаружил, что модем случайно не смог подключиться. Примерно тогда наши телефоны начали звонить, а рассерженные клиенты не могли пользоваться своим приложением.
После долгого стука зубов и выдергивания волос я решил, что проблема связана с инициализацией последовательного порта. Младший программист закомментировал запись в один из регистров управления. Регистр оставался неинициализированным, и была около 10% вероятности, что он будет содержать недопустимое значение - в зависимости от конфигурации пользователя и того, какие приложения он запускал заранее.
Когда его спросили об этом, программист заявил, что это заставило его работать на его машине.
Поэтому нам пришлось заново записать эти 2000+ дискет и отслеживать каждого клиента, чтобы отозвать их. Не самое интересное занятие, особенно с уже выгоренной командой.
Мы сильно ударились по этому поводу. Наш клиент утверждал, что, поскольку это была наша ошибка, нам придется покрыть расходы по отзыву. Наш график следующего релиза был перенесен на месяц. И наши отношения с клиентом испортились.
В настоящее время я гораздо менее гибок с добавлением новых функций в последнюю минуту и стараюсь лучше общаться со своей командой.
Это немного не по теме (поэтому я сделал это сообществом).
Но «Букашка» Эллен Уллман - фантастическая художественная книга именно на эту тему.
Однажды разработал многопоточную (дрожащую) систему в реальном времени, которая опрашивала изображения с нескольких сетевых камер наблюдения и творила с изображениями все виды магии.
Ошибка просто приводила к сбою системы, и, конечно же, с некоторыми критическими разделами обращались неправильно. Я понятия не имел, как вызвать сбой напрямую, но мне пришлось ждать, пока это произойдет, что происходило примерно раз в три или четыре дня (шансы: примерно 1 из 15000000 при 30 кадрах в секунду).
Я должен был подготовить все, что мог, отладить выходные сообщения, загрязняющие код, инструменты трассировки, инструменты удаленной отладки на камере, и этот список можно продолжить. Тогда мне просто пришлось ждать два-три дня и надеяться получить всю информацию для поиска неисправного мьютекса или чего-то еще. Потребовалось четыре таких захода, прежде чем я его выследил, четыре недели! Еще один прогон, и я бы нарушил дедлайн по заказу ..
Парсер jpeg, работающий на камере наблюдения, который давал сбой каждый раз, когда генеральный директор компании входил в комнату.
100% воспроизводимая ошибка.
Я не шучу!
Вот почему:
Для тех, кто мало разбирается в сжатии JPEG - изображение разбито на матрицу небольших блоков, которые затем кодируются с помощью магии и т. д.
Парсер задыхался, когда генеральный директор входил в комнату, потому что у него всегда была рубашка с квадратным рисунком на ней, что запускало какой-то особый случай алгоритмов контраста и границ блоков.
Поистине классика.
Святая корова, я даже не знаю, как бы я попытался решить эту проблему. Потрясающе.
Это удивительно. Ты - ветер под моими крыльями.
Как на это отреагировал генеральный директор? ;)
напоминает мне эту историю ... "Кормящая мать однажды обратилась к Рогачоверу Ребе, блестящему исследователю Торы в Европе в XIX веке, с непонятной проблемой ..." tfdixie.com/parshat/mishpatim/018.htm
Во время тестирования некоторых новых функций, которые я недавно добавил в торговое приложение, я случайно заметил, что код для отображения результатов определенного типа торговли никогда не будет работать должным образом. Посмотрев на систему управления версиями, стало очевидно, что эта ошибка существует не менее года, и я был удивлен, что ни один из трейдеров никогда ее не заметил.
Поразмыслив некоторое время и посоветовавшись с коллегой, я исправил ошибку и продолжил тестирование своей новой функциональности. Минут через 3 зазвонил мой телефон. На другом конце провода был разгневанный трейдер, который жаловался, что одна из его сделок показывалась некорректно.
После дальнейшего расследования я понял, что трейдер столкнулся с той же ошибкой, которую я заметил в коде за 3 минуты до этого. Эта ошибка валялась около года, просто ожидая, когда придет разработчик и обнаружит ее, чтобы она могла ударить по-настоящему.
Это хороший пример ошибки, известной как Schroedinbug. Хотя большинство из нас слышали об этих необычных существах, когда вы действительно сталкиваетесь с ними в дикой природе, возникает жуткое ощущение.
Это было тогда, когда я думал, что C++ и цифровые часы были довольно хороши ...
Я получил репутацию человека, способного устранять сложные утечки памяти. У другой команды была утечка, которую она не могла отследить. Они попросили меня расследовать.
В данном случае это были COM-объекты. В основе системы лежал компонент, выдающий множество маленьких извилистых COM-объектов, которые выглядели более или менее одинаково. Каждый из них был передан множеству разных клиентов, каждый из которых отвечал за выполнение AddRef() и Release() одинаковое количество раз.
Не было способа автоматически рассчитать, кто звонил каждому AddRef и есть ли у них Released.
Я провел несколько дней в отладчике, записывая шестнадцатеричные адреса на маленьких листочках. Мой офис был завален ими. Наконец я нашел виноватого. Команда, обратившаяся ко мне за помощью, была очень благодарна.
На следующий день я перешел на язык с GC. *
(* На самом деле неправда, но это был бы хороший финал истории.)
Я чувствую себя с тобой. То же самое делал с коллегами. Моя специальность - поиск double free () для глобальных или объектных переменных ;-)
В двух словах: утечки памяти.
Брайан Кантрилл из Sun Microsystems провел отличное выступление в Google Tech Talk по поводу ошибки, которую он обнаружил с помощью инструмента dtrace, который он помог разработать.
Технический разговор забавный, вызывающий, информативный и очень впечатляющий (и длинная, около 78 минут).
Я не буду здесь давать никаких спойлеров о том, в чем была ошибка, но он начинает раскрывать виновника около 53:00.
Моя первая «настоящая» работа была в компании, которая писала программное обеспечение для автоматизации продаж клиент-сервер. Наши клиенты запускали клиентское приложение на своих (15-фунтовых) ноутбуках, и в конце дня они подключались к нашим серверам unix для синхронизации с базой данных Mother. После серии жалоб мы обнаружили, что астрономическое количество вызовов прерывается в самом начале, во время аутентификации.
После нескольких недель отладки мы обнаружили, что аутентификация всегда не удалась, если на входящий вызов ответил процесс getty на сервере, идентификатор процесса которого содержал четное число, за которым сразу следовало 9. Оказывается, аутентификация была самодельной схемой, которая зависела от 8-символьное строковое представление PID; ошибка вызвала сбой вызывающего ошибку PID getty, который возродился с новым PID. Второй или третий звонок обычно обнаруживал приемлемый PID, а автоматический повторный набор номера избавлял клиентов от необходимости вмешиваться, поэтому это не считалось серьезной проблемой до тех пор, пока телефонные счета не приходили в конце месяца.
«Исправление» (кхм) заключалось в том, чтобы преобразовать PID в строку, представляющую его значение в восьмеричный, а не в десятичном формате, что сделало невозможным использование 9 и ненужным для решения основной проблемы.
Понимаешь ... Это приложение (и ваше имя) звучит очень знакомо ...
@ Джеймс: Да, это так. Мне было интересно, когда ты установишь связь! Надеюсь, у вас все хорошо ... все еще «украшаете эту линию цветами»?
@ Адам: Совершенно верно. Кроме того, проверяя свой LinkedIn, кажется, что мы снова прошли. Я был в Ratitan в течение 6 месяцев 2006-07 (и моя сестра работала в Cryptek в VA)
Был код, который устанавливает дату истечения срока действия на текущую дату плюс один год, добавляя 1 к текущему году и сохраняя день и месяц одинаковыми. Это провалилось 29 февраля 2008 г., потому что база данных отказалась принять 29 февраля 2009 г. !!
Не знаю, можно ли это назвать «жестким», но это был странный код, который, конечно же, сразу же переписали!
Когда я только начал работать в компании, в которой я работал, я много делал CPR, чтобы изучить продукты.
Этот встраиваемый продукт, написанный на сборке HC11, имел функцию, которая появлялась каждые восемь часов. Оказывается, прерывание, уменьшающее значение, сработало во время выполнения кода, проверявшего счетчик. Добавил немного CLI / STI в код, и все было в порядке. Я отследил это, сделав так, чтобы событие происходило дважды в секунду, а не каждые восемь часов.
Урок, который я извлек из этого, заключался в том, что при отладке кода, который редко дает сбой, я должен сначала проверить переменные, используемые прерываниями.
Вышеупомянутое сообщение Адама Лисса, в котором говорилось о проекте, над которым мы оба работали, напомнило мне о забавной ошибке, с которой мне пришлось столкнуться. На самом деле это не было ошибкой, но мы вернемся к этому через минуту.
Краткое изложение приложения на случай, если вы еще не видели сообщение Адама: программное обеспечение для автоматизации продаж ... на ноутбуках ... в конце дня они набирали номер ... для синхронизации с базой данных Mother.
Один пользователь жаловался, что каждый раз, когда он пытался дозвониться, приложение вылетало. Специалисты службы поддержки проделали все свои обычные диагностические приемы по телефону и ничего не нашли. Таким образом, им пришлось уступить до последнего: доставить пользователю FedEx ноутбук в наши офисы. (Это было очень важно, поскольку локальная база данных каждого ноутбука настраивалась под пользователя, поэтому необходимо было подготовить новый ноутбук, отправить его пользователю, чтобы он мог использовать его, пока мы работали над его оригиналом, затем нам пришлось поменять местами обратно и пусть он наконец синхронизирует данные на первом оригинальном ноутбуке).
Итак, когда прибыл ноутбук, его дали мне разобраться в проблеме. Теперь для синхронизации необходимо подключить телефонную линию к внутреннему модему, перейти на страницу «Связь» нашего приложения и выбрать номер телефона из раскрывающегося списка (с предварительно выбранным последним использованным номером). Цифры в DDL были частью настройки и в основном представляли собой номер офиса, номер офиса с префиксом «+1», номер офиса с префиксом «9 ,,,» в случае, если они были звонок из гостиницы и т. д.
Итак, я щелкнул значок «СВЯЗЬ» и нажал клавишу возврата. Он набрал номер, подключился к модему - и тут же вылетел. Я устал еще пару раз. 100% повторяемость.
Итак, a подключил область данных между ноутбуком и телефонной линией и посмотрел на данные, проходящие через линию. Это выглядело довольно странно ... Самое странное было то, что я мог это читать!
Пользователь явно хотел использовать свой ноутбук для подключения к локальной системе BBS, и поэтому изменил конфигурацию приложения, чтобы использовать номер телефона BBS вместо номера компании. Наше приложение ожидало наш проприетарный двоичный протокол, а не длинные потоки текста ASCII. Буферы переполнены - KaBoom!
Тот факт, что проблема с набором номера началась сразу после того, как он изменил номер телефона, может дать среднему пользователю ключ к разгадке, что это причина проблемы, но этот парень никогда не упоминал об этом.
Я зафиксировал номер телефона и отправил его обратно в службу поддержки с запиской, что этот парень был избран «пользователем недели Bonehead». (*)
(*) OkOkOk ... Вероятно, очень велика вероятность того, что на самом деле произошло в том, что ребенок этого парня, видя, как его отец звонит каждую ночь, решил, что так же вы звоните на BBS, и изменил номер телефона когда-то, когда он был один дома с ноутбуком. Когда он разбился, он не хотел признаваться, что коснулся ноутбука, не говоря уже о том, чтобы сломать его; поэтому он просто убрал это и никому не сказал.
По сути, все, что связано с потоками.
Однажды я работал в компании, в которой я имел сомнительную репутацию одного из немногих, кто достаточно хорошо разбирался в многопоточности для отладки неприятных проблем. Ужас. Прежде чем вы сможете писать многопоточный код, вам необходимо пройти сертификацию.
Благодаря вспышке вдохновения это не заняло много времени, но, тем не менее, было немного странно. Небольшое приложение, используемое только другими людьми в ИТ-отделе. Он, в свою очередь, подключается ко всем настольным ПК в домене. Многие из них отключены, и время ожидания соединения истекает через AGES, поэтому оно выполняется в пуле потоков. Он просто сканирует AD и помещает тысячи рабочих элементов в очередь в пул потоков. Все работало нормально. Несколько лет спустя я разговаривал с другим сотрудником, который на самом деле использует это приложение, и он упомянул, что это сделало ПК непригодным для использования. Во время работы попытки открытия веб-страниц или просмотра сетевого диска занимали минуты или никогда не происходили. проблема оказалась в полуоткрытом tcp-ограничении XP. Исходный ПК был двухпроцессорным, поэтому .NET выделяет 50 (или 100, не уверен) потоков в пул, без проблем. Теперь у нас есть двухъядерный двухъядерный процессор, теперь у нас больше потоков в пуле потоков, чем может быть полуоткрытых соединений, поэтому другая сетевая активность становится невозможной во время работы приложения.
Теперь он исправлен, он проверяет машины перед попыткой подключения к ним, поэтому время ожидания намного короче и использует небольшое фиксированное количество потоков для выполнения фактической работы.
У меня была проблема с оборудованием ...
Раньше я использовал DEC VaxStation с большим 21-дюймовым ЭЛТ-монитором. Мы переехали в лабораторию в нашем новом здании и установили две станции VaxStation в противоположных углах комнаты. При включении мой монитор мерцал, как дискотека. (да, это были 80-е), а вот другой монитор - нет.
Хорошо, поменяйте местами мониторы. Другой монитор (теперь подключенный к моей VaxStation) мигал, а мой бывший монитор (перемещался через комнату) - нет.
Я вспомнил, что мониторы на основе ЭЛТ восприимчивы к магнитным полям. Фактически, они были очень восприимчивы к переменным магнитным полям частотой 60 Гц. Я сразу заподозрил, что что-то в моей рабочей зоне генерирует изменяющееся магнитное поле с частотой 60 Гц.
Сначала я что-то заподозрил в своем рабочем месте. К сожалению, монитор все еще мерцал, даже когда все остальное оборудование было выключено и отключено от сети. В этот момент я начал подозревать что-то в здании.
Чтобы проверить эту теорию, мы превратили VaxStation и его 85-фунтовый монитор в портативную систему. Мы поместили всю систему на поворотную тележку и подключили ее к 100-футовому удлинителю оранжевой конструкции. План состоял в том, чтобы использовать эту установку в качестве портативного измерителя напряженности поля, чтобы определить местонахождение неисправного оборудования.
Катание монитора нас полностью сбило с толку. Монитор мерцал ровно в одной половине комнаты, но не в другой. Комната имела форму квадрата с дверями в противоположных углах, и монитор мигал с одной стороны диагностической линии, соединяющей двери, но не с другой стороны. Комнату со всех четырех сторон окружали коридоры. Мы вытолкнули монитор в коридор, и мерцание прекратилось. Фактически, мы обнаружили, что мерцание происходило только в одной треугольной половине комнаты и нигде больше.
После периода полного замешательства я вспомнил, что в комнате была двухсторонняя потолочная система освещения с выключателями на каждой двери. В тот момент я понял, в чем дело.
Я переместил монитор в ту половину комнаты, где возникла проблема, и выключил потолочное освещение. Мерцание прекратилось. Когда я включил свет, мерцание возобновилось. Включение или выключение света с помощью любого выключателя включало или выключало мерцание в пределах половины комнаты.
Проблема была вызвана тем, что кто-то срезал углы при подключении потолочного освещения. При подключении двухпозиционного переключателя в цепи освещения вы прокладываете пару проводов между контактами переключателя SPDT и одиночный провод от общего провода на одном переключателе, через фонари и до общего провода на другом переключателе.
Обычно эти провода скручиваются вместе. Они выходят группой из одной распределительной коробки, бегут к потолочному светильнику и переходят к другой коробке. Ключевая идея состоит в том, что все токоведущие провода соединены вместе.
Когда в здании была проведена проводка, единственный провод между переключателями и светом был проложен через потолок, но провода, проходящие между переключателями, были проложены через стены.
Если все провода проходят близко и параллельно друг другу, то магнитное поле, создаваемое током в одном проводе, компенсируется магнитным полем, создаваемым равным и противоположным током в соседнем проводе. К сожалению, из-за того, что свет был подключен, половина комнаты находилась внутри большой первичной обмотки одновиткового трансформатора. Когда свет был включен, ток протекал по петле, и плохой монитор в основном находился внутри большого электромагнита.
Мораль истории: горячая и нейтральная линии в вашей силовой проводке переменного тока расположены рядом друг с другом по уважительной причине.
Теперь все, что мне нужно было сделать, это объяснить руководству, почему им пришлось перемонтировать часть своего нового здания ...
Отлаживаете здание? Это вообще связано с программированием? +1 :)
Это случилось со мной, когда я работал в компьютерном магазине.
Однажды один покупатель пришел в магазин и сказал нам, что его новый компьютер отлично работал по вечерам и ночью, но не работает вообще в полдень или поздно утром. Беда в том, что указатель мыши в это время не двигается.
Первым делом мы заменили его мышку на новую, но проблему не устранили. Конечно, обе мыши работали в магазине без сбоев.
После нескольких попыток мы обнаружили, что проблема связана с этой конкретной маркой и моделью мыши. Рабочее место клиента было близко к очень большому окну, и в полдень мышь находилась под прямыми солнечными лучами. Его пластик был настолько тонким, что в этих условиях он становился полупрозрачным, и солнечный свет мешал оптико-механическому колесу работать: |
Однажды я удалил PHP. Вручную. Множество ошибок исправлено одним движением ...
В игре, над которой я работал, конкретный спрайт больше не отображался в режиме выпуска, но работал нормально в режиме отладки и только в одном конкретном выпуске. Другой программист 2 дня пытался найти этот баг, потом уехал в отпуск. На мои плечи легла попытка найти ошибку за ~ 5 часов до релиза.
Поскольку сборка отладки работала, мне пришлось отлаживать сборку выпуска. Visual Studio поддерживает некоторую отладку в сборке Release, но вы не можете полагаться на все, что отладчик сообщает вам как правильное (особенно с агрессивными настройками оптимизации, которые мы использовали). Поэтому мне приходилось просматривать половину листинга кода и половину списка ассемблера, иногда глядя на объекты непосредственно в шестнадцатеричном дампе, а не в красиво отформатированном представлении отладчика.
Потратив некоторое время на то, чтобы убедиться, что все вызовы отрисовки выполняются правильно, я обнаружил, что цвет материала спрайта был неправильным - он должен был быть полностью оранжевым, но вместо этого был установлен черный и полностью прозрачный. Цвет был взят из палитры, находящейся в массиве const в нашем классе EditionManager. Первоначально он был настроен как правильный оранжевый цвет, но когда фактический цвет был извлечен из кода рисования спрайта, он снова стал прозрачным черным. Я установил точку останова памяти, которая сработала в конструкторе EditionManager. Запись в другой массив вызвала изменение значения в массиве палитры.
Как оказалось, другой программист изменил существенное перечисление системы:
enum {
EDITION_A = 0,
EDITION_B,
//EDITION_DEMO,
EDITION_MAX,
EDITION_DEMO,
};
Он поместил EDITION_DEMO сразу после EDITION_MAX, и массив, в который выполнялась запись, был проиндексирован с помощью EDITION_DEMO, поэтому он переполнился в палитре и установил там неправильные значения. Однако я не мог изменить перечисление обратно, поскольку номера редакций больше не могли меняться (они использовались при двоичной передаче). Поэтому я закончил тем, что сделал запись EDITION_REAL_MAX в перечислении и использовал ее в качестве размера массива.
Это была крошечная ошибка в Rhino (интерпретатор Javascript на Java), из-за которой один скрипт не работал. Это было сложно, потому что я мало знал о том, как будет работать интерпретатор, но мне пришлось вмешаться, чтобы исправить ошибку как можно быстрее ради другого проекта.
Сначала я отследил, какой вызов в Javascript дал сбой, чтобы воспроизвести проблему. Я прошел через работающий интерпретатор в режиме отладки, поначалу совершенно потерявшись, но медленно изучая детали того, как он работает. (Чтение документации немного помогло.) Я добавил printlns / logging в моменты, которые, по моему мнению, могут быть важными.
Я сравнил (очищенный) файл журнала рабочего прогона с прерванным прогоном, чтобы увидеть, в какой момент они впервые начали расходиться. Повторно запустив и добавив множество точек останова, я нашел свой путь к цепочке событий, которые привели к сбою. Где-то там была строка кода, которая, если написать немного иначе, решила проблему! (Это было что-то очень простое, например, nextNode () должен возвращать null вместо IndexOutOfBounds.)
Через две недели после этого я понял, что мое исправление ломает скрипты в некоторых других ситуациях, и изменил строку, чтобы она работала хорошо во всех случаях.
Я был в незнакомой среде. Так что я просто пробовал много разных вещей, пока один из них не сработал или, по крайней мере, помог добиться некоторого прогресса / понимания. Это сделал заняло некоторое время, но я был рад, что в итоге добрался до него!
Если бы я делал это снова сейчас, я бы поискал IRC-канал проекта (а не только его список рассылки), чтобы задать несколько вежливых вопросов и найти указатели.
Я не могу представить, как они это кодировали: Вы не можете назначить IP-адрес 127.0.0.1 адаптеру обратной связи, потому что это зарезервированный адрес для устройств обратной связи - Microsoft (r) WindowsXP PROFESSIONAL
У меня был фрагмент кода delphi, который выполнял долгую процедуру обработки, обновляя индикатор выполнения по ходу. Код работал нормально в 16-битном Delphi 1, однако, когда мы обновились до delphi 2, процесс, который занимал 2 минуты, внезапно занял около часа.
После нескольких недель разборки подпрограммы выясняется, что проблема была вызвана строкой, обновляющей индикатор выполнения, для каждой итерации мы проверяли количество записей с помощью table1.recordcount, в delphi 1 это работало нормально, но, похоже, в более поздних версиях вызова delphi table.recordcount в таблице dbase берет копию таблицы, подсчитывает записи и возвращает сумму, вызывая это при каждом шаге нашего прогресса, что вызывало загрузку таблицы из сети с каждым повторением и подсчет. Решением было подсчитать количество записей до начала обработки и сохранить сумму в переменной.
Потребовались годы, чтобы найти, но оказалось, что все так просто.
Я слышал о классической ошибке еще в старшей школе; терминал, в который можно было войти, только если вы сидели в кресле перед ним. (Если бы вы стояли, он отклонил бы ваш пароль.)
Воспроизводится довольно надежно для большинства людей; вы можете сесть в кресло, войти в систему, выйти из системы ... но если вы встанете, вам будет отказано каждый раз.
В конце концов выяснилось, что какой-то придурок поменял местами пару соседних клавиш на клавиатуре, E / R и C / V IIRC, и когда вы садились, вы печатали вслепую и входили, но когда вы стояли, вам приходилось охотиться '' n клевать, значит, вы посмотрели неправильные ярлыки и потерпели неудачу.
Я работаю в большом общественном колледже, и в прошлом году мы перешли с Blackboard на Moodle. Moodle использует номенклатуру «курсов» и «групп». Курс может быть, например, «Микроэкономика ECO-150», а группы - это то, что мы бы назвали разделами (OL1, OL2, 01, 14, W09 в качестве примеров).
В любом случае мы примитивны. У нас даже нет LDAP. Все это текстовые файлы, таблицы Excel и базы данных Microsoft Access GD. Моя работа заключается в создании веб-приложения, которое принимает все вышеперечисленное в качестве входных данных и создает еще больше текстовых файлов, чем можно затем загрузить в Moodle для создания курсов, групп в курсах и пользователей и размещения пользователей в курсах и группах. Вся установка явно византийская, с примерно 17 отдельными шагами, которые необходимо выполнить по порядку. Но это работает и заменяет процесс, который раньше занимал несколько дней в самое загруженное время семестра.
Но была одна проблема. Иногда мы получали то, что я назвал «Сумасшедшие группы». Таким образом, вместо создания курса с 4 группами по 20 студентов в каждой, будет создан курс с 80 группами по 1 студенту в каждой. Хуже всего то, что программно невозможно попасть в cpanel (к которому у меня нет доступа), чтобы удалить группу после ее создания. Это ручной процесс, который занимает около 5 нажатий кнопок. Таким образом, каждый раз, когда создавался курс с Crazy Groups, мне приходилось либо удалять курс, что предпочтительно, но не вариант, если учитель уже начал добавлять контент в курс, либо мне приходилось тратить час, повторяя одну и ту же схему: Выберите группу, отобразите группу, отредактируйте группу, удалите группу, вы действительно хотите удалить группу? Да ради всего святого!
И не было никакого способа узнать, возникали ли сумасшедшие группы, если вы вручную не открывали каждый курс и не просматривали (с сотнями курсов) или пока не получили жалобу. Сумасшедшие группы казалось возникли случайным образом, а форумы Google и Moodle не помогли, похоже, все остальные используют эту штуку, называемую LDAP или РЕАЛЬНОЙ базой данных, поэтому они никогда не сталкивались с проблемой.
Наконец, после того, как я не знаю, сколько времени исследую и удаляю сумасшедшие группы больше, чем я когда-либо хотел признать, я понял это. Это была ошибка в Moodle, а не в моем коде! Это доставило мне немалое удовольствие. Вы видите, что способ создать группу - это просто попытаться зарегистрировать кого-то в группе, и если группа еще не существует, Moodle создает ее. И это отлично работало для групп с именами OL1 или W12 или даже SugarCandyMountain, но если вы попытались создать группу с номером в качестве имени, скажем, 01 или 14, ЭТО когда возникнут сумасшедшие группы. Moodle неправильно сравнивает числа как строки. Независимо от того, сколько групп с именем 01 внутри курса есть, он всегда будет думать, что группа еще не существует, и поэтому создаст ее. Таким образом получается 80 групп по 1 человеку в каждой.
Гордясь своим открытием, я зашел на форум Moodle и опубликовал свои результаты с указанием шагов по воспроизведению проблемы по своему желанию. Это было около года назад, и, насколько мне известно, проблема все еще существует внутри Moodle, никто, кажется, не заинтересован в ее исправлении, потому что никто, кроме нас, примитивов, не использует регистрацию текстовых файлов. Мое решение - просто убедиться, что все имена наших групп содержат хотя бы 1 нечисловой символ. Сумасшедшие группы ушли навсегда, по крайней мере, для нас, но я сочувствую тому парню, который работает в муниципальном колледже за пределами Монголии, который только что загрузил семестровые курсы и вот-вот проснется. По крайней мере, на этот раз Google может ему помочь, потому что я написал ему это сообщение в бутылке о волнах киберпространства.
DevExpress XPO разговаривает с базой данных Oracle, которая резко падает (например, программа завершает работу без вывода сообщений), если путь к каталогу, в который установлено приложение, не содержит хотя бы одного пробела, а словарь данных, который XPO проверяет, не на 100% правильно заключен в база данных.
Проблема описана здесь.
Я могу сказать вам следующее: я был вот-вот заплакал, когда мы выяснили, как обойти проблему. Я до сих пор не знаю, какова настоящая причина проблемы, но наш продукт не будет поддерживать Oracle в будущей версии, поэтому я на самом деле не сообщаю ... больше.
Однажды у меня была ошибка с кастомной программой синхронизации. Он использовал отметку даты / времени файлов / папок для сравнения того, что было изменено для синхронизации данных с флэш-ключа с общим сетевым ресурсом в Windows, с некоторой дополнительной целостностью и встроенной бизнес-логикой.
Однажды оператор сообщил, что его синхронизация длилась вечно ... после просмотра журналов по какой-то причине программное обеспечение посчитало, что каждый файл на флешке (или сервере) был на 3 часа старше, чем должен быть, обновив все 8 гигов данных! Я использовал UTC, как, черт возьми, это могло быть?
Оказывается, этот конкретный оператор действительно установил свой часовой пояс на тихоокеанское время, а не на восточное, что вызвало проблему, но этого не должно было быть, потому что весь код использовал UTC - боже, что это могло быть ?! Он работал при тестировании в моей локальной системе ... что дает?
На этом этапе мы попросили всех операторов убедиться, что их ноутбуки были настроены на восточное время перед синхронизацией, и ошибка оставалась в очереди до тех пор, пока у нас не будет больше времени для расследования.
Затем наступил октябрь и БУМ! Летнее время! Какого черта!? Теперь все жаловались, что синхронизация длилась вечно! Пришлось исправить, и быстро!
Я отследил это, изменив тестовый пример, чтобы он запускался с флешки, а не с моего локального жесткого диска, и, конечно же, он не удался ... фу, должна быть штука с картой памяти - подождите, она отформатирована в FAT32 ... А-ХА! FAT32 использует местное время при записи метки времени файла!
http://msdn.microsoft.com/en-us/library/ms724290(VS.85).aspx
Итак, программное обеспечение было переписано так, что при записи на носитель FAT32 мы программно устанавливали его на UTC ...
В CS435 в Purdue нам пришлось написать трассировщик лучей для нашего последнего проекта. Все, что я создал, имело ярко-оранжевый оттенок, но я мог видеть каждый из объектов в моей сцене. В конце концов я сдался и отправил его как есть, и попросил профессора просмотреть мой код, чтобы найти ошибку, а когда он не смог ее найти, я провел большую часть лета, копаясь, чтобы найти, что, черт возьми, было не так.
Погруженный глубоко в код, как часть функции вычисления цвета, я наконец понял, что делю int и передаю его функции OpenGL, которая ожидала значение с плавающей запятой. Один из цветовых компонентов был достаточно низким на протяжении большей части сцены, чтобы округлить его до 0, вызывая оранжевый оттенок. Бросок на поплавок всего в одном месте (до деления) исправил ошибку.
Всегда проверяйте вводимые данные и ожидаемые типы.
Тупик в приложении Java Server. Но не простой тупик с двумя потоками. Я обнаружил тупик, связанный с восемью потоками. Поток 1 ожидает потока 2, который ожидает поток 3 и т. д., И, наконец, поток 8 ожидает потока 1.
Мне потребовался целый день, чтобы понять, что происходит, а затем всего 15 минут, чтобы исправить это. Я использую eclipse для отслеживания около 40 потоков, пока не обнаружу тупик.
Это было во время моей дипломной работы. Я писал программу для моделирования воздействия высокоинтенсивного лазера на атом гелия с помощью FORTRAN.
Один тестовый прогон работал так:
В целом они должны быть постоянными, но это не так. Они творили разные странные вещи.
После отладки в течение двух недель я пришел в ярость от ведения журнала и регистрировал каждую переменную на каждом этапе моделирования, включая константы.
Таким образом я обнаружил, что написал поверх конца массива, который изменил константу!
Друг сказал, что однажды он поменял букву 2 с такой ошибкой.
Самая серьезная ошибка должна быть, когда программист выводит в журнал «General Error!». Посмотрев код, он везде разлетелся с текстом «Общая ошибка!». Попробуйте пригвоздить его.
По крайней мере, написание макроса для вывода __LINE__ или __FUNCTION__ было бы немного более полезным для добавления в вывод отладки.
Гонка между методом ToString класса OracleDecimal (который P / вызывает собственную версию той же функциональности) и сборщиком мусора, вызванная отсутствующим вызовом GC.KeepAlive, который может заставить OracleDecimal.ToString () вернуть практически произвольный мусор, если его пространство кучи происходит перезапись до завершения вызова.
Я написал подробный отчет об ошибке и никогда не получал ответа, насколько я знаю, это все еще существует. У меня даже был тестовый набор, который ничего не делал, кроме как создавать новые OracleDecimal представления числа 1, вызывать на них ToString и сравнивать результат с «1». Он терпел неудачу каждый десятимиллионный раз или около того с сумасшедшей тарабарщиной (огромные числа, отрицательные числа и даже буквенно-цифровые строки мусора).
Будьте осторожны со своими вызовами P / Invoke! Сборщик мусора .NET может собирать ваш экземпляр, пока вызов метода экземпляра в этом экземпляре еще не завершен, пока метод экземпляра завершил использование ссылки this.
Отражатель - абсолютная палочка-выручалочка для подобных вещей.
В Python у меня был поток, делавший что-то вроде этого:
while True:
with some_mutex:
...
clock.tick(60)
clock.tick(60) приостанавливает поток так, чтобы он выполнялся не более 60 раз в секунду.
Проблема заключалась в том, что большую часть времени программа просто показывала черный экран. Если я позволю ему поработать какое-то время, он, наконец, покажет игровой экран.
Это потому, что поток делал паузу при сохранении мьютекса. Таким образом, он редко позволяет другим потокам получать мьютекс. Здесь это может показаться очевидным, но мне потребовалось два дня, чтобы понять это. Решение - просто удалить уровень отступа:
while True:
with some_mutex:
...
clock.tick(60)
Произошел сбой в DLL, загруженной из службы. Срабатывает при выключении системы.
Исправить ошибку было несложно, но на то, чтобы найти ее, потребовалось около недели - и много разочарований.
Несколько лет назад я потратил несколько дней, пытаясь найти и исправить небольшую ошибку в dbx, текстовом отладчике в AIX. Я не помню точную ошибку. Что сделало это трудным, так это то, что я использовал установленный dbx для отладки dev-версии dbx, над которой я работал. Было очень сложно уследить за тем, где я был. Более чем однажды я готовился уйти на день и дважды выходил из dbx (версия для разработчиков и установленная версия) только для того, чтобы увидеть, что у меня по-прежнему работает внутри dbx, иногда на два или более уровня «глубже».
--
bmb
Heisenbug, где главная трудность заключалась в том, чтобы не понимать, что это вообще не моя ошибка.
Проблема заключалась в интерфейсе API. Вызов любой реальной функции (в отличие от настройки) имел очень высокую вероятность сбоя с нарушением защиты. Пошаговое выполнение функции (насколько это возможно, вызовет прерывание, и вы не сможете проследить за этой точкой - это было еще тогда, когда вы использовали прерывания для разговора с системой) производил правильный вывод, без сбоев.
После долгих тщетных поисков того, что я делал неправильно, я, наконец, покопался в процедурах RTL, чтобы попытаться понять, что я делаю неправильно. Что я делал неправильно, так это полагал, что подпрограммы работают - все подпрограммы, которые взорвали, манипулировали указателем реального режима с типом указателя защищенного режима. Если значение сегмента реального режима не оказалось действительным в защищенном режиме, это пошло на убыль.
Однако что-то в том, как отладчик манипулирует программой, вызывало правильную работу при пошаговом режиме, я никогда не удосужился выяснить, почему.
может показаться забавным, но когда я учился, я потратил весь день, пытаясь выяснить, почему статус if всегда оценивается как true, я использовал = вместо ==: d Я дважды переписал все на другом компьютере :)
Коробка разбилась на сайте крупного клиента, и нам пришлось подключиться через сеанс WebX к компьютеру ИТ-специалиста, который был подключен к нашему ящику. Я копался около часа, собирая трассировки стека, дампы регистров, статистику, счетчики и выгружая разделы памяти, которые казались важными.
Их айтишники прислали мне по электронной почте стенограмму моего сеанса, и я приступил к работе.
Через несколько часов я проследил его до массива структур, которые содержали метаданные пакета, за которыми следовали данные пакета. Одна из метаданных пакета была повреждена и выглядела так, как будто она была перезаписана несколькими байтами пакетных данных. У Bugzilla ничего подобного не было.
Углубившись в код, я проверил все очевидные вещи. Код, копирующий пакетные данные в буфер, тщательно следил за тем, чтобы не выходить за его границы: буфер был размером MTU для интерфейса, а процедура копирования проверяла, что данные не превышают размер MTU. Мои дампы памяти позволили мне подтвердить, что да, foo-> bar действительно было 4, когда произошел сбой. Ничего не сложилось. Ничего не было неправильным, что должно было вызвать проблему. В следующем заголовке было то, что выглядело как 16 байтов пакетных данных.
Через пару дней я начал проверять все, что только мог придумать.
Я заметил, что длина буфера данных была правильной. То есть количество байтов от начала данных до конца данных было MTU, даже если следующий заголовок начинался с MTU-16.
Когда эти структуры были malloc'd, указатели на каждый элемент помещались в массив, и я сбрасывал этот массив. Я начал измерять расстояние между указателями. 6888 ... 6888 ... 6888 ... 6872 ... 6904 ... 6880 ... 6880 ...
Чего ждать?
Я начал смотреть на внутренние указатели и смещения в обеих структурах. Все сложилось. Просто это выглядело так, как будто моя единственная плохая структура - та, которая была частично затерта - оказалась в памяти всего на 16 байт раньше, чем нужно.
Процедура выделения памяти распределила этих парней как кусок, а затем разделила их на цикл:
for (i = 0; i < NUM_ELEMS; i++) {
array[i] = &head[i*sizeof(foo)];
}
(с учетом центровки и т. д.).
Когда массив был заполнен, значение моего поврежденного указателя должно было быть прочитано как 0x8a1128ac вместо 0x8a1129ac.
Я пришел к выводу, что стал жертвой 1-битной ошибки памяти во время выделения (я знаю, я знаю! Я тоже не поверил, но мы видели их раньше на этом оборудовании - значения NULL которые читались как 0x00800000). В любом случае мне удалось убедить моего начальника и коллег в том, что другого разумного объяснения не существует, и что мое объяснение точно объясняет то, что мы наблюдаем.
Итак, коробка RMA'd.
Устаревшее приложение на основе базы данных (с доступной только частью исходного кода) аварийно завершало работу, когда один конкретный пользователь обращался к определенной функции инвентаризации. Он отлично работал для всех остальных пользователей. Профиль пользователя правильно? Неа. При входе в систему как другой пользователь (даже как администратор) у одного и того же пользователя возникла та же проблема.
Компьютерная проблема? Неа. Тот же пользователь, другой компьютер (под ее логином или любым другим логином) все равно вылетел.
Проблема: при входе в программу отображалась заставка с авторскими правами, которую можно было закрыть либо щелчком «X» для закрытия окна, либо нажатием любой клавиши. При входе в систему этот пользователь всегда щелкал «X» там, где другие пользователи всегда нажимали клавишу. Это привело к утечке памяти, которая возникла, но только при доступе к инвентарному поиску.
Исправление: не нажимайте X.
У нас был сервер RMI, работающий в командной строке DOS Кто-то «выбрал» окно, что приостановило процесс.
Исправить было довольно просто ... нажмите Enter.
Это был довольно мучительный день ...
Самые сложные ошибки, которые я когда-либо исправлял, появились довольно рано в моей карьере. Я работал над системой реального времени для электростанции, которая использовала пары компьютеров GEC 2050 с общей памятью.
В ОСРВ 2050 была основная таблица планирования, которая состояла из одного слота на процесс, содержимое которой представляло собой либо команду добавления 1, X для неактивного процесса, либо переход для исполняемого процесса. Выполнение этой таблицы с X, установленным в ноль, означало, что первый запускаемый процесс автоматически вводился с регистром X, являющимся номером процесса. Тот, кто разработал это, очевидно, чувствовал себя очень умным!
В архитектуре 2050 года также была функция безопасности, при которой нераспознанный код операции всегда приводил к остановке. Поскольку у 2050 года была полноразмерная передняя панель, вы могли затем использовать ее, чтобы попытаться выяснить, что сломалось. Поскольку регистр X всегда содержал идентификатор текущего процесса, это обычно было довольно просто.
Не было ни сегментации памяти, ни защиты, поэтому процесс вполне мог повредить любой другой процесс, находящийся в настоящее время в памяти, или вообще что-либо в системной области.
Пока все соответствует эпохе (конец 70-х).
Поскольку эта конкретная система имела общую память между двумя процессорами, конфигурация системы помещала системные таблицы в общую память, чтобы один процессор мог запускать и останавливать процессы в другом без необходимости проходить через какой-либо простой безопасный интерфейс.
К сожалению, это также позволило «дикому» процессу одного ЦП повредить таблицы для другого ЦП, так что один ЦП мог благополучно вывести из строя другой. Если это произошло, то, что работало в отказавшем процессоре, не имело никакого отношения к фактической ошибке. Между тем другой ЦП успешно продолжил работу, поэтому не было возможности определить, вызвал ли он проблему.
Излишне говорить, что это обеспечило несколько проблем, которые трудно исправить!
После небольшого разрыва волос я закончил тем, что написал довольно существенный патч для O / S, который искал повреждения в таблице планировщика для другого процессора и приводил к сбою процессора, на котором он работал. Это было подключено к обычному прерыванию, поэтому, хотя оно не было идеально синхронизировано, по крайней мере, у него были хорошие шансы поймать нарушающий процесс.
Это помогло мне прояснить довольно много проблем с обоими процессорами ...
Необъяснимые тайм-ауты и периодическая блокировка SQL Server
У нас была проблема, из-за которой наши пользователи теряли время ожидания без всякой причины. Некоторое время я наблюдал за SQL Server и обнаружил, что время от времени происходит много блокировок. Поэтому мне нужно найти причину этого и исправить.
Если происходит блокировка, значит, где-то в цепочке сохраненных вызовов процедур должны быть исключительные блокировки. Верно?
Я прошел через полный список сохраненных процессов, которые были вызваны, и все последующие сохраненные процессы, функции и представления. Иногда эта иерархия была глубокой и даже рекурсивной.
Я искал какие-либо инструкции UPDATE или INSERT…. Их не было (за исключением временных таблиц, в которых была только область действия сохраненной процедуры, поэтому они не учитывались).
При дальнейших исследованиях я обнаружил, что блокировка вызвана следующим:
A. Если вы используете SELECT INTO для создания своей временной таблицы, то SQL Sever устанавливает блокировки на системные объекты. В нашей процедуре getUserPrivileges было следующее:
--get all permissions for the specified user
select permissionLocationId,
permissionId,
siteNodeHierarchyPermissionId,
contactDescr as contactName,
l.locationId, description, siteNodeId, roleId
into #tmpPLoc
from vw_PermissionLocationUsers vplu
inner join vw_ContactAllTypes vcat on vplu.contactId = vcat.contactId
inner join Location l on vplu.locationId = l.locationId
where isSelected = 1 and
contactStatusId = 1 and
vplu.contactId = @contactId
Процедура getUserPrivileges вызывается при каждом запросе страницы (она находится на базовых страницах). Она не была кэширована, как вы могли ожидать. Это не похоже, но приведенный выше SQL ссылается на 23 таблицы в предложениях FROM или JOIN. Ни на одной из этих таблиц нет подсказки «with (nolock)», поэтому на это уходит больше времени, чем следовало бы. Если я удалю предложение WHERE, чтобы получить представление о количестве задействованных строк, оно вернет 159 710 строк, а выполнение займет от 3 до 5 секунд (в нерабочее время, когда на сервере больше никого нет).
Итак, если эта сохраненная процедура может запускаться только по одному из-за блокировки, и она вызывается один раз на страницу, и она удерживает блокировки в системных таблицах на время создания таблицы select и temp, вы можете увидеть, как это может повлиять на производительность всего приложения.
Исправление для этого было бы: 1. Используйте кэширование на уровне сеанса, поэтому оно вызывается только один раз за сеанс. 2. Замените SELECT INTO кодом, который создает таблицу с помощью стандартных операторов DDL Transact-SQL, а затем используйте INSERT INTO для заполнения таблицы. 3. Ставьте «with (nolock)» на все, что связано с этим звонком.
Б. Если у сохраненной процедуры getUserPrivileges не было достаточно проблем, позвольте мне добавить: она, вероятно, перекомпилируется при каждом вызове. Таким образом, SQL Server получает блокировку COMPILE при каждом вызове.
Причина, по которой он перекомпилируется, заключается в том, что создается временная таблица, а затем из нее удаляется множество строк (если переданы @locationId или @permissionLocationId). Это приведет к перекомпиляции сохраненной процедуры в следующем SELECT (да, в середине выполнения сохраненной процедуры). В других процессах я заметил оператор DECLARE CURSOR, оператор SELECT которого ссылается на временную таблицу - это вызовет перекомпилировать тоже.
Для получения дополнительной информации о перекомпиляции см .: http://support.microsoft.com/kb/243586/en-us
Исправление для этого было бы: 1. Опять же, используйте кеширование гораздо реже, чем раньше. 2. Применяйте фильтрацию @locationId или @permissionLocationId в предложении WHERE во время создания таблицы. 3. Замените временные таблицы табличными переменными - они уменьшат количество перекомпиляций.
Если что-то работает не так, как вы ожидаете, вы можете проводить много времени, глядя на что-то, не пытаясь понять, что не так.
В настоящее время я учусь в университете, и самая серьезная ошибка, с которой я столкнулся, была связана с классом программирования там. В предыдущие два семестра мы просто написали весь наш собственный код. Но в третьем семестре профессор и TA напишут половину кода, а мы должны были написать вторую половину. Это должно было помочь нам научиться читать код.
Нашим первым заданием в этом семестре было написать программу, имитирующую расщепление гена ДНК. По сути, нам просто нужно было найти подстроку в более крупной и обработать результаты. Судя по всему, профессор и TA были заняты на той неделе и дали нам свою половину кода, еще не завершив собственную полную реализацию. У них не было времени написать вторую половину, чтобы действовать как решение. Их половина скомпилировалась, но без полного кода решения у них не было возможности его протестировать. Нам сказали не изменять код профессора. У всех в классе была одна и та же ошибка, но мы все еще предполагали, что все совершаем одну и ту же ошибку.
Программа пожирала гигабайты памяти, а затем заканчивалась и зависала. Мы (студенты) все предполагали, что в нашей половине кода должна быть какая-то неясная утечка памяти. Все в классе две недели копались в коде и снова и снова прогоняли его через отладчик. Наш входной файл представлял собой строку размером 5,7 МБ, и мы находили в нем сотни подстрок и сохраняли их. Это использовалось в коде профессора / ТА.
myString = myString.substr(0,pos);
Видите проблему? Когда вы назначаете строковую переменную ее собственной подстроке, память не перераспределяется. Это лакомый кусочек информации, который не знал никто (даже профессор или TA). Таким образом, у myString было 5,7 МБ выделенной памяти только для хранения нескольких байтов фактических данных. Это повторялось сотни раз; таким образом, массовое использование памяти. Я потратил на эту проблему две недели. Первую неделю я проверил свой код на предмет утечек памяти. В своем разочаровании я, наконец, пришел к выводу, что у половины профессора / ТА должна быть утечка, поэтому я провел вторую неделю, проверяя их код. Но даже тогда мне потребовалось так много времени, чтобы найти, потому что технически это не было утечкой. В конечном итоге все выделения были освобождены, и программа работала нормально, когда наши входные данные составляли всего лишь дюжину килобайт. Единственная причина, по которой я это обнаружил, заключалась в том, что я отправил психа с ума и решил проанализировать каждую последнюю переменную; даже временный выброс. Я также тратил много времени на проверку того, сколько символов на самом деле было в строке, а не сколько было выделено. Я предполагал, что об этом позаботился строковый класс. Вот и решение, изменение одной строчки, которое устранило недели разочарований и принесло мне пятерку за задание по поиску / исправлению кода учителя.
myString.substr(0,pos).swap(myString);
Метод swap вызывает перераспределение.
Я исправляю чью-то ошибку с помощью приведенного ниже кода:
private void foo(Bar bar) {
bar = new Bar();
bar.setXXX(yyy);
}
Он ожидал, что bar будет изменен за пределами foo!
Однажды у меня была ошибка в приложении .NET, которая приводила к сбою CLR - да, CLR просто выйдет с ненулевым результатом и не будет никакой отладочной информации.
Я засыпал код сообщениями трассировки консоли, пытаясь выяснить, где была проблема (ошибка возникала при запуске), и в конечном итоге нашел несколько строк, вызывающих проблему. Я пытался изолировать проблему, но каждый раз, когда я это делал, единичный случай срабатывал!
В конце концов я изменил код с:
int value = obj.CalculateSomething();
к
int value;
value = obj.CalculateSomething();
Не спрашивайте меня, почему, но это сработало.
Был ли obj типом значения на MarshalByRefObject?
Самая сложная ошибка, с которой я когда-либо сталкивался, была вызвана не мной, хотя из-за нее мой код зависал! это был TurboPascal под DOS. Компилятор компилятора TurboPascal подвергся незначительному обновлению, и внезапно мой двоичный файл начал давать сбой. Оказалось, что в новой версии память распределялась начиная только с границ сегмента. конечно, моя программа никогда не проверяла такие вещи, потому что почему? откуда программисту знать такие вещи? кто-то из групп по интересам старого compuserve опубликовал эту подсказку и обходной путь:
поскольку сегменты были длиной в 4 слова, исправление заключалось в том, чтобы всегда выполнять мод (4) для вычисления размера выделяемой памяти.
Эту тему должен прочитать ЛЮБОЙ начинающий программист. Хороший вопрос!