Давненько я не писал на Хабр, да и тема интересная появилась, так что пора поправить это постыдное упущение. Далее собственно детектив как оно есть, "расследование" которого ещё не окончено, можно присоединиться кстати… Пост будет обновляться, по окончанию (я надеюсь что баг таки найдётся) пост изменит название получив префикс "[SOLVED]"...
Продолжение и надеюсь окончание истории см. в этом посте. Постучался тут человечек на GH, с ошибкой типа "Fail2ban ведет себя как-будто он временами в будущем". Первой мыслью было — что опять! ну снова кто-то во временных зонах потерялся.
Но нет, всё оказалось несколько хуже — иногда, редко, Fail2ban пишет в логи дату из 2023-го года.
И не только пишет, а по всей видимости действительно начинает считать что он где-то в 2023-м, со всеми вытекающими — снятием бана для блокированных адресов по истечению срока действия и т.д. и т.п.
Причем делает это для всех потоков, а чуть позже возвращается в 2021-й, чтобы позднее снова на короткое время прыгнуть в 2023-й и так снова и снова. Т.к. Fail2ban пишет в лог системное время, я ответил товарищу что оно вероятнее всего просто постоянно меняется, т.е. либо синхронизация времени NTP каким-то образом «нарушена», либо (что весьма вероятно) какой-то сервис или скрипт постоянно изменяет системные часы на короткое время. И на этом закрыл вопрос как 3rd party issue. Ну и человек через некоторое время вернулся, попробовав мой тест-скрипт вида
while true; do echo "$(date +%Y-%m-%dT%H:%M:%S.%-03N%z)"; sleep 0.5; done (даже с sleep 0.001), и не обнаружил там временных аномалий (в то время как Fail2ban по его заявлению прыгал туда-сюда во времени), а также указал что дескать он, Fail2ban, единственный сервис что периодически так делает — всё остальное, вплоть до журналов syslog, остаётся в родном 2021-м. На вопрос что же наблюдается для вот этого: ответ был (здесь обрезаный короткий отрывок, diff синтаксис в качестве "ударения"): На чем вопрос в Fail2ban можно было бы действительно закрыть, но…
Захотелось помочь человеку, плюс интрига, интересно как это так и что это за магия машина времени. Что мы тут собственно видим — "голый" python тут прыгает во времени почем зря, при этом даже на monotonic time тот "прыжок" иногда заметен, однако там он как бы больше "дрейф" чем "прыжок", ибо разница составляет лишь 514 секунд, но пока не суть важно.
Внимательный читатель наверняка заметил еще одну странность — unix time 1705M находится в 2024-м году, а не в 2023-м как strftime нам заявляет (об этом подробнее ниже). О чем товарищу было сказано собственно, с прувом и описанием всех остальных странностей в том числе и некоторым сомнением что всё это прикол шутка юмора. Организованная им проверка на вшивость для syscall clock_gettime показывает, что оно дескать всегда возвращает корректное значение, т.е. подозрение пока на python (версия 3.9.2) собственно. Проверка на возможные изменения /etc/localtime или чего-то в этом роде, определением TZ=UTC перед вызовом питона показала те же результаты (только со смещением GMT+0000 в локальном времени). Тест того же в python 2.7 на той же самой машине показал, что 2-й питон не подвержен временной аномалии от слова совсем. Попытка оттрейсить это с помощью strace провалилась (clock_gettime не наблюдается вовсе), ибо ванильный python 3.9.2 (в Debian 11) или скомпилирован как-то со статически слинкованой libc (и оно там в юзерспейсе) или что-то в этом духе, т.е. имеем такой результат (в сравнении с тем же 3.9 собранным из исходниках на моём Debian 10 или ванильным 3.7 для него же): (хотя согласно исходникам он однозначно вызывает clock_gettime и в 3.9.2). Ну на нет и суда нет, в сторону strace, заряжаем ltrace... И… многочисленные попытки уточнить что это за свинство такое с помощью ltrace (с собственным конфигом, чтобы оттрейсить локальный clock_gettime и т.п.) показывают совершенно ненормальные результаты — что-то внутри python "арифметики" ломает верные значения clock_gettime почём зря, при этом по видимому многократно. В результате для: имеем следующее (снова короткий отрывок, diff синтаксис в качестве "ударения"): По поводу ненормальности результатов, как это выглядит: Т.е. time.time вызывая clock_gettime(CLOCK_REALTIME) получает {1640803035, 222419463}, преобразуя это в double (если смотреть в исходники, это происходит через long long) оно внезапно возвращает 1705615768.690 (вместо 1640803035.222) и сохраняет его в t. А затем при вычислении time.localtime(t), что есть функция от t, наше время (в качестве локального базиса) становится вдруг 1674361765.555368 и в результате имеем { 25 сек., 29 мин., 20 час., 21 день, 0 + 1 мес., 123 + 1900 год., ... } что и соответствует 2023-01-21T20:29:25 в итоге при выводе в консоль через strftime. Как это объяснить кроме как магией, сломанной математикой double, перемешиванием инструкций byte-code питона на лету (в результате какой-то непонятной оптимизации) или вообще UB (удивительно стабильным, ну как — относительно стабильным) я не очень пока понимаю.
Возможно это какая-либо проблема синхронизации, но в простейшем тесте то всё работает однопоточно…
Ошибки в железе? Тогда почему это проявляется исключительно в питоне. Кроме того при прыжках наблюдается еще более странное поведение — эти "непонятные" значения t (те что 1705M) иногда отличаются на 120 и более секунд (иррегулярно), притом что исходные значения (те что 1640M) и значения lt (и промежуточной арифметики, те что 1674M) отличаются (как будто так и положено) на 1 секунду, например: Я постучался с этим в IRC#python, там люди поудивлялись немного, но конкретных предложений не поступало.
Анализ исходников, кроме того, что оно там переписывалось по сто раз до 3.10 включительно (не говоря уже про master), ничего не дал (пока во всяком случае, т.к. времени очень мало свободного). Промежуточные результаты расследования иcследования или как issue видится мне в виде краткой выжимки: Пока на этом всё. Всех с новым годом, и если есть идеи, предложения и желание поучаствовать, добро пожаловать в комменты или на github.  Однострочник для bash на попробовать воспроизвести: выводит строчку с актуальными значениями каждые полсекунды и останавливается если отклонение превышает 1 час от начального (или соответственно ровно через 1 час). Продолжение ... Senior Engineer; Data Scientist; Security Auditor