I'm trying to send an e-mail with sendmail
in a separate pthread
. This code works 99.9% of the time.
void* emailClientThreadFct(void* emailClientPtr)
{
EmailClient* emailClient = static_cast<EmailClient*>(emailClientPtr);
try
{
emailClient->Send();
}
catch (const exception& excep)
{
SYSLOG_ERROR("E-mail client exception: %s", excep.what());
}
delete emailClient;
return NULL;
}
// Send email for current output in a separate thread
pthread_t emailThread;
pthread_attr_t attr;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&emailThread, &attr, emailClientThreadFct, emailClientObj);
0.1% of the time, I get the error fwrite error Broken Pipe
when I do the following call. From what I read, Broken Pipe (EPIPE 32) is usually a receiver problem, but sendmail is a local process... Could it be that I'm sending too much data to fwrite? Or that I'm doing something bad in my pthread instanciation? Or has sendmail crashed?
void EmailClient::Send() const
{
// Flush all open output streams, as recommended by popen man page
fflush(NULL);
string popen_command = "sendmail -t -oi >/dev/null 2>&1");
// Open pipe to Mail Transport Agent (MTA)
errno = 0;
FILE* stream = popen(popen_command.c_str(), "w");
if (stream == NULL)
{
throw exception("Cannot send email popen");
}
errno = 0;
if (fwrite(message.data(), message.size(), 1, stream) < 1)
{
pclose(stream);
throw exception("fwrite error ", strerror(errno));
}
// Close MTA
errno = 0;
if (pclose(stream) == -1)
printf("\"Error closing the MTA pipe (%s)\"", strerror(errno))
}
EPIPE
means that the other end (the process you are writing to) has died. This could happen if there is a fork failure (popen
invokes the shell, so there is another subprocess involved) because there are temporarily too many processes in the system. A more direct cause would be sendmail
failing and exiting prematurely, before reading all of standard input, say due to malformed email headers.
popen
is unfortunately not a very reliable interface. You might be better off using fork
/execve
or posix_spawn
, either with a temporary file for the input or I/O multiplexing using poll
, just to be able to capture any error that sendmail
might generate. Alternatively, you could try to call sendmail
with -oee
, which is supposed to report any errors by email, but it won't help if the creation of the sendmail
itself fails.