У нас завіс запыт, а разам з ім завіс паток, які яго зрабіў. Я сказаў тое, што казаў і раней: кліент без таймаўта чакае вечна, таму таймаўт стаў. Гэтую параду я і зараз дам. Проста я ніводнага разу не праверыў, ці слушная яе першая палова. Яна няслушная.
У ядра ёсць сваё меркаванне пра тое, колькі чакаць, і гэтае меркаванне дастаецца задарма.
Стэнд гэта 2 машыны і 1 адрас, які нікуды не вядзе. Дыяпазон 192.0.2.0/24 зарэзерваваны пад дакументацыю, таму пакеты туды дзесьці па дарозе выкідваюцца. Адказ не прыходзіць ніколі. Значыць, злучэнне з 192.0.2.1:80 робіць роўна тое, што я хацеў засекчы: чакае адказу, якога не будзе.
На серверы, дзе tcp_syn_retries стаіць па змаўчанні роўным 6, яно чакала 135.3 секунды і ўпала з ETIMEDOUT. Гэты лік не выбірала ніводная радок майго кода.
Падваенне гэта ўся механіка. Першы пакет сыходзіць. Калі ніхто не адказаў, наступны ідзе праз 1 секунду, потым праз 2, потым праз 4 і гэтак далей, таму пры 6 паўторах сума паўз дае 127 секунд. Мадэль каштуе роўна столькі, колькі каштуюць яе прадказанні, таму я памяняў наладу і засек нанова. Пры 2 паўторах ядро здалося за 7.2 секунды супраць прадказаных 7. Пры 3 паўторах выйшла 19.4 секунды супраць прадказаных 15.
То бок на кароткім канцы мадэль слушная, а на доўгім прамахваецца спачатку на 4.4, потым на 8.3 секунды. Першая здагадка была пра памяць ядра: Linux кэшуе ацэнку часу абароту па адрасе прызначэння, і сапсаваная ацэнка расцягнула б кожны наступны прагон. Справа не ў гэтым. Запісу па гэтым адрасе няма ўвогуле, а пасля скіду кэша выходзяць тыя ж 19.4 секунды, да дзясятых. Адкуль бяруцца лішнія секунды, я не ведаю.
Палова, якая сапраўды вечнасць
Таймаўт на злучэнне гэта тая палова, якую табе даюць. Другую не даюць.
Я падняў лакальны сервер, які прымае злучэнне, чытае запыт і не адказвае ніколі. Кліент, у якога нічога свайго не выстаўлена, усё яшчэ чакаў, калі я спыніў яго на 30 секундах. Паўтараць тут няма чаго і выяўляць ядру няма чаго, бо злучэнне здаровае, а суразмоўца проста маўчыць. Ні ў чаго ніжэй за бібліятэку няма прычыны ўмешвацца. Той жа кліент з выстаўленымі 2000 мілісекундамі памёр на 2.0 секундах.
Вось форма пасткі. Адмова, пра якую думаюць усе, гэта ўпалая машына, і яна ўжо апрацаваная: дрэнна, але абмежавана. Адмова, пад якую ніхто не заводзіць будзільнік, гэта суразмоўца, які прыняў твой запыт, нічога не сказаў і цяпер трымае твой паток да самай смерці працэса.
Дзе я спатыкнуўся
Першы прагон заняў 0.0 секунды і адрапартаваў пра поспех. Я запускаў яго дома. Мая хатняя сетка адказвае на 192.0.2.1 па 80 порце, на адрас, які па стандарце не належыць нікому. На 10.255.255.1 яна адказвае гэтак жа. У сетцы, дзе заўсёды хтосьці здымае слухаўку, чорнай дзіркі для замеру няма, таму замер пераехаў на машыну з сумленным маршрутам. Сумленная версія майго першага выніку гучыць так: я памераў свой роўтар, а не ядро.
Чаго я не правяраў
Адкуль бяруцца лішнія 8.3 секунды. Ці тыя ж лікі на Windows пры сумленным маршруце: дома праверыць гэта няма на чым. Ці пакідае бібліятэчны таймаўт, які спрацаваў пасярод запыту, сокет у стане, які пул потым перавыкарыстае. Гэта і ёсць тая адмова, за якую мне трывожна на прадакшне, і яна патрабуе іншага стэнда. І паўторы: любы знаёмы мне кліент кладзе рэтрай паверх таймаўта, таму той, хто выклікае, чакае здабытак 2 налад, а не любую з іх.
Вузкае сцвярджэнне такое. Пасля таго як злучэнне паднялося, час не лічыць ніхто. Да таго як яно паднялося, ядро лічыць прыкладна да 2 хвілін лікам, якога ніхто ў кодзе ніводнага разу не бачыў.