Problem/Motivation

The site I'm working on has a large number of nodes (~15,500). We fairly consistently run into an issue where views_bulk_operations doesn't process all of the selected items when I "Select all on this page", nor when I "Select all results in the view" I've included some steps to reproduce with the "Select all results in the view"

When I did some debugging for the "Select all on this page", I find that view_bulk_operations seems to successfully POST all 50 checkbox values to the backend (i.e.: /views-bulk-operations/ajax/content/page_1, and the confirmation step seems to show 50 nodes; but when the actions actually run, they don't act on all the items. The number of items processed also seems to vary... sometimes its 42, sometimes it's 46, sometimes it's 48, sometimes its all 50. When I select all items in the view, it's often low, like 2 items processed, or 0 items processed.

Doing a bit more debugging of the data stored in the private tempstore, I see...

{
  "owner": "25",
  "data": [
    "view_id" => "content",
    "display_id" => "page_1",
    "list" => [
      // Sometimes this is empty and, when exclude_mode = false, nothing gets processed.
      // Sometimes there are less than 50 nodes here, and less than 50 nodes get processed.
      // Sometimes there are 50 nodes here, and all 50 nodes get processed.
    ],
    "exclude_mode" => false,
    "batch" => true,
    "batch_size" => 10,
    "total_results" => "15472",
    "relationship_id" => "none",
    "arguments" => [],
    "exposed_input" => [
      // (redacted)
    ],
    "bulk_form_keys" => [
      // This seems to always have the correct number of entries, i.e.: if I select all on a page, I see 50 values here, even if there are less than 50 values in object.data['list'] above.
    ],
    "redirect_url" => Drupal\Core\Url {#5336},
  ],
  "updated": 1637280422,
}

My guess is that whatever is supposed to copy/move data from object.data['bulk_form_keys'] to object.data['list'] doesn't always move everything.

It's also hard to reproduce this on sites without many nodes, fields, or content types. Maybe it runs out of time or memory? But I'd expect to see an error or message in the log if that happened.

Steps to reproduce

  1. git clone --branch '9.3.x' https://git.drupalcode.org/project/drupal.git
    • The latest commit when I ran this test case was 72d15850a271e3718bf74d71a8207614c79b6918, committed Tue Dec 7 16:55:28 2021 +0100 (authored Thu Nov 25 15:52:00 2021 +0100 - it was a cherry-pick).
  2. composer install
  3. composer require 'drush/drush' 'drupal/devel:^4.1'
  4. cd modules/
  5. git clone --branch '4.0.x' https://git.drupalcode.org/project/views_bulk_operations.git
    • The latest commit when I ran this test case was d83b999e87dac745057a1a746a0ba038d63258fd committed Mon Nov 15 10:26:13 2021 +0100.
  6. cd ..
  7. drush -y si minimal
  8. Log in as the super-administrator
  9. Go to /admin/modules. Install the modules with machine names views_bulk_operations, views_ui, devel_generate and all their dependencies.
  10. Go to /admin/structure/views/view/content:
    1. Add field "Global: Views bulk operations". Select Action "Unpublish content item" and check "Add confirmation step". Select Action "Publish content item" and check "Add confirmation step". Apply.
    2. Remove field "Content: Node operations bulk form". Apply.
    3. Save the view.
  11. Go to /admin/structure/types/add. Set name = Page. Leave all other settings at default. Click Save content type
  12. Go to /admin/config/development/generate/content. Set: Content type = (checked) (this will check "Page" automatically), and How many nodes would you like to generate? = 12000. Leave all other settings at their default values. Click Generate
  13. Go to /admin/content. You see a table of 50 nodes, ordered by "Updated", descending.
  14. Select all items on the page using the checkbox. Select Action "Unpublish content item".
    • Expected behavior: The #edit-multipage details element says "Selected 50 items in this view". Expanding the details element shows a list of all 50 items selected.
    • Actual behavior: The #edit-multipage details element says "Selected 50 items in this view". Expanding the details element shows "No items".
  15. Click Apply to selected items.
  16. You see a page saying "Are you sure you wish to perform "Unpublish content item" action on 50 entities", and a list of 50 "Items selected". Click Execute action.
  17. A batch process runs.
  18. You are returned to /admin/content. You see the status message "Action processing results: Unpublish content item (50).". You see a table of 50 nodes, ordered by "Updated", descending. The first 50 rows in the table are unpublished.
  19. Check "Select / deselect all results in this view (all pages, 12000 total)" Select Action "Unpublish content item". Click Apply to selected items.
  20. You see a page saying "Are you sure you wish to perform "Publish content item" action on 12000 entities? / Action will be executed on all items in the view." Click Execute action.
  21. A batch process runs.
  22. You are returned to /admin/content. You see the status message "Action processing results: Publish content item (12000)."
  23. You see a table of 50 nodes, ordered by "Updated", descending.
    • Expected behavior: The first 50 rows in the table are published.
    • Actual behavior: The first 28 rows in the table are published. The remaining 22 rows are unpublished. (or at least, that's what happened in my run)

Note that there are no notice, warning, or error messages:

$ drush -y wd-list
---- -------------- ---------------- ---------- -----------------------------------------
ID   Date           Type             Severity   Message
---- -------------- ---------------- ---------- -----------------------------------------
21   08/Dec 09:13   node             Notice     Added content type Page.
20   08/Dec 09:12   system           Info       devel_generate module installed.
19   08/Dec 09:03   page not found   Warning    /admin/stucture
18   08/Dec 09:02   system           Info       views_ui module installed.
17   08/Dec 09:02   page not found   Warning    /admin/structure/views
16   08/Dec 09:02   page not found   Warning    /admin/structure/veiws
15   08/Dec 09:02   system           Info       views_bulk_operations module installed.
14   08/Dec 09:02   system           Info       views module installed.
13   08/Dec 09:01   user             Notice     Session opened for admin.
12   08/Dec 09:01   cron             Info       Cron run completed.
---- -------------- ---------------- ---------- -----------------------------------------

Proposed resolution

(To be determined)

Remaining tasks

(To be determined)

User interface changes

(To be determined)

API changes

(To be determined)

Data model changes

(To be determined)

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mparker17 created an issue. See original summary.

mparker17’s picture

(fixed some formatting issues in the issue summary)

liquidcms’s picture

graber’s picture

Interesting.. selection uses AJAX to save data.. maybe those callbacks take too long with large numbers of entities or are not executed at all due to some browser error?
Did you check the browser console?

graber’s picture

Status: Active » Postponed (maintainer needs more info)

Also.. it may be an issue with your hosting not processing some of those requests. Just tried to reproduce taking the described steps and no luck - all works fine and AJAX requests are super fast with over 12000 nodes (using Docker locally).

Please include more details if possible, maybe then someone will be able to help.

liquidcms’s picture

BTW.. my issue, with D9.2 and VBO 4.0.0. is on a page with < 40 nodes.

mparker17’s picture

Status: Postponed (maintainer needs more info) » Active

@Graber, thanks for the response. I'll try to answer your questions as best I can. I spent some time digging into the problem earlier this month, and while I can't point to a specific problem yet, I'll try to communicate what I know.

To answer the easiest questions first:

  1. There are no notable messages in the browser console.
  2. I was mainly testing on a DrupalVM box (Vagrant, VirtualBox), (amd64 architecture) with 8 GB of RAM, and 4x 2.4 GHz Intel Core i9 processors exclusively allocated to the virtual machine (the host machine is a macOS 11.6 "Big Sur" host machine with 8x 2.4 GHz Intel Core i9 processors, 64 GB of RAM and an NVMe x4 SSD hard drive).
    The issue was originally discovered on a server dedicated to this website (i.e.: not shared hosting) with 2x 3.1 GHz Intel Xenon Platinum 8175M CPUs and 8 GB of RAM.
    All servers running nginx, PHP 7.3 FPM and PostgreSQL 10.17.
    The drupalvm box has a memory_limit of -1 (i.e.: unlimited); the Xenon server has a memory_limit of 512M.
    The drupalvm box had a max_execution_time of 0 (i.e.: unlimited); the Xenon server has a max_execution_time of 60 seconds.
  3. I didn't keep track of how many times I ran these tests while working on this, but I'd estimate ~50-75 times
  4. 0 test runs produced errors indicating that PHP had reached its memory_limit or max_execution_time. Note that error_reporting was set to E_ALL
  5. I mostly tested on PostgreSQL 10.17, but I can reproduce the exact same issues on Oracle MySQL 5.7.34
  6. I was using a Standard install of Drupal 9.3.x at commit 4d7c1ebc4e (from 2021-12-08); a clone of views_bulk_operations on branch 4.0.x at commit a6b668b (from 2022-01-03); and a copy of drupal/devel at version 4.0.0 (note this is not the latest version, due to a typo in my composer.json that I just noticed while typing this

Since there are so many nodes, and problems only seem to start showing up in batch operations after ~6000 nodes have been processed, step-through debugging is fairly impractical, so what I was doing was inserting \Drupal::logger('channel')->debug('Message', ['@variable' => $foo]); statements into VBO, and then using SQL to analyze the results.

I've noticed some notably-unusual behaviours:

  1. In the UI at /admin/content, when I select all results on the first page, and then use the pager to go to subsequent pages, I see some of the results that I checked in the first page appear on the subsequent pages.
    1. That is to say, rows get repeated when the list is broken into chunks.
    2. My gut thinks this has the same root cause as the issue we're trying to solve, but that is only a gut feeling - I'm not actually certain what's causing that to happen.
  2. During the first phase of the bulk operation, ViewsBulkOperationsActionProcessor::initialize() tries to enqueue all 12,000 nodes exactly once (i.e.: expected behaviour).

    During the second phase of the bulk operation, ViewsBulkOperationsActionProcessor::populateQueue() queues up 10 nodes to be processed, and ViewsBulkOperationsActionProcessor::process() processes the 10 queued nodes. This enqueue-10-process-10 is repeated 1200 times (i.e.: expected behaviour), but...

    1. In both ::populateQueue() and ::process() , we start to see nodes that were already queued and processed get queued and processed again; while other nodes never get queued or processed at all.
      1. That is to say, rows get repeated when the list is broken into chunks.
      2. Fairly consistently, these repeating rows start happening after 5,999 rows have already been processed.
        1. I have no idea where this magic number 5,999 comes from.

Reading the code in ViewsBulkOperationsActionProcessor, it doesn't seem like ::populateQueue() nor ::process() have any obvious logic errors.

Something I noted, though, is that they both call the \Drupal\views_bulk_operations\Service\ViewsBulkOperationsViewData service to get data about the rows. This service also gets called when the VBO field is being displayed, and given the manner that ViewsBulkOperationsActionProcessor uses the ViewsBulkOperationsViewData service, it is at least a possibility that ViewsBulkOperationsViewData could be returning duplicate results. Reading that service, I don't see any obvious logic errors, and I'm not certain that it is the thing returning duplicate rows, or if it something deeper (i.e.: Views).

graber’s picture

Can you try to add an ID sort to the view (as the last sort)? You shouldn't have same results on subsequent pages but I'll be surprised if your issue will go away since that's what ViewsBulkOperationsActionProcessor does when creating a list..

mparker17’s picture

StatusFileSize
new374.05 KB
new130.2 KB
new125.26 KB

Can you try to add an ID sort to the view (as the last sort)? You shouldn't have same results on subsequent pages

I tried this on a freshly-created site, with newly-generated content, and a bunch of the 50 items from the front page reappeared as early as page 2 of the results. I added a column of NIDs to make it easier to see the duplicates. Please find screenshots attached, including one of the View itself and the queries run during the preview. Note that, while the last sort item is by NID, the table display's click-sort overrides this (it sorts by updated date).

To eliminate Views itself as the culprit, I tried the same experiment, on yet another freshly-created site, with the Views Bulk Operations module enabled, but not used in the view. I was not able to reproduce repeated NIDs in the first 10 pages, nor a randomly selected set of other pages (although admittedly, I did not check all 240 pages in the view).

krystalcode’s picture

Adding here that I see the same with really low number of entities where only 1 or 2 of 3 items selected are processed. Therefore it is not an issue of processing a large number of items, and it is not a memory issue or environment issue - can be reproduced in different environments. As described, confirmation page displays correctly 3 items, but only 1 or 2 items are passed to the executeMultiple method.

krystalcode’s picture

In my case I narrowed down the problem to the views query in the populateQueue on the action processor return less results when called by the batch operation. Some of the selected entities are therefore not in the view results and they are filtered out. When I disable batch, the results are the correct. I commented out the code in that method that hooks alter hooks to eliminate the possibility that some other module would be altering the queue, same results.

It is reproducible for me and the same entity in a set of 3 is skipped every time (not sure if it is the entity that matters or its position in the list). When I select that entity individually it is processed correctly.

I'm on 3.13 so some code might have changed, but the problems looks the same.

I don't know the architecture of the module well but I'm wondering, why are we making a database query again after the results have been selected? That opens a can of worms since there could be many uncontrolled reasons why the results could be different. I have selected the entities already and confirmed. Would it not be better to directly process the selected entities without filtering via the views results?

graber’s picture

Version: 4.0.x-dev » 4.1.x-dev
Issue tags: +Needs tests

I think there's one way we can push this forward: please add an automated test that fails. Once we'll have that it'll be reproducible and I can help.

Bumping to 4.1.x as I don't think this has been solved in the meanwhile.

krystalcode’s picture

Ok thanks.

Meanwhile can I get a response to my question though as I want to understand how this works.

I don't know the architecture of the module well but I'm wondering, why are we making a database query again after the results have been selected? That opens a can of worms since there could be many uncontrolled reasons why the results could be different. I have selected the entities already and confirmed. Would it not be better to directly process the selected entities without filtering via the views results?

rossidrup’s picture

it happens to me too, all the time, 3-4 percent of items stay unprocessed...so I have to select the failed ones and do it again

super_romeo’s picture

#14 same thing.
Interesting that occurs only when there are a few pages.
When there is only one page all is OK.

rossidrup’s picture

i cant use it on 1500 items....not possible i need to devide it in pages

graber’s picture

Did anything change in 4.1.3? There was a bug causing not all items to be processed when there are translations.
#3210957: VBO is skipping translated entities during batch process

rossidrup’s picture

yes I saw new release vir VBO...havent tried it yet...I will let you know

liquidcms’s picture

I last commented on this in Jan/22. Tried simple case today (4.1.2):

Std admin content page:
- filter by content type and language: gives me 92 items across 2 pages
- pick first page (50)
- select core action to Publish
- only runs on 28 items -> no errors.

Upgraded just now to 4.1.4. Rearan same test. Works as expected. Woo hoo!!

graber’s picture

Thank you for checking this. Leaving the issue open as this definitely needs automated tests for multi-language content processing so this’ll never happen again.

grndlvl’s picture

We are still experiencing the issue. It seems the same page results retrieved from ViewsBulkOperationsActionProcessor::getPageList() is duplicated causing it to omit some items.

Simplified example:
Page 9 has: 1, 2, 3, 4, 5, 6, 7
Page 13 will have: 3, 5, 6, 7, 8, 9, 10

I have debugged the page/offset/limit and sorting of the view and everything appears to be in order (with the exception of the return result set 0_0). This is very odd I am not sure why this might be happening yet.

Tried versions 4.0.0, 4.1.4, & 4.2.1.

jayelless’s picture

I have been experiencing what I believe is this same problem, which manifested itself as an error message:
Error: Typed property Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase::$view must not be accessed before initialization in Drupal\vbo_export\Plugin\Action\VboExportBase->setHeader()
that was generated when the code in ViewsBulkOperationsActionProcessor::populateQueue() executed a "return 0;" command prior to the end of the function (because it could not find any base field values), leaving the definition of the current view uninitialised in the plugin.

The basic cause of this is that the batch processing is expected to run for more than one iteration, but the unsorted nature of the results has allowed the results returned from query run in the batch processing initialisation to be less than a full batch as a consequence of the unspecified order of query results.

The problem was solved for me by adding a additional sort argument using the base entity id, which forced a fixed order every time the query was run.

jayelless’s picture

As an additional comment. The problem did not exist with views_bulk_operations 8.x-3.4.

graber’s picture

Interesting.. I remember that ID sort was added sometime in the past but not 100% sure. Maybe it was accidentally removed at some point? Worth checking code in ViewsBulkOperationsActionProcessor from 8.x-3.4.

EDIT: No. Solution from #3020922: Overcome nondeterministic behaviour in mysql ORDER BY is in place as well as the missing translations fix from #3210957: VBO is skipping translated entities during batch process. There's always an ID sort (or it seems so). I'd start from checking if any case exists when that ID sort from ViewsBulkOperationsActionProcessor::getPageList() is not applied if anyone wants to investigate on this.

It'll be easier for those that can actually reproduce the issue on their environments as I can't.

grota’s picture

I think this is the way to reproduce it:
- create a view using a table style plugin.
- add some fields: at least a vbo field, configured to use batch, and a date field, used for the next point.
- configure the table style plugin to use a date field as default sorting option.

This should be enough.

The problem is in ViewsBulkOperationsActionProcessor::getPageList.
The workaround from 3020922 is nullified when it calls $this->view->build(); since it calls $this->style_plugin->buildSortPost() which in our case is in Drupal\views\Plugin\views\style\Table::buildSortPost() which in turn ends up calling Drupal\views\Plugin\views\field\EntityField->clickSort() which sets the order by statement based on the table's default sorting field.

I don't know what a proper fix should be, but I hope the previous description can be useful to find one.

I have put the following workaround in place based on hook_views_query_alter:
As a note, I also tried an approach based on hook_views_pre_execute without success (probably because the query was already built).

use Drupal\views\Plugin\views\query\Sql;

/**
 * Whether the view is configured in batch mode.
 */
function xxx_vbo_view_is_vbo_configured_in_batch_mode(ViewExecutable $view) : bool {
  $fieldHandlers = $view->getHandlers('field');
  foreach ($fieldHandlers as $fieldHandler) {
    if (($fieldHandler['plugin_id'] ?? NULL) === 'views_bulk_operations_bulk_form') {
      return $fieldHandler['batch'] ?? FALSE;
    }
  }
  return FALSE;
}

/**
 * Get base field alias for a view.
 *
 * Algorithm copied from
 * \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor::populateQueue.
 */
function xxx_get_view_base_field_alias(ViewExecutable $view) : string {
  $base_field = $view->storage->get('base_field');
  if (isset($view->query->fields[$base_field])) {
    if (!empty($view->query->fields[$base_field]['table'])) {
      $base_field_alias = $view->query->fields[$base_field]['table'] . '.' . $view->query->fields[$base_field]['alias'];
    }
    else {
      $base_field_alias = $view->query->fields[$base_field]['alias'];
    }
  }
  else {
    $base_field_alias = $base_field;
  }
  return $base_field_alias;
}

/**
 * Implements hook_views_query_alter().
 */
function xxx_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  // Operate only on views currently run by VBO.
  if (!isset($view->views_bulk_operations_processor_built)) {
    return;
  }
  // Operate only on VBOs that are configured in batch mode.
  if (!xxx_vbo_view_is_vbo_configured_in_batch_mode($view)) {
    return;
  }
  if (!$query instanceof Sql) {
    return;
  }
  $base_field_alias = xxx_get_view_base_field_alias($view);
  $query->orderby = [
    [
      'field' => $base_field_alias,
      'direction' => 'ASC',
    ],
  ];
}
graber’s picture

Hmm, can you try setting style plugin to default just before $this->view->build(); in the action processor?
$this->view->setHandler($this->bulkFormData['display_id'], 'style', 'default', []); (not sure about the last argument..).

graber’s picture

ok, more like

    $this->view->style_plugin = Views::pluginManager('style')->createInstance('default');
    $this->view->style_plugin->init($this->view, $this->view->getDisplay());
    $this->view->build();

Please try.

grota’s picture

Hi @Graber, sorry for the delay in answering. My proposed fix above had to be reverted because it actually introduced other regressions (incorrect elements on which ultimately the action is executed on) while selecting items across pages.
As soon as we'll be able to have another go at fixing the problem I'll let you know.

grota’s picture

Version: 4.1.x-dev » 4.2.x-dev
Status: Active » Needs review

Hi @Graber,
I saw that you added those 2 lines in 22d6240fd295ec59bb758e8279046507fbbc528b. I tested them and they fixed the issue for me.
I created this MR to add a test.

As far as I know there are no other scenarios right now where the set of selected rows is incorrect, so for me this issue can be closed.

  • Graber committed 49535d55 on 4.2.x authored by grota
    Issue #3253404 by mparker17: VBO 4 doesn't process every selected node
    
graber’s picture

Status: Needs review » Fixed

TBH.. I accidentally added those lines in that commit, that should've been tested.. Oh well, I'm glad it worked and thank you for the test coverage!

Status: Fixed » Closed (fixed)

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