Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.1] Yet another attempt to fix Swift Mailer related SSL failures (Issue #4573) #13583

Merged
merged 8 commits into from
Jun 11, 2016

Conversation

schanzel
Copy link
Contributor

Closing swift mailer connection after sending the mail (see #4573 (comment))

    - close mailer connection after sending message
    - fix return statements and related phpdoc
    Fixed code style according to StyleCI
    fixed code style according to StyleCI
    StyleCI still not happy... :-(
@@ -392,7 +378,10 @@ protected function sendSwiftMessage($message)
}

if (! $this->pretending) {
return $this->swift->send($message, $this->failedRecipients);
$result = $this->getSwiftMailer()->send($message, $this->failedRecipients);
$this->getSwiftMailer()->getTransport()->stop();
Copy link
Member

@GrahamCampbell GrahamCampbell May 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do a try finally here.

@GrahamCampbell GrahamCampbell changed the title Yet another attempt to fix Swift Mailer related SSL failures (Issue #4573) [5.1] Yet another attempt to fix Swift Mailer related SSL failures (Issue #4573) May 17, 2016
@GrahamCampbell
Copy link
Member

This will kill performance for someone sending lots of messages.

@@ -392,7 +378,10 @@ protected function sendSwiftMessage($message)
}

if (! $this->pretending) {
return $this->swift->send($message, $this->failedRecipients);
$result = $this->getSwiftMailer()->send($message, $this->failedRecipients);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't modify this line

@schanzel
Copy link
Contributor Author

The connection is stopped on each mail being sent, regardless of this change. The only difference here is the stop() being called after sending an email instead of calling it beforehand. I don't see any performance regressions here.

- wrap try-finally around swift->send(); transport->stop();
@lifeofguenter
Copy link
Contributor

Ok, so what is better: unreliable application (restarting the queue-workers via cronjob does not make an application more robust) - or faster delivery - which can be achieved running multiple queue-workers in parallel?

The correct way would be to detect a faulty connection and then only reconnect then (which seems to be the current intend, but obviously not working).

Will take a stable application that I can scale predictably any-time over some faulty hacked functionality. But that's just me :)

try {
return $this->swift->send($message, $this->failedRecipients);
} finally {
$this->getSwiftMailer()->getTransport()->stop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->swift->getTransport()->stop()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of inconsistent here anyway since Mailer::forceReconnection() uses the public getter, but I've changed it according to your comment.

@taylorotwell
Copy link
Member

Have y'all tested this with your own applications to verify it fixes the problem?

@YOzaz
Copy link

YOzaz commented May 18, 2016

@taylorotwell @lifeofguenter how about implementing my try-catch multiblocks?
https://github.com/YOzaz/Laravel-SwiftMailer/blob/master/src/YOzaz/LaravelSwiftmailer/Mailer.php#L155
It basically reconnects when required. Note, this was written by SwiftMailer author, so should cover all possible issues. Therefore it won't autoclose before or after sending, and shouldn't have an impact for performance.
As a side note, this won't solve SSL connection problems like this one: YOzaz/Laravel-SwiftMailer#1 (comment)

By the way, PRs are still here, may need some adjustment though:
#8222
#8225

try {
return $this->swift->send($message, $this->failedRecipients);
} finally {
$this->swift->getTransport()->stop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just call "forceReconnection" here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taylorotwell the problem is that stop() may throw an exception, but even if it'll be catched - it won't reset transport. You need to call either reset(), either stop() > start().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because calling "forceReconnection" would not make sense form a semantic point of view.

@YOzaz
Copy link

YOzaz commented May 18, 2016

Guys, you're missing the point here. STOPping SMTP connection is slow and expensive operation, and shouldn't be done every time for every email to be sent. RESET is much faster and suits here better - however, in case of timeout it won't succeed.

@taylorotwell
Copy link
Member

@YOzaz if you have a better solution then PR it.

@lifeofguenter
Copy link
Contributor

@YOzaz as said, what does it help if the fix is only suited for certain situations? Would still make email sending via SMTP super unreliable.

If you need to send higher volumes, you still have the possibility to launch multiple queue-workers or outsource via services such as mandrill, that offer an async http API for sending bulk mails.

@taylorotwell
Copy link
Member

@schanzel are you actually running this fix in production to verify that it works?

@schanzel
Copy link
Contributor Author

@YOzaz What you are suggesting sounds a lot like premature optimisation and would add lots of unnecessary complexity. What you would normally want to do is open a connection, send a mail and finally close the connection. Handling any performance or network related problems should not be in focus here. Anything related to that should be handled by a dedicated connection pool, if needed (IMO).

@taylorotwell
Copy link
Member

Can someone just answer the question as to whether this fix has actually been tested to solve the problem? 😆

@YOzaz
Copy link

YOzaz commented May 18, 2016

@taylorotwell PR is here: #13609
And yes, I have tested it in my package, and code is copy-paste from there. Haven't tested with Laravel Framework 5.1 yet, tough.

@YOzaz
Copy link

YOzaz commented May 18, 2016

@schanzel you're basically right, but - following this ideology SMTP connection in daemon workers should be handled same way like DB reconnection, as suggested here: https://laravel.com/docs/5.1/queues (see Coding Considerations For Daemon Queue Listeners).
However, at the moment, Laravel developers have no way to force SMTP reconnection manually, unless forceReconnection() will become public and renamed to something like Mailer::reset().

@taylorotwell
Copy link
Member

I should note that calling DB::reconnect is not needed in 5.2+…

On May 18, 2016, at 10:55 AM, Marijus Plančiūnas notifications@github.com wrote:

@schanzel https://github.com/schanzel you're basically right, but - following this ideology SMTP connection in daemon workers should be handled same way like DB reconnection, as suggested here: https://laravel.com/docs/5.1/queues https://laravel.com/docs/5.1/queues (see Coding Considerations For Daemon Queue Listeners).
However, at the moment, Laravel developers have no way to force SMTP reconnection manually, unless forceReconnection() will become public and renamed to something like Mailer::reset().


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub #13583 (comment)

@YOzaz
Copy link

YOzaz commented May 18, 2016

@lifeofguenter sending emails through multiple queue tubes does not solve the problem in general - which is handling SMTP connections for long-running processes.
My suggestion would to use reset() method on a transport, which is fast, and actually built for this. It may fail only on specific scenarios with TTL/SSL - therefore additional try-catch blocks are required.
Mandrill is a separate discussion, especially when it became paid. Trust me, it has it's own problems and possibilities as well 😄

@taylorotwell
Copy link
Member

@schanzel still need to know if this has been tested on a real app.

@YOzaz
Copy link

YOzaz commented May 18, 2016

Right, I'll leave PR as it is for now - Travis is failing because of change, therefore it needs more testing.

@schanzel
Copy link
Contributor Author

Just deployed the fix at a larger scale to get somewhat more relibale information about whether or not this fix does the trick. The failures occure sporadically and are hard to reproduce. I'll get back to it once I got more information about it.

@taylorotwell
Copy link
Member

Whats the status of this? We have this PR and @YOzaz's hanging out here and I'd like to get a solution merged in.

@schanzel
Copy link
Contributor Author

We've had some internal problems which required a rollback, due to which this change was rollbacked also. I just deployed it again. I think I can give an answer by Wednesday or so. Please note that the rollback of the application was not related to this change ;-)

@taylorotwell
Copy link
Member

Any updates on this?

@taylorotwell taylorotwell reopened this Jun 7, 2016
@dbpolito
Copy link
Contributor

dbpolito commented Jun 7, 2016

This looks good to me, seems to be the right solution and fix the issue.

The only remaining change is remove method forceReconnection as it's not used anymore...

@schanzel
Copy link
Contributor Author

schanzel commented Jun 7, 2016

Since this fix is on production for a few days now, there are no SSL errors so far. I'd take this as an indication that the issue would be solved by this PR.

@schanzel
Copy link
Contributor Author

schanzel commented Jun 7, 2016

@dbpolito Removing forceReconnection was considered a breaking change, therefore I reverted the removal.

@dbpolito
Copy link
Contributor

dbpolito commented Jun 7, 2016

Gotcha.

I'm going to need to push this fix to my prod manually too... Looking forward to get this merged and released into 5.2! 👍

@dbpolito
Copy link
Contributor

dbpolito commented Jun 7, 2016

This is my temp fix:

Queue::after(function (JobProcessed $event) {
    Mail::getSwiftMailer()->getTransport()->stop();
});

Which may also be another approach for fixing this.

@GrahamCampbell
Copy link
Member

This is my temp fix:

That can lead to terrible performance though.

@taylorotwell
Copy link
Member

Define terrible performance? @GrahamCampbell

@GrahamCampbell
Copy link
Member

Define terrible performance? @GrahamCampbell

If you turn off keeping the connection open, you have to negotiate everything. If negotiation takes 0.1 seconds, and sending an email takes 0.05, then sending 10 emails takes 3 times as long after this change.

@overint
Copy link

overint commented Jul 4, 2016

Can confirm it's now fixed, but emails do take a little longer to send.
Thanks everyone.

@driade
Copy link
Contributor

driade commented Dec 9, 2016

Thanks to @dbpolito , stopped the connection at the end of every Job that sends an email and everything seems fine now. Using Laravel 4.2.

@yincongcyincong
Copy link

it solve my problem! thanks

@Jubeki Jubeki mentioned this pull request Aug 30, 2021
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants