views:

408

answers:

3

I have a cron job that sends emails to a list of subscribers, one at a time in a foreach loop, with a PDF attachment. I got this message from the cron script:

Fatal error: Allowed memory size of 94371840 bytes exhausted (tried to allocate 78643193 bytes)

What do I need to do to prevent this error?

Also, I'm pretty sure that it didn't finish sending to all the subscribers, so how should I keep track of this so it knows where to pick up again if it didn't send to everyone?

Updater: Here's a code sample: (I'm using the Zend Framework by the way)

public function send(Default_Model_MyEmail $myEmail)
{
    if (null != ($id = $myEmail->attachmentId)) {
        $file = new Default_Model_File();
        $file->find($id);
        $filepath = APPLICATION_UPLOADS_DIR . '/' . $file->getActualFilename();

        $attachment = new Zend_Mime_Part(file_get_contents($filepath));
        $attachment->type = $file->getMimeType();
        $attachment->disposition = Zend_Mime::DISPOSITION_ATTACHMENT;
        $attachment->encoding = Zend_Mime::ENCODING_BASE64;
        $attachment->filename = $file->getDisplayFilename();    
    }
    $transport = new Zend_Mail_Transport_Smtp('localhost');

    $mail = new Zend_Mail('utf-8');
    $mail->setFrom('from@address', 'From Name');
    $mail->setReplyTo('replyto@address');
    $mail->setSubject($myEmail->subject);
    if (isset($attachment)) {
        $mail->addAttachment($attachment);
    }

    $subscribers = $this->getSubscribers();
    foreach ($subscribers as $subscriber) {
        $mail->addTo($subscriber->email);
        $bodyText = $myEmail->body
            . "\r\n\r\nIf for any reason you would like to be removed from this mailing list, "
            . "please visit \r\nhttp://myapp.com/myemail/unsubscribe/email/"
            . $subscriber->email;
        $mail->setBodyText($bodyText);
        $mail->send($transport);
        $mail->clearRecipients();
    }
}

Update: I am reusing the $transport variable. I was under the impression this was the correct way to send to multiple subscribers, but maybe this is the cause? What do you think?

Update: I've added a bunch of log statements that print memory usage statements, but I don't really know what to do now. The memory usage increases with every email. With a subscriber list of 200, it gets to 160 then runs out of memory. What should I do?

A: 

Reduce memory usage, or increase the memory limit.

Malfist
That's an awfully detailed reply :-)
Eric J.
It's an awfully detailed question.
Malfist
And yet all the other answers offer a starting point to find a solution...
Eric J.
+1  A: 

It looks like your code is trying to allocate a chunk of 78MB based on the error message.

Check your code for anything that might try and allocate a really large chunk of memory at once. This is probably not caused by a failure to release smaller objects, since the failed allocation block is large.

If you post a snippet of code that is causing this, I would be happy to have a look and try for a more detailed reply.

As for finding out if you sent to everyone or not, check of your email server (SMTP server) writes a log of sent messages. If so, you may be able to get a list of people who received the email. In general, I would suggest you modify your PHP code to log to a file or database each email that was sent, in case you have a crash in the future.

EDIT after seeing code:

On the surface, it looks like the object that represents the email and attachment is created once and re-used.

I suggest you debug the code locally.

First, establish a memory limit equal to what you have in production. Try this resource to understand how.

Then, add some debug output inside your loop to see how much memory is available after each iteration.

Finally, run the code locally, but substitute either your email address or a known bad email address preferably at your domain (so you don't spam people). Watch memory usage as your send progresses.

This should hopefully help you narrow down the cause of the error.

EDIT 2:

OK, after seeing that memory usage is continually growing, a little google magic turned up that this is a known issue with a reported workaround.

Eric J.
added a code sample
Andrew
I've done what you said, I have a log full of memory usage statements, but I don't really know what to do about it. The memory usage increases with every email. With a subscriber list of 200, it gets to 160 then runs out of memory.
Andrew
Found a probable solution. See EDIT 2 in my answer.
Eric J.
+1  A: 

In addition to @Malfist's answer, you can:

  • Make sure you're reusing variables instead of adding new ones for each iteration of the loop.
  • Have the system take note when an e-mail has been successfully sent, and stop sending after a certain number of e-mails. The next time cron runs, it should figure out who is left to be sent to and resume.
ceejayoz