I've created some demo code that shows there is a problem with views and memory usage. In short

  $view->destroy();
  unset($view);

does not clear out the view object from memory if pre_execute() is ran.

Comments

mikeytown2’s picture

Title: Memory leak with $view->pre_execute() & $view->execute() » Memory leak with $view->pre_execute()

Output:

$view->pre_execute() skipped, $view->execute() skipped; no memory leak
Before: 27525120
After: 27525120
Diff: 0


$view->pre_execute() ran, $view->execute() skipped; memory leak
Before: 27525120
After: 42205184
Diff: 14 mb


$view->pre_execute() skipped, $view->execute() ran; big memory leak
Before: 42205184
After: 85983232
Diff: 41.75 mb


$view->pre_execute() ran, $view->execute() ran; huge memory leak
Before: 85983232
After: 146800640
Diff: 58 mb


test code below


ini_set('memory_limit','512M');

// Prime views code (static caches)
test_view(TRUE, TRUE);


echo '$view->pre_execute() skipped, $view->execute() skipped; no memory leak<br>';
test_run(FALSE, FALSE);
echo '<br><br>';

echo '$view->pre_execute() ran, $view->execute() skipped; memory leak<br>';
test_run(TRUE, FALSE);
echo '<br><br>';

echo '$view->pre_execute() skipped, $view->execute() ran; big memory leak<br>';
test_run(FALSE, TRUE);
echo '<br><br>';

echo '$view->pre_execute() ran, $view->execute() ran; huge memory leak<br>';
test_run(TRUE, TRUE);
echo '<br><br>';


function test_run($pre_execute, $execute) {
  $before = memory_get_usage(TRUE);
  echo 'Before: ' . $before . '<br>';
  for ($i=0; $i<500; $i++) {
    test_view($pre_execute, $execute);
  }
  $after = memory_get_usage(TRUE);
  echo 'After: ' . $after . '<br>';
  $diff = test_convert($after - $before);
  echo 'Diff: ' . $diff . '<br>';
  unset($before);
  unset($after);
  unset($diff);
}

function test_view($pre_execute, $execute) {
  $view = views_get_view('frontpage', TRUE);
  $view->set_display('page');
  if ($pre_execute) {
    $view->pre_execute();
  }
  if ($execute) {
    $view->execute();
  }
  count($view->result);
  $view->destroy();
  unset($view);
  unset($pre_execute);
  unset($execute);
}

// php.net/memory-get-usage#96280
function test_convert($size) {
  $unit=array('b','kb','mb','gb','tb','pb');
  return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}

mikeytown2’s picture

Title: Memory leak with $view->pre_execute() » Memory leak with $view->pre_execute() & $view->execute()
mikeytown2’s picture

Title: Memory leak with $view->pre_execute() » Memory leak with $view->pre_execute() & $view->execute()

This solves one of the issues; going to dig into execute() next

  /**
   * Unset references so that a $view object may be properly garbage
   * collected.
   */
  function destroy() {
...
    unset($this->old_view);
...
mikeytown2’s picture

Status: Active » Needs review
StatusFileSize
new2.04 KB
$this->display_handler;

Has recursion in it, so my guess is it doesn't get destroyed correctly

Patch below fixes pre_execute(); still no solution to execute() leaking.

$view->pre_execute() skipped, $view->execute() skipped; no memory leak
Before: 23855104
After: 23855104
Diff: 0


$view->pre_execute() ran, $view->execute() skipped; memory leak
Before: 23855104
After: 23855104
Diff: 0


$view->pre_execute() skipped, $view->execute() ran; big memory leak
Before: 23855104
After: 67108864
Diff: 41.25 mb


$view->pre_execute() ran, $view->execute() ran; huge memory leak
Before: 67108864
After: 110624768
Diff: 41.5 mb
mikeytown2’s picture

StatusFileSize
new27.91 KB

Screen shot showing the before and after on one of our servers. This patch helps a lot; but it could use more help.

mikeytown2’s picture

Calling this instead of destroy()

function test_view_destroy(&$var) {
  if (is_array($var)) {
    foreach ($var as $key => $value) {
      if (is_array($value) || is_object($value)) {
        test_view_destroy($var[$key]);
      }
      unset($var[$key]);
    }
  }
  if (is_object($var)) {
    if (method_exists($var, 'destroy')) {
      echo get_class($var) . '<br>';
      $var->destroy();
    }
    foreach ($var as $key => $value) {
      if (is_array($value) || is_object($value)) {
        test_view_destroy($var->{$key});
      }
      unset($var->{$key});
    }
  }
  else {
    unset($var);
  }
}

These are the objects with a destroy method

view
views_plugin_display_default
views_plugin_display_page
views_handler_sort
views_handler_sort_date
views_handler_filter_boolean_operator
views_handler_filter_boolean_operator
views_plugin_display_feed
views_plugin_display_page
views_handler_sort
views_handler_sort_date
views_handler_filter_boolean_operator
views_handler_filter_boolean_operator
views_plugin_style_default
views_plugin_row_node_view

I've commented out views destroy() will try the rest and go from there.

mikeytown2’s picture

Alt with a limit of 1000 in depth and it still doesn't clear out the memory; in fact I see no change in comparison.

  //$view->destroy();
  test_view_destroy($view);
function test_view_destroy(&$var, $level=0) {
  $level++;
  if ($level > 1000) {
    unset($var);
    return;
  }

  if (is_array($var)) {
    foreach ($var as $key => $value) {
      if (is_array($value) || is_object($value)) {
        test_view_destroy($var[$key], $level);
      }
      unset($var[$key]);
    }
  }
  if (is_object($var)) {
//     if (method_exists($var, 'destroy')) {
//       echo get_class($var) . '<br>';
//       $var->destroy();
//     }
    foreach ($var as $key => $value) {
      if (is_array($value) || is_object($value)) {
        test_view_destroy($var->{$key}, $level);
      }
      unset($var->{$key});
    }
  }
  else {
    unset($var);
  }
}
mikeytown2’s picture

all objects inside the view

view
stdClass
views_display
views_plugin_display_default
views_plugin_display_page
views_plugin_display_feed
views_query
views_handler_sort
views_handler_sort_date
merlinofchaos’s picture

Did you learn any more about the possible memory leaks?

It might be worth doing some more focused testing. For example, do just init_display() and then destroy. Do just init_handlers() then destroy. Do just build() without execute, then destroy. That might narrow down, a little, where to look?

mikeytown2’s picture

Title: Memory leak with $view->pre_execute() & $view->execute() » Memory leak with $view->pre_execute() & $view->attach_displays()

Thanks for the pointers :) Attach_displays leaks; Here is the line in question

        $this->display[$id]->handler->attach_to($this->current_display);

http://drupalcode.org/viewvc/drupal/contributions/modules/views/includes...

mikeytown2’s picture

pinpointed it further; its in class views_plugin_display_feed; attach_to method.

    if ($plugin) {
      $clone = $this->view->clone_view();
      $clone->set_display($this->display->id);
      $clone->build_title();
      $plugin->attach_to($display_id, $this->get_path(), $clone->get_title());
    }

http://drupalcode.org/viewvc/drupal/contributions/modules/views/plugins/...

merlinofchaos’s picture

Ahh! The clone isn't getting destroyed. Does adding $clone->destroy() there fix it? Break attachments?

mikeytown2’s picture

StatusFileSize
new2.8 KB

Fixes memory leak!
This patch should get some testing before getting committed. Views still works from my point of view with the patch applied.

merlinofchaos’s picture

Assigned: Unassigned » bojanz

Need to ensure this doesn't break anything. bojanz, would you do some testing please?

dawehner’s picture

StatusFileSize
new14.77 KB

Here is a rerole for 6.x-3.x with a test for view::destroy

dawehner’s picture

StatusFileSize
new14.98 KB

Updated test

merlinofchaos’s picture

Version: 6.x-2.11 » 7.x-3.x-dev
Status: Needs review » Patch (to be ported)

Committed to 2.x and 3.x -- needs porting to 7.x. SHould be easy.

dawehner’s picture

Status: Patch (to be ported) » Fixed

This problem was just a previous codestyle fix :(

Commited to the 7.x branch.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.