Документация Engee

Устранение зависаний предварительной компиляции из-за открытых задач или ввода-вывода

Страница в процессе перевода.

В Julia версии 1.10 и более поздних можно увидеть следующее сообщение:

Оно может выводиться повторно. Если это продолжает повторяться без каких-либо намеков на самостоятельное разрешение, возможно, произошло «зависание предварительной компиляции», которое требует исправления. Даже если это временное явление, лучше устранить его, чтобы не беспокоить пользователей этим предупреждением. На этой странице вы узнаете, как анализировать и устранять такие проблемы.

Если вы последуете совету и нажмете сочетание клавиш Ctrl-C, вы увидите следующее.

^C Interrupted: Exiting precompilation...

  1 dependency had warnings during precompilation:
┌ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982]
│  [pid 2745] waiting for IO to finish:
│   Handle type        uv_handle_t->data
│   timer              0x55580decd1e0->0x7f94c3a4c340

Это сообщение информирует о двух ключевых моментах:

  • зависание возникает во время предварительной компиляции Test1, зависимости Test2 (пакета, который мы пытались загрузить с помощью using Test2);

  • во время предварительной компиляции Test1 Julia создала объект Timer (используйте ?Timer, если вы не знакомы с таймерами), который все еще открыт. Пока он не закроется, процесс будет висеть.

Если этого достаточно, чтобы понять, как создается timer = Timer(args...), рекомендуется добавить wait(timer), если timer в конечном итоге завершается самостоятельно, или close(timer), если нужно принудительно закрыть его перед окончательным завершением (end) модуля.

Однако в некоторых случаях все не так просто. Обычно лучше всего начать с определения того, связано ли зависание с кодом в Test1 или с одной из зависимостей Test1:

  • Вариант 1. Pkg.add("Aqua") и используйте Aqua.test_persistent_tasks. Это поможет вам определить, какой пакет вызывает проблему, после чего следует выполнить приведенные ниже инструкции. При необходимости можно создать PkgId как Base.PkgId(UUID("..."), "Test1"), где ... берется из записи uuid в Test1/Project.toml.

  • Вариант 2. Диагностика причины зависания вручную.

Для выполнения диагностики вручную:

  1. Pkg.develop("Test1")

  2. Закомментируйте весь код, включенный (include) или определенный в Test1, кроме операторов using/import.

  3. Попробуйте using Test2 (или даже using Test1, если он тоже зависает) еще раз.

Теперь мы стоим на распутье: либо

Диагностика и устранение зависаний из-за зависимости пакета

Используйте бинарный поиск для выявления проблемной зависимости: начните с комментирования половины зависимостей, затем, когда вы определите, какая из них ответственна, закомментируйте половину этой половины и т. д. (необязательно удалять их из проекта, просто закомментируйте операторы using/import).

Определив предполагаемую проблему (здесь мы будем называть ее ThePackageYouThinkIsCausingTheProblem), сначала попробуйте выполнить предварительную компиляцию этого пакета. Если он также зависает во время предварительной компиляции, продолжайте искать проблему в обратном направлении.

Однако, скорее всего, предварительная компиляция ThePackageYouThinkIsCausingTheProblem будет выполняться нормально. Это говорит о том, что проблема в функции ThePackageYouThinkIsCausingTheProblem.__init__, которая не выполняется во время предварительной компиляции ThePackageYouThinkIsCausingTheProblem, но выполняется в любом пакете, который загружает ThePackageYouThinkIsCausingTheProblem. Чтобы проверить эту теорию, создайте минимальный рабочий пример (MWE), что-то вроде следующего:

(@v1.10) pkg> generate MWE
  Generating  project MWE:
    MWE\Project.toml
    MWE\src\MWE.jl

где исходный код MWE.jl имеет вид

module MWE
using ThePackageYouThinkIsCausingTheProblem
end

и вы добавили ThePackageYouThinkIsCausingTheProblem к зависимостям MWE.

Если этот MWE воспроизводит зависание, вы нашли виновника: ThePackageYouThinkIsCausingTheProblem.__init__ должен создавать объект Timer. Если объект таймера может быть безопасно закрыт (close), это хороший вариант. В противном случае наиболее распространенным решением будет не создавать таймер, пока выполняется предварительная компиляция любого пакета: добавьте

ccall(:jl_generating_output, Cint, ()) == 1 && return nothing

в качестве первой строки ThePackageYouThinkIsCausingTheProblem.__init__, что позволит избежать инициализации в любом процессе Julia, целью которого является предварительная компиляция пакетов.

Исправление кода пакета для предотвращения зависаний

Найдите в пакете наводящие слова (например, Timer) и попробуйте определить, где возникает проблема. Обратите внимание, что определение метода вида

maketimer() = Timer(timer -> println("hi"), 0; interval=1)

само по себе не является проблемным: оно может вызвать эту проблему только в том случае, если maketimer вызывается во время определения модуля. Это может происходить из оператора верхнего уровня, такого как

const GLOBAL_TIMER = maketimer()

Если вам сложно определить причинные строки, воспользуйтесь бинарным поиском: закомментируйте секции вашего пакета (или включите (include) строки для пропуска целых файлов), пока не уменьшите масштаб проблемы.