code: def _finish_jobs(self):
      # must be called from within lock
      # clear the global tofinish such that parallel calls do not interfere
      async def postprocess():
          for job in self._tofinish:
              if not self.workflow.dryrun:
                  try:
                      if self.workflow.exec_mode == ExecMode.DEFAULT:
                          await job.postprocess(
                              store_in_storage=True,
                              handle_log=True,
                              handle_touch=True,
                              ignore_missing_output=self.touch,
                          )
                      elif (
                          self.workflow.exec_mode == ExecMode.SUBPROCESS
                      ):
                          await job.postprocess(
                              store_in_storage=False,
                              handle_log=True,
                              handle_touch=True,
                          )
                      else:
                          await job.postprocess(
                              store_in_storage=False,
                              handle_log=True,
                              handle_touch=True,
                          )
                  except (RuleException, WorkflowError) as e:
                      # if an error occurs while processing job output,
                      # we do the same as in case of errors during execution
                      print_exception(e, self.workflow.linemaps)
                      await job.postprocess(error=True)
                      self._handle_error(job, postprocess_job=False)
                      continue
  
              if self.handle_job_success:
                  self.get_executor(job).handle_job_success(job)
  
              if self.update_resources:
                  # normal jobs have len=1, group jobs have len>1
                  self.finished_jobs += len(job)
                  logger.debug(
                      f"jobs registered as running before removal {self.running}"
                  )
                  self.running.remove(job)
                  self._free_resources(job)
  
              if self.print_progress:
                  if job.is_group():
                      for j in job:
                          logger.job_finished(jobid=j.jobid)
                  else:
                      logger.job_finished(jobid=job.jobid)
                  self.progress()
  
              await self.workflow.dag.finish(
                  job,
                  update_checkpoint_dependencies=self.update_checkpoint_dependencies,
              )
  
      async_run(postprocess())
      self._tofinish.clear()
  
imports: import asyncio
  from collections import defaultdict
  import math
  import os, signal, sys
  import threading
  from functools import partial
  from itertools import chain, accumulate
  from contextlib import ContextDecorator
  from snakemake_interface_executor_plugins.scheduler import JobSchedulerExecutorInterface
  from snakemake_interface_executor_plugins.registry import ExecutorPluginRegistry
  from snakemake_interface_executor_plugins.registry import Plugin
  from snakemake_interface_executor_plugins.settings import ExecMode
  from snakemake.common import async_run
  from snakemake.exceptions import RuleException, WorkflowError, print_exception
  from snakemake.logging import logger
  from fractions import Fraction
label_desc: Use image node_id 7 for calling the JobScheduler obj's underlying member method code with example usage: obj._finish_jobs() without return types