Удалось исправить некоторые "случайные" ошибки сегментации (segfault) в
Guile-SSH — они оказались не такие уж случайные.
Всё дело в том, что я использовал средства логирования
libssh, чтобы писать сообщения из мира Guile (Scheme) в общий libssh лог: Guile-объекты передавались, как обычные безликие указатели в процедуры логирования libssh, откуда уже, проходя по лаберинтам вызовов, попадали обратно в мир Guile, и передавались в предоставленный пользователем (или умолчальный) callback — Scheme-процедуру, которая обрабатывала сообщения.
Проблема в том, что при таком подходе в некоторых случаях эти несчастные Guile-объекты не добирались до конца лабиринта вызовов внутри libssh, и на свет выбрасывало только их жалкие останки, покусанные сборщиком мусора Guile. Как только процедура записи лога пыталась эти останки использовать, возникала ошибка сегментации — память-то уже освобождена.
Почему же так происходило? Потому, что сборщик мусора работает, если говорить по-простому, считая ссылки на объекты. Те объекты, на которые нет видимых ссылок из мира Guile (выбившиеся "из стаи") считаются брошенными и уничтожаются сборщиком мусора. Когда указатель на объект передаётся в виде указателя в процедуру логирования libssh, сборщик мусора этого не видит, и объекту наступает кирдык. Но не всегда — иногда он успевает выбраться "на свет" до того, как сборщик мусора что-то заподозрит.
Одим из "озарений" было то, что я могу вообще обойти процедуры логирования libssh, если логирование вызывается из Guile-SSH — я же уже знаю, какая процедура указана пользователем в качестве callback'а, и могу её вызвать сразу! И все параметры для процедуры логирования всегда будут иметь ссылки, видимые сборщиком мусора (по крайней мере, я могу это гарантировать средствами Guile.)
Таким образом была решена значительная часть проблем. Помучал тестами сделанные исправления, часть ошибок исчезла.
Другая часть ошибок состояла в том, что фреймворк
SRFI-64, который я использую для написания тестов на Scheme, очень не любит тесты, которые порождают процессы (a-la через вызов
fork
) и пытаются что-то делать, без вызова одного из вариантов
exec
. Поскольку процесс раздываивается при
fork
, то получается две копии запущенного теста, и как бы я не пытался "успокоить" запускатор тестов, всё равно иногда вылазили странные ошибки тестов — вроде все тесты прошли тормально, но при этом результат считается провальным. А всё потому, что один процесс правильно рапортавал успешное завершение тестов, но параллельно шёл второй, полученный через
fork
, который мог выкинуть ошибку.
Это уже удалось вылечить путём вынесения любой деятельности после
fork
в отдельную программу на Scheme, запускаемую новым вызовом
guile
через
execle
.