Фолклорът на програмистите и инженерите (част 1)

Фолклорът на програмистите и инженерите (част 1)

Това е селекция от истории от интернет за това как грешките понякога имат напълно невероятни прояви. Може би и вие имате какво да кажете.

Автомобилна алергия към ванилов сладолед

История за инженери, които разбират, че очевидното не винаги е отговорът и че колкото и пресилени да изглеждат фактите, те все още са факти. Подразделението Pontiac на General Motors Corporation получи оплакване:

Пиша ви за втори път и не ви обвинявам, че не отговаряте, защото звучи налудничаво. Нашето семейство има традиция да яде сладолед всяка вечер след вечеря. Видовете сладолед се сменят всеки път, а след вечеря цялото семейство избира кой сладолед да купи, след което отивам до магазина. Наскоро си купих нов Pontiac и оттогава пътуванията ми за сладолед се превърнаха в проблем. Виждате ли, всеки път, когато си купя ванилов сладолед и се върна от магазина, колата не пали. Ако нося друг сладолед, колата пали без проблем. Искам да задам един сериозен въпрос, колкото и глупаво да звучи: „Какво му е на Pontiac, което го кара да не пали, когато нося ванилов сладолед, но пали лесно, когато нося друг вкус сладолед?“

Както можете да си представите, президентът на дивизията беше скептичен относно писмото. Все пак за всеки случай изпратих инженер да провери. Той беше изненадан, че го срещна богат, добре образован мъж, живеещ в красив район. Разбрали се да се видят веднага след вечеря, за да отидат двамата до магазина за сладолед. Същата вечер беше ванилия и когато се върнаха при колата, тя не запали.

Инженерът дойде още три вечери. Първият път сладоледът беше шоколадов. Колата запали. Вторият път имаше ягодов сладолед. Колата запали. На третата вечер той поиска да вземе ванилия. Колата не запали.

Разсъждавайки рационално, инженерът отказа да повярва, че колата е алергична към ванилов сладолед. Затова се разбрах със собственика на колата да продължи посещенията си, докато намери решение на проблема. И по пътя започна да си води бележки: записа цялата информация, час на деня, вид бензин, час на пристигане и връщане от магазина и т.н.

Инженерът скоро осъзна, че собственикът на колата прекарва по-малко време в купуване на ванилов сладолед. Причината е разположението на стоките в магазина. Ваниловият сладолед беше най-популярен и се съхраняваше в отделен фризер в предната част на магазина, за да бъде по-лесен за намиране. И всички останали сортове бяха в задната част на магазина и отнемаше много повече време, за да се намери правилният сорт и да се плати.

Сега въпросът беше за инженера: защо колата не запали, ако е минало по-малко време от момента, в който двигателят е бил изключен? Тъй като проблемът беше във времето, а не във ваниловия сладолед, инженерът бързо намери отговора: беше газов шлюз. Това се случваше всяка вечер, но когато собственикът на колата прекарваше повече време в търсене на сладолед, двигателят успяваше да се охлади достатъчно и запали лесно. И когато мъжът купи ванилов сладолед, двигателят все още беше твърде горещ и газовата ключалка нямаше време да се разтвори.

Морал: Дори напълно лудите проблеми понякога са реални.

Crash Bandicoot

Болезнено е да изпиташ това. Като програмист свикваш да обвиняваш кода си първо, второ, трето... и някъде на десетхилядното място обвиняваш компилатора. И по-надолу в списъка вече обвинявате оборудването.

Ето моята история за хардуерния бъг.

За играта Crash Bandicoot написах код за зареждане и запазване на карта с памет. За такъв самодоволен разработчик на игри това беше като разходка в парка: мислех, че работата ще отнеме няколко дни. В крайна сметка обаче дебъгвах кода в продължение на шест седмици. По пътя реших други проблеми, но на всеки няколко дни се връщах към този код за няколко часа. Беше агония.

Симптомът изглеждаше така: когато запазите текущото изиграване на играта и получите достъп до картата с памет, всичко почти винаги върви добре... Но понякога операциите за четене или запис изтичат без очевидна причина. Краткият запис често поврежда картата с памет. Когато играч се опита да спаси, той не само не успява да спаси, но и унищожава картата. глупости.

След известно време нашият продуцент в Sony, Connie Bus, започна да се паникьосва. Не можахме да изпратим играта с този бъг и шест седмици по-късно не разбрах какво причинява проблема. Чрез Connie се свързахме с други разработчици на PS1: някой срещал ли е нещо подобно? Не. Никой не е имал проблеми с картата памет.

Когато нямате идеи за отстраняване на грешки, единственият оставащ подход е „разделяй и владей“: премахвайте все повече и повече код от грешната програма, докато остане относително малък фрагмент, който все още причинява проблема. Тоест, отрязвате програмата част по част, докато остане частта, която съдържа грешката.

Но работата е там, че е много трудно да се изрежат парчета от видео игра. Как да го стартирате, ако сте премахнали кода, който емулира гравитацията? Или рисуване на герои?

Следователно трябва да заменим цели модули с мъничета, които се преструват, че правят нещо полезно, но всъщност правят нещо много просто, което не може да съдържа грешки. Трябва да напишем такива патерици, за да може играта поне да работи. Това е бавен и болезнен процес.

Накратко, успях. Премахнах все повече и повече парчета код, докато останах с първоначалния код, който конфигурира системата да стартира играта, инициализира хардуера за изобразяване и т.н. Разбира се, на този етап не можех да създам меню за запазване и зареждане, защото трябваше да създам мъниче за целия графичен код. Но мога да се преструвам на потребител, използващ (невидимия) екран за запазване и зареждане и да поискам да запазя и след това да запиша на картата с памет.

Това ме остави с малка част от кода, която все още имаше горния проблем - но все още се случваше на случаен принцип! Най-често всичко работеше добре, но понякога имаше проблеми. Премахнах почти целия код на играта, но грешката все още беше жива. Това беше озадачаващо: останалият код всъщност не направи нищо.

По някое време, може би около три през нощта, ми хрумна една мисъл. Операциите за четене и запис (вход/изход) включват точно време за изпълнение. Когато работите с твърд диск, карта с памет или Bluetooth модул, кодът на ниско ниво, отговорен за четене и запис, прави това в съответствие с тактовите импулси.

С помощта на часовник устройство, което не е директно свързано с процесора, се синхронизира с кода, изпълняван на процесора. Часовникът определя скоростта на предаване - скоростта, с която се предават данните. Ако има объркване с времената, тогава или хардуерът, или софтуерът, или и двете, също са объркани. И това е много лошо, защото данните могат да бъдат повредени.

Ами ако нещо в нашия код обърка времената? Проверих всичко свързано с това в тестовия програмен код и забелязах, че сме настроили програмируемия таймер в PS1 на 1 kHz (1000 такта в секунда). Това е доста, по подразбиране, когато стартира конзолата, тя работи на 100 Hz. И повечето игри използват тази честота.

Анди, разработчикът на играта, настрои таймера на 1 kHz, така че движенията да се изчисляват по-точно. Анди има склонност да прекалява и ако подражаваме на гравитацията, го правим възможно най-точно!

Но какво ще стане, ако ускоряването на таймера по някакъв начин повлияе на цялостното време на програмата и следователно на часовника, който регулира скоростта на предаване за картата с памет?

Коментирах кода на таймера. Грешката не се повтори. Но това не означава, че сме го поправили, защото повредата се появи случайно. Ами ако просто имах късмет?

Няколко дни по-късно експериментирах отново с тестовата програма. Грешката не се повтори. Върнах се към пълната кодова база на играта и промених кода за запазване и зареждане, така че програмируемият таймер да се нулира до първоначалната си стойност (100Hz) преди достъп до картата с памет и след това да се нулира обратно на 1kHz. Нямаше повече катастрофи.

Но защо се случи това?

Отново се върнах към тестовата програма. Опитах се да намеря някакъв модел при възникване на грешка с таймер от 1 kHz. В крайна сметка забелязах, че грешката възниква, когато някой играе с PS1 контролер. Тъй като рядко бих направил това сам - защо ще ми трябва контролер, когато тествам код за запазване и зареждане? - Дори не забелязах тази зависимост. Но един ден един от нашите артисти ме чакаше да свърша с тестването - сигурно ругаех в този момент - и нервно въртеше контролера в ръцете си. Възникна грешка. "Чакаме какво?!" Е, направи го отново!“

Когато разбрах, че тези две събития са взаимосвързани, успях лесно да възпроизведа грешката: започнах да записвам на картата с памет, преместих контролера и съсипах картата с памет. За мен изглеждаше като хардуерен бъг.

Дойдох при Кони и й казах за моето откритие. Тя предаде информацията на един от инженерите, проектирал PS1. „Невъзможно“, отговори той, „не може да е хардуерен проблем.“ Помолих Кони да уреди разговор за нас.

Инженерът ми се обади и поспорихме на неговия развален английски и моя (изключително) развален японски. Накрая казах: „Нека просто изпратя моята тестова програма от 30 реда, където преместването на контролера причинява грешка.“ Той се съгласи. Каза, че е загуба на време и че е ужасно зает да работи по нов проект, но ще се предаде, защото ние сме много важен разработчик за Sony. Изчистих тестовата си програма и му я изпратих.

На следващата вечер (ние бяхме в Лос Анджелис, а той беше в Токио) той ми се обади и свенливо се извини. Беше хардуерен проблем.

Не знам какъв точно беше бъгът, но от това, което чух в централата на Sony, ако настроите таймера на достатъчно висока стойност, той пречи на компоненти на дънната платка в близост до кристала на таймера. Един от тях беше контролер на скоростта на предаване за картата с памет, който също задаваше скоростта на предаване на контролерите. Не съм инженер, така че може да съм объркал нещо.

Но основното е, че имаше смущения между компонентите на дънната платка. И при едновременно предаване на данни през порта на контролера и порта на картата с памет с таймер, работещ на 1 kHz, битовете се губят, данните се губят и картата се поврежда.

Лоши крави

През 1980-те години моят наставник Сергей написа софтуер за SM-1800, съветски клонинг на PDP-11. Този микрокомпютър току-що беше инсталиран на железопътна гара близо до Свердловск, важен транспортен възел в СССР. Новата система е предназначена за маршрутизиране на вагони и товарен трафик. Но съдържаше досадна грешка, която доведе до случайни сривове и сривове. Паданията винаги се случваха, когато някой се прибираше вечер. Но въпреки задълбочената проверка на следващия ден, компютърът работеше правилно при всички ръчни и автоматични тестове. Това обикновено показва състояние на състезание или някакъв друг конкурентен бъг, който възниква при определени условия. Уморен от обаждания късно през нощта, Сергей реши да стигне до дъното и преди всичко да разбере какви условия на складовата площадка са довели до повредата на компютъра.

Първо, той събра статистика за всички необясними падания и създаде графика по дата и час. Моделът беше очевиден. След като наблюдаваше още няколко дни, Сергей осъзна, че може лесно да предскаже времето на бъдещи системни повреди.

Той скоро научи, че смущения възникват само когато гарата сортира влакове с добитък от Северна Украйна и Западна Русия, насочени към близката кланица. Това само по себе си беше странно, тъй като кланицата се доставяше от ферми, разположени много по-близо, в Казахстан.

Атомната електроцентрала в Чернобил избухна през 1986 г. и радиоактивните отпадъци направиха околните райони необитаеми. Огромни райони в Северна Украйна, Беларус и Западна Русия бяха заразени. Подозирайки високи нива на радиация в пристигащите вагони, Сергей разработи метод за проверка на тази теория. На населението беше забранено да има дозиметри, така че Сергей се регистрира при няколко военни на гарата. След няколко питиета водка той успява да убеди войник да измери нивото на радиация в един от подозрителните вагони. Оказало се, че нивото е в пъти над нормалните стойности.

Говедата не само излъчваха много радиация, нивото й беше толкова високо, че доведе до произволна загуба на битове в паметта на SM-1800, който се намираше в сграда до станцията.

В СССР имаше недостиг на храна и властите решиха да смесят месото от Чернобил с месо от други региони на страната. Това даде възможност да се намали общото ниво на радиоактивност, без да се губят ценни ресурси. След като научи за това, Сергей веднага попълни документи за емиграция. И компютърните сривове спряха сами, когато нивото на радиация намаля с течение на времето.

През тръбите

Имало едно време Movietech Solutions създаде софтуер за кина, предназначен за счетоводство, продажба на билети и общо управление. DOS версията на водещото приложение беше доста популярна сред малките и средни вериги киносалони в Северна Америка. Така че не е изненадващо, че когато беше обявена версия на Windows 95, интегрирана с най-новите сензорни екрани и павилиони за самообслужване и оборудвана с всякакви инструменти за отчитане, тя също бързо стана популярна. Най-често актуализацията вървеше без проблеми. Местният ИТ персонал инсталира ново оборудване, мигрира данни и бизнесът продължи. Освен когато не продължи. Когато това се случи, компанията изпраща Джеймс с прякор „Чистача“.

Въпреки че псевдонимът подсказва нечестив тип, чистачът е просто комбинация от инструктор, монтажник и майстор на всички сделки. Джеймс щеше да прекара няколко дни на сайта на клиента, за да сглоби всички компоненти, а след това прекара още няколко дни, обучавайки персонала как да използва новата система, решавайки всички възникнали хардуерни проблеми и по същество помагайки на софтуера през началния му етап.

Ето защо не е изненадващо, че в тези забързани времена Джеймс пристигна в офиса сутринта и преди да успее да стигне до бюрото си, беше посрещнат от мениджъра, пълен с кофеин извън обичайното.

„Страхувам се, че трябва да отидете в Анаполис, Нова Скотия, възможно най-скоро.“ Цялата им система падна и след цяла нощ работа с техните инженери не можем да разберем какво се е случило. Изглежда, че мрежата на сървъра е неуспешна. Но само след като системата е работила няколко минути.

— Не се ли върнаха към старата система? – напълно сериозно отговори Джеймс, макар че мислено ококори очи от изненада.

— Точно така: техният ИТ специалист „промени приоритетите“ и реши да си тръгне със стария си сървър. Джеймс, те инсталираха системата в шест обекта и току-що платиха за първокласна поддръжка и сега бизнесът им се управлява както през 1950-те години на миналия век.

Джеймс леко се изправи.

- Това е друг въпрос. Добре, да започваме.

Когато пристигна в Анаполис, първото нещо, което направи, беше да намери първия театър на клиента, който имаше проблем. На картата, направена на летището, всичко изглеждаше прилично, но районът около желания адрес изглеждаше подозрителен. Не е гето, но напомня на филм ноар. Докато Джеймс паркира на тротоара в центъра, към него се приближи проститутка. Като се има предвид размерът на Анаполис, той най-вероятно беше единственият в целия град. Появата й веднага напомни за известния персонаж, предлагащ секс за пари на големия екран. Не, не за Джулия Робъртс, а за Джон Войт [алюзия към филма "Среднощен каубой" - ок. платно].

След като изпрати проститутката на път, Джеймс отиде на кино. Районът наоколо беше станал по-добър, но все още създаваше впечатлението, че е запуснат. Не че Джеймс беше твърде притеснен. И преди е бил на нещастни места. И това беше Канада, където дори крадците са достатъчно учтиви да кажат „благодаря“, след като ви вземат портфейла.

Страничният вход на киното беше във влажна уличка. Джеймс отиде до вратата и почука. Скоро изскърца и леко се отвори.

- Ти чистач ли си? - чу се дрезгав глас отвътре.

- Да, аз съм... дойдох да оправя всичко.

Джеймс влезе във фоайето на киното. Очевидно нямайки друг избор, персоналът започна да раздава хартиени билети на посетителите. Това затрудни финансовото отчитане, да не говорим за по-интересни подробности. Но персоналът посрещна Джеймс с облекчение и веднага го отведе в сървърната стая.

На пръв поглед всичко беше наред. Джеймс влезе в сървъра и провери обичайните подозрителни места. Няма проблем. Въпреки това, поради прекомерна предпазливост, Джеймс изключи сървъра, замени мрежовата карта и върна системата обратно. Тя веднага започна да работи пълноценно. Персоналът отново започна да продава билети.

Джеймс се обади на Марк и го информира за ситуацията. Не е трудно да си представим, че Джеймс може да иска да остане и да види дали ще се случи нещо неочаквано. Той слезе по стълбите и започна да разпитва служителите какво се е случило. Очевидно системата е спряла да работи. Изключиха го и го включиха, всичко работи. Но след 10 минути системата падна.

Точно в този момент се случи нещо подобно. Изведнъж системата за билети започна да извежда грешки. Персоналът въздъхна и грабна хартиените билети, а Джеймс забърза към сървърната стая. Всичко изглеждаше добре със сървъра.

Тогава влезе един от служителите.

— Системата отново работи.

Джеймс беше озадачен, защото не беше направил нищо. По-точно нищо, което да накара системата да работи. Той излезе, вдигна телефона си и се обади на линията за поддръжка на компанията си. Скоро същият служител влезе в сървърното помещение.

- Системата не работи.

Джеймс погледна сървъра. На екрана танцува интересен и познат модел от разноцветни фигури - хаотично гърчещи се и преплитащи се тръби. Всички сме виждали този скрийнсейвър в някакъв момент. Беше красиво изобразено и буквално хипнотизиращо.


Джеймс натисна бутон и моделът изчезна. Той забърза към гишето и по пътя срещна служител, който се връщаше при него.

— Системата отново работи.

Ако можеш да направиш мисловен facepalm, точно това направи Джеймс. Скрийнсейвър. Използва OpenGL. И следователно, по време на работа, той консумира всички ресурси на сървърния процесор. В резултат на това всяко повикване към сървъра завършва с таймаут.

Джеймс се върна в сървърната стая, влезе и замени скрийнсейвъра с красивите тръби с празен екран. Тоест, вместо скрийнсейвър, който консумира 100% ресурси на процесора, инсталирах друг, който не консумира ресурси. След това изчаках 10 минути, за да проверя предположението си.

Когато Джеймс пристигна в следващото кино, той се чудеше как да обясни на мениджъра си, че току-що е прелетял 800 км, за да изключи скрийнсейвъра.

Срив по време на определена фаза на луната

Истинска история. Един ден се появи софтуерен бъг, който зависеше от фазата на луната. Имаше малка рутина, която обикновено се използваше в различни програми на MIT за изчисляване на приближението до истинската фаза на Луната. GLS вгради тази рутина в LISP програма, която, когато пише файл, ще изведе ред с времева марка с дължина почти 80 знака. Беше много рядко първият ред на съобщение да се окаже твърде дълъг и да доведе до следващия ред. И когато по-късно програмата прочете този файл, тя изпсува. Дължината на първия ред зависи от точната дата и час, както и от дължината на спецификацията на фазата към момента на отпечатване на времевия печат. Тоест бъгът буквално зависеше от фазата на луната!

Първо хартиено издание Файл с жаргон (Steele-1983) съдържа пример за такъв ред, който води до описания бъг, но наборчикът го „поправи“. Оттогава това е описано като „бъг във фазата на луната“.

Все пак внимавайте с предположенията. Преди няколко години инженери от CERN (Европейски център за ядрени изследвания) се натъкнаха на грешки в експериментите, проведени в Големия електрон-позитронен колайдер. Тъй като компютрите активно обработват огромното количество данни, генерирани от това устройство, преди да покажат резултата на учените, мнозина спекулираха, че софтуерът по някакъв начин е чувствителен към фазата на луната. Няколко отчаяни инженери стигнаха до дъното на истината. Грешката е възникнала поради лека промяна в геометрията на пръстена с дължина 27 км, дължаща се на деформацията на Земята по време на преминаването на Луната! Тази история е влязла във фолклора на физиката като „Отмъщението на Нютон над физиката на елементарните частици“ и е пример за връзката между най-простите и най-стари закони на физиката и най-напредналите научни концепции.

Пускането на водата в тоалетната спира влака

Най-добрият хардуерен бъг, за който съм чувал, беше във високоскоростен влак във Франция. Бъгът доведе до аварийно спиране на влака, но само ако на борда имаше пътници. При всеки такъв случай влакът е извеждан от движение, проверяван е, но нищо не е открито. След това той беше върнат обратно на линията и той веднага се разби и спря.

При една от проверките пътуващ във влака инженер отишъл до тоалетната. Той скоро се изми, БУМ! Авариен стоп.

Инженерът се свърза с шофьора и попита:

— Какво правехте точно преди да спрете?

- Е, намалих скоростта на слизане...

Това беше странно, защото при нормална работа влакът намалява скоростта при спускания десетки пъти. Влакът продължи и на следващото спускане машинистът предупреди:

- Ще намаля.

Нищо не се е случило.

— Какво направихте при последното спиране? - попита шофьорът.

- Ами... бях в тоалетната...

- Е, тогава отивай до тоалетната и прави каквото направи, когато слезем отново!

Инженерът отиде до тоалетната и когато шофьорът предупреди: „Намалявам“, той пусна водата. Разбира се, влакът веднага спря.

Сега те можеха да възпроизведат проблема и трябваше да намерят причината.

След две минути те забелязаха, че кабелът за дистанционно управление на спирачката на двигателя (влакът имаше по един двигател във всеки край) беше изключен от стената на електрическия шкаф и лежеше върху релето, което управлява соленоида на тоалетната... Когато релето беше включен, създаваше смущения в кабела на спирачката, а защитата на системата срещу повреди просто включваше аварийно спиране.

Порталът, който мразеше FORTRAN

Преди няколко месеца забелязахме, че мрежовите връзки на континента [това беше в Хавай] стават много, много бавни. Това може да продължи 10-15 минути и след това внезапно да се повтори. След известно време колегата ми се оплака, че мрежовите връзки на континента като цяло не работи. Той имаше някакъв FORTRAN код, който трябваше да бъде копиран на машина на континента, но не можа, защото „мрежата не издържа достатъчно дълго, за да завърши FTP качването“.

Да, оказа се, че мрежови повреди са възникнали, когато колега се е опитал да прехвърли по FTP файл с изходен код на FORTRAN към машина на континента. Опитахме се да архивираме файла: след това той беше копиран гладко (но целевата машина нямаше програма за разопаковане, така че проблемът не беше решен). Накрая „разделихме“ кода на FORTRAN на много малки части и ги изпратихме една по една. Повечето от фрагментите бяха копирани без проблеми, но няколко парчета не минаха или минаха след това многоброен опити.

Когато разгледахме проблемните пасажи, открихме, че те имат нещо общо: всички те съдържат блокове за коментари, които започват и завършват с редове, състоящи се от главно C (както един колега предпочита да коментира във FORTRAN). Изпратихме имейл на мрежови експерти от континента и помолихме за помощ. Разбира се, те искаха да видят проби от нашите файлове, които не могат да бъдат прехвърлени през FTP... но нашите писма не стигнаха до тях. Най-накрая измислихме просто описвамкак изглеждат непрехвърляемите файлове. Работи :) [Смея ли да добавя пример за един от проблемните FORTRAN коментари тук? Вероятно не си струва!]

В крайна сметка успяхме да го разберем. Наскоро беше инсталиран нов шлюз между нашата част от кампуса и континенталната мрежа. Имаше ОГРОМНИ затруднения при предаването на пакети, които съдържаха повтарящи се битове от главни букви C! Само няколко от тези пакети могат да заемат всички ресурси на шлюза и да попречат на повечето други пакети да преминат. Оплакахме се на производителя на шлюза... и те отговориха: „О, да, изправени сте пред грешка на повтарящо се C! Вече знаем за него.” В крайна сметка решихме проблема, като закупихме нов шлюз от друг производител (в защита на първия, невъзможността за прехвърляне на FORTRAN програми може да е предимство за някои!).

Трудни времена

Преди няколко години, докато работех върху създаването на ETL система в Perl, за да намаля разходите за фаза 40 клинични изпитвания, трябваше да обработя около 000 1 дати. Двама от тях не издържаха теста. Това не ме притесни много, защото тези дати бяха взети от предоставени от клиента данни, които често бяха, да кажем, изненадващи. Но когато проверих оригиналните данни, се оказа, че тези дати са 2011 януари 1 г. и 2007 януари 30 г. Мислех, че грешката се съдържа в програмата, която току-що бях написал, но се оказа, че вече е от XNUMX години стар. Това може да звучи мистериозно за тези, които не са запознати със софтуерната екосистема. Поради дългогодишното решение на друга компания да печели пари, моят клиент ми плати да коригирам грешка, която едната компания беше въвела случайно, а другата нарочно. За да разберете за какво говоря, трябва да говоря за компанията, която добави функцията, която в крайна сметка се превърна в грешка, както и няколко други интересни събития, които допринесоха за мистериозната грешка, която поправих.

В добрите стари времена компютрите на Apple понякога спонтанно връщаха датата си на 1 януари 1904 г. Причината беше проста: използваше захранван от батерии „системен часовник“, за да следи датата и часа. Какво се случи, когато батерията умря? Компютрите започнаха да проследяват датата по броя на секундите от началото на епохата. Под епоха имахме предвид референтната оригинална дата, а за Macintoshes беше 1 януари 1904 г. И след като батерията умря, текущата дата беше нулирана на посочената. Но защо се случи това?

Преди това Apple използваше 32 бита за съхраняване на броя секунди от първоначалната дата. Един бит може да съхранява една от две стойности - 1 или 0. Два бита могат да съхраняват една от четири стойности: 00, 01, 10, 11. Три бита - една стойност от осем: 000, 001, 010, 011, 100 , 101, 110, 111 и т.н. А 32 може да съхранява една от 232 стойности, тоест 4 294 967 296 секунди. За датите на Apple това се равнява на около 136 години, така че по-старите Mac не могат да обработват дати след 2040 г. И ако системната батерия умре, датата се нулира на 0 секунди от началото на епохата и трябва ръчно да задавате датата всеки път, когато включите компютъра (или докато не закупите нова батерия).

Въпреки това, решението на Apple да съхранява датите като секунди от епохата означаваше, че не можем да се справим с дати преди епохата, което имаше далечни последици, както ще видим. Apple представи функция, а не грешка. Освен всичко друго, това означаваше, че операционната система Macintosh е имунизирана срещу „бъга на хилядолетието“ (което не може да се каже за много приложения на Mac, които имат свои собствени системи за дата, за да заобиколят ограниченията).

Продължавай. Използвахме Lotus 1-2-3, „убийственото приложение“ на IBM, което помогна за стартирането на компютърната революция, въпреки че компютрите на Apple имаха VisiCalc, което направи персоналния компютър успешен. Честно казано, ако не се беше появил 1-2-3, персоналните компютри едва ли биха се развили и историята на персоналните компютри можеше да се развие много по-различно. Lotus 1-2-3 неправилно третира 1900 г. като високосна година. Когато Microsoft пусна първата си електронна таблица, Multiplan, тя завладя малък дял от пазара. И когато стартираха проекта Excel, те решиха не само да копират схемата за именуване на редове и колони от Lotus 1-2-3, но и да осигурят съвместимост с грешки, като умишлено третираха 1900 г. като високосна година. Този проблем съществува и днес. Тоест, в 1-2-3 това беше грешка, но в Excel това беше съзнателно решение, което гарантира, че всички потребители на 1-2-3 могат да импортират своите таблици в Excel, без да променят данните, дори и да са неправилни.

Но имаше и друг проблем. Първо Microsoft пусна Excel за Macintosh, който не разпознаваше дати преди 1 януари 1904 г. А в Excel 1 януари 1900 г. се смяташе за начало на ерата. Поради това разработчиците направиха промяна, така че тяхната програма да разпознава типа ера и да съхранява данни в себе си в съответствие с желаната ера. Microsoft дори написа обяснителна статия за това. И това решение доведе до моя бъг.

Моята ETL система получи Excel електронни таблици от клиенти, които бяха създадени на Windows, но можеха да бъдат създадени и на Mac. Следователно началото на ерата в таблицата може да бъде или 1 януари 1900 г., или 1 януари 1904 г. Как да разберем? Файловият формат на Excel показва необходимата информация, но анализаторът, който използвах, не я показва (сега го прави) и предполага, че знаете епохата за конкретна таблица. Вероятно можех да отделя повече време за разбиране на двоичния формат на Excel и за изпращане на корекция до автора на анализатора, но трябваше да направя много повече за клиента, така че бързо написах евристика, за да определя епохата. Тя беше проста.

В Excel датата 5 юли 1998 г. може да бъде представена във формат "07-05-98" (безполезна американска система), "Jul 5, 98", "July 5, 1998", "5-Jul-98" или друг безполезен формат (по ирония на съдбата един от форматите, които моята версия на Excel не предлагаше, беше ISO 8601). В рамките на таблицата обаче неформатираната дата беше съхранена или като „35981“ за епоха-1900, или като „34519“ за епоха-1904 (числата представляват броя на дните от епохата). Просто използвах прост анализатор, за да извлека годината от форматираната дата, и след това използвах анализатора на Excel, за да извлека годината от неформатираната дата. Ако и двете стойности се различаваха с 4 години, тогава знаех, че използвам система с епоха-1904.

Защо просто не използвах форматирани дати? Защото 5 юли 1998 г. може да се форматира като "юли 98" със загубен ден от месеца. Получихме таблици от толкова много компании, които са ги създали по толкова различни начини, че зависи от нас (в този случай от мен) да разберем датите. Освен това, ако Excel го прави правилно, тогава трябва и ние!

В същото време срещнах 39082. Позволете ми да ви напомня, че Lotus 1-2-3 смята 1900 за високосна година и това вярно се повтаря в Excel. И тъй като това добавя един ден към годината 1900, много функции за изчисляване на дата може да са грешни точно за този ден. Тоест 39082 може да е 1 януари 2011 г. (на Macs) или 31 декември 2006 г. (на Windows). Ако моят „синтактичен анализатор на години“ извлече годината 2011 от форматираната стойност, тогава всичко е наред. Но тъй като анализаторът на Excel не знае коя епоха се използва, той по подразбиране е епоха-1900, връщайки годината 2006. Приложението ми видя, че разликата е 5 години, сметна го за грешка, регистрира го и върна неформатирана стойност.

За да заобиколя това, написах това (псевдокод):

diff = formatted_year - parsed_year
if 0 == diff
    assume 1900 date system
if 4 == diff
    assume 1904 date system
if 5 == diff and month is December and day is 31
    assume 1904 date system

И тогава всичките 40 000 дати бяха анализирани правилно.

В средата на големи задания за печат

В началото на 1980-те години баща ми работеше в Storage Technology, вече несъществуващо подразделение, което създаваше лентови устройства и пневматични системи за високоскоростно подаване на лента.

Те преработиха устройствата, така че да могат да имат едно централно устройство „A“, свързано към седем устройства „B“, а малката ОС в RAM, която контролира устройството „A“, можеше да делегира операции за четене и запис на всички устройства „B“.

При всяко стартиране на устройство “A” беше необходимо да се постави флопи диск в свързаното към “A” периферно устройство, за да се зареди операционната система в неговата памет. Беше изключително примитивен: изчислителната мощност се осигуряваше от 8-битов микроконтролер.

Целевата аудитория за такова оборудване бяха компании с много големи хранилища за данни - банки, търговски вериги и т.н. - които трябваше да отпечатат много адресни етикети или банкови извлечения.

Един клиент имаше проблем. По време на задание за печат едно конкретно устройство „A“ може да спре да работи, което да доведе до спиране на цялото задание. За да възстанови работата на устройството, персоналът трябваше да рестартира всичко. И ако това се случи по средата на шестчасова задача, тогава беше загубено огромно количество скъпо компютърно време и графикът на цялата операция беше нарушен.

Техници бяха изпратени от Storage Technologies. Но въпреки всичките им усилия, те не успяха да възпроизведат грешката при тестови условия: изглеждаше, че се появява в средата на големи задания за печат. Проблемът не беше в хардуера, смениха всичко, което можаха: RAM, микроконтролер, флопи устройство, всяка възможна част от лентовото устройство - проблемът продължаваше.

Тогава техниците се обадили в централата и извикали Експерта.

Експертът грабна стол и чаша кафе, седна в компютърната зала - в онези дни имаше стаи, предназначени за компютри - и наблюдаваше как персоналът реди на опашка голяма задача за печат. Експертът чакаше да се случи провал - и той се случи. Всички погледнаха Експерта, но той нямаше представа защо се случи това. Затова той нареди работата да бъде поставена на опашка и целият персонал и техници се върнаха на работа.

Експертът отново седна на стола и започна да чака провал. Минаха около шест часа и настъпи повреда. Експертът отново нямаше никакви идеи, освен че всичко се случи в стая, пълна с хора. Той нареди мисията да бъде рестартирана, седна отново и зачака.

При третия отказ Експертът забеляза нещо. Повредата е възникнала, когато персоналът е сменил лентите в чуждо устройство. Освен това повредата настъпи веднага щом един от служителите мина през определена плочка на пода.

Повдигнатият под беше направен от алуминиеви плочки, положени на височина от 6 до 8 инча. Множество кабели от компютри минаваха под повдигнатия под, за да предотвратят случайно стъпване на важен кабел. Плочките бяха положени много плътно, за да се предотврати попадането на отломки под повдигнатия под.

Експертът установил, че една от плочките е деформирана. Когато служител стъпи на ъгъла му, краищата на плочката се триеха в съседните плочки. Пластмасовите части, които свързват плочките, също се трият с тях, което причинява статични микроразряди, които създават радиочестотни смущения.

Днес RAM паметта е много по-добре защитена от радиочестотни смущения. Но в онези години това не беше така. Експертът разбра, че тази намеса нарушава паметта, а с това и работата на операционната система. Обади се на поддръжката, поръча нови плочки, постави ги сам и проблемът изчезна.

Прилив е!

Историята се разигра в сървърна стая, на четвъртия или петия етаж на офис в Портсмут (мисля), в района на доковете.

Един ден Unix сървърът с основната база данни се срина. Те го рестартираха, но той щастливо продължи да пада отново и отново. Решихме да се обадим на някой от поддръжката.

Човекът от поддръжката... Мисля, че се казваше Марк, но това няма значение... Не мисля, че го познавам. Няма значение, наистина. Нека се придържаме към Марк, става ли? Страхотен.

И така, няколко часа по-късно Марк пристигна (не е дълъг път от Лийдс до Портсмут, знаете), включи сървъра и всичко заработи без проблеми. Типична проклета поддръжка, клиентът много се разстройва от това. Марк преглежда регистрационните файлове и не намира нищо лошо. Така че Марк се качва отново на влака (или с какъвто и да е вид транспорт, с който е пристигнал, доколкото знам, можеше да е куца крава... както и да е, няма значение, нали?) и се отправя обратно към Лийдс, пропилян Денят.

Същата вечер сървърът се срива отново. Историята е същата... сървъра не става. Марк се опитва да помогне дистанционно, но клиентът не може да стартира сървъра.

Още един влак, автобус, лимонов меренг или някакви други глупости и Марк се завръща в Портсмут. Вижте, сървърът се зарежда без никакви проблеми! чудо. Марк прекарва няколко часа в проверка дали всичко е наред с операционната система или софтуера и тръгва към Лийдс.

Около средата на деня сървърът се срива (успокойте се!). Този път изглежда разумно да се привлекат хора от хардуерната поддръжка, които да заменят сървъра. Но не, след около 10 часа и той пада.

Ситуацията се повтаря няколко дни. Сървърът работи, забива след около 10 часа и не тръгва следващите 2 часа. Провериха охлаждане, изтичане на памет, провериха всичко, но не намериха нищо. След това катастрофите спряха.

Седмицата мина безгрижно... всички бяха щастливи. Щастлив, докато всичко започне отначало. Картинката е същата. 10 часа работа, 2-3 часа престой...

И тогава някой (мисля, че ми казаха, че този човек няма нищо общо с ИТ) каза:

— Това е приливът!

Възклицанието беше посрещнато с празни погледи и нечия ръка вероятно се поколеба на бутона за повикване на охраната.

„Спира да работи с прилива.“

Това изглежда напълно чужда концепция за служителите в ИТ поддръжката, които е малко вероятно да четат Tide Yearbook, докато седят на кафе. Те обясниха, че това по никакъв начин не може да бъде свързано с прилива, тъй като сървърът е работил седмица без сривове.

„Миналата седмица приливът беше слаб, но тази седмица е висок.“

Малко терминология за тези, които нямат лиценз за яхта. Приливите и отливите зависят от лунния цикъл. И докато Земята се върти, на всеки 12,5 часа гравитационното привличане на Слънцето и Луната създава приливна вълна. В началото на 12,5-часовия цикъл има прилив, в средата на цикъла има отлив, а в края отново има прилив. Но с промяната на орбитата на луната се променя и разликата между отлив и отлив. Когато Луната е между Слънцето и Земята или на противоположната страна на Земята (пълнолуние или без луна), получаваме Сизигински приливи - най-високите приливи и най-ниските отливи. На полулуна получаваме квадратурни приливи и отливи - най-ниските приливи и отливи. Разликата между двете крайности намалява значително. Лунният цикъл продължава 28 дни: сизигий - квадратура - сизигий - квадратура.

Когато на техниците беше обяснена същността на приливните сили, те веднага решиха, че трябва да се обадят на полицията. И съвсем логично. Но се оказа, че пичът е прав. Две седмици по-рано разрушител акостира недалеч от офиса. Всеки път, когато приливът го издигаше до определена височина, радарният пост на кораба се озоваваше на нивото на пода на сървърната стая. А радарът (или оборудването за електронна война, или някоя друга военна играчка) създаде хаос в компютрите.

Полетна мисия за ракетата

Бях натоварен с пренасянето на голяма (около 400 хиляди реда) система за контрол и наблюдение на изстрелването на ракети към нови версии на операционната система, компилатора и езика. По-точно, от Solaris 2.5.1 до Solaris 7 и от Verdix Ada Development System (VADS), написана на Ada 83, до системата Rational Apex Ada, написана на Ada 95. VADS беше закупена от Rational и нейният продукт беше остарял, въпреки че Rational се опита да внедри съвместими версии на специфични за VADS пакети, за да улесни прехода към компилатора Apex.

Трима души ми помогнаха просто да компилирам чисто кода. Отне две седмици. И тогава работих сам, за да заработя системата. Накратко, това беше най-лошата архитектура и реализация на софтуерна система, с която се бях сблъсквал, така че отне още два месеца, за да завърши порта. След това системата беше изпратена за тестване, което отне още няколко месеца. Веднага коригирах грешките, открити по време на тестването, но техният брой бързо намаля (изходният код беше производствена система, така че функционалността му работеше доста надеждно, просто трябваше да премахна грешките, възникнали по време на адаптирането към новия компилатор). В крайна сметка, когато всичко работеше както трябва, бях прехвърлен в друг проект.

И в петък преди Деня на благодарността телефонът звънна.

Изстрелването на ракетата трябваше да бъде изпробвано след около три седмици, а по време на лабораторните тестове на обратното броене последователността от команди беше блокирана. В реалния живот това би прекъснало теста и ако блокирането се случи в рамките на няколко секунди след стартиране на двигателя, ще възникнат няколко необратими действия в спомагателните системи, което ще изисква дълга - и скъпа - готовност на ракетата. Нямаше да започне, но много хора щяха да бъдат много разстроени от загубата на време и много, много пари. Не позволявайте на никого да ви каже, че Министерството на отбраната харчи пари безразсъдно - никога не съм срещал мениджър по договори, който да не е поставил бюджета на първо или второ място, последвано от график.

През предишните месеци това предизвикателство за обратно броене беше изпълнявано стотици пъти в много варианти, само с няколко незначителни пропуска. Така че вероятността това да се случи беше много малка, но последствията бяха много значителни. Умножете и двата фактора и ще разберете, че новините предричаха провалена празнична седмица за мен и десетки инженери и мениджъри.

И беше обърнато внимание на мен като човек, който е пренесъл системата.

Както при повечето критични за сигурността системи, много параметри бяха регистрирани, така че беше доста лесно да се идентифицират няколкото реда код, които бяха изпълнени преди системата да се срине. И разбира се, в тях нямаше абсолютно нищо необичайно; едни и същи изрази бяха успешно изпълнени буквално хиляди пъти по време на едно и също изпълнение.

Повикахме хората от Apex в Rational, защото те бяха тези, които разработиха компилатора и някои от рутинните процедури, които разработиха, бяха извикани в подозрителния код. Те (и всички останали) бяха впечатлени, че трябва да се стигне до корена на проблем от буквално национално значение.

Тъй като в дневниците нямаше нищо интересно, решихме да опитаме да възпроизведем проблема в местна лаборатория. Това не беше лесна задача, тъй като събитието се случваше приблизително веднъж на 1000 изпълнения. Една предполагаема причина беше, че извикване на разработена от доставчика mutex функция (част от пакета за миграция на VADS) Unlock не доведе до отключване. Нишката за обработка, която извика функцията, обработи сърдечни съобщения, които номинално пристигаха всяка секунда. Вдигнахме честотата на 10 Hz, тоест 10 пъти в секунда, и започнахме да бягаме. Около час по-късно системата се заключи. В дневника видяхме, че последователността от записани съобщения е същата като по време на неуспешния тест. Направихме още няколко пускания, системата беше постоянно блокирана 45-90 минути след началото и всеки път дневникът съдържаше същия маршрут. Въпреки че технически изпълнявахме различен код - честотата на съобщенията беше различна - поведението на системата беше същото, така че бяхме уверени, че този сценарий на натоварване причинява същия проблем.

Сега трябваше да разберем къде точно е настъпило блокирането в последователността от изрази.

Тази реализация на системата използва системата за задачи Ada и я използва невероятно лошо. Задачите са едновременно изпълнима конструкция на високо ниво в Ada, нещо като нишки за изпълнение, вградени само в самия език. Когато две задачи трябва да комуникират, те „задават среща“, обменят необходимите данни и след това спират срещата и се връщат към своите независими изпълнения. Системата обаче беше внедрена по различен начин. След срещата на целевата задача, тази целева задача се среща с друга задача, която след това се среща с трета задача и така нататък, докато не завърши някаква обработка. След това всички тези срещи бяха завършени и всяка задача трябваше да се върне към своето изпълнение. Тоест, имахме работа с най-скъпата система за извикване на функции в света, която спря целия процес на „многозадачност“, докато обработваше част от входните данни. А преди това не водеше до проблеми само защото пропускателната способност беше много ниска.

Описах този механизъм на задачите, защото когато срещата е поискана или се очаква да завърши, може да възникне „превключване на задача“. Това означава, че процесорът може да започне да обработва друга задача, която е готова за изпълнение. Оказва се, че когато една задача е готова за среща с друга задача, напълно различна задача може да започне да се изпълнява и в крайна сметка контролът се връща към първата среща. И могат да възникнат други събития, които да предизвикат превключване на задачата; едно такова събитие е извикване на системна функция, като отпечатване или изпълнение на mutex.

За да разбера кой ред от код причинява проблема, трябваше да намеря начин да запиша напредъка през поредица от изрази, без да задействам превключване на задачи, което би предотвратило възникването на срив. Така че не можах да се възползвам Put_Line()за да избегнете извършването на I/O операции. Бих могъл да задам променлива на брояча или нещо подобно, но как мога да видя стойността й, ако не мога да я покажа на екрана?

Също така, при изследване на дневника се оказа, че въпреки замразяването на обработката на сърдечните съобщения, което блокира всички I/O операции на процеса и не позволява извършването на друга обработка, други независими задачи продължават да се изпълняват. Тоест работата не беше блокирана изцяло, а само (критична) верига от задачи.

Това беше уликата, необходима за оценка на блокиращия израз.

Направих пакет Ada, който съдържа задача, изброен тип и глобална променлива от този тип. Изброими литерали бяха обвързани със специфични изрази на проблемната последователност (напр. Incrementing_Buffer_Index, Locking_Mutex, Mutex_Unlocked), и след това вмъкна изрази за присвояване в него, които присвоиха съответното изброяване на глобална променлива. Тъй като обектният код на всичко това просто съхраняваше константа в паметта, превключването на задачи в резултат на нейното изпълнение беше изключително малко вероятно. Бяхме предимно подозрителни към изрази, които биха могли да превключат задачата, тъй като блокирането се случи при изпълнение, вместо да се върне при превключване на задачата обратно (по няколко причини).

Задачата за проследяване просто се изпълняваше в цикъл и периодично проверяваше дали стойността на глобалната променлива се е променила. При всяка промяна стойността се записва във файл. След това кратко изчакване и нова проверка. Написах променливата във файла, защото задачата беше изпълнена само когато системата я избра за изпълнение при превключване на задачата в проблемната област. Каквото и да се случи в тази задача, няма да засегне други, несвързани блокирани задачи.

Очакваше се, че когато системата достигне точката на изпълнение на проблемния код, глобалната променлива ще бъде нулирана при преминаване към всеки следващ израз. Тогава ще се случи нещо, което кара задачата да се превключи и тъй като нейната честота на изпълнение (10 Hz) е по-ниска от тази на задачата за наблюдение, мониторът може да улови стойността на глобалната променлива и да я запише. В нормална ситуация бих могъл да получа повтаряща се последователност от подмножество от изброявания: последните стойности на променливата в момента на превключване на задачата. При увисване глобалната променлива вече не трябва да се променя и последната записана стойност ще покаже кой израз не е завършен.

Изпълних кода с проследяване. Той замръзна. И мониторингът работеше като часовник.

Регистърът съдържаше очакваната последователност, която беше прекъсната от стойност, показваща, че е бил извикан mutex Unlock, а задачата не е завършена - какъвто е случаят с хиляди предишни обаждания.

Инженерите на Apex трескаво анализираха кода си по това време и намериха място в мютекса, където теоретично може да възникне заключване. Но вероятността за това беше много ниска, тъй като само определена последователност от събития, настъпващи в определен момент, можеше да доведе до блокиране. Законът на Мърфи, момчета, това е законът на Мърфи.

За да защитя частта от кода, от която се нуждаех, замених извикванията на функцията mutex (изградена върху функцията mutex на OS) с малък нативен пакет за mutex на Ada, за да контролирам достъпа на mutex до тази част.

Вмъкнах го в кода и пуснах теста. Седем часа по-късно кодът все още работеше.

Моят код беше изпратен на Rational, където го компилираха, разглобиха и провериха, че не използва същия подход, който беше използван в проблемните mutex функции.

Това беше най-натовареният преглед на кода в моята кариера 🙂 Имаше около десет инженери и мениджъри в стаята с мен, други десет души бяха на конферентен разговор - и всички те прегледаха около 20 реда код.

Кодът беше прегледан, нови изпълними файлове бяха събрани и изпратени за формално регресионно тестване. Няколко седмици по-късно тестът за обратно броене беше успешен и ракетата излетя.

Добре, всичко това е хубаво, но какъв е смисълът на историята?

Беше абсолютно отвратителен проблем. Стотици хиляди редове код, паралелно изпълнение, над дузина взаимодействащи процеси, лоша архитектура и лошо внедряване, интерфейси за вградени системи и похарчени милиони долари. Без натиск, нали.

Не бях единственият, който работеше по този проблем, въпреки че бях в светлината на прожекторите, докато правех пренасянето. Но въпреки че го направих, това не означава, че разбрах всичките стотици хиляди редове код или дори ги прегледах набързо. Кодът и регистрационните файлове бяха анализирани от инженери от цялата страна, но когато ми казаха своите хипотези за причините за повредата, ми отне само половин минута да ги опровергая. И когато бях помолен да анализирам теории, щях да го предам на някой друг, защото за мен беше очевидно, че тези инженери вървят по грешен път. Звучи ли ви самонадеяно? Да, така е, но отхвърлих хипотезите и исканията по друга причина.

Разбрах естеството на проблема. Не знаех точно къде се случва или защо, но знаех какво се случва.

През годините натрупах много знания и опит. Бях един от пионерите в използването на Ada и разбрах нейните предимства и недостатъци. Знам как библиотеките за изпълнение на Ada обработват задачи и се справят с паралелното изпълнение. И аз разбирам ниско ниво на програмиране на ниво памет, регистри и асемблер. С други думи, имам дълбоки познания в моята област. И ги използвах, за да открия причината за проблема. Не просто заобиколих грешката, разбрах как да я намеря в много чувствителна среда за изпълнение.

Такива истории за борба с кода не са много интересни за тези, които не са запознати с характеристиките и условията на такава борба. Но тези истории ни помагат да разберем какво е необходимо за решаването на наистина трудни проблеми.

За да разрешите наистина трудни проблеми, трябва да сте нещо повече от програмист. Трябва да разберете „съдбата“ на кода, как той взаимодейства със своята среда и как самата среда работи.

И тогава ще имате своя собствена съсипана празнична седмица.

Да продължи.

Източник: www.habr.com

Добавяне на нов коментар