docs: def handle_func(self, multiprocessing_enabled, func, data):
          '''
          Execute this method in a multiprocess or thread
          '''
          if salt.utils.platform.is_windows() \
                  or self.opts.get('transport') == 'zeromq':
              # Since function references can't be pickled and pickling
              # is required when spawning new processes on Windows, regenerate
              # the functions and returners.
              # This also needed for ZeroMQ transport to reset all functions
              # context data that could keep paretns connections. ZeroMQ will
              # hang on polling parents connections from the child process.
              if self.opts['__role'] == 'master':
                  self.functions = salt.loader.runner(self.opts, utils=self.utils)
              else:
                  self.functions = salt.loader.minion_mods(self.opts, proxy=self.proxy, utils=self.utils)
              self.returners = salt.loader.returners(self.opts, self.functions, proxy=self.proxy)
          ret = {'id': self.opts.get('id', 'master'),
                 'fun': func,
                 'fun_args': [],
                 'schedule': data['name'],
                 'jid': salt.utils.jid.gen_jid(self.opts)}
  
          if 'metadata' in data:
              if isinstance(data['metadata'], dict):
                  ret['metadata'] = data['metadata']
                  ret['metadata']['_TOS'] = self.time_offset
                  ret['metadata']['_TS'] = time.ctime()
                  ret['metadata']['_TT'] = time.strftime('%Y %B %d %a %H %m', time.gmtime())
              else:
                  log.warning('schedule: The metadata parameter must be '
                              'specified as a dictionary.  Ignoring.')
  
          if multiprocessing_enabled:
              # We just want to modify the process name if we're on a different process
              salt.utils.process.appendproctitle('{0} {1}'.format(self.__class__.__name__, ret['jid']))
          data_returner = data.get('returner', None)
  
          if not self.standalone:
              proc_fn = os.path.join(
                  salt.minion.get_proc_dir(self.opts['cachedir']),
                  ret['jid']
              )
  
          if multiprocessing_enabled and not salt.utils.platform.is_windows():
              # Reconfigure multiprocessing logging after daemonizing
              log_setup.setup_multiprocessing_logging()
  
          if multiprocessing_enabled:
              # Don't *BEFORE* to go into try to don't let it triple execute the finally section.
              salt.utils.process.daemonize_if(self.opts)
  
          # TODO: Make it readable! Splt to funcs, remove nested try-except-finally sections.
          try:
  
              minion_blackout_violation = False
              if self.opts.get('pillar', {}).get('minion_blackout', False):
                  whitelist = self.opts.get('pillar', {}).get('minion_blackout_whitelist', [])
                  # this minion is blacked out. Only allow saltutil.refresh_pillar and the whitelist
                  if func != 'saltutil.refresh_pillar' and func not in whitelist:
                      minion_blackout_violation = True
              elif self.opts.get('grains', {}).get('minion_blackout', False):
                  whitelist = self.opts.get('grains', {}).get('minion_blackout_whitelist', [])
                  if func != 'saltutil.refresh_pillar' and func not in whitelist:
                      minion_blackout_violation = True
              if minion_blackout_violation:
                  raise SaltInvocationError('Minion in blackout mode. Set \'minion_blackout\' '
                                            'to False in pillar or grains to resume operations. Only '
                                            'saltutil.refresh_pillar allowed in blackout mode.')
  
              ret['pid'] = os.getpid()
  
              if not self.standalone:
                  if 'jid_include' not in data or data['jid_include']:
                      log.debug(
                          'schedule.handle_func: adding this job to the '
                          'jobcache with data %s', ret
                      )
                      # write this to /var/cache/salt/minion/proc
                      with salt.utils.files.fopen(proc_fn, 'w+b') as fp_:
                          fp_.write(salt.payload.Serial(self.opts).dumps(ret))
  
              args = tuple()
              if 'args' in data:
                  args = data['args']
                  ret['fun_args'].extend(data['args'])
  
              kwargs = {}
              if 'kwargs' in data:
                  kwargs = data['kwargs']
                  ret['fun_args'].append(copy.deepcopy(kwargs))
  
              if func not in self.functions:
                  ret['return'] = self.functions.missing_fun_string(func)
                  salt.utils.error.raise_error(
                      message=self.functions.missing_fun_string(func))
  
              # if the func support **kwargs, lets pack in the pub data we have
              # TODO: pack the *same* pub data as a minion?
              argspec = salt.utils.args.get_function_argspec(self.functions[func])
              if argspec.keywords:
                  # this function accepts **kwargs, pack in the publish data
                  for key, val in six.iteritems(ret):
                      if key is not 'kwargs':
                          kwargs['__pub_{0}'.format(key)] = copy.deepcopy(val)
  
              # Only include these when running runner modules
              if self.opts['__role'] == 'master':
                  jid = salt.utils.jid.gen_jid(self.opts)
                  tag = salt.utils.event.tagify(jid, prefix='salt/scheduler/')
  
                  event = salt.utils.event.get_event(
                          self.opts['__role'],
                          self.opts['sock_dir'],
                          self.opts['transport'],
                          opts=self.opts,
                          listen=False)
  
                  namespaced_event = salt.utils.event.NamespacedEvent(
                      event,
                      tag,
                      print_func=None
                  )
  
                  func_globals = {
                      '__jid__': jid,
                      '__user__': salt.utils.user.get_user(),
                      '__tag__': tag,
                      '__jid_event__': weakref.proxy(namespaced_event),
                  }
                  self_functions = copy.copy(self.functions)
                  salt.utils.lazy.verify_fun(self_functions, func)
  
                  # Inject some useful globals to *all* the function's global
                  # namespace only once per module-- not per func
                  completed_funcs = []
  
                  for mod_name in six.iterkeys(self_functions):
                      if '.' not in mod_name:
                          continue
                      mod, _ = mod_name.split('.', 1)
                      if mod in completed_funcs:
                          continue
                      completed_funcs.append(mod)
                      for global_key, value in six.iteritems(func_globals):
                          self.functions[mod_name].__globals__[global_key] = value
  
              self.functions.pack['__context__']['retcode'] = 0
  
              ret['return'] = self.functions[func](*args, **kwargs)
  
              if not self.standalone:
                  # runners do not provide retcode
                  if 'retcode' in self.functions.pack['__context__']:
                      ret['retcode'] = self.functions.pack['__context__']['retcode']
  
                  ret['success'] = True
  
                  if data_returner or self.schedule_returner:
                      if 'return_config' in data:
                          ret['ret_config'] = data['return_config']
                      if 'return_kwargs' in data:
                          ret['ret_kwargs'] = data['return_kwargs']
                      rets = []
                      for returner in [data_returner, self.schedule_returner]:
                          if isinstance(returner, six.string_types):
                              rets.append(returner)
                          elif isinstance(returner, list):
                              rets.extend(returner)
                      # simple de-duplication with order retained
                      for returner in OrderedDict.fromkeys(rets):
                          ret_str = '{0}.returner'.format(returner)
                          if ret_str in self.returners:
                              self.returners[ret_str](ret)
                          else:
                              log.info(
                                  'Job %s using invalid returner: %s. Ignoring.',
                                  func, returner
                              )
  
          except Exception:
              log.exception('Unhandled exception running %s', ret['fun'])
              # Although catch-all exception handlers are bad, the exception here
              # is to let the exception bubble up to the top of the thread context,
              # where the thread will die silently, which is worse.
              if 'return' not in ret:
                  ret['return'] = "Unhandled exception running {0}".format(ret['fun'])
              ret['success'] = False
              ret['retcode'] = 254
          finally:
              # Only attempt to return data to the master if the scheduled job is running
              # on a master itself or a minion.
              if '__role' in self.opts and self.opts['__role'] in ('master', 'minion'):
                  # The 'return_job' option is enabled by default even if not set
                  if 'return_job' in data and not data['return_job']:
                      pass
                  else:
                      # Send back to master so the job is included in the job list
                      mret = ret.copy()
                      # No returners defined, so we're only sending back to the master
                      if not data_returner and not self.schedule_returner:
                          mret['jid'] = 'req'
                          if data.get('return_job') == 'nocache':
                              # overwrite 'req' to signal to master that
                              # this job shouldn't be stored
                              mret['jid'] = 'nocache'
                      load = {'cmd': '_return', 'id': self.opts['id']}
                      for key, value in six.iteritems(mret):
                          load[key] = value
  
                      if '__role' in self.opts and self.opts['__role'] == 'minion':
                          event = salt.utils.event.get_event('minion',
                                                             opts=self.opts,
                                                             listen=False)
                      elif '__role' in self.opts and self.opts['__role'] == 'master':
                          event = salt.utils.event.get_master_event(self.opts,
                                                                    self.opts['sock_dir'])
                      try:
                          event.fire_event(load, '__schedule_return')
                      except Exception as exc:
                          log.exception('Unhandled exception firing __schedule_return event')
  
              if not self.standalone:
                  log.debug('schedule.handle_func: Removing %s', proc_fn)
  
                  try:
                      os.unlink(proc_fn)
                  except OSError as exc:
                      if exc.errno == errno.EEXIST or exc.errno == errno.ENOENT:
                          # EEXIST and ENOENT are OK because the file is gone and that's what
                          # we wanted
                          pass
                      else:
                          log.error("Failed to delete '%s': %s", proc_fn, exc.errno)
                          # Otherwise, failing to delete this file is not something
                          # we can cleanly handle.
                          raise
                  finally:
                      if multiprocessing_enabled:
                          # Let's make sure we exit the process!
                          sys.exit(salt.defaults.exitcodes.EX_GENERIC)