docs: def _run_play(self, play):
          ''' run a list of tasks for a given pattern, in order '''
  
          self.callbacks.on_play_start(play.name)
          # if no hosts matches this play, drop out
          if not self.inventory.list_hosts(play.hosts):
              self.callbacks.on_no_hosts_matched()
              return True
  
          # get facts from system
          self._do_setup_step(play)
  
          # now with that data, handle contentional variable file imports!
  
          all_hosts = self._list_available_hosts(play.hosts)
          play.update_vars_files(all_hosts)
  
          serialized_batch = []
          if play.serial <= 0:
              serialized_batch = [all_hosts]
          else:
              # do N forks all the way through before moving to next
              while len(all_hosts) > 0:
                  play_hosts = []
                  for x in range(play.serial):
                      if len(all_hosts) > 0:
                          play_hosts.append(all_hosts.pop())
                  serialized_batch.append(play_hosts)
  
          for on_hosts in serialized_batch:
  
              self.inventory.also_restrict_to(on_hosts)
  
              for task in play.tasks():
  
                  # only run the task if the requested tags match
                  should_run = False
                  for x in self.only_tags:
                      for y in task.tags:
                          if (x==y):
                              should_run = True
                              break
                  if should_run:
                      if not self._run_task(play, task, False):
                          # whether no hosts matched is fatal or not depends if it was on the initial step.
                          # if we got exactly no hosts on the first step (setup!) then the host group
                          # just didn't match anything and that's ok
                          return False
  
                  host_list = self._list_available_hosts(play.hosts)
  
                  # if no hosts remain, drop out
                  if not host_list:
                      self.callbacks.on_no_hosts_remaining()
                      return False
  
              # run notify actions
              for handler in play.handlers():
                  if len(handler.notified_by) > 0:
                      self.inventory.restrict_to(handler.notified_by)
                      self._run_task(play, handler, True)
                      self.inventory.lift_restriction()
                      handler.notified_by = []
  
              self.inventory.lift_also_restriction()
  
          return True