Здесь лежит окончание "расследования" Новогодний детектив: странный хайзенбаг в «питоньих» часах.
Изначально хотел просто обновить статью и написать соответствующий комментарий, но понял что апдейт выходит чуть не длиннее самой статьи. Напомню краткое содержание предыдущей части: python, как впрочем и всё на нем написанное, временами прыгает в будущее, а конкретно в 2023-й год в локальной временной зоне, и по некоторым данным в 2024-й в UTC/GMT (но это не точно) и побыв там некоторое время возвращается обратно в настоящее. Во время прыжка оно ведет себя довольно стабильно (т.е. считает нано-, микро- и миллисекунды, а то и секунды, как будто время идет как ни в чём не бывало) в 2023-м т.е. локально, при том что в результате повторных прыжков время вновь продолжается как будто по возвращению оно (время) течет в какой-то параллельной вселенной. Однако странное его "отражение" в UTC/GMT, ну то что как будто бы в 2024-м, выглядит менее стабильно, ибо для него наблюдается странные дрейфы дополнительно к смещению прыжка. Хотя куда уж страннее. Всё это в результате хоть и выглядит как "битая память", вряд ли этим объясняется, ибо во время аномалий наблюдаются совершенно ненормальные величины промежуточных значений unix time, для которых во главу угла можно положить преобразование вида: Что как бы ни в целочисленной форме ни в формате с плавающей запятой простейшими битовыми операциями (& или |) не объяснить… Ну и как вишенка этот до странности стабильный результат для lt, при этом еще и "корректно" тикающий в течении временных аномалий. "Расследование" затянулось чуть-чуть — праздники, ну и человек, у которого оно поломано, никуда не торопится по всей видимости занятой очень, т.к. отвечает с паузами в два-три дня. Сразу скажу что python был полностью неувиноватым… Во-первых, выяснилось, что ваабще неувиновный он. Ваабще чистой воды неувиновный. Крайней степени неувиновный.  А если серьёзно, кто бы сомневался, однако лучше всё же по порядку... Итак, что еще делалось на той машине чтобы поймать багу (для тех кто не видел апдейты первой части): Поскольку с ltrace дальнейшее продвижение не видится возможным, а товарищ уже сам собирал питон из исходников, захотелось мне "подебажить" его немного на предмет уточнить в котором месте конкретно то всё ломается, т.е. запустить тест на дебажном и несколько пропатченом python. Чтобы не пугать человека gdb, conditional breakpoint и тому подобными конструкциями да из под консоли, на коленке была написана простенькая заплатка для python 3.10.2, с целью вывести некоторые промежуточные состояния и переменные в каждой функции в цепочке от clock_gettime до собственно местами кривого ответа time.time() и как он затем долетает до time.localtime(): Как заплата выглядит можно посмотреть в этом примере (вырезаны только релевантные куски кода из pytime.c и timemodule.c, которые тут немного упрощены и добавлены пояснения для наглядности): Скрипт для всего действа выглядит как то так: Заплатка и скрипт улетели до бенефициара и ожидаемо через пару дней прилетел ответ, где наглядно так прослеживается, что питон неуиноватый прыжки в будущее наблюдаются там где они тупо не должны наблюдаться (а они, как тот суслик, в наличии). Однако всё по порядку…  Кстати, внимательный читатель возможно заметил, что цифры успели немного подрасти, что 1642M (которая месяц назад была еще 1640M), что 1676M (которая ранее была 1674M), и сделали они это пропорционально (что возможно еще один аргумент против битой памяти). Берём первый кусок в котором наблюдаем 1676M вместо положенного 1642M и пытаемся через него выйти на того "мерцающего" вдалеке суслика.  Итак собственно выхлоп теста для первой аномалии (diff синтакс для ударения, т.е. подсветки первого следа зверька): Т.е. смотрим выше в код для _PyFloat_FromPyTime, и что мы там видим? А собственно, то что _PyTime_AsSecondsDouble(t) вдруг возвращает 1676M вместо 1642M, которое затем, уже в испорченном виде, оборачивается в объект.
И это при верном t, которое было 1642516506243048649, что при делении на 1e9 вдруг почему-то даёт нам 1676140652.455! Если же мы заглянем в _PyTime_AsSecondsDouble, которая по всей видимости и есть суть искомая машина времени, нам там наглядно продемонстрируют глубокий философский смысл фразы "Суслика видишь?.. Вот и я не вижу… А он есть": Поскольку никакой магии тут нет и быть не может: volatile для d — это попытка избежать оптимизации, изменяющей порядок округления чисел, а первая ветка служит для предотвращения небольшой ошибки в расчетах на системах с 64-битным FP, если число делится на единицу с 9 нулями без остатка (имеем только круглые секунды). Т.е. деление организованно тут либо в целочисленной для int64_t, либо в FP арифметике для double (зависит от остатка при делении t на SEC_TO_NS, то есть на тот самый лярд). Если короче, то на самом деле эта функция абсолютно детерминирована, что полностью исключает какие-либо отклонения от совершенно обычного действия деления t / 1e9. Что для 1642516506243048649 / 1e9 должно быть 1642516506.243 а никак не 1676140652.455.
Ну, в случае корректно работающей системы, естественно. Отсюда вывод, что она (та система) работает некорректно. Как собственно в принципе и предполагалось... "А где же тот 1707M (который результат time.time()) в промежуточных значениях" спросит цепкий и наблюдательный читатель. И я ему незамедлительно отвечу: либо мы до них еще не добрались (нужна еще парочка printf где-нибудь), либо это так не дебажится (хайзенбаг тут всё-таки или погулять вышел), либо, что скорее всего, мы имеем похожую ошибку в FP при округлении числа (или форматировании оного на %.3f) в питоньей реализации интерполяции %-оператором. Как-то так. В результате подозрение на следующие причины: На этом можно было бы и завершить, порекомендовав человеку либо как-то заставить провайдера сменить для виртуалки host, либо поменять собственно хостера. Однако, захотелось помочь людям выяснить что это такое за беда. Набросал на коленке небольшой скрипт, компилирующий простенький код на сях (безо всяких питонов) и запускающий его экзешник с целью проверить, что у нас тут собственно сломано FPU или память. Если в результате теста time-jump будет воспроизведен (правильные наносекунды, но неверные секунды), это скорее всего будет сигнализировать о «сломанном» FPU, в противном же случае это может сигнализировать о «сломанной» памяти, так как по сравнению с python, этот код всегда использует одни и те же адреса памяти в стеке или вообще только регистры. Пока без ответа, два дня еще не прошло.
 Всё — тема закрыта, суслик убег, магия кончилась: хост вылечился заменой CPU. За сим позвольте откланяться, всем участвовавшим большое спасибо. Ах да, ниже небольшой опрос, как говорится «Messieurs, faites vos jeux»... Senior Engineer; Data Scientist; Security Auditor