docs: def execute_substep(stmt,
                      global_def,
                      global_vars,
                      task='',
                      task_params='',
                      proc_vars={},
                      shared_vars=[],
                      config={}):
      '''Execute a substep with specific input etc
  
      Substep executed by this function should be self-contained. It can contain
      tasks (which will be sent to the master process) but not nested workflows.
  
      The executor checks step signatures and might skip the substep if it has
      been executed and the signature matches.
  
      The executor accepts connections to the controller, and a socket using
      which the results will be returned. However, the calling process should
      take care of the connection and disconnection of controller sockets and
      this function only takes care of the connection and disconnection of
      result socket.
  
      stmt:
          Main statement of the substep
  
      global_def:
          Global definitions, might define functions useful to the substep
  
      task:
          External task
  
      proc_vars:
          Environmental variables, signature variables etc
  
      shared_vars:
          Variables that should be returned after the execution
  
      config:
          Runmode, signature mode, verbosity, etc.
  
      The return value should be a dictionary with the following keys:
  
      index: index of the substep within the step
      ret_code: (all) return code, 0 for successful
      sig_skipped: (optional) return if the step is skipped due to signature
      shared: (optional) shared variable as specified by 'shared_vars'
      stdout: (optional) if in interactive mode
      stderr: (optional) if in interactive mode
      exception: (optional) if an exception occures
      '''
      assert not env.zmq_context.closed
      assert 'workflow_id' in proc_vars
      assert 'step_id' in proc_vars
      assert '_input' in proc_vars
      assert '_output' in proc_vars
      assert '_depends' in proc_vars
      assert 'step_output' in proc_vars
      assert '_index' in proc_vars
      assert 'result_push_socket' in config["sockets"]
  
      # this should not happen but check nevertheless
      if env.result_socket_port is not None and env.result_socket_port != config[
              "sockets"]["result_push_socket"]:
          close_socket(env.result_socket)
          env.result_socket = None
  
      if env.result_socket is None:
          env.result_socket = create_socket(env.zmq_context, zmq.PUSH)
          env.result_socket_port = config["sockets"]["result_push_socket"]
          env.result_socket.connect(f'tcp://127.0.0.1:{env.result_socket_port}')
  
      res = _execute_substep(
          stmt=stmt,
          global_def=global_def,
          global_vars=global_vars,
          task=task,
          task_params=task_params,
          proc_vars=proc_vars,
          shared_vars=shared_vars,
          config=config)
      env.result_socket.send_pyobj(res)