По ходу доработок упростил проверку простоя PoolExecutor-а - просто вывел из него число незаконченных задач от очереди (то самое, что в очереди + на исполнении). В любой момент можно спросить PoolExecutor сколько у него незаконченных задач и понять - стоит он или трудится в поте лица своего. Избавился от необходимости создавать отдельный поток (вся задача которого висеть на join() очереди и когда очередь его отпустит - зарепортить простой экзекутора.
Собственно в процессе работы я немного увлекся потоками и столько их разных наплодил, что оказалось очень нетривиально все их собрать и закрыть при выходе из приложения.
Пришлось изрядно повозиться с тем чтобы корректно все закрывалось... Но это позволило подчистить ненужное и упростить во многих местах код.
Сейчас в проекте достигнута первая ступень: Все изменения в локальном каталоге транслируются на диск. Но правда есть еще нюансы.... правильнее сказать "все неспешные или все простые...."
В чем суть проблемы:
Модель, при которой поток событий от файловой системы перелопачивается параллельно в независимых потоках дает заметное преимущество при однотипных операциях (заливка на диск пачки файлов, удаление пачки файлов), но дает почти 100% сбой при последовательных операциях с одним файлом.
К примеру набор команд выполненный в синхронизируемом каталоге:
touch 1/f2; mv 1/f2 .;rm f2
а вот что в логе клиента:
IN_Event: IN_CREATE, path: 1/f2 # поймали "touch 1/f2"
submit Cloud.upload of ('/home/stc/yd/1/f2', '1/f2') # закинули в очередь задачу #1 на создание файла 1/f2
IN_Event: IN_MOVED_FROM, path: 1/f2 # поймали первую половину от "mv 1/f2 ." - откуда
IN_Event: IN_MOVED_TO, path: f2 # поймали вторую половину от "mv 1/f2 ." - куда
submit Cloud.move of <C ('1/f2', 'f2') # поженили половинки и закинули в очередь задачу#2:
# переместить 1/f2 в f2
IN_Event: IN_DELETE, path: f2 # поймали "rm f2"
submit Cloud.delete of ('f2',) # закинули в очередь задачу#3: удалить f2
Done: (False, '404 : del f2'), 2 unfinished # быстрее всех обломилось удаление (задача#3) - нечего удалять
Done: (False, '404 : move 1/f2 to f2'), 1 unfinished # затем упало перемещение (задачу#2) нет еще на
# диске файла 1/f2 т.к. задача #1 еще не завершилась.
Done: (False, 'FileNotFoundError : up 1/f2'), 0 unfinished # ну и наконец упала задача#1: она не нашла в локальном
# каталоге 1/f2 т.к. он был сразу же перемещен
Эпик фейл детектед
Причем ведь все правильно и логично: правильно отлавливаются и ставятся в очередь события, задачи правильно обрабатываются и... правильно падают
Но я не вижу как можно такие ситуации разрулить, ведь сценариев может быть море. И главное - одновременно (параллельно) могут развиваться самые разные сценарии с кучей разных файлов.
Причем ведь даже последовательное выполнение задач (в одном потоке) точно так же сбойнет на рассмотренном примере. Ведь задача на upload нового файла не успеет закончится до того момента, когда он будет перемещен или удален.
Ну собственно такие "подводные камни" я и ожидал получить в этом проекте т.к. никогда до этого вопросами онлайн синхронизации плотно не занимался.
Какие видятся решения:
А красивых решений собственно и нет (я по крайней мере - не вижу). Вижу только такие:
1. Можно очень тупо: по первому событию запустить таймер на заданный интервал, и продлевать таймаут пока не кончится поток событий. А когда поток событий закончится ... правильно - выкинуть все события (потому что тольку от них не слишком много) и сделать полную сверку облака с локальным диском.
2. Другой вариант - делать все по событиям забивая на ошибки, но если были ошибки, то по окончании потока событий запустить опять же - полную сверку.
Первый вариант хорош тем, что может в общую кучу отловить даже несколько последовательных обновлений одного файла и залить его в облако всего один раз. Но страдает тем, что пока с синхронизируемым каталогом интенсивно работают - то никакой синхронизации вовсе нет.
Второй вариант хорош тем, что обеспечивает высокую оперативность заливки на диск потока простых (несвязанных) операций, и благодаря этому мы получим гораздо меньший объем, который придется дополнительно синхронизировать после полной сверки. Но вот последовательные изменения в небольшом файле могут заливаться на диск много раз (иногда падая с ошибками).
Как мне кажется выбирать вариант надо исходя из экспериментов. Поэтому сейчас не буду сильно ломать голову, а займусь в плотную процедурой полной сверки.
Полный список файлов с облака я уже научился получать. Сейчас я правда получаю все файлы в одном запросе, что явно неразумно - нужно получать кусками (и для этого у яндекса все предусмотрено, просто я не заморачивался пока).
Получить такой же список с файловой системы - не вопрос (все просто сделать через стандартные питоновские библиотеки
glob и/или pathlib в итоге сделал через os.walk ).
Осталось просто все это собрать в один алгоритм, желательно опять же не все чехом обрабатывать, а по пачкам.