Ошибка: записать EPIPE в errnoException (net.js:770:11)

У меня есть эта система, которая пытается отправлять массовые письма, написанные на NodeJS.

Он использует 2 модуля: fs (встроенный в узел v0.8.18) и nodemailer (из NPM, v0.3.42).

Теперь при исполнении с node newsletter.js иногда он завершается и завершается, но иногда он может случайно произойти сбой в случайной точке со следующей ошибкой:

stream.js:81
      throw er; // Unhandled stream error in pipe.
            ^
Error: write EPIPE
    at errnoException (net.js:770:11)
    at Object.afterWrite (net.js:594:19)

Насколько я понимаю, ошибка EPIPE возникает из-за того, что другой конец соединения оборвался, а затем мы попытались записать в это соединение. Все существующие отчеты об этой ошибке EPIPE находятся в контексте открытия http-соединения или аналогичного.

В том, что я имею ниже, есть две вещи, которые могут быть причиной ошибки: fs.readFileSync в NewsletterEmail или mailer.send в NewsleterMailer. Более вероятно, что ошибка будет в mailer.send, а nodemailer где-нибудь открывает соединение. Однако никакая ошибка не передается обратно через аргумент ошибки throw или callback, так что, похоже, нет никакого способа увидеть причину ошибки.

Большинство людей предлагают определить обработчик ошибок. Однако в nodemailer или модуле fs нет ничего, что позволяло бы мне определять обработчик ошибок. Обратный вызов в nodemailer из mailer.send Вызов действительно передает аргумент ошибки в обратном вызове, однако эта конкретная ошибка не проходит через него.

Я пробовал следующие вещи:

  • добавив попробовать / поймать вокруг обоих fs.readFileSync и нодмалер отправляет вызовы sendEmail.
  • Удаление вызовов fs.readFileSync и вставка HTML - когда я делаю это, ошибка, кажется, не возникает. Но опять же, документы по узлам не имеют указаний на то, что ошибки EPIPE должны возникать в readFileSync, и, конечно, нет способа добавить обработчик ошибок.

Код, который вызывает эту ошибку ниже:

var nodemailer = require('nodemailer');
var fs = require('fs');

/**
 * Provides a way to build newsletters when given a folder containing
 * the relevant template and images in a standard format. This folder
 * must contain a newsletter.html file, a newsletter.txt file and a images
 * directory containing any images.
 * 
 * @param {Object} settings The full folder path to the
 */
function NewsletterEmail(newsletterGroup, newsletterName)
{
    var folder = '/var/newsletters/' + newsletterGroup + '/' + newsletterName;
    this._html = fs.readFileSync(folder + '/newsletter.html', 'utf-8');
    this._text = fs.readFileSync(folder + '/newsletter.txt', 'utf-8');
}

NewsletterEmail.prototype.getSubject = function()
{
    return 'Testing';
}

/**
 * Generates the HTML version of a newsletter.
 *
 * @return {String}
 */
NewsletterEmail.prototype.buildHTML = function(email)
{
    var htmlPart = this._html;

    return htmlPart;
}

/**
 * Generates the text counterpart of a newsletter.
 * 
 * @return {String}
 */
NewsletterEmail.prototype.buildText = function(email)
{
    var textPart = this._text;

    return textPart;
}

/**
 * Creates a NewsletterEmail from the given folder.
 * 
 * @param  {String} folder The folder containing the html template, text template and images for a newsletter.
 * @return {NewsletterEmail}
 */
NewsletterEmail.create = function(newsleterGroup, newsletterName) {
    return new NewsletterEmail(newsleterGroup, newsletterName);
}

function NewsletterMailer(fromEmail)
{
    this._from = fromEmail;
    this._transport = nodemailer.createTransport('sendmail');
}

NewsletterMailer.prototype = {
    send: function(email, newsletterEmail, callback) {
        var mailOptions = {
            to: email,
            from: this._from,
            subject: newsletterEmail.getSubject(),
            html: newsletterEmail.buildHTML(email),
            text: newsletterEmail.buildText(email)
        };

        this._transport.sendMail(mailOptions, callback);

    },

    close: function() {
        this._transport.close();
    }
}

function Newsletter()
{
    this._id = 1;
    this.countSent = 0;
    this.emailsToSend = ['email1@example.com', 'email2@example.com', 'email3@example.com', 'email4@example.com', 'email5@example.com', 'email6@example.com'];
}

Newsletter.prototype.send = function() {
    var newsletter = this;

    var newsletterEmail = NewsletterEmail.create('company1', '2013-01-24-mynewsleter');
    var mailer = new NewsletterMailer('company@example.com');

    function sendEmail() {
        var email = newsletter.emailsToSend.pop();

        mailer.send(email, newsletterEmail, function(mailerErr) {
            if (mailerErr) {
                console.log('Mailer error: ', mailerErr);
            }

            newsletter.countSent++;

            console.log('progress ' + newsletter.countSent);

            if (newsletter.emailsToSend.length > 0) {
                sendEmail();
            }
            else {
                mailer.close();
                console.log('complete');
            }
        });
    }

    sendEmail();
}

var nl = new Newsletter();
nl.send();

Кто-нибудь еще сталкивался с подобными ошибками? У вас есть какие-либо советы по отладке или возможные решения.

Теперь с трассировкой стека я нахожусь в тупике. Ниже приведен вывод strace. Кажется, сначала всегда умирает на этой границе mimepart:

futex(0x7f039c0008c8, FUTEX_WAKE_PRIVATE, 1) = 1
write(8, "------Nodemailer-0.3.42-?=_1-136"..., 131) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=13813, si_uid=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=13818, si_status=0, si_utime=0, si_stime=0} ---
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
rt_sigreturn()                          = -1 EPIPE (Broken pipe)
futex(0x7f039c0008c8, FUTEX_WAKE_PRIVATE, 1) = 1
write(8, "<!DOCTYPE HTML PUBLIC =22-//W3C/"..., 18098) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=13813, si_uid=0} ---
futex(0x7f039c0008c8, FUTEX_WAKE_PRIVATE, 1) = 1
close(8)                                = 0
epoll_wait(3, {{EPOLLIN|EPOLLHUP, {u32=9, u64=4294967305}}, {EPOLLIN|EPOLLHUP, {u32=11, u64=4294967307}}, {EPOLLIN, {u32=4, u64=4294967300}}}, 64, 0) = 3
epoll_ctl(3, EPOLL_CTL_MOD, 9, {EPOLLIN, {u32=9, u64=4294967305}}) = 0
epoll_ctl(3, EPOLL_CTL_MOD, 11, {EPOLLIN, {u32=11, u64=4294967307}}) = 0
read(4, "\1\0\0\0\0\0\0\0", 8)          = 8
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED|WCONTINUED, NULL) = 13818
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff2d6f4340) = -1 EINVAL (Invalid argument)
fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
write(1, "progress 1\n", 11progress 1
)            = 11
socketpair(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC, 0, [7, 8]) = 0
socketpair(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC, 0, [10, 12]) = 0
socketpair(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC, 0, [13, 14]) = 0
pipe2([15, 16], O_NONBLOCK|O_CLOEXEC)   = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f03a4b35a10) = 13820
close(16)                               = 0
poll([{fd=15, events=POLLIN|POLLHUP}], 1, -1) = 1 ([{fd=15, revents=POLLHUP}])
close(15)                               = 0
close(7)                                = 0
ioctl(8, FIONBIO, [1])                  = 0
close(12)                               = 0
ioctl(10, FIONBIO, [1])                 = 0
close(14)                               = 0
ioctl(13, FIONBIO, [1])                 = 0
wait4(-1, 0x7fff2d6f529c, WNOHANG|WSTOPPED|WCONTINUED, NULL) = 0
futex(0x7f039c0008c8, FUTEX_WAKE_PRIVATE, 1) = 1
brk(0x932000)                           = 0x932000
read(11, "", 65536)                     = 0
close(11)                               = 0
read(9, "", 65536)                      = 0
futex(0x7f039c0008c8, FUTEX_WAKE_PRIVATE, 1) = 1
close(9)                                = 0
write(2, "\n", 1
)                       = 1
write(2, "events.js:71\n", 13events.js:71

1 ответ

Это было вызвано переносом строк в модуле nodemailer длиной 75 символов. Одна строка длиной ровно 76 символов с точкой на конце. Эта точка была обернута на собственную линию.

Для SMTP-серверов точка на строке сама по себе обозначает конец сообщения и соединение закрывается. Это приводило к преждевременному закрытию соединения, и последующие записи не выполнялись с ошибкой EPIPE.

С помощью сопровождающего nodemailer это было исправлено в выпуске 0.3.43 путем добавления флага hte -i к вызову sendmail, который сообщает sendmail разрешать строки с одной точкой.

Более подробная информация доступна здесь: https://github.com/andris9/Nodemailer/issues/141.

Другие вопросы по тегам