Некоторое время назад мне нужно было загрузить несколько больших двоичных файлов с удаленного сервера, и в то время у меня было очень нестабильное интернет-соединение. Примерно после пятой попытки загрузить файл и через 15 минут после того, как мой интернет упал, я сдался. Диспетчер загрузки Chrome не резал его, поэтому я открыл GitHub, быстро посмотрел, как другие пишут инструменты загрузки, и наткнулся на Snatch.

Урвать

Описание Snatch было «Простой, быстрый и прерывистый ускоритель загрузки, написанный на Rust». Именно то, что я хотел, и написанное на интересующем меня языке! Мало того, они также были популярным репозиторием Rust в то время и имели почти 600 звезд. При внимательном рассмотрении файла readme репозитория это волнение довольно быстро сменилось разочарованием.

функция Interruptable еще не реализована.

Какого черта…

Как у вас может быть прерываемый ускоритель загрузки без прерываемой части ?!

Предложение прерывания

Я попытался освоиться с кодовой базой, чтобы увидеть, будет ли трудно писать прерываемую функцию. Я открыл проблему, написал PR, чтобы исправить ее, и еще немного подумал об этой функции. Я формализовал свои мысли о том, как я считаю, что функция должна быть написана, и написал проект предложения по ней в открытом вопросе о прерывании репо.

Snatch работал с предварительным выделением места, необходимого для загрузки файла. Запустите n поток через равные промежутки времени для файла и пусть каждый из потоков загрузит свой раздел. Если загрузка была прервана, не было возможности определить, сколько файла было загружено каждым потоком, поэтому все потоки должны были запускаться с начала своего раздела.

Чтобы эта функция заработала, я предложил следующие изменения:

  • Первые 32 бита будут 32-битным целым числом, которое представляет количество блоков размером 64 КБ в файле.
  • Позвоните по этому номеру n
  • Следующие n биты были битовыми флагами, показывающими, был ли загружен соответствующий фрагмент.
  • например Для файла размером 256 КБ 0101 будет означать, что 2-й и 4-й фрагменты были полностью загружены.
  • Остальная часть файла будет следовать
 1    2             3
|-|-------|---------------------|
1: 32-bit number holding the number of 64kb chunks
2: n bits holding bit flags for each chunk
3: File contents

При перезапуске загрузки каждый поток может посмотреть на битовые флаги в разделе 2 и определить, можно ли пропустить часть загрузки. Когда загрузка была завершена, первые 32 бита могли быть прочитаны, что дало бы нам знать, сколько битов в разделе 2, поэтому мы могли удалить как раздел 1, так и раздел 2, не касаясь содержимого файла.

У этого подхода определенно было несколько ограничений. Поскольку мы могли удерживать состояние только для фрагментов размером 2³² по 64 КБ, максимальный размер файла составлял 256 ТБ (2³² * 64 * 1024 / 1024⁴). Потокам необходимо будет загрузить диапазон байтов, которые начинаются и заканчиваются в блоке размером 64 КБ, чтобы ни один блок не загружался двумя отдельными потоками. Также было несколько других небольших предостережений, которые означают небольшие изменения в логике загрузки и потоковой передачи.

Я надеялся, что после того, как все это изложит автору проекта, он одобрит это, и я смогу продвинуться вперед и построить его. К сожалению, он не выглядел слишком заинтересованным в написании функции прерывания, и вместо этого хотел отполировать каждый маленький аспект части загрузки, прежде чем рассматривать возможность прерывания.

Поэтому, естественно, как разработчик, я просто пошел и написал свой собственный пакет.

Схватить

Представляю Grapple, актуальный ускоритель прерывистой загрузки. Я черпал вдохновение из Snatch, поскольку Grapple изначально задумывался как доказательство концепции этой функции, но быстро затмил набор функций Snatch.

Фактическая реализация немного отличалась от того, что я предлагал изначально. Оказывается, очень сложно эффективно удалить первые n байтов файла. И наоборот, обрезать конец файла очень легко, поэтому одно из изменений заключалось в перемещении всех метаданных в конец файла. Кроме того, чтобы резко увеличить ограничение на размер файла, я использовал 64-битное целое число для количества фрагментов и увеличил размер фрагмента с 64 КБ до 128 КБ. Это увеличило новый предел размера файла до 2 147 483 648 ПБ. Я, вероятно, умру прежде, чем файлы станут большими.

Новый макет файла:

           3              2    1
|---------------------|-------|-|
1: 64-bit number holding the number of 128kb chunks
2: n bits holding bit flags for each chunk
3: File contents

Я использую Grapple примерно раз в неделю, и он еще не разбился о мне (касаться дерева). Я до сих пор возвращаюсь к нему время от времени, чтобы внести некоторые улучшения, совсем недавно я добавил ограничение пропускной способности для каждого потока, чтобы не перегружать мое интернет-соединение и не расстраивать моих соседей по дому. Grapple определенно был одним из самых полезных инструментов, которые я написал, и я намерен постоянно обновлять его, поскольку мне нужно от него все больше и больше.

Просто для удовольствия, когда я писал это, я вернулся, чтобы посмотреть на Snatch, чтобы узнать, была ли реализована функция прерывания. Snatch заменен вилкой Zou, и это было в readme:

Функция прерывания по-прежнему еще не реализована. Думаю, я просто буду использовать Grapple.