4

NodeJS で書かれた大量の電子メールを送信しようとするこのシステムがあります。

fs (ノード v0.8.18 にビルド) と nodemailer (NPM、v0.3.42 から) の 2 つのモジュールを使用します。

実行する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 接続を開く場合などに関連しています。

エラーの原因となっている可能性のある 2 つのことはfs.readFileSync、NewsletterEmail またはmailer.sendNewsleterMailer にあります。エラーが mailer.send にあり、nodemailer がどこかで接続を開いている可能性が高くなります。ただし、スローまたはコールバックのエラー引数を介してエラーが戻されていないため、エラーの原因を確認する方法がないようです。

ほとんどの人は、エラー ハンドラを定義することを提案しています。ただし、nodemailer または fs モジュールには、エラー ハンドラを定義できるものは何もありません。呼び出しからの nodemailer のコールバックはmailer.send、そのコールバックでエラー引数を渡しますが、この特定のエラーはそこを通っていません。

私は次のことを試しました:

  • fs.readFileSyncnodemailer トランスポート sendEmail 呼び出しの両方に try/catch を追加します。
  • fs.readFileSync 呼び出しを削除し、html をインライン化します。これを行うと、エラーは発生しないようです。しかし、繰り返しになりますが、ノードのドキュメントには、readFileSync で EPIPE エラーが発生する必要があることは示されておらず、エラー ハンドラーを追加する方法はまったくありません。

このエラーをトリガーするコードは次のとおりです。

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
4

1 に答える 1

6

これは、nodemailer モジュールが行を 75 文字で折り返すことが原因でした。1 行の長さはちょうど 76 文字で、最後にドットがありました。このドットは、独自の行に折り返されていました。

SMTP サーバーの場合、行のドットはそれ自体がメッセージの終わりを意味し、接続が閉じられます。これにより、接続が早期に閉じられ、後続の書き込みが EPIPE エラーで失敗していました。

nodemailer メンテナの助けを借りて、これは issue 0.3.43 で修正されました。hte -i フラグを sendmail 呼び出しに追加することで、sendmail に 1 つのドットを含む行を許可するように指示します。

詳細については、https ://github.com/andris9/Nodemailer/issues/141 をご覧ください。

于 2013-02-28T14:46:00.667 に答える