angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Python ввод/вывод файлов на Windows и Linux - это две разные вещи

При использовании потоков вы можете столкнуться с проблемами, вызванными различным поведением функций ввода-вывода файлов.

8 декабря 2021
В Python
post main image
https://unsplash.com/@momofactory

У меня есть программа Python , которая прекрасно работает на Linux. Несколько месяцев назад я хотел запустить ее на Windows.

Это был первый раз, когда я использовал Python на Windows. Установите приложение Python , создайте virtual environment, скопируйте и запустите. Никаких проблем ... но была проблема. Моя сессия иногда исчезала ... WTF! Я заметил проблему, повторно нажав F5 в течение очень короткого времени. Настало время для тщательного расследования.

Приложение и хранилище ключевых значений сессии

Приложение Python является приложением Flask . Оно использует интерфейс файловой системы Flask-Session для хранения сессий в виде файлов. Flask-Session использует другой пакет PyPi для обработки всех операций ввода-вывода файлов. Как вы можете себе представить, этот пакет реализует хранилище ключевых значений.

В этом пакете есть два основных метода:

  • set(key, value), для записи/обновления данных сессии
  • get(key), для получения данных сессии.

В пакете метод set(key, value) создает временный файл, а затем вызывает функцию Python os.replace() для замены файла сессии. Метод get(key) вызывает функцию чтения Python .

Тестирование с использованием потоков

Когда вы запускаете Flask в режиме разработки, вы увидите много запросов к хранилищу сессий, потому что Flask также обслуживает images, CSS файлы и т.д. В производственной среде, где вы обслуживаете статическое содержимое через веб-сервер, вы с меньшей вероятностью столкнетесь с этой проблемой, но она все равно существует!
Вот как мне пришла в голову идея написать небольшой тест с использованием потоков.

import cachelib
import threading

fsc = cachelib.file.FileSystemCache('.')

def set_get(i):
    fsc.set('key', 'val')
    val = fsc.get('key')

for i in range(10):
    t = threading.Thread(target=set_get, args=(i,))
    t.start()

На Linux не было никаких ошибок, ничего. Но на Windows это вызвало случайные исключения:

[WinError 5] Access is denied
...
[Errno 13] Permission denied

Исключение [WinError 5] было сгенерировано функцией os.replace() Python . Исключение [Errno 13] было сгенерировано функцией Python read().

Что здесь происходит?

Файловый ввод/вывод на Windows и Linux - это две разные вещи.

Я предполагал, что Python защитит меня от реализации, специфичной для конкретной платформы. Это так во многих функциях, но не во всех. Особенно при использовании потоков вы можете столкнуться с проблемами, вызванными различным поведением функций ввода-вывода файлов.

Из Python Bug Tracker Issue46003:

Как говорится, нет такой вещи, как "переносимое программное обеспечение", есть только "программное обеспечение, которое было перенесено".
Особенно в такой области, как файловый ввод-вывод: как только вы выходите за рамки простого "один процесс открывает, записывает и закрывает", а другой процесс затем "открывает, читает и закрывает", возникает множество проблем, специфичных для конкретной платформы. Python не пытается абстрагироваться от всех возможных проблем файлового ввода-вывода.

Python функция read()

В библиотеке, которую я использую, метод get(key) использует функцию Python read().

На Linux достаточно поместить функцию read() в try-except:

    try:
        with open(f, 'r') as fo:
            return fo.read()
    except Exception as e:
        return None

Функция будет ждать, пока данные не станут доступны. Исключение будет вызвано только в случае таймаута, который на большинстве систем Linux составляет 60 секунд, или какой-либо другой непредвиденной ошибки.

На Windows это произойдет немедленно, если к файлу обратится другой поток. Чтобы создать такое же поведение, как в случае с Linux , мы должны добавить повторные попытки и задержку, например:

    max_sleep_time = 10
    total_sleep_time = 0
    sleep_time = 0.02
    while total_sleep_time < max_sleep_time:
        try:
            with open(f, 'r') as fo:
                return fo.read()
        except OSError as e:
            errno = getattr(e, 'errno', None)
            if errno == 13:
                # permission error
                time.sleep(sleep_time)
                total_sleep_time += sleep_time
                sleep_time *= 2
            else:
                # some other error             
                return None
        except Exception as e:
            return None

    # out of retries
    return None

Python функция os.replace()

В библиотеке, которую я использую, метод set(key, value) использует функцию Python os.replace().

На Linux достаточно поместить функцию os.replace() в try-except:

    try:
        os.replace(src, dst)
        return True
    except Exception as e:
        return False

Функция будет ждать, пока файл не будет заменен. Исключение будет вызвано только в случае таймаута, который на большинстве систем Linux составляет 60 секунд, или какой-либо другой непредвиденной ошибки.

На Windows это произойдет немедленно, если к файлу обратится другой поток. Чтобы создать такое же поведение, как в случае с Linux , мы должны добавить повторные попытки и задержку, например:

    max_sleep_time = 10
    total_sleep_time = 0
    sleep_time = 0.02
    while total_sleep_time < max_sleep_time:
        try:
            os.replace(src, dst)
            return True
        except Exception as e:
            winerror = getattr(e, 'winerror', None)
            if winerror == 5:
                time.sleep(sleep_time)
                total_sleep_time += sleep_time
                sleep_time *= 2
            else:
                # some other error
                return False

    # out of retries
    return False

Заключение

Создание программы Python , которая может работать на нескольких платформах, может быть сложным, потому что вы можете столкнуться с проблемами, подобными описанным выше. Сначала я был удивлен, что Python не скрывает от меня сложности Windows . Придя из Linux , я подумал: Python , почему бы вам не сделать так, чтобы это работало на Windows так же, как на Linux?
Но такой выбор сделали разработчики Python . Возможно, это тоже невозможно. Я не смог найти в Интернете ни одной строки в документации Python , которая бы меня насторожила, и заметил, что многие люди с этим сталкиваются. Я отправил сообщение об ошибке с просьбой добавить предупреждение для разработчиков при разработке для нескольких платформ. Но позже я воздержался от этого, потому что понимаю, что я очень предвзят, исходя из Linux.

Ссылки / кредиты

backports.py
https://github.com/flennerhag/mlens/blob/master/mlens/externals/joblib/backports.py

os.replace
https://docs.python.org/3/library/os.html?highlight=os%20replace#os.replace

os.replace is not cross-platform: at least improve documentation
https://bugs.python.org/issue46003

Подробнее

Threads

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.