Python ввод/вывод файлов на Windows и Linux - это две разные вещи
При использовании потоков вы можете столкнуться с проблемами, вызванными различным поведением функций ввода-вывода файлов.
У меня есть программа 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
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу