Устранение зависаний предварительной компиляции из-за открытых задач или ввода-вывода
Страница в процессе перевода. |
В 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. Диагностика причины зависания вручную.
Для выполнения диагностики вручную:
-
Pkg.develop("Test1")
-
Закомментируйте весь код, включенный (
include
) или определенный вTest1
, кроме операторовusing/import
. -
Попробуйте
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
) строки для пропуска целых файлов), пока не уменьшите масштаб проблемы.