Проблемы с SMTP в Голанге (паника, сигнал SIGSEGV: нарушение сегментации) - PullRequest
0 голосов
/ 11 мая 2018

У меня небольшое приложение, которое может проверить, существует ли электронная почта на почтовом сервере. Я знаю, что моя реализация не дает 100% результата, но пусть так и будет. Итак, я получил func, который получает срез электронной почты и проверяет каждое письмо в этом срезе:

func CheckMails(mails []string) []string {
    var existingMails []string
    fmt.Printf("!!!!!!!!!!!!!!STARTING!!!!!!!!!!!! %s \n\n\n", mails[1])
    for i := 0; i < len(mails); i++ {
        err := validateHost(mails[i])
        if err != nil {
            fmt.Printf("Error validating host. %s", err)
        }
        smtpErr, ok := err.(checkmail.SmtpError)
        if ok {
            fmt.Printf("Code: %s, Msg: %s", smtpErr.Code(), smtpErr)
            if smtpErr.Code() == "dia" {
                break
            }
        } else {
            fmt.Println("Email exists")
            existingMails = append(existingMails, mails[i])
        }
    }
    fmt.Printf("!!!!!!!!!!!!!!ENDING!!!!!!!!!!!! %s \n\n\n", mails[1])
    return existingMails
}

Далее - моя функция, которая буквально проверяет, существует ли электронная почта (кстати, эта функция взята из небольшой библиотеки github.com/badoux/checkmail, но если я использую только эту функцию, я копирую ее в свой код для регистрации ошибок, переменных и т. Д.). ):

func validateHost(email string) error {
    _, host := split(email)
    mx, err := net.LookupMX(host)
    if err != nil {
        fmt.Printf("Error, UnresolvableHost! %s", err)
    }

    client, err := smtp.Dial(fmt.Sprintf("%s:%d", mx[0].Host, 25))
    //  fmt.Println(client)
    defer client.Close()
    if err != nil {
        //fmt.Println(client)
        //log.Fatalln(err)
        fmt.Printf("SmtpError! %s \n", err)
    }

    t := time.AfterFunc(forceDisconnectAfter, func() { client.Close() })
    defer t.Stop()

    // t := NewTimer(10, func() { client.Close() })
    // defer t.Stop()

    err = client.Hello("checkmail.me")
    //err = client.Hello("gmail.com")
    //  fmt.Println(client)
    if err != nil {
        //log.Fatalln(err)
        fmt.Printf("client.Hello SmtpError! %s \n", err)
    }

    err = client.Mail("lansome-cowboy@gmail.com")
    if err != nil {
        fmt.Printf("client.MailSmtpError! %s \n", err)
    }
    err = client.Rcpt(email)
    if err != nil {
        fmt.Printf("client.Rcpt SmtpError! %s \n", err)
    }
    return nil
}

А также я сделал функцию поддержки, которая прерывает проверку, если ответ от сервера слишком длинный:

   // CheckMailsWithExpectedInterval want to get expectected number of seconds which you are ready to wait while
// the mail trys to be validated (this time is for ONE mail, slice could have a lot of mails to check)
// and slice of mails which should be validated
func CheckMailsWithExpectedInterval(expectedSec int, mails []string) (ok bool, existingMails []string) {
    done := make(chan struct{})
    t1 := time.Now()
    var eMails []string
    go func() {
        eMails = CheckMails(mails)
        close(done)
    }()

    select {
    case <-done:
        if len(eMails) > 1 {
            ok = false
        } else {
            ok = true
            existingMails = eMails
        }
    case <-time.After(time.Duration(expectedSec) * time.Second):
    }

    fmt.Printf("\nTime since:")
    fmt.Println(time.Since(t1))
    return ok, existingMails
}

И другие вспомогательные функции, которые необходимы для функций выше:

    func split(email string) (account, host string) {
    i := strings.LastIndexByte(email, '@')
    account = email[:i]
    host = email[i+1:]
    return
}

const forceDisconnectAfter = time.Second * 10

Пример среза, который входит в функции:

email1 := []string{
        "andreasId@fromatob.com",
        "Andreas.Wolff@fromatob.com",
        "AndreasWolff@fromatob.com",
        "Wolff.Andreas@fromatob.com",
        "WolffAndreas@fromatob.com",
        "Andreas@fromatob.com,Wolff@fromatob.com",
        "A.Wolff@fromatob.com",
        "AWolff@fromatob.com",
        "Andreas.W@fromatob.com",
        "AndreasW@fromatob.com",
        "Wolff.A@fromatob.com",
        "WolffA@fromatob.com",
        "W.Andreas@fromatob.com",
        "WAndreas@fromatob.com",
    }

И у меня следующая проблема. Иногда мое приложение паникует со следующим:

    panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x84fefa]
goroutine 260 [running]:
net/smtp.(*Client).Close(0x0, 0x0, 0x0)
    /usr/lib/go-1.10/src/net/smtp/smtp.go:76 +0x2a
panic(0x90a360, 0xb94ae0)
    /usr/lib/go-1.10/src/runtime/panic.go:502 +0x24a
net/smtp.(*Client).Hello(0x0, 0x97d912, 0x9, 0x0, 0x0)
    /usr/lib/go-1.10/src/net/smtp/smtp.go:100 +0x78
magictool/mailchecker.validateHost(0xc4204c7020, 0x23, 0x0, 0x0)
    /home/username/go/src/magictool/mailchecker/mailchecker.go:115 +0x749
magictool/mailchecker.CheckMails(0xc4201c0000, 0xf, 0xf, 0x0, 0x0, 0x0)
    /home/username/go/src/magictool/mailchecker/mailchecker.go:20 +0x1d1
magictool/mailchecker.CheckMailsWithExpectedInterval.func1(0xc4201c0000, 0xf, 0xf, 0xc42038f5c0, 0xc4203b7ec0)
    /home/username/go/src/magictool/mailchecker/mailchecker.go:49 +0x3f
created by magictool/mailchecker.CheckMailsWithExpectedInterval
    /home/username/go/src/magictool/mailchecker/mailchecker.go:48 +0x12c

Я действительно не могу понять, как справиться с этой паникой. Кроме того, к вашему сведению, приложение рухнуло после проверки ~ 3-5 фрагментов, пару часов назад было проверено 10 фрагментов писем и после этого происходит сбой.

И, что немаловажно, за прошедшие 1-2 недели все было хорошо и никаких ошибок / паники. Я даю функции 20-50-100-150 срезов электронных писем (1 срез около 15 электронных писем), и это не было никаких ошибок.

Буду признателен за любую помощь!

1 Ответ

0 голосов
/ 11 мая 2018

Вы не делаете правильную обработку ошибок.Это, вероятно, вызывает вашу проблему.В частности, по трассировке стека вы заметите, что в строке 76 файла smtp.go стандартной библиотеки появляется паника.Эта строка гласит:

return c.Text.Close()

Похоже, вы пытаетесь закрыть недопустимое соединение.

Это имеет интуитивный смысл, когда мы читаем ваш код:

client, err := smtp.Dial(fmt.Sprintf("%s:%d", mx[0].Host, 25))
//  fmt.Println(client)
defer client.Close()
if err != nil {
    //fmt.Println(client)
    //log.Fatalln(err)
    fmt.Printf("SmtpError! %s \n", err)
}

Вы звоните defer client.Close() еще до того, как узнаете, действительно ли соединение клиента.Это означает, что в случае сбоя SMTP-соединения вы будете пытаться закрыть недопустимое соединение, что очень вероятно приведет к наблюдаемому поведению.

Чтобы исправить это, необходимо внести два изменения.

  1. Не просто печатать ошибки, на самом деле обрабатывать их.Вероятно, это просто означает их возврат:

    client, err := smtp.Dial(fmt.Sprintf("%s:%d", mx[0].Host, 25))
    defer client.Close()
    if err != nil {
        return err
    }
    

    Сделайте это для каждой из ваших ошибок .

  2. Позвоните только client.Close() после васзнаю, что у вас есть действительное соединение!

    client, err := smtp.Dial(fmt.Sprintf("%s:%d", mx[0].Host, 25))
    if err != nil {
        return err
    }
    defer client.Close()
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...