Closed kamahen closed 1 year ago
You can argue a bit how useful it is to acquire the standard streams. They won't be dropped asychronously anyway and Sfprintf() family does the required locking anyway. Only I/O errors will be raised properly and will only later lead to issues without acquire/release of the streams. So, it surely is not bad to do so.
Aside from using PL_{acquire,release}_stream(), I don't see an obvious way for a programmer to report an error when writing to Suser_output (for example). It appears that reportStreamError() does the work, and that appears to be only called from PL_release_stream() and S__close() [plus a few related places].
However, this gets into problems because C++ destructors aren't supposed throw exceptions; so I haven't figured out a way of handling an error from PL_release_stream() in the destructor for PlAcquireStream [maybe I should rename it PlLockkStream]. (There doesn't seem to be an easy way for me to do a "return false" from inside the destructor, without things getting messy, which negates the advantage of PlAcquireStream taking care of calling PL_release_stream())
When SWI-Prolog terminates, it appears that closing Suser_output will raise some kind of an error ... is this correct? If that's the case, then maybe there's no need to do PlAcquireStream for Suser_output ... any I/O error will be delayed. The situations where this could occur are somewhat limited - e.g. redirect stdout to a file and the disk gets full. In that situation, the only thing that can be done is halt - and that seems to be the general advice for C++ destructors that need to throw an exception.
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-dtor-fail
I'm not sure I/O errors to stdin/stdout cause a status != 0 in the end. They should. Otherwise I don't know. Printing from foreign code is rare, so I never considered this much of a problem. Code accessing streams better use acquire/release and apparently C++ objects are unsuitable for that :(
You're probably right that most foreign predicates don't print things; some of the examples in the documentation have print statements, of course, do but I wouldn't want to clutter the examples up with ultra-correct code.
Anyway, I've added a release()
method to the C++ PlAcquireStream
and I think that this will do what I want with 2 more lines of boilerplate (sample code below). Also, if there's a situation where a destructor is invoked by a stack unwind due to another exception, it seems that C++ will terminate the program (presumably with a nasty message).
PREDICATE(write_list, 1)
{ PlTerm_tail tail(A1);
PlTerm_var e;
PlAcquireStream strm(Scurrent_output);
try
{ while( tail.next(e) )
Sfprintf(strm, "%s\n", e.as_string().c_str());
} PREDICATE_CATCH({strm.release(); return false;})
return true;
}
This also adds PL_{acquire,release}_stream for all the streams used by foreign predicates in the test code. If this is the correct way of doing this (i.e., using PL_acquire_stream() for Suser_output), then I'll update the documentation (and also document PlAcquireStream).