Из документации Foreign.Foreign.newForeignPtr :
Обратите внимание, что нет гарантии того, как скоро будет выполнен финализатор после того, как была удалена последняя ссылка; это зависит от деталей менеджера хранилищ Haskell. Действительно, нет никакой гарантии, что финализатор будет выполнен вообще; программа может завершиться с выдающимися финализаторами.
Таким образом, вы сталкиваетесь с неопределенным поведением: то есть, может произойти все, что угодно, и оно может меняться от платформы к платформе (как мы видели в Windows) или выпуск от выпуска.
Причина различий в поведении, которое вы видите между двумя функциями, может быть указана в документации для Foreign.Concurrent.newForeignPtr :
Эти финализаторы обязательно запускаются в отдельном потоке ...
Если в финализаторах для версии Foreign.Foreign функции используется основной поток, а в Foreign.Concurrent используется отдельный поток, вполне возможно, что основной поток завершает свою работу, не ожидая завершения работы других потоков. поэтому другие потоки никогда не смогут запустить финализацию.
Конечно, документы для версии Foreign.Concurrent действительно требуют,
Единственная гарантия - запуск финализатора до завершения программы.
Я не уверен, что они действительно должны требовать этого, поскольку, если финализаторы работают в других потоках, они могут занять произвольное количество времени, чтобы выполнить свою работу (даже заблокировать навсегда), и, следовательно, основной поток никогда не сможет заставить программу выйти. Это может противоречить этому из Control.Concurrent :
В автономной программе GHC для завершения процесса требуется только основной поток. Таким образом, все остальные разветвленные потоки просто завершатся в то же время, что и основной поток (терминология для этого вида поведения - «демонические потоки»).