diff --git a/core/modules/update/lib/Drupal/update/Controller/UpdateController.php b/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
index 6816466..6081361 100644
--- a/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
+++ b/core/modules/update/lib/Drupal/update/Controller/UpdateController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\update\UpdateManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -24,13 +25,24 @@ class UpdateController implements ContainerInjectionInterface {
   protected $moduleHandler;
 
   /**
+   * Update manager service.
+   *
+   * @var \Drupal\update\UpdateManager
+   */
+  protected $updateManager;
+
+  /**
    * Constructs update status data.
    *
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   Module Handler Service.
+   *
+   * @param \Drupal\update\UpdateManager $update_manager
+   *   Update Manager Service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler) {
+  public function __construct(ModuleHandlerInterface $module_handler, UpdateManager $update_manager) {
     $this->moduleHandler = $module_handler;
+    $this->updateManager = $update_manager;
   }
 
   /**
@@ -38,7 +50,8 @@ public function __construct(ModuleHandlerInterface $module_handler) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('module_handler')
+      $container->get('module_handler'),
+      $container->get('update.manager')
     );
   }
 
@@ -63,11 +76,21 @@ public function updateStatus() {
   }
 
   /**
-   * @todo Remove update_manual_status().
+   * Manually checks the update status without the use of cron.
    */
   public function updateStatusManually() {
-    module_load_include('fetch.inc', 'update');
-    return update_manual_status();
+    $this->updateManager->refreshUpdateData();
+    $batch = array(
+      'operations' => array(
+        array(array($this->updateManager, 'fetchDataBatch'), array()),
+      ),
+      'finished' => 'update_fetch_data_finished',
+      'title' => t('Checking available update data'),
+      'progress_message' => t('Trying to check available update data ...'),
+      'error_message' => t('Error checking available update data.'),
+    );
+    batch_set($batch);
+    return batch_process('admin/reports/updates');
   }
 
 }
diff --git a/core/modules/update/lib/Drupal/update/UpdateFetcher.php b/core/modules/update/lib/Drupal/update/UpdateFetcher.php
index 6893210..f1600a7 100644
--- a/core/modules/update/lib/Drupal/update/UpdateFetcher.php
+++ b/core/modules/update/lib/Drupal/update/UpdateFetcher.php
@@ -29,6 +29,13 @@ class UpdateFetcher {
   protected $fetchUrl;
 
   /**
+   * The update settings
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $updateSettings;
+
+  /**
    * The HTTP client to fetch the feed data with.
    *
    * @var \Guzzle\Http\ClientInterface
@@ -46,6 +53,7 @@ class UpdateFetcher {
   public function __construct(ConfigFactory $config_factory, ClientInterface $http_client) {
     $this->fetchUrl = $config_factory->get('update.settings')->get('fetch.url');
     $this->httpClient = $http_client;
+    $this->updateSettings = $config_factory->get('update.settings');
   }
 
   /**
@@ -89,9 +97,9 @@ public function fetchProjectData(array $project, $site_key = '') {
    * @return string
    *   The URL for fetching information about updates to the specified project.
    *
-   * @see update_fetch_data()
-   * @see _update_process_fetch_task()
-   * @see update_get_projects()
+   * @see \Drupal\update\UpdateProcessor::fetchData()
+   * @see \Drupal\update\UpdateProcessor::processFetchTask()
+   * @see \Drupal\update\UpdateManager::getProjects()
    */
   public function buildFetchUrl(array $project, $site_key = '') {
     $name = $project['name'];
@@ -130,8 +138,6 @@ public function buildFetchUrl(array $project, $site_key = '') {
    *   The base of the URL used for fetching available update data. This does
    *   not include the path elements to specify a particular project, version,
    *   site_key, etc.
-   *
-   * @see \Drupal\update\UpdateFetcher::getFetchBaseUrl()
    */
   public function getFetchBaseUrl($project) {
     if (isset($project['info']['project status url'])) {
diff --git a/core/modules/update/lib/Drupal/update/UpdateManager.php b/core/modules/update/lib/Drupal/update/UpdateManager.php
new file mode 100644
index 0000000..6129e76
--- /dev/null
+++ b/core/modules/update/lib/Drupal/update/UpdateManager.php
@@ -0,0 +1,298 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\update\UpdateManager.
+ */
+
+namespace Drupal\update;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Utility\ProjectInfo;
+
+/**
+ * Manages project update information.
+ */
+class UpdateManager {
+
+  /**
+   * The update settings
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $updateSettings;
+
+  /**
+   * Module Handler Service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Update Processor Service.
+   *
+   * @var \Drupal\update\UpdateProcessor
+   */
+  protected $updateProcessor;
+
+  /**
+   * An array of installed and enabled projects.
+   *
+   * @var array
+   */
+  protected $projects;
+
+  /**
+   * The key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $keyValueStore;
+
+  /**
+   * Update available releases key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $availableReleasesTempStore;
+
+  /**
+   * The translation service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translation;
+
+  /**
+   * Constructs a UpdateManager.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The Module Handler service
+   * @param \Drupal\update\UpdateProcessor $update_processor
+   *   The Update Processor service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The translation service.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory
+   *   The expirable key/value factory.
+   */
+  public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler, UpdateProcessor $update_processor, TranslationInterface $translation, KeyValueFactoryInterface $key_value_expirable_factory) {
+    $this->updateSettings = $config_factory->get('update.settings');
+    $this->moduleHandler = $module_handler;
+    $this->updateProcessor = $update_processor;
+    $this->translation = $translation;
+    $this->keyValueStore = $key_value_expirable_factory->get('update');
+    $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
+    $this->projects = array();
+  }
+
+  /**
+   * Clears out all the available update data and initiates re-fetching.
+   */
+  public function refreshUpdateData() {
+
+    // Since we're fetching new available update data, we want to clear
+    // of both the projects we care about, and the current update status of the
+    // site. We do *not* want to clear the cache of available releases just yet,
+    // since that data (even if it's stale) can be useful during
+    // update_get_projects(); for example, to modules that implement
+    // hook_system_info_alter() such as cvs_deploy.
+    $this->keyValueStore->delete('update_project_projects');
+    $this->keyValueStore->delete('update_project_data');
+
+    $projects = $this->getProjects();
+
+    // Now that we have the list of projects, we should also clear the available
+    // release data, since even if we fail to fetch new data, we need to clear
+    // out the stale data at this point.
+    $this->availableReleasesTempStore->deleteAll();
+
+    foreach ($projects as $project) {
+      $this->updateProcessor->createFetchTask($project);
+    }
+  }
+
+  /**
+   * Fetches an array of installed and enabled projects.
+   *
+   * This is only responsible for generating an array of projects (taking into
+   * account projects that include more than one module or theme). Other
+   * information like the specific version and install type (official release,
+   * dev snapshot, etc) is handled later in update_process_project_info() since
+   * that logic is only required when preparing the status report, not for
+   * fetching the available release data.
+   *
+   * This array is fairly expensive to construct, since it involves a lot of disk
+   * I/O, so we store the results. However, since this is not the data about
+   * available updates fetched from the network, it is acceptable to invalidate it
+   * somewhat quickly. If we keep this data for very long, site administrators are
+   * more likely to see incorrect results if they upgrade to a newer version of a
+   * module or theme but do not visit certain pages that automatically clear this
+   * data.
+   *
+   * @return
+   *   An associative array of currently enabled projects keyed by the
+   *   machine-readable project short name. Each project contains:
+   *   - name: The machine-readable project short name.
+   *   - info: An array with values from the main .info.yml file for this project.
+   *     - name: The human-readable name of the project.
+   *     - package: The package that the project is grouped under.
+   *     - version: The version of the project.
+   *     - project: The Drupal.org project name.
+   *     - datestamp: The date stamp of the project's main .info.yml file.
+   *     - _info_file_ctime: The maximum file change time for all of the .info.yml
+   *       files included in this project.
+   *   - datestamp: The date stamp when the project was released, if known.
+   *   - includes: An associative array containing all projects included with this
+   *     project, keyed by the machine-readable short name with the human-readable
+   *     name as value.
+   *   - project_type: The type of project. Allowed values are 'module' and
+   *     'theme'.
+   *   - project_status: This indicates if the project is enabled and will always
+   *     be TRUE, as the function only returns enabled projects.
+   *   - sub_themes: If the project is a theme it contains an associative array of
+   *     all sub-themes.
+   *   - base_themes: If the project is a theme it contains an associative array
+   *     of all base-themes.
+   *
+   * @see update_process_project_info()
+   * @see update_calculate_project_data()
+   * @see \Drupal\update\UpdateManager::projectStorage()
+   */
+  public function getProjects() {
+    if (empty($this->projects)) {
+      // Retrieve the projects from storage, if present.
+      $this->projects = $this->projectStorage('update_project_projects');
+      if (empty($this->projects)) {
+        // Still empty, so we have to rebuild.
+        $module_data = system_rebuild_module_data();
+        $theme_data = system_rebuild_theme_data();
+        $project_info = new ProjectInfo();
+        $project_info->processInfoList($this->projects, $module_data, 'module', TRUE);
+        $project_info->processInfoList($this->projects, $theme_data, 'theme', TRUE);
+        if ($this->updateSettings->get('check.disabled_extensions')) {
+          $project_info->processInfoList($this->projects, $module_data, 'module', FALSE);
+          $project_info->processInfoList($this->projects, $theme_data, 'theme', FALSE);
+        }
+        // Allow other modules to alter projects before fetching and comparing.
+        $this->moduleHandler->alter('update_projects', $this->projects);
+        // Store the site's project data for at most 1 hour.
+        $this->keyValueStore->setWithExpire('update_project_projects', $this->projects, 3600);
+      }
+    }
+    return $this->projects;
+  }
+
+  /**
+   * Retrieves update storage data or empties it.
+   *
+   * Two very expensive arrays computed by this module are the list of all
+   * installed modules and themes (and .info.yml data, project associations, etc), and
+   * the current status of the site relative to the currently available releases.
+   * These two arrays are stored and used whenever possible. The data is cleared
+   * whenever the administrator visits the status report, available updates
+   * report, or the module or theme administration pages, since we should always
+   * recompute the most current values on any of those pages.
+   *
+   * Note: while both of these arrays are expensive to compute (in terms of disk
+   * I/O and some fairly heavy CPU processing), neither of these is the actual
+   * data about available updates that we have to fetch over the network from
+   * updates.drupal.org. That information is stored in the
+   * 'update_available_releases' collection -- it needs to persist longer than 1
+   * hour and never get invalidated just by visiting a page on the site.
+   *
+   * @param $key
+   *   The key of data to return. Valid options are 'update_project_data' and
+   *   'update_project_projects'.
+   *
+   * @return
+   *   The stored value of the $projects array generated by
+   *   update_calculate_project_data() or update_get_projects(), or an empty array
+   *   when the storage is cleared.
+   */
+  public function projectStorage($key) {
+    $projects = array();
+
+    // On certain paths, we should clear the data and recompute the projects for
+    // update status of the site to avoid presenting stale information.
+    $paths = array(
+      'admin/modules',
+      'admin/modules/update',
+      'admin/appearance',
+      'admin/appearance/update',
+      'admin/reports',
+      'admin/reports/updates',
+      'admin/reports/updates/update',
+      'admin/reports/status',
+      'admin/reports/updates/check',
+    );
+    if (in_array(current_path(), $paths)) {
+      $this->keyValueStore->delete($key);
+    }
+    else {
+      $projects = $this->keyValueStore->get($key);
+    }
+    return $projects;
+  }
+
+  /**
+   * Batch callback: Processes a step in batch for fetching available update data.
+   *
+   * @param $context
+   *   Reference to an array used for Batch API storage.
+   */
+  public function fetchDataBatch(&$context) {
+    if (empty($context['sandbox']['max'])) {
+      $context['finished'] = 0;
+      $context['sandbox']['max'] = $this->updateProcessor->numberOfQueueItems();
+      $context['sandbox']['progress'] = 0;
+      $context['message'] = $this->t('Checking available update data ...');
+      $context['results']['updated'] = 0;
+      $context['results']['failures'] = 0;
+      $context['results']['processed'] = 0;
+    }
+
+    // Grab another item from the fetch queue.
+    for ($i = 0; $i < 5; $i++) {
+      if ($item = $this->updateProcessor->claimQueueItem()) {
+        if ($this->updateProcessor->processFetchTask($item->data)) {
+          $context['results']['updated']++;
+          $context['message'] = $this->t('Checked available update data for %title.', array('%title' => $item->data['info']['name']));
+        }
+        else {
+          $context['message'] = $this->t('Failed to check available update data for %title.', array('%title' => $item->data['info']['name']));
+          $context['results']['failures']++;
+        }
+        $context['sandbox']['progress']++;
+        $context['results']['processed']++;
+        $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+        $this->updateProcessor->deleteQueueItem($item);
+      }
+      else {
+        // If the queue is currently empty, we're done. It's possible that
+        // another thread might have added new fetch tasks while we were
+        // processing this batch. In that case, the usual 'finished' math could
+        // get confused, since we'd end up processing more tasks that we thought
+        // we had when we started and initialized 'max' with numberOfItems(). By
+        // forcing 'finished' to be exactly 1 here, we ensure that batch
+        // processing is terminated.
+        $context['finished'] = 1;
+        return;
+      }
+    }
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * See the t() documentation for details.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translation->translate($string, $args, $options);
+  }
+
+}
diff --git a/core/modules/update/lib/Drupal/update/UpdateProcessor.php b/core/modules/update/lib/Drupal/update/UpdateProcessor.php
new file mode 100644
index 0000000..0237fda
--- /dev/null
+++ b/core/modules/update/lib/Drupal/update/UpdateProcessor.php
@@ -0,0 +1,311 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\update\UpdateProcessor.
+ */
+
+namespace Drupal\update;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+use Drupal\Core\KeyValueStore\StateInterface;
+use Drupal\Core\PrivateKey;
+use Drupal\Core\Queue\QueueFactory;
+
+/**
+ * Process project update information.
+ */
+class UpdateProcessor {
+
+  /**
+   * The update settings
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $updateSettings;
+
+  /**
+   * The UpdateFetcher service.
+   *
+   * @var \Drupal\update\UpdateFetcher
+   */
+  protected $updateFetcher;
+
+  /**
+   * The update fetch queue.
+   *
+   * @var \Drupal\Core\Queue\QueueInterface
+   */
+  protected $fetchQueue;
+
+  /**
+   * Update key/value store
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $tempStore;
+
+  /**
+   * Update Fetch Task Store
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $fetchTaskStore;
+
+  /**
+   * Update avaiable releases store
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $availableReleasesTempStore;
+
+  /**
+   * Array of release history URLs that we have failed to fetch
+   *
+   * @var array
+   */
+  protected $failed;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface
+   */
+  protected $stateStore;
+
+  /**
+   * The private key.
+   *
+   * @var \Drupal\Core\PrivateKey
+   */
+  protected $privateKey;
+
+  /**
+   * Constructs a UpdateProcessor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Queue\QueueFactory $queue_factory
+   *   The queue factory
+   * @param \Drupal\update\UpdateFetcher $update_fetcher
+   *   The update fetcher service
+   * @param \Drupal\Core\KeyValueStore\StateInterface $state_store
+   *   The state service.
+   * @param \Drupal\Core\PrivateKey $private_key
+   *   The private key factory service.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The key/value factory.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_expirable_factory
+   *   The expirable key/value factory.
+   */
+  public function __construct(ConfigFactory $config_factory, QueueFactory $queue_factory, UpdateFetcher $update_fetcher, StateInterface $state_store, PrivateKey $private_key, KeyValueFactoryInterface $key_value_factory, KeyValueFactoryInterface $key_value_expirable_factory) {
+    $this->updateFetcher = $update_fetcher;
+    $this->updateSettings = $config_factory->get('update.settings');
+    $this->fetchQueue = $queue_factory->get('update_fetch_tasks');
+    $this->tempStore = $key_value_expirable_factory->get('update');
+    $this->fetchTaskStore = $key_value_factory->get('update_fetch_task');
+    $this->availableReleasesTempStore = $key_value_expirable_factory->get('update_available_releases');
+    $this->stateStore = $state_store;
+    $this->privateKey = $private_key;
+    $this->fetchTasks = array();
+    $this->failed = array();
+  }
+
+  /**
+   * Adds a task to the queue for fetching release history data for a project.
+   *
+   * We only create a new fetch task if there's no task already in the queue for
+   * this particular project (based on 'update_fetch_task' key-value collection).
+   *
+   * @param array $project
+   *   Associative array of information about a project as created by
+   *   update_get_projects(), including keys such as 'name' (short name), and the
+   *   'info' array with data from a .info.yml file for the project.
+   *
+   * @see \Drupal\update\UpdateManager::getProjects()
+   * @see update_get_available()
+   * @see \Drupal\update\UpdateManager::refreshUpdateData()
+   * @see \Drupal\update\UpdateProcessor::fetchData()
+   * @see \Drupal\update\UpdateProcessor::processFetchTask()
+   */
+  public function createFetchTask($project) {
+    if (empty($this->fetchTasks)) {
+      $this->fetchTasks = $this->fetchTaskStore->getAll();
+    }
+    if (empty($this->fetchTasks[$project['name']])) {
+      $this->fetchQueue->createItem($project);
+      $this->fetchTaskStore->set($project['name'], $project);
+      $this->fetchTasks[$project['name']] = REQUEST_TIME;
+    }
+  }
+
+  /**
+   * Attempts to drain the queue of tasks for release history data to fetch.
+   */
+  public function fetchData() {
+    $end = time() + $this->updateSettings->get('fetch.timeout');
+    while (time() < $end && ($item = $this->fetchQueue->claimItem())) {
+      $this->processFetchTask($item->data);
+      $this->fetchQueue->deleteItem($item);
+    }
+  }
+
+  /**
+   * Processes a task to fetch available update data for a single project.
+   *
+   * Once the release history XML data is downloaded, it is parsed and saved in
+   * an entry just for that project.
+   *
+   * @param array $project
+   *   Associative array of information about the project to fetch data for.
+   *
+   * @return bool
+   *   TRUE if we fetched parsable XML, otherwise FALSE.
+   */
+  public function processFetchTask($project) {
+    global $base_url;
+
+    // This can be in the middle of a long-running batch, so REQUEST_TIME won't
+    // necessarily be valid.
+    $request_time_difference = time() - REQUEST_TIME;
+    if (empty($this->failed)) {
+      // If we have valid data about release history XML servers that we have
+      // failed to fetch from on previous attempts, load that.
+      $this->failed = $this->tempStore->get('fetch_failures');
+    }
+
+    $max_fetch_attempts = $this->updateSettings->get('fetch.max_attempts');
+
+    $success = FALSE;
+    $available = array();
+    $site_key = Crypt::hmacBase64($base_url, $this->privateKey->get());
+    $fetch_url_base = $this->updateFetcher->getFetchBaseUrl($project);
+    $project_name = $project['name'];
+
+    if (empty($this->failed[$fetch_url_base]) || $this->failed[$fetch_url_base] < $max_fetch_attempts) {
+      $data = $this->updateFetcher->fetchProjectData($project, $site_key);
+    }
+    if (!empty($data)) {
+      $available = $this->parseXml($data);
+      // @todo: Purge release data we don't need (http://drupal.org/node/238950).
+      if (!empty($available)) {
+        // Only if we fetched and parsed something sane do we return success.
+        $success = TRUE;
+      }
+    }
+    else {
+      $available['project_status'] = 'not-fetched';
+      if (empty($this->failed[$fetch_url_base])) {
+        $this->failed[$fetch_url_base] = 1;
+      }
+      else {
+        $this->failed[$fetch_url_base]++;
+      }
+    }
+
+    $frequency = $this->updateSettings->get('check.interval_days');
+    $available['last_fetch'] = REQUEST_TIME + $request_time_difference;
+    $this->availableReleasesTempStore->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency));
+
+    // Stash the $this->failed data back in the DB for the next 5 minutes.
+    $this->tempStore->setWithExpire('fetch_failures', $this->failed, $request_time_difference + (60 * 5));
+
+    // Whether this worked or not, we did just (try to) check for updates.
+    $this->stateStore->set('update.last_check', REQUEST_TIME + $request_time_difference);
+
+    // Now that we processed the fetch task for this project, clear out the
+    // record for this task so we're willing to fetch again.
+    $this->fetchTaskStore->delete($project_name);
+
+    return $success;
+  }
+
+  /**
+   * Parses the XML of the Drupal release history info files.
+   *
+   * @param string $raw_xml
+   *   A raw XML string of available release data for a given project.
+   *
+   * @return array
+   *   Array of parsed data about releases for a given project, or NULL if there
+   *   was an error parsing the string.
+   */
+  protected function parseXml($raw_xml) {
+    try {
+      $xml = new \SimpleXMLElement($raw_xml);
+    }
+    catch (\Exception $e) {
+      // SimpleXMLElement::__construct produces an E_WARNING error message for
+      // each error found in the XML data and throws an exception if errors
+      // were detected. Catch any exception and return failure (NULL).
+      return NULL;
+    }
+    // If there is no valid project data, the XML is invalid, so return failure.
+    if (!isset($xml->short_name)) {
+      return NULL;
+    }
+    $data = array();
+    foreach ($xml as $k => $v) {
+      $data[$k] = (string) $v;
+    }
+    $data['releases'] = array();
+    if (isset($xml->releases)) {
+      foreach ($xml->releases->children() as $release) {
+        $version = (string) $release->version;
+        $data['releases'][$version] = array();
+        foreach ($release->children() as $k => $v) {
+          $data['releases'][$version][$k] = (string) $v;
+        }
+        $data['releases'][$version]['terms'] = array();
+        if ($release->terms) {
+          foreach ($release->terms->children() as $term) {
+            if (!isset($data['releases'][$version]['terms'][(string) $term->name])) {
+              $data['releases'][$version]['terms'][(string) $term->name] = array();
+            }
+            $data['releases'][$version]['terms'][(string) $term->name][] = (string) $term->value;
+          }
+        }
+      }
+    }
+    return $data;
+  }
+
+  /**
+   * Retrieves the number of items in the update fetch queue.
+   *
+   * @return int
+   *   An integer estimate of the number of items in the queue.
+   *
+   * @see \Drupal\Core\Queue\QueueInterface::numberOfItems()
+   */
+  public function numberOfQueueItems() {
+    return $this->fetchQueue->numberOfItems();
+  }
+
+  /**
+   * Claims an item in the update fetch queue for processing.
+   *
+   * @return
+   *   On success we return an item object. If the queue is unable to claim an
+   *   item it returns false.
+   *
+   * @see \Drupal\Core\Queue\QueueInterface::claimItem()
+   */
+  public function claimQueueItem() {
+    return $this->fetchQueue->claimItem();
+  }
+
+  /**
+   * Deletes a finished item from the update fetch queue.
+   *
+   * @param $item
+   *   The item returned by \Drupal\Core\Queue\QueueInterface::claimItem().
+   *
+   * @see \Drupal\Core\Queue\QueueInterface::deleteItem()
+   */
+  public function deleteQueueItem($item) {
+    return $this->fetchQueue->deleteItem($item);
+  }
+
+}
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index 15446a3..492e650 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -5,8 +5,6 @@
  * Code required only when comparing available updates to existing data.
  */
 
-use Drupal\Core\Utility\ProjectInfo;
-
 /**
  * Fetches an array of installed and enabled projects.
  *
@@ -50,33 +48,13 @@
  *   - base_themes: If the project is a theme it contains an associative array
  *     of all base-themes.
  *
- * @see update_process_project_info()
- * @see update_calculate_project_data()
- * @see update_project_storage()
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::getProjects()
+ *
+ * @see \Drupal\update\UpdateManager::getProjects()
  */
 function update_get_projects() {
-  $projects = &drupal_static(__FUNCTION__, array());
-  if (empty($projects)) {
-    // Retrieve the projects from storage, if present.
-    $projects = update_project_storage('update_project_projects');
-    if (empty($projects)) {
-      // Still empty, so we have to rebuild.
-      $module_data = system_rebuild_module_data();
-      $theme_data = system_rebuild_theme_data();
-      $project_info = new ProjectInfo();
-      $project_info->processInfoList($projects, $module_data, 'module', TRUE);
-      $project_info->processInfoList($projects, $theme_data, 'theme', TRUE);
-      if (\Drupal::config('update.settings')->get('check.disabled_extensions')) {
-        $project_info->processInfoList($projects, $module_data, 'module', FALSE);
-        $project_info->processInfoList($projects, $theme_data, 'theme', FALSE);
-      }
-      // Allow other modules to alter projects before fetching and comparing.
-      drupal_alter('update_projects', $projects);
-      // Store the site's project data for at most 1 hour.
-      \Drupal::keyValueExpirable('update')->setWithExpire('update_project_projects', $projects, 3600);
-    }
-  }
-  return $projects;
+  return \Drupal::service('update.manager')->getProjects();
 }
 
 /**
@@ -593,28 +571,12 @@ function update_calculate_project_update_status(&$project_data, $available) {
  *   The stored value of the $projects array generated by
  *   update_calculate_project_data() or update_get_projects(), or an empty array
  *   when the storage is cleared.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::projectStorage()
+ *
+ * @see \Drupal\update\UpdateManager::projectStorage()
  */
 function update_project_storage($key) {
-  $projects = array();
-
-  // On certain paths, we should clear the data and recompute the projects for
-  // update status of the site to avoid presenting stale information.
-  $paths = array(
-    'admin/modules',
-    'admin/modules/update',
-    'admin/appearance',
-    'admin/appearance/update',
-    'admin/reports',
-    'admin/reports/updates',
-    'admin/reports/updates/update',
-    'admin/reports/status',
-    'admin/reports/updates/check',
-  );
-  if (in_array(current_path(), $paths)) {
-    \Drupal::keyValueExpirable('update')->delete($key);
-  }
-  else {
-    $projects = \Drupal::keyValueExpirable('update')->get($key);
-  }
-  return $projects;
+  return \Drupal::service('update.manager')->projectStorage($key);
 }
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index 20eb0b5..be7d84d 100644
--- a/core/modules/update/update.fetch.inc
+++ b/core/modules/update/update.fetch.inc
@@ -5,118 +5,31 @@
  * Code required only when fetching information about available updates.
  */
 
-use Drupal\Component\Utility\Crypt;
-use Drupal\update\UpdateFetcher;
-
-/**
- * Page callback: Checks for updates and displays the update status report.
- *
- * Manually checks the update status without the use of cron.
- *
- * @see update_menu()
- *
- * @deprecated Use \Drupal\update\Controller\UpdateController::updateStatusManually()
- */
-function update_manual_status() {
-  _update_refresh();
-  $batch = array(
-    'operations' => array(
-      array('update_fetch_data_batch', array()),
-    ),
-    'finished' => 'update_fetch_data_finished',
-    'title' => t('Checking available update data'),
-    'progress_message' => t('Trying to check available update data ...'),
-    'error_message' => t('Error checking available update data.'),
-    'file' => drupal_get_path('module', 'update') . '/update.fetch.inc',
-  );
-  batch_set($batch);
-  return batch_process('admin/reports/updates');
-}
-
 /**
  * Batch callback: Processes a step in batch for fetching available update data.
  *
  * @param $context
  *   Reference to an array used for Batch API storage.
- */
-function update_fetch_data_batch(&$context) {
-  $queue = \Drupal::queue('update_fetch_tasks');
-  if (empty($context['sandbox']['max'])) {
-    $context['finished'] = 0;
-    $context['sandbox']['max'] = $queue->numberOfItems();
-    $context['sandbox']['progress'] = 0;
-    $context['message'] = t('Checking available update data ...');
-    $context['results']['updated'] = 0;
-    $context['results']['failures'] = 0;
-    $context['results']['processed'] = 0;
-  }
-
-  // Grab another item from the fetch queue.
-  for ($i = 0; $i < 5; $i++) {
-    if ($item = $queue->claimItem()) {
-      if (_update_process_fetch_task($item->data)) {
-        $context['results']['updated']++;
-        $context['message'] = t('Checked available update data for %title.', array('%title' => $item->data['info']['name']));
-      }
-      else {
-        $context['message'] = t('Failed to check available update data for %title.', array('%title' => $item->data['info']['name']));
-        $context['results']['failures']++;
-      }
-      $context['sandbox']['progress']++;
-      $context['results']['processed']++;
-      $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
-      $queue->deleteItem($item);
-    }
-    else {
-      // If the queue is currently empty, we're done. It's possible that
-      // another thread might have added new fetch tasks while we were
-      // processing this batch. In that case, the usual 'finished' math could
-      // get confused, since we'd end up processing more tasks that we thought
-      // we had when we started and initialized 'max' with numberOfItems(). By
-      // forcing 'finished' to be exactly 1 here, we ensure that batch
-      // processing is terminated.
-      $context['finished'] = 1;
-      return;
-    }
-  }
-}
-
-/**
- * Batch callback: Performs actions when all fetch tasks have been completed.
  *
- * @param $success
- *   TRUE if the batch operation was successful; FALSE if there were errors.
- * @param $results
- *   An associative array of results from the batch operation, including the key
- *   'updated' which holds the total number of projects we fetched available
- *   update data for.
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::fetchDataBatch()
+ *
+ * @see \Drupal\update\UpdateManager::fetchDataBatch()
  */
-function update_fetch_data_finished($success, $results) {
-  if ($success) {
-    if (!empty($results)) {
-      if (!empty($results['updated'])) {
-        drupal_set_message(format_plural($results['updated'], 'Checked available update data for one project.', 'Checked available update data for @count projects.'));
-      }
-      if (!empty($results['failures'])) {
-        drupal_set_message(format_plural($results['failures'], 'Failed to get available update data for one project.', 'Failed to get available update data for @count projects.'), 'error');
-      }
-    }
-  }
-  else {
-    drupal_set_message(t('An error occurred trying to get available update data.'), 'error');
-  }
+function update_fetch_data_batch(&$context) {
+  \Drupal::service('update.manager')->fetchDataBatch($context);
 }
 
 /**
  * Attempts to drain the queue of tasks for release history data to fetch.
+ *
+ *  @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateFetcher::fetchData()
+ *
+ * @see \Drupal\update\UpdateFetcher::fetchData()
  */
 function _update_fetch_data() {
-  $queue = \Drupal::queue('update_fetch_tasks');
-  $end = time() + \Drupal::config('update.settings')->get('fetch.timeout');
-  while (time() < $end && ($item = $queue->claimItem())) {
-    _update_process_fetch_task($item->data);
-    $queue->deleteItem($item);
-  }
+  \Drupal::service('update.processor')->fetchData();
 }
 
 /**
@@ -130,93 +43,26 @@ function _update_fetch_data() {
  *
  * @return
  *   TRUE if we fetched parsable XML, otherwise FALSE.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateFetcher::processFetchTask()
+ *
+ * @see \Drupal\update\UpdateFetcher::processFetchTask()
  */
 function _update_process_fetch_task($project) {
-  global $base_url;
-  $update_config = \Drupal::config('update.settings');
-  $fail = &drupal_static(__FUNCTION__, array());
-  // This can be in the middle of a long-running batch, so REQUEST_TIME won't
-  // necessarily be valid.
-  $request_time_difference = time() - REQUEST_TIME;
-  if (empty($fail)) {
-    // If we have valid data about release history XML servers that we have
-    // failed to fetch from on previous attempts, load that.
-    $fail = \Drupal::keyValueExpirable('update')->get('fetch_failures');
-  }
-
-  $max_fetch_attempts = $update_config->get('fetch.max_attempts');
-
-  $success = FALSE;
-  $available = array();
-  $site_key = Crypt::hmacBase64($base_url, \Drupal::service('private_key')->get());
-  $update_fetcher = new UpdateFetcher(\Drupal::service('config.factory'), \Drupal::service('http_default_client'));
-  $fetch_url_base = $update_fetcher->getFetchBaseUrl($project);
-  $project_name = $project['name'];
-
-  if (empty($fail[$fetch_url_base]) || $fail[$fetch_url_base] < $max_fetch_attempts) {
-    $data = $update_fetcher->fetchProjectData($project, $site_key);
-  }
-
-  if (!empty($data)) {
-    $available = update_parse_xml($data);
-    // @todo: Purge release data we don't need (http://drupal.org/node/238950).
-    if (!empty($available)) {
-      // Only if we fetched and parsed something sane do we return success.
-      $success = TRUE;
-    }
-  }
-  else {
-    $available['project_status'] = 'not-fetched';
-    if (empty($fail[$fetch_url_base])) {
-      $fail[$fetch_url_base] = 1;
-    }
-    else {
-      $fail[$fetch_url_base]++;
-    }
-  }
-
-  $frequency = $update_config->get('check.interval_days');
-  $available['last_fetch'] = REQUEST_TIME + $request_time_difference;
-  \Drupal::keyValueExpirable('update_available_releases')->setWithExpire($project_name, $available, $request_time_difference + (60 * 60 * 24 * $frequency));
-
-  // Stash the $fail data back in the DB for the next 5 minutes.
-  \Drupal::keyValueExpirable('update')->setWithExpire('fetch_failures', $fail, $request_time_difference + (60 * 5));
-
-  // Whether this worked or not, we did just (try to) check for updates.
-  \Drupal::state()->set('update.last_check', REQUEST_TIME + $request_time_difference);
-
-  // Now that we processed the fetch task for this project, clear out the
-  // record for this task so we're willing to fetch again.
-  \Drupal::keyValue('update_fetch_task')->delete($project_name);
-
-  return $success;
+  return \Drupal::service('update.processor')->processFetchTask($project);
 }
 
 /**
  * Clears out all the available update data and initiates re-fetching.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateManager::refreshUpdateData()
+ *
+ * @see \Drupal\update\UpdateManager::refreshUpdateData()
  */
 function _update_refresh() {
-  module_load_include('inc', 'update', 'update.compare');
-
-  // Since we're fetching new available update data, we want to clear
-  // of both the projects we care about, and the current update status of the
-  // site. We do *not* want to clear the cache of available releases just yet,
-  // since that data (even if it's stale) can be useful during
-  // update_get_projects(); for example, to modules that implement
-  // hook_system_info_alter() such as cvs_deploy.
-  \Drupal::keyValueExpirable('update')->delete('update_project_projects');
-  \Drupal::keyValueExpirable('update')->delete('update_project_data');
-
-  $projects = update_get_projects();
-
-  // Now that we have the list of projects, we should also clear the available
-  // release data, since even if we fail to fetch new data, we need to clear
-  // out the stale data at this point.
-  \Drupal::keyValueExpirable('update_available_releases')->deleteAll();
-
-  foreach ($projects as $project) {
-    update_create_fetch_task($project);
-  }
+  \Drupal::service('update.manager')->refreshUpdateData();
 }
 
 /**
@@ -230,23 +76,13 @@ function _update_refresh() {
  *   update_get_projects(), including keys such as 'name' (short name), and the
  *   'info' array with data from a .info.yml file for the project.
  *
- * @see update_get_projects()
- * @see update_get_available()
- * @see update_refresh()
- * @see update_fetch_data()
- * @see _update_process_fetch_task()
+ * @deprecated as of Drupal 8.0. Use
+ *   \Drupal\update\UpdateFetcher::createFetchTask()
+ *
+ * @see \Drupal\update\UpdateFetcher::createFetchTask()
  */
 function _update_create_fetch_task($project) {
-  $fetch_tasks = &drupal_static(__FUNCTION__, array());
-  if (empty($fetch_tasks)) {
-    $fetch_tasks = \Drupal::keyValue('update_fetch_task')->getAll();
-  }
-  if (empty($fetch_tasks[$project['name']])) {
-    $queue = \Drupal::queue('update_fetch_tasks');
-    $queue->createItem($project);
-    \Drupal::keyValue('update_fetch_task')->set($project['name'], $project);
-    $fetch_tasks[$project['name']] = REQUEST_TIME;
-  }
+  \Drupal::service('update.processor')->createFetchTask($project);
 }
 
 /**
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index af8143b..550bdfb 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -332,12 +332,12 @@ function update_get_available($refresh = FALSE) {
 
   // Grab whatever data we currently have.
   $available = \Drupal::keyValueExpirable('update_available_releases')->getAll();
-
-  $projects = update_get_projects();
+  $projects = \Drupal::service('update.manager')->getProjects();
   foreach ($projects as $key => $project) {
     // If there's no data at all, we clearly need to fetch some.
     if (empty($available[$key])) {
-      update_create_fetch_task($project);
+      //update_create_fetch_task($project);
+      \Drupal::service('update.processor')->createFetchTask($project);
       $needs_refresh = TRUE;
       continue;
     }
@@ -360,7 +360,7 @@ function update_get_available($refresh = FALSE) {
     // If we think this project needs to fetch, actually create the task now
     // and remember that we think we're missing some data.
     if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) {
-      update_create_fetch_task($project);
+      \Drupal::service('update.processor')->createFetchTask($project);
       $needs_refresh = TRUE;
     }
   }
@@ -411,6 +411,32 @@ function update_fetch_data() {
 }
 
 /**
+ * Batch callback: Performs actions when all fetch tasks have been completed.
+ *
+ * @param $success
+ *   TRUE if the batch operation was successful; FALSE if there were errors.
+ * @param $results
+ *   An associative array of results from the batch operation, including the key
+ *   'updated' which holds the total number of projects we fetched available
+ *   update data for.
+ */
+function update_fetch_data_finished($success, $results) {
+  if ($success) {
+    if (!empty($results)) {
+      if (!empty($results['updated'])) {
+        drupal_set_message(format_plural($results['updated'], 'Checked available update data for one project.', 'Checked available update data for @count projects.'));
+      }
+      if (!empty($results['failures'])) {
+        drupal_set_message(format_plural($results['failures'], 'Failed to get available update data for one project.', 'Failed to get available update data for @count projects.'), 'error');
+      }
+    }
+  }
+  else {
+    drupal_set_message(t('An error occurred trying to get available update data.'), 'error');
+  }
+}
+
+/**
  * Implements hook_mail().
  *
  * Constructs the e-mail notification message when the site is out of date.
diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml
index 0ffafc2..e335631 100644
--- a/core/modules/update/update.services.yml
+++ b/core/modules/update/update.services.yml
@@ -4,3 +4,12 @@ services:
     arguments: ['@settings']
     tags:
       - { name: access_check }
+  update.manager:
+    class: Drupal\update\UpdateManager
+    arguments: ['@config.factory', '@module_handler', '@update.processor', '@string_translation', '@keyvalue.expirable']
+  update.processor:
+    class: Drupal\update\UpdateProcessor
+    arguments: ['@config.factory', '@queue', '@update.fetcher', '@state', '@private_key', '@keyvalue', '@keyvalue.expirable']
+  update.fetcher:
+    class: Drupal\update\UpdateFetcher
+    arguments: ['@config.factory', '@http_default_client']
