diff --git a/core/includes/update.inc b/core/includes/update.inc
index 5b045f6..df5ca00 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -13,6 +13,8 @@
 use Drupal\Core\Config\ConfigException;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Batch\BatchManager;
+use Drupal\Core\Batch\Batch;
 
 /**
  * Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
@@ -573,20 +575,22 @@ function update_module_enable(array $modules) {
  * and set the #finished property to the percentage completed that it is, as a
  * fraction of 1.
  *
- * @param $module
- *   The module whose update will be run.
- * @param $number
- *   The update number to run.
- * @param $dependency_map
- *   An array whose keys are the names of all update functions that will be
- *   performed during this batch process, and whose values are arrays of other
- *   update functions that each one depends on.
+ * @param $data
+ *   Array containing operation specific data:
+ *   - The module whose update will be run.
+ *   - The update number to run.
+ *   - The dependency map.
+ *     An array whose keys are the names of all update functions that will be
+ *     performed during this batch process, and whose values are arrays of
+ *     other update functions that each one depends on.
  * @param $context
- *   The batch context array.
+ *   The current batch context data.
  *
  * @see update_resolve_dependencies()
  */
-function update_do_one($module, $number, $dependency_map, &$context) {
+function update_do_one($data, &$context) {
+  list($module, $number, $dependency_map) = $data;
+
   $function = $module . '_update_' . $number;
 
   // If this update was aborted in a previous step, or has a dependency that
@@ -614,11 +618,6 @@ function update_do_one($module, $number, $dependency_map, &$context) {
     }
   }
 
-  if (isset($context['sandbox']['#finished'])) {
-    $context['finished'] = $context['sandbox']['#finished'];
-    unset($context['sandbox']['#finished']);
-  }
-
   if (!isset($context['results'][$module])) {
     $context['results'][$module] = array();
   }
@@ -633,11 +632,13 @@ function update_do_one($module, $number, $dependency_map, &$context) {
   }
 
   // Record the schema update if it was completed successfully.
-  if ($context['finished'] == 1 && empty($ret['#abort'])) {
+  if (empty($ret['#abort'])) {
     drupal_set_installed_schema_version($module, $number);
   }
 
+  /*
   $context['message'] = 'Updating ' . check_plain($module) . ' module';
+  */
 }
 
 /**
@@ -691,7 +692,15 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
     $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
   }
 
-  $operations = array();
+  // Initialize batch.
+  $context = array('finished' => array(), 'sandbox' => array(), 'results' => array());
+  $batch = new Batch('Updating', $context, array(
+    'file' => 'core/includes/update.inc',
+    'redirect' => $redirect,
+    'redirect_options' => array('external' => TRUE),
+    'finished' => 'update_finished',
+  ), new BatchManager());
+
   foreach ($updates as $update) {
     if ($update['allowed']) {
       // Set the installed version of each module so updates will start at the
@@ -703,10 +712,11 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
       }
       // Add this update function to the batch.
       $function = $update['module'] . '_update_' . $update['number'];
-      $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
+      $batch->addOperation('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
     }
   }
-  $batch['operations'] = $operations;
+
+  /*
   $batch += array(
     'title' => 'Updating',
     'init_message' => 'Starting updates',
@@ -716,6 +726,8 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
   );
   batch_set($batch);
   batch_process($redirect, $url, $redirect_callback);
+  */
+
 }
 
 /**
@@ -726,22 +738,20 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
  * page in update.php). Additionally, if the site was off-line, now that the
  * update process is completed, the site is set back online.
  *
- * @param $success
- *   Indicate that the batch API tasks were all completed successfully.
- * @param $results
- *   An array of all the results that were updated in update_do_one().
- * @param $operations
- *   A list of all the operations that had not been completed by the batch API.
+ * @param $data
+ *   Final arbitrary context of data.
+ * @param $queue_name
+ *   Name of the queue containing all the operations that had not been
+ *   completed by the batch API.
  *
  * @see update_batch()
  */
-function update_finished($success, $results, $operations) {
+function update_finished($data, $queue_name) {
   // Clear the caches in case the data has been updated.
   drupal_flush_all_caches();
 
-  $_SESSION['update_results'] = $results;
-  $_SESSION['update_success'] = $success;
-  $_SESSION['updates_remaining'] = $operations;
+  $_SESSION['update_results'] = $data['results'];
+  $_SESSION['update_remaining'] = $queue_name;
 
   // Now that the update is done, we can put the site back online if it was
   // previously in maintenance mode.
diff --git a/core/lib/Drupal/Core/Batch/Batch.php b/core/lib/Drupal/Core/Batch/Batch.php
new file mode 100644
index 0000000..7c3ba96
--- /dev/null
+++ b/core/lib/Drupal/Core/Batch/Batch.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\Core\Batch;
+
+/**
+ * Batch class, wrapping BatchManager functionality for easier batch creation.
+ */
+class Batch {
+
+  /**
+   * @var BatchManager
+   */
+  protected $batchManager;
+
+  /**
+   * @var ReliableQueueInterface
+   */
+  protected $queue;
+
+  /**
+   * Constructor.
+   *
+   * @param string $description
+   *   Human readable name for this batch (optional).
+   * @param mixed $data
+   *   Arbitrary context for the batch. This data will be passed to each
+   *   operation. It should be used for data necessary accross multiple
+   *   operations and to pass results between different operations.
+   *   The batch system itself is not doing anything with it.
+   * @param array $options
+   *   Assocciative array of batch options to set.
+   *   - file: Path to the file containing the definitions of the 'operations'
+   *     and 'finished' functions, for instance if they don't reside in the
+   *     main .module file. The path should be relative to base_path(), and
+   *     thus should be built using drupal_get_path().
+   *   - finished: A function to call when the batch was finished.
+   *     This callback will get the following parameters passed:
+   *     - Final arbitrary context of data.
+   *     - Name of the operation queue containing all the operations that had
+   *       not been completed by the batch API (ReliableQueueInterface).
+   *       Each queue item consists of an assocciative array:
+   *       - callback: The operation's function callback.
+   *       - data: The operation's data.
+   *   - redirect: Path to redirect to after the batch was finished.
+   *   - redirect_options: Options to pass to url() with the redirect path.
+   * @param BatchManager $batch_manager
+   *   The BatchManager object to make use of.
+   */
+  public function __construct($description = NULL, $data = NULL, $options = NULL, BatchManager $batch_manager = NULL) {
+    if ($batch_manager) {
+      $this->batchManager = $batch_manager;
+    }
+    else {
+      $this->batchManager = drupal_container()->get('batch.manager');
+    }
+    $this->queue = $this->batchManager->create($description, $data, $options);
+  }
+
+  /**
+   * Adds an operation to the batch queue.
+   *
+   * @param string $callback
+   *   The callback handling the data.
+   * @param mixed $data
+   *   The data for this item.
+   */
+  public function addOperation($callback, $data) {
+    $this->batchManager->createItem($this->queue, $data, $callback);
+  }
+
+  /**
+   * Adds multiple operations of the same callback to the batch queue.
+   *
+   * @param string $callback
+   *   The callback handling the data.
+   * @param array $data
+   *   An array if mixed data to pass to the operations.
+   * @param int $count
+   *   Count of array elements from $data to pass to each operation.
+   */
+  public function addOperations($callback, $data, $count) {
+    $overall_count = count($data);
+    for ($i= 0; $i < $overall_count; $i += $count) {
+      $this->addOperation($callback, array_slice($data, $i, $count));
+    }
+  }
+
+  /**
+   * Starts the batch processing.
+   *
+   * Currently just redirects to the users batch page.
+   */
+  public function start() {
+    global $user;
+    drupal_goto('user/' . $user->uid . '/batch');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Batch/BatchManager.php b/core/lib/Drupal/Core/Batch/BatchManager.php
new file mode 100644
index 0000000..e7e6852
--- /dev/null
+++ b/core/lib/Drupal/Core/Batch/BatchManager.php
@@ -0,0 +1,290 @@
+<?php
+
+namespace Drupal\Core\Batch;
+
+use Drupal\Core\KeyValueStore\KeyValueFactory;
+use Drupal\Core\Queue\ReliableQueueInterface;
+use Drupal\Component\Uuid\Uuid;
+
+/**
+ * Manages batch queues.
+ */
+class BatchManager {
+
+  /**
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactory
+   */
+  protected $keyValueFactory;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactory $key_value_factory
+   */
+  public function __construct(KeyValueFactory $key_value_factory = NULL) {
+    if ($key_value_factory) {
+      $this->keyValueFactory = $key_value_factory;
+    }
+    else {
+      $this->keyValueFactory = drupal_container()->get('keyvalue');
+    }
+  }
+
+  /**
+   * Returns the passed uid if given or the current users uid.
+   *
+   * @param $uid
+   *   User id to check.
+   * @return int
+   *   The user id to make use of.
+   */
+  protected function getUid($uid) {
+    return $uid ?: $GLOBALS['user']->uid;
+  }
+
+  /**
+   * Creates a batch and its queue.
+   *
+   * @param string $description
+   *   Human readable name for this batch (optional).
+   * @param mixed $data
+   *   Arbitrary context for the batch. This data will be passed to each
+   *   operation. It should be used for data necessary accross multiple
+   *   operations and to pass results between different operations.
+   *   The batch system itself is not doing anything with it.
+   * @param array $options
+   *   Assocciative array of batch options to set.
+   *   - file: Path to the file containing the definitions of the 'operations'
+   *     and 'finished' functions, for instance if they don't reside in the
+   *     main .module file. The path should be relative to base_path(), and
+   *     thus should be built using drupal_get_path().
+   *     - Name of the operation queue containing all the operations that had
+   *       not been completed by the batch API.
+   *       Each queue item consists of an assocciative array:
+   *       - callback: The operation's function callback.
+   *       - data: The operation's data.
+   *   - redirect: Path to redirect to after the batch was finished.
+   *   - redirect_options: Options to pass to url() with the redirect path.
+   * @param BatchManager $batch_manager
+   *   The BatchManager object to make use of.
+   * @param int $uid
+   *   The user this batch belongs to (optional). Defaults to the current user.
+   * @return \Drupal\Core\Queue\QueueInterface
+   *   The queue object for this batch.
+   */
+  public function create($description = NULL, $data = NULL, $options = NULL, $uid = NULL) {
+    $uid = $this->getUid($uid);
+    $uuid = new Uuid;
+    $queue_name = $uuid->generate();
+    $queue = queue($queue_name, TRUE);
+    $queue->createQueue();
+    // To allow users to find their queues, store the queue name in a Key-value
+    // collection based on the uid.
+    $this->keyValueFactory->get("queue.$uid")->set($description ?: $queue_name, $queue_name);
+    if ($data) {
+      $this->keyValueFactory->get("queue.data.$uid")->set($queue_name, $data);
+    }
+    if ($options) {
+      $this->keyValueFactory->get("queue.options.$uid")->set($queue_name, $options);
+    }
+    // For background runners, add queue name to the batch queue.
+    $batch = queue('batch', TRUE);
+    $batch->createQueue();
+    $batch->createItem($queue_name);
+    return $queue;
+  }
+
+  /**
+   * Adds an operation item to the batch queue.
+   *
+   * @param $queue
+   *   The queue object returned by create().
+   * @param $data
+   *   The data for this item.
+   * @param $callback
+   *   The callback handling the data.
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see \Drupal\Core\Queue\ReliableQueueInterface::createItem().
+   */
+  public function createItem(ReliableQueueInterface $queue, $data, $callback) {
+    $item = array(
+      'data' => $data,
+      'callback' => $callback,
+    );
+    return $queue->createItem($item);
+  }
+
+  /**
+   * Returns the current items in the queue, cleans up if necessary.
+   *
+   * @param $uid
+   *   The user this batch belongs to.
+   * @param $queue_name
+   *   The name of the queue.
+   *
+   * @return int|bool
+   *   The number of items on success, FALSE otherwise.
+   */
+  public function numberOfItems($uid, $queue_name) {
+    $n = queue($queue_name)->numberOfItems();
+    if (!$n) {
+      $this->keyValueFactory->get("queue.$uid")->delete($queue_name);
+      $this->keyValueFactory->get("queue.data.$uid")->delete($queue_name);
+    }
+    return $n;
+  }
+
+  /**
+   * Returns all available batches for the given user.
+   *
+   * @param int $uid
+   *   The user this batch belongs to (optional). Defaults to the current user.
+   *
+   * @return array
+   *   The keys are the either the UUIDs of the batches or their description,
+   *   and the values are always the queue UUID (queue name).
+   */
+  public function getBatches($uid = NULL) {
+    $uid = $this->getUid($uid);
+    return $this->keyValueFactory->get("queue.$uid")->getAll();
+  }
+
+  /**
+   * Retuns the current context data of the batch.
+   *
+   * @param string $queue_name
+   *   The queues name.
+   * @param int $uid
+   *   The user this batch belongs to.
+   *
+   * @return mixed
+   *   On success the arbitrary context data.
+   */
+  public function getData($queue_name, $uid = NULL) {
+    $uid = $this->getUid($uid);
+    return $this->keyValueFactory->get("queue.data.$uid")->get($queue_name);
+  }
+
+  /**
+   * Updates the current context data of the batch.
+   *
+   * @param string $queue_name
+   *   The queues name.
+   * @param int $uid
+   *   The user this batch belongs to.
+   * @param mixed $data
+   *   New arbitrary context data.
+   */
+  public function setData($queue_name, $data, $uid = NULL) {
+    $uid = $this->getUid($uid);
+    $this->keyValueFactory->get("queue.data.$uid")->set($queue_name, $data);
+  }
+
+  /**
+   * Returns the current batch state.
+   *
+   * @param $uid
+   *   The user this batch belongs to.
+   *
+   * @return array
+   *   An associative array containing:
+   *   - The total count of processed operations.
+   *   - An associative array of batch queues:
+   *     The keys are the either the UUIDs of the batches or their description,
+   *     and the values are always the queue UUID (queue name).
+   */
+  public function getTotals($uid) {
+    $batches = $this->getBatches($uid);
+    $total = 0;
+    $items = array();
+    foreach ($batches as $description => $queue_name) {
+      $total += queue($queue_name)->numberOfItems();
+      $items[] = $description;
+    }
+    return array($total, $items);
+  }
+
+  /**
+   * Executes the next operation in queue.
+   *
+   * @param $uid
+   *   The user this batch belongs to.
+   */
+  public function runOne($uid) {
+    $batches = $this->getBatches($uid);
+    $queue_name = reset($batches);
+    $queue = queue($queue_name);
+    // Check whether there's a file to include.
+    $options = $this->keyValueFactory->get("queue.options.$uid")->get($queue_name);
+    if ($options && isset($options['file']) && is_file($options['file'])) {
+      include_once DRUPAL_ROOT . '/' . $options['file'];
+    }
+    // Execute the operation.
+    if ($item = $queue->claimItem()) {
+      if (function_exists($item->data['callback'])) {
+        // Get the batch's data.
+        $data = $this->getData($queue_name, $uid);
+        // Call the user function and pass it callback and batch specific data.
+        $item->data['callback']($item->data['data'], $data);
+        // Update the batch's data.
+        $this->setData($queue_name, $data, $uid);
+        $queue->deleteItem($item);
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Finishes the batch.
+   *
+   * This executes the finished-callback when set in the batch options,
+   * cleans up the batch data and redirects to the given target.
+   *
+   * @param $uid
+   *   The user this batch belongs to.
+   */
+  public function finish($uid) {
+    $batches = $this->getBatches($uid);
+    $queue_name = reset($batches);
+    $data = $this->getData($queue_name, $uid);
+    $options = $this->keyValueFactory->get("queue.options.$uid")->get($queue_name);
+    // Clean up.
+    $this->cleanup($uid);
+    // Execute finished callback.
+    if (isset($options['finished'])) {
+      if ($options && isset($options['file']) && is_file($options['file'])) {
+        include_once DRUPAL_ROOT . '/' . $options['file'];
+      }
+      if (function_exists($options['finished'])) {
+        $options['finished']($data, $queue_name);
+      }
+    }
+    // And redirect.
+    if (isset($options['redirect'])) {
+      if (isset($options['redirect_options'])) {
+        drupal_goto($options['redirect'], $options['redirect_options']);
+      }
+      drupal_goto($options['redirect']);
+    }
+  }
+
+  /**
+   * Cleans up the batch data from key-value storage.
+   *
+   * @param $uid
+   *   The user this batch belongs to.
+   */
+  public function cleanup($uid) {
+    $batches = $this->getBatches($uid);
+    foreach ($batches as $queue_name => $description) {
+      if (!queue($queue_name)->numberOfItems()) {
+        $this->keyValueFactory->get("queue.$uid")->delete($queue_name);
+        $this->keyValueFactory->get("queue.data.$uid")->delete($queue_name);
+        $this->keyValueFactory->get("queue.options.$uid")->delete($queue_name);
+      }
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Batch/Controllers.php b/core/lib/Drupal/Core/Batch/Controllers.php
new file mode 100644
index 0000000..ccba1d7
--- /dev/null
+++ b/core/lib/Drupal/Core/Batch/Controllers.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core\Batch;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * This is a temporary solution for providing page callbacks for the batch api.
+ */
+class Controllers {
+
+  /**
+   * @var BatchManager
+   */
+  protected $batchManager;
+
+  /**
+   * @param BatchManager $batch_manager
+   */
+  public function __construct(BatchManager $batch_manager) {
+    $this->batchManager = $batch_manager;
+  }
+
+  /**
+   * The batch start page.
+   *
+   * @param int $uid
+   *   User id to load batches for.
+   *
+   * @return array
+   *   Renderable array of the progress page.
+   */
+  public function startPage($uid) {
+    list($total, $items) = $this->batchManager->getTotals($uid);
+    $js_setting = array(
+      'batch' => array(
+        'errorMessage' => 'None',
+        'initMessage' => t('Running queues'),
+        'uri' => url(current_path(), array(
+          'query' => array('total' => $total) + drupal_get_query_parameters(),
+          'absolute' => TRUE,
+          // @todo we need to include the scriptname for update.php,
+          // but we actually don't want it for index.php.
+          'script' => basename($_SERVER['SCRIPT_NAME']) . '/',
+        )),
+      ),
+    );
+    drupal_add_js($js_setting, 'setting');
+    drupal_add_library('system', 'drupal.batch');
+    $build = array(
+      '#theme' => 'progress_bar',
+      '#percent' => 0,
+      '#message' => theme('item_list', array('items' => $items)),
+    );
+    return $build;
+  }
+
+  /**
+   * Batch callback for the actual processing.
+   *
+   * @param int $uid
+   *   User id to load batches for.
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   The JSON response for the progressbar javascript.
+   */
+  public function doPage($uid) {
+    if (!is_numeric($_GET['total'])) {
+      throw new AccessDeniedHttpException;
+    }
+    $total = intval($_GET['total']);
+    $percentage = 100;
+    if ($this->batchManager->runOne($uid)) {
+      list($current_total, $items) = $this->batchManager->getTotals($uid);
+      $percentage -= 100 * $current_total / $total;
+    }
+    return new JsonResponse(array(
+      'status' => TRUE,
+      'percentage' => (string) round($percentage, 2),
+      'message' => theme('item_list', array('items' => $items)),
+    ));
+  }
+
+  /**
+   * Batch finished page callback for clean up and redirection.
+   *
+   * @param int $uid
+   *   User id to load batches for.
+   *
+   * @return array
+   *   Renderable array.
+   */
+  public function finishedPage($uid) {
+    $this->batchManager->finish($uid);
+    drupal_set_message(t('Finished executing all operations.'));
+    return array();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 568efec..d799f89 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -117,6 +117,12 @@ public function build(ContainerBuilder $container) {
     $container->register('entity.query', 'Drupal\Core\Entity\Query\QueryFactory')
       ->addArgument(new Reference('service_container'));
 
+    // Add the batch manager.
+    $container->register('batch.manager', 'Drupal\Core\Batch\BatchManager')
+      ->addArgument(new Reference('keyvalue'));
+    $container->register('batch.controllers', 'Drupal\Core\Batch\Controllers')
+      ->addArgument(new Reference('batch.manager'));
+
     $container->register('router.dumper', 'Drupal\Core\Routing\MatcherDumper')
       ->addArgument(new Reference('database'));
     $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index e171b75..4dd58bc 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Batch\Batch;
 
 /**
  * Page callback: Form constructor for the permission rebuild confirmation form.
@@ -277,9 +278,22 @@ function node_filter_form_submit($form, &$form_state) {
  *   Array of key/value pairs with node field names and the value to update that
  *   field to.
  */
-function node_mass_update($nodes, $updates) {
+function node_mass_update($nids, $updates) {
   // We use batch processing to prevent timeout when updating a large number
   // of nodes.
+  if ($nids) {
+    $n = count($nids);
+    $batch = new Batch('Node mass update', $updates, array(
+      // The operations do not live in the .module file, so we need to
+      // tell the batch engine which file to load before calling them.
+      'file' => drupal_get_path('module', 'node') . '/node.admin.inc',
+      'finished' => '_node_mass_update_batch_finished',
+      'redirect' => current_path(),
+    ));
+    $batch->addOperations('_node_mass_update_batch_process', $nids, 10);
+    $batch->start();
+  }
+  /*
   if (count($nodes) > 10) {
     $batch = array(
       'operations' => array(
@@ -296,12 +310,25 @@ function node_mass_update($nodes, $updates) {
       'file' => drupal_get_path('module', 'node') . '/node.admin.inc',
     );
     batch_set($batch);
-  }
-  else {
-    foreach ($nodes as $nid) {
-      _node_mass_update_helper($nid, $updates);
-    }
-    drupal_set_message(t('The update has been performed.'));
+  } */
+}
+
+/**
+ * Executes a batch operation for node_mass_update().
+ *
+ * @param array $nodes
+ *   An array of node IDs.
+ * @param array $updates
+ *   Associative array of updates.
+ * @param array $context
+ *   An array of contextual key/values.
+ */
+function _node_mass_update_batch_process($nids, $updates) {
+  $nodes = node_load_multiple($nids);
+  while ($nodes) {
+    // For each nid, load the node, reset the values, and save it.
+    $node = array_shift($nodes);
+    _node_mass_update_helper($node, $updates);
   }
 }
 
@@ -318,8 +345,7 @@ function node_mass_update($nodes, $updates) {
  *
  * @see node_mass_update()
  */
-function _node_mass_update_helper($nid, $updates) {
-  $node = node_load($nid, TRUE);
+function _node_mass_update_helper($node, $updates) {
   // For efficiency manually save the original node before applying any changes.
   $node->original = clone $node;
   foreach ($updates as $name => $value) {
@@ -330,59 +356,21 @@ function _node_mass_update_helper($nid, $updates) {
 }
 
 /**
- * Executes a batch operation for node_mass_update().
- *
- * @param array $nodes
- *   An array of node IDs.
- * @param array $updates
- *   Associative array of updates.
- * @param array $context
- *   An array of contextual key/values.
- */
-function _node_mass_update_batch_process($nodes, $updates, &$context) {
-  if (!isset($context['sandbox']['progress'])) {
-    $context['sandbox']['progress'] = 0;
-    $context['sandbox']['max'] = count($nodes);
-    $context['sandbox']['nodes'] = $nodes;
-  }
-
-  // Process nodes by groups of 5.
-  $count = min(5, count($context['sandbox']['nodes']));
-  for ($i = 1; $i <= $count; $i++) {
-    // For each nid, load the node, reset the values, and save it.
-    $nid = array_shift($context['sandbox']['nodes']);
-    $node = _node_mass_update_helper($nid, $updates);
-
-    // Store result for post-processing in the finished callback.
-    $context['results'][] = l($node->label(), 'node/' . $node->nid);
-
-    // Update our progress information.
-    $context['sandbox']['progress']++;
-  }
-
-  // Inform the batch engine that we are not finished,
-  // and provide an estimation of the completion level we reached.
-  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
-    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
-  }
-}
-
-/**
  * Reports the 'finished' status of batch operation for node_mass_update().
  *
- * @param bool $success
- *   A boolean indicating whether the batch mass update operation successfully
- *   concluded.
- * @param int $results
- *   The number of nodes updated via the batch mode process.
- * @param array $operations
- *   An array of function calls (not used in this function).
+ * @param $data
+ *   Final arbitrary context of data.
+ * @param $queue_name
+ *   Name of the queue containing all the operations that had not been
+ *   completed by the batch API.
  */
-function _node_mass_update_batch_finished($success, $results, $operations) {
-  if ($success) {
+function _node_mass_update_batch_finished($data, $queue_name) {
+  $queue = queue($queue_name);
+  if ($queue && $queue->numberOfItems() == 0) {
     drupal_set_message(t('The update has been performed.'));
   }
   else {
+    $results = $data['results'];
     drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
     $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
     $message .= theme('item_list', array('items' => $results));
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 08d7810..1dfbf90 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -19,6 +19,7 @@
 use Drupal\node\Plugin\Core\Entity\Node;
 use Drupal\file\Plugin\Core\Entity\File;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Batch\Batch;
 
 /**
  * Denotes that the node is not published.
@@ -3214,6 +3215,7 @@ function node_access_rebuild($batch_mode = FALSE) {
   // Only recalculate if the site is using a node_access module.
   if (count(module_implements('node_grants'))) {
     if ($batch_mode) {
+      /*
       $batch = array(
         'title' => t('Rebuilding content access permissions'),
         'operations' => array(
@@ -3222,6 +3224,16 @@ function node_access_rebuild($batch_mode = FALSE) {
         'finished' => '_node_access_rebuild_batch_finished'
       );
       batch_set($batch);
+      */
+      $nids = db_query("SELECT nid FROM {node} ORDER BY nid DESC")->fetchCol();
+      if ($nids) {
+        $batch = new Batch('Rebuilding content access permissions', NULL, array(
+          'finished' => '_node_access_rebuild_batch_finished',
+          'redirect' => current_path(),
+        ));
+        $batch->addOperations('_node_access_rebuild_batch_finished', $nids, 20);
+        $batch->start();
+      }
     }
     else {
       // Try to allocate enough time to rebuild node grants
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index ac3e225..5ba2943 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1320,6 +1320,16 @@ function user_menu() {
     'type' => MENU_LOCAL_TASK,
     'file' => 'user.pages.inc',
   );
+  $items['user/%user/batch'] = array(
+    'title' => 'Batch',
+    'page callback' => 'user_batch_page',
+    'page arguments' => array(1),
+    'access callback' => 'user_view_access',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.pages.inc',
+  );
+
   return $items;
 }
 
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 20af863..69df39c 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -6,6 +6,8 @@
  */
 
 use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Drupal\user\Plugin\Core\Entity\User;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -423,3 +425,14 @@ function user_page() {
     return drupal_get_form('user_login_form');
   }
 }
+
+function user_batch_page(User $account) {
+  $batch_controllers = drupal_container()->get('batch.controllers');
+  $uid = $account->id();
+  $op = isset($_GET['op']) ? $_GET['op'] : 'start';
+  $method = $op . 'Page';
+  if (method_exists($batch_controllers, $method)) {
+    return $batch_controllers->$method($uid);
+  }
+  throw new NotFoundHttpException();
+}
diff --git a/core/update.php b/core/update.php
index 98296bb..c1607f2 100644
--- a/core/update.php
+++ b/core/update.php
@@ -17,6 +17,7 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\DependencyInjection\Reference;
+use Drupal\Core\Batch\BatchManager;
 
 // Change the directory to the Drupal root.
 chdir('..');
@@ -196,11 +197,13 @@ function update_results_page() {
     $log_message = ' All errors have been logged.';
   }
 
-  if ($_SESSION['update_success']) {
+  $queue = queue($_SESSION['update_remaining']);
+  if ($queue && $queue->numberOfItems() == 0) {
     $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily back to your <a href="' . base_path() . '">site</a>. Otherwise, you may need to update your database manually.' . $log_message . '</p>';
   }
   else {
-    list($module, $version) = array_pop(reset($_SESSION['updates_remaining']));
+    $item = $queue->claimItem();
+    list($module, $version) = $item['data'];
     $output = '<p class="error">The update process was aborted prematurely while running <strong>update #' . $version . ' in ' . $module . '.module</strong>.' . $log_message;
     if (module_exists('dblog')) {
       $output .= ' You may need to check the <code>watchdog</code> database table manually.';
@@ -258,7 +261,6 @@ function update_results_page() {
     }
   }
   unset($_SESSION['update_results']);
-  unset($_SESSION['update_success']);
 
   return $output;
 }
@@ -471,7 +473,7 @@ function update_check_requirements($skip_warnings = FALSE) {
 if (update_access_allowed()) {
 
   include_once DRUPAL_ROOT . '/core/includes/install.inc';
-  include_once DRUPAL_ROOT . '/core/includes/batch.inc';
+  // include_once DRUPAL_ROOT . '/core/includes/batch.inc';
   drupal_load_updates();
 
   update_fix_compatibility();
@@ -503,6 +505,7 @@ function update_check_requirements($skip_warnings = FALSE) {
         $batch_url = $base_root . drupal_current_script_url();
         $redirect_url = $base_root . drupal_current_script_url(array('op' => 'results'));
         update_batch($request->request->get('start'), $redirect_url, $batch_url);
+        drupal_goto($base_root . drupal_current_script_url(array('op' => 'start'), array('external' => TRUE)));
         break;
       }
 
@@ -517,7 +520,13 @@ function update_check_requirements($skip_warnings = FALSE) {
     // Regular batch ops : defer to batch processing API.
     default:
       update_task_list('run');
-      $output = _batch_page();
+      $batch_controllers = new \Drupal\Core\Batch\Controllers(new BatchManager());
+      $method = $op . 'Page';
+      if (method_exists($batch_controllers, $method)) {
+        $build = $batch_controllers->$method($user->uid);
+        $output = render($build);
+      }
+      // $output = _batch_page();
       break;
   }
 }
