[SOLVED] howto catch a swift exception in php4
Moderators: Chris Corbyn, General Moderators
-
billgoodyear
- Forum Newbie
- Posts: 16
- Joined: Fri Apr 20, 2007 11:00 am
[SOLVED] howto catch a swift exception in php4
I have a batch mail that loads emails into our email system. After running a while it dies with the following error:
PHP Fatal error: Uncaught Error of type [swift_connection_exception] with message [There was a problem reading line 1 of an SMTP response. The response so far was:
[]. It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps? (fsockopen: #0) ]
@0 emailmanager::queueprocess() in /xfsdata/www/htdocs_deepercontrol/batchcenter/hourly/batchmail.php on line 26
@1 emailmanager::sendemail() in /xfsdata/www/phpincludes/classes/email/emailManager.php on line 360
@2 swift::batchsend() in /xfsdata/www/phpincludes/classes/email/emailManager.php on line 504
@3 swift::send() in /xfsdata/www/phpincludes/classes/swiftmailer/Swift-3.1.3-php4/lib/Swift.php on line 557
in /xfsdata/www/phpincludes/classes/swiftmailer/Swift-3.1.3-php4/lib/Swift/Errors.php on line 99
You have new mail in /var/spool/mail/root
What is the correct way to "catch" swift errors in php4?
feyd | made text more legible.
PHP Fatal error: Uncaught Error of type [swift_connection_exception] with message [There was a problem reading line 1 of an SMTP response. The response so far was:
[]. It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps? (fsockopen: #0) ]
@0 emailmanager::queueprocess() in /xfsdata/www/htdocs_deepercontrol/batchcenter/hourly/batchmail.php on line 26
@1 emailmanager::sendemail() in /xfsdata/www/phpincludes/classes/email/emailManager.php on line 360
@2 swift::batchsend() in /xfsdata/www/phpincludes/classes/email/emailManager.php on line 504
@3 swift::send() in /xfsdata/www/phpincludes/classes/swiftmailer/Swift-3.1.3-php4/lib/Swift.php on line 557
in /xfsdata/www/phpincludes/classes/swiftmailer/Swift-3.1.3-php4/lib/Swift/Errors.php on line 99
You have new mail in /var/spool/mail/root
What is the correct way to "catch" swift errors in php4?
feyd | made text more legible.
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
set_error_handler(). These aren't real exceptions like you can use in PHP5. This is the biggest difference with the PHP5 and PHP4 versions. In the PHP5 version (main version while I'm developing) I used exceptions for program flow, but in PHP4 this isn't possible so I have created a workaround internally. The workaround I have created doesn't really make for a friendly solution to end-users so I'd just use set_error_handler().
However, I'd suggest acting on the cause of the problem and running smaller batches, or using the throttler because even you catch this error Swift isn't going to be able to proceed because the connection is dead (beyond Swift's control - the socket closed on it).
Be sure to call (statically) Swift_Errors::reset() from within the error handler if you plan and trying to get Swift going again (reconnecting).
How many emails are you trying to send in one go? This usually happens when the server closes the connection because you're asking to do more than it's configured to accept. Usually; not always.
However, I'd suggest acting on the cause of the problem and running smaller batches, or using the throttler because even you catch this error Swift isn't going to be able to proceed because the connection is dead (beyond Swift's control - the socket closed on it).
Be sure to call (statically) Swift_Errors::reset() from within the error handler if you plan and trying to get Swift going again (reconnecting).
How many emails are you trying to send in one go? This usually happens when the server closes the connection because you're asking to do more than it's configured to accept. Usually; not always.
-
billgoodyear
- Forum Newbie
- Posts: 16
- Joined: Fri Apr 20, 2007 11:00 am
Im sending 50,000 emails.
I have the emails divided up between 800 online stores that we run (we whitelabel our service to various other organizations).
For each store, i open a connection to the SMTP server, and then batch send the emails in groups of 100. It worked fine with only 7 stores timing out. Of those though, one store has 10,000 email addresses so thats a pretty major deal. I need to be able to save where im up to so that if we restart i dont send duplicate emials. And to do that i just need a way to catch the error.
What id like to do is catch the exceptions, sleep for a few seconds and then try resuming by opening a new connection to the server. If that does not work then i'll just gracefully exit and update our log table with some information.
I know PHP5 is much better, but im using CENTOS4.4 as the OS which is a redhat ES 4 clone and they are still on PHP4.
I have the emails divided up between 800 online stores that we run (we whitelabel our service to various other organizations).
For each store, i open a connection to the SMTP server, and then batch send the emails in groups of 100. It worked fine with only 7 stores timing out. Of those though, one store has 10,000 email addresses so thats a pretty major deal. I need to be able to save where im up to so that if we restart i dont send duplicate emials. And to do that i just need a way to catch the error.
What id like to do is catch the exceptions, sleep for a few seconds and then try resuming by opening a new connection to the server. If that does not work then i'll just gracefully exit and update our log table with some information.
I know PHP5 is much better, but im using CENTOS4.4 as the OS which is a redhat ES 4 clone and they are still on PHP4.
I quote billgoodyear here, i have the same need. I hope that i'm not sounding rude , but i really would like to know morebillgoodyear wrote:Im sending 50,000 emails.
I have the emails divided up between 800 online stores that we run (we whitelabel our service to various other organizations).
For each store, i open a connection to the SMTP server, and then batch send the emails in groups of 100. It worked fine with only 7 stores timing out. Of those though, one store has 10,000 email addresses so thats a pretty major deal. I need to be able to save where im up to so that if we restart i dont send duplicate emials. And to do that i just need a way to catch the error.
Since i'm using a dedicated mail server server i can customize the behaviour of my code, but when i receive a message a message like this:
[swift_connection_exception] with message [There was a problem reading line 1 of an SMTP response. The response so far was:<br />[]. It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps?]
i really don't know how to handle it... How can i resume the operation ? How many email were sent ? Which was the last one ?
During my tests, swift is sending about 200 email per minute using throttler, which is absolutely fantastic... now if i could grasp these missing parts, it would be perfect
Thanks for your help Chris!
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
This mass mailing stuff is becoming a bit of a big task for Swift
I think quite clearly (from the number of support requests arising from mass mailing) the developments towards 3.2 need to be focused on adding a whole new featureset for management of a large batch job (with observers to pick up errors before they are thrown).
If you run a dedicated server my first piece of advice is use PHP5 because it has try/catch which makes this work flawlessly. My second bit of advice is raise the timeout limit because when the server is under stress it *will* gradually slow down if you don't use AntiFlood.
The same method exists for the sendmail connection.
If you're stuck with PHP4 you'll have to write a slightly more aggressive solution using set_error_handler(). I'll post an example in a minute.
If you want to track how far Swift got when sending a batch, you have two options:
1. Don't use batchSend(), just use send() in a loop. If you look at the source code of batchSend() you'll see this is exactly how I do it.
2. Write a simple plugin to track the status. If you read the documentation for the VerboseSending plugin you could theoretically extend the abstract View component to log to a database, or simply store in an array, although semantically that was never the intention of that plugin.
*sigh* actually I'm just going to write a class in this thread which you can use.
It'll take me 20 mins or so to knock together and if it works, I promise to modify batchSend() asap to handle errors and restart Swift etc by the time 3.2 comes out because that is the ideal place to do it. My modification may actually just pass the invokation of batchSend() to a stress tested version of the code I'm about to post. All I'm going to do is use set_error_handler() to catch errors from Swift alongside tracking the recipients I send to and disconnect(), connect(), Swift_Errors::reset() then continue where we go up to
I think quite clearly (from the number of support requests arising from mass mailing) the developments towards 3.2 need to be focused on adding a whole new featureset for management of a large batch job (with observers to pick up errors before they are thrown).
If you run a dedicated server my first piece of advice is use PHP5 because it has try/catch which makes this work flawlessly. My second bit of advice is raise the timeout limit because when the server is under stress it *will* gradually slow down if you don't use AntiFlood.
Code: Select all
$smtp->setTimeout(30); //default is 10, raise it to 30
$swift =& new Swift($smtp);If you're stuck with PHP4 you'll have to write a slightly more aggressive solution using set_error_handler(). I'll post an example in a minute.
If you want to track how far Swift got when sending a batch, you have two options:
1. Don't use batchSend(), just use send() in a loop. If you look at the source code of batchSend() you'll see this is exactly how I do it.
2. Write a simple plugin to track the status. If you read the documentation for the VerboseSending plugin you could theoretically extend the abstract View component to log to a database, or simply store in an array, although semantically that was never the intention of that plugin.
*sigh* actually I'm just going to write a class in this thread which you can use.
It'll take me 20 mins or so to knock together and if it works, I promise to modify batchSend() asap to handle errors and restart Swift etc by the time 3.2 comes out because that is the ideal place to do it. My modification may actually just pass the invokation of batchSend() to a stress tested version of the code I'm about to post. All I'm going to do is use set_error_handler() to catch errors from Swift alongside tracking the recipients I send to and disconnect(), connect(), Swift_Errors::reset() then continue where we go up to
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Here's a smarter batchSend().
Save this code to a file at lib/Swift/BatchMailer.php
Then use it like this:
If Swift throws such errors the class will handle the error, restart Swift, then pick up where it left off.
It hasn't been thoroughly tested (I tried it twice
) but I'll develop this further as a part of the library and in Swift 3.2 I'll have $swift->batchSend() call upon this class itself.
Good luck!
Save this code to a file at lib/Swift/BatchMailer.php
Code: Select all
<?php
/**
* Handles batch mailing with Swift Mailer with fail-safe support.
* Restarts the connection if it dies and then continues where it left off.
* @package Swift
* @author Chris Corbyn <chris@w3style.co.uk>
* @license LGPL
*/
class Swift_BatchMailer
{
/**
* The current instance of Swift.
* @var Swift
*/
var $swift;
/**
* Sets true after an error has occurred.
* @var boolean
*/
var $doRestart = false;
/**
* Constructor.
* @param Swift The current instance of Swift
*/
function Swift_BatchMailer(&$swift)
{
$this->setSwift($swift);
}
/**
* Set the current Swift instance.
* @param Swift The instance
*/
function setSwift(&$swift)
{
$this->swift =& $swift;
}
/**
* Get the Swift instance which is running.
* @return Swift
*/
function &getSwift()
{
return $this->swift;
}
/**
* Does nothing but covers errors when Swift throws them.
* This is agressive because I haven't had time to develop it more.
*/
function handleError($errno, $errstr, $errfile, $errline)
{
$this->doRestart = true;
}
/**
* Restarts Swift forcibly.
*/
function forceRestartSwift()
{
Swift_Errors::reset();
//Pre-empting problems trying to issue "QUIT" to a dead connection
$this->swift->connection->stop();
$this->swift->connection->start();
$this->swift->disconnect();
//Restart swift
$this->swift->connect();
$this->doRestart = false;
}
/**
* NULLs out the To and From header in case Swift didn't get chance.
* @param Swift_Message The message object
*/
function prepareMessageHeaders(&$message)
{
$message->headers->set("To", null);
$message->headers->set("From", null);
}
/**
* Run a batch send in a fail-safe manner.
* This operates as Swift::batchSend() except it deals with errors itself.
* @param Swift_Message To send
* @param Swift_RecipientList Recipients (To: only)
* @param Swift_Address The sender's address
* @return int The number sent to
*/
function send(&$message, &$recipients, $sender)
{
$sent = 0;
$this->prepareMessageHeaders($message);
set_error_handler(array(&$this, "handleError"));
foreach ($recipients->getTo() as $recipient)
{
if ($this->swift->send($message, $recipient, $sender))
{
$sent++;
}
else
{
if ($this->doRestart)
{
$this->forceRestartSwift();
//Give it one more go
$this->prepareMessageHeaders($message);
$sent += $this->swift->send($message, $recipient, $sender);
}
}
}
restore_error_handler();
return $sent;
}
}Code: Select all
<?php
require_once "lib/Swift.php";
require_once "lib/Swift/Connection/Sendmail.php";
require_once "lib/Swift/BatchMailer.php";
$swift =& new Swift(new Swift_Connection_Sendmail());
$batch =& new Swift_BatchMailer($swift);
$recipients =& new Swift_RecipientList();
$recipients->addTo("address1@foo.tld");
$recipients->addTo("address2@foo.tld");
$recipients->addTo("address3@foo.tld");
$message =& new Swift_Message("Test Message", "This is just a test");
echo $batch->send($message, $recipients, "d11wtq@users.sourceforge.net");
$swift->disconnect();It hasn't been thoroughly tested (I tried it twice
Good luck!
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
I preferred your previous post more than this onegarethjax wrote:There's something strange, the page remains in "loading" state like it was in an endless loop.d11wtq wrote:Here's a smarter batchSend().
Yeah I didn't expect it to go so smoothly without a bit of fiddling. Hopefully not stating the obvious, but are you sure Swift isn't just busy sending emails before I start modifying this? Also, are you using Sendmail or SMTP?
I cannot imagine whyd11wtq wrote:
I preferred your previous post more than this one
I've copied the code "as is" in order to test it and i've put some of my emails (3) as recipients. I've done another test and the lineYeah I didn't expect it to go so smoothly without a bit of fiddling. Hopefully not stating the obvious, but are you sure Swift isn't just busy sending emails before I start modifying this? Also, are you using Sendmail or SMTP?
$swift =& new Swift(new Swift_Connection_Sendmail());
seems responsible for the endless loop.
i've replaced it with
$smtp =& new Swift_Connection_SMTP(......);
and it worked.
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
I suspected as much. It'll be the sendmail connection that I need to work on. There's a niggly bit of code which copies references to the process into $GLOBALS because PHP4 hangs if it cannot find the resource. I think I know what the problem is here.garethjax wrote:I cannot imagine whyd11wtq wrote:
I preferred your previous post more than this one
I've copied the code "as is" in order to test it and i've put some of my emails (3) as recipients. I've done another test and the lineYeah I didn't expect it to go so smoothly without a bit of fiddling. Hopefully not stating the obvious, but are you sure Swift isn't just busy sending emails before I start modifying this? Also, are you using Sendmail or SMTP?
$swift =& new Swift(new Swift_Connection_Sendmail());
seems responsible for the endless loop.
i've replaced it with
$smtp =& new Swift_Connection_SMTP(......);
and it worked.
Does the sendmail connection work normally for you (without this batch sending code)?
Oh, actually, I see you're using 3.1.3. Something got changed in 3.1.4 which fixed a hang in the sendmail connection.... I suspect this is the culprit. Grab an update:
http://sourceforge.net/project/showfile ... _id=193923
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Does this hang too?
Code: Select all
<?php
require_once 'lib/Swift/Connection/Sendmail.php';
require_once 'lib/Swift.php';
$sendmail =& new Swift_Connection_Sendmail();
$sendmail->start();
echo "I got here ok";