Удалось исправить некоторые "случайные" ошибки сегментации (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.