Jump to content

Memory exhausted because of render()


suntrop
 Share

Recommended Posts

I am creating a module that sends pages as en email. I use ->render() to get the content of a PW page and send it as the body of the mail. But something is wrong and I am running out of memory (currently set to 100 MB).

This function checks, if there is a page flagged for sending. $mailtempplate is the corpus delicti …

public function checkSchedule($event) {

		// Memory usage
		$memory = 'Memory Start: ' . memory_get_usage();
		wire('session')->message($memory);

		// Find pages, that are scheduled and have a schedule date that is older than current time plus 60 seconds
		$scheduled_pages = wire('pages')->find('template=newsletter-mailing, schedule=1, schedule_date<=' . time() + 60);

		// Check if there are scheduled pages, and stop, if nothing is scheduled
		if (!count($scheduled_pages)) {
			return;
		}

		// Iterate scheduled pages
		foreach ($scheduled_pages as $scheduled_page) {

			// Get the mail-template, i.e. the rendered page (this becomes the mail-body later)
                        // If I just use $mailtemplate = 'Test'; everything works fine
			$mailtemplate = $scheduled_page->render();


			// Convert local/relative paths to absolute URL for links (href) and images (src)
			if (!strpos($mailtemplate,'href="/') === false) {
				$mailtemplate = str_replace('href="/', 'href="' . wire('config')->httpUrl, $mailtemplate);
			}
			if (!strpos($mailtemplate,'src="/') === false) {
				$mailtemplate = str_replace('src="/', 'src="' . wire('config')->httpUrl, $mailtemplate);
			}

			// Dispatch this scheduled page ("Newsletter") for email
			$dispatched = $this->dispatchMailing($scheduled_page->title, $scheduled_page->newsletter_recipients, $mailtemplate);
			if ($dispatched) {
				$memory .= ' und Memory Ende: ' . memory_get_usage();

				// Send reporting mail
				mail(…);
			} else {
				// Something went wrong while sending
				mail(…);
			}

			// Uncheck schedule checkbox, even if there was an error whiele sending!
			$p = $scheduled_page;
			$p->schedule = 0;
			$p->save();

			// Clear the mailtemplate for the next scheduled page/mailing
			unset($mailtemplate);
		}
	}

I moved $mailtemplate = $scheduled_page->render() out of the foreach() but it doesn't work either.

Appreciate any help and tips :-)

Link to comment
Share on other sites

Is memory exhausted on the first call to $scheduled_page->render() or are you handling a large amount of these pages?

Depending on the amount of pages you're fetching and rendering, 100 MB getting exhausted doesn't sound too surprising. You could try running wire('pages')->uncacheAll() every once in a while -- it will result in slower execution times, but will also free the memory.

Link to comment
Share on other sites

teppo, thanks for your help!

There is just one page that is scheduled for sending, so it is on first call of render(). The scheduled page (which I want to render) has only 3 fields and currently 1 child with 4 fields. I would say it is just a typical webpage, nothing special.

I put wire('pages')->uncacheAll(); below my unset($mailtemplate); but it doesn't help.

Link to comment
Share on other sites

Perhaps I found the error. I had this in my module init() …

$this->addHookAfter('Page::render', $this, 'checkSchedule');

That ends in an infinite loop, since every time I called my render() template the hook fires my module again.

What hook should I use instead? The LazyCron?

Link to comment
Share on other sites

LazyCron is one option, though you might consider running this with regular cron too. LazyCron is (in some cases) easier to setup, but has to be triggered by user.. and because it's going to be tied to that user's session, it's also prone to problems when used for something slow-running.

One simple way to work with regular cron is creating a script that bootstraps ProcessWire, loads your module and executes a method:

<?php
require "index.php"; // ProcessWire's index.php
wire('modules')->yourModuleName->checkSchedule();

After that you'll just have to setup a cron job (usually with crontab, but that depends on your environment) to run this script regularly with PHP (something like "5 0 * * * /usr/bin/php /path/to/script.php", which would run the script every day five minutes after midnight) and you're all set up.

.. and if you still want to use LazyCron, you could always hook into LazyCron::everyDay, LazyCron::everyHour etc. depending on your needs. Just be aware that this method may cause issues for you in the long run.

Link to comment
Share on other sites

Render hook with a render inside results in endless recursion.

You can avoid it by setting a property to the page rendered already and check for it in the start of the hook, and if this property is true, skip it.

if($p->skipRender) return;
...
$p->skipRender = true;
$p->render();
...
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...