Search code examples
c++pthreadssendmailpopenbroken-pipe

C++: Calling sendmail from pthread results in Broken Pipe


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))
}

Solution

  • 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.