class_docstr: A class the contains a naive implementation for concurrent sub-process
  execution. It uses `subprocess.Popen` and threads to read from stdout and
  stderr of the subprocess, and to write to stdin of the subprocess.
  
  All read data and timeouts are passed to a protocol instance, which can
  create the final result.
class_docstr_tok: ["A","class","the","contains","a","naive","implementation","for","concurrent","sub-process","execution",".","It","uses","`","subprocess.Popen","`","and","threads","to","read","from","stdout","and","stderr","of","the","subprocess",",","and","to","write","to","stdin","of","the","subprocess",".","All","read","data","and","timeouts","are","passed","to","a","protocol","instance",",","which","can","create","the","final","result","."]
docstr: Parameters
  ----------
  cmd : list or str
      Command to be executed, passed to `subprocess.Popen`. If cmd
      is a str, `subprocess.Popen will be called with `shell=True`.
  
  protocol : WitlessProtocol class or subclass which will be
      instantiated for managing communication with the subprocess.
  
      If the protocol is a subclass of
      `datalad.runner.protocol.GeneratorMixIn`, this function will
      return a `Generator` which yields whatever the protocol callback
      fed into `GeneratorMixIn.send_result()`.
  
      If the protocol is not a subclass of
      `datalad.runner.protocol.GeneratorMixIn`, the function will return
      the result created by the protocol method `_generate_result`.
  
  stdin : file-like, bytes, Queue, or None
      If stdin is a file-like, it will be directly used as stdin for the
      subprocess. The caller is responsible for writing to it and closing
      it. If stdin is a bytes, it will be fed to stdin of the subprocess.
      If all data is written, stdin will be closed.
      If stdin is a Queue, all elements (bytes) put into the Queue will
      be passed to stdin until None is read from the queue. If None is
      read, stdin of the subprocess is closed.
      If stdin is None, nothing will be sent to stdin of the subprocess.
      More precisely, `subprocess.Popen` will be called with `stdin=None`.
  
  protocol_kwargs : dict, optional
      Passed to the protocol class constructor.
  
  timeout : float, optional
      If a non-`None` timeout is specified, the `timeout`-method of
      the protocol will be called if:
  
      - stdin-write, stdout-read, or stderr-read time out. In this case
        the file descriptor will be given as argument to the
        timeout-method. If the timeout-method return `True`, the file
        descriptor will be closed.
  
      - process.wait() timeout: if waiting for process completion after
        stdin, stderr, and stdout takes longer than `timeout` seconds,
        the timeout-method will be called with the argument `None`. If
        it returns `True`, the process will be terminated.
  
  exception_on_error : bool, optional
      This argument is only interpreted if the protocol is a subclass
      of `GeneratorMixIn`. If it is `True` (default), a
      `CommandErrorException` is raised by the generator if the
      sub process exited with a return code not equal to zero. If the
      parameter is `False`, no exception is raised. In both cases the
      return code can be read from the attribute `return_code` of
      the generator.
  
  popen_kwargs : dict, optional
      Passed to `subprocess.Popen`, will typically be parameters
      supported by `subprocess.Popen`. Note that `bufsize`, `stdin`,
      `stdout`, `stderr`, and `shell` will be overwritten internally.
docstr_tok: ["Parameters","--","--","--","--","--","cmd",":","list","or","str","Command","to","be","executed",",","passed","to","`","subprocess.Popen","`",".","If","cmd","is","a","str",",","`","subprocess.Popen","will","be","called","with","`","shell=True","`",".","protocol",":","WitlessProtocol","class","or","subclass","which","will","be","instantiated","for","managing","communication","with","the","subprocess",".","If","the","protocol","is","a","subclass","of","`","datalad.runner.protocol.GeneratorMixIn","`",",","this","function","will","return","a","`","Generator","`","which","yields","whatever","the","protocol","callback","fed","into","`","GeneratorMixIn.send_result","(",")","`",".","If","the","protocol","is","not","a","subclass","of","`","datalad.runner.protocol.GeneratorMixIn","`",",","the","function","will","return","the","result","created","by","the","protocol","method","`","_generate_result","`",".","stdin",":","file-like",",","bytes",",","Queue",",","or","None","If","stdin","is","a","file-like",",","it","will","be","directly","used","as","stdin","for","the","subprocess",".","The","caller","is","responsible","for","writing","to","it","and","closing","it",".","If","stdin","is","a","bytes",",","it","will","be","fed","to","stdin","of","the","subprocess",".","If","all","data","is","written",",","stdin","will","be","closed",".","If","stdin","is","a","Queue",",","all","elements","(","bytes",")","put","into","the","Queue","will","be","passed","to","stdin","until","None","is","read","from","the","queue",".","If","None","is","read",",","stdin","of","the","subprocess","is","closed",".","If","stdin","is","None",",","nothing","will","be","sent","to","stdin","of","the","subprocess",".","More","precisely",",","`","subprocess.Popen","`","will","be","called","with","`","stdin=None","`",".","protocol_kwargs",":","dict",",","optional","Passed","to","the","protocol","class","constructor",".","timeout",":","float",",","optional","If","a","non-","`","None","`","timeout","is","specified",",","the","`","timeout","`","-method","of","the","protocol","will","be","called","if",":","-","stdin-write",",","stdout-read",",","or","stderr-read","time","out",".","In","this","case","the","file","descriptor","will","be","given","as","argument","to","the","timeout-method",".","If","the","timeout-method","return","`","True","`",",","the","file","descriptor","will","be","closed",".","-","process.wait","(",")","timeout",":","if","waiting","for","process","completion","after","stdin",",","stderr",",","and","stdout","takes","longer","than","`","timeout","`","seconds",",","the","timeout-method","will","be","called","with","the","argument","`","None","`",".","If","it","returns","`","True","`",",","the","process","will","be","terminated",".","exception_on_error",":","bool",",","optional","This","argument","is","only","interpreted","if","the","protocol","is","a","subclass","of","`","GeneratorMixIn","`",".","If","it","is","`","True","`","(","default",")",",","a","`","CommandErrorException","`","is","raised","by","the","generator","if","the","sub","process","exited","with","a","return","code","not","equal","to","zero",".","If","the","parameter","is","`","False","`",",","no","exception","is","raised",".","In","both","cases","the","return","code","can","be","read","from","the","attribute","`","return_code","`","of","the","generator",".","popen_kwargs",":","dict",",","optional","Passed","to","`","subprocess.Popen","`",",","will","typically","be","parameters","supported","by","`","subprocess.Popen","`",".","Note","that","`","bufsize","`",",","`","stdin","`",",","`","stdout","`",",","`","stderr","`",",","and","`","shell","`","will","be","overwritten","internally","."]
code: def __init__(
      self,
      cmd: str | list,
      protocol_class: type[WitlessProtocol],
      stdin: int | IO | bytes | Queue[Optional[bytes]] | None,
      protocol_kwargs: Optional[dict] = None,
      timeout: Optional[float] = None,
      exception_on_error: bool = True,
      **popen_kwargs,
  ):
      """
      Parameters
      ----------
      cmd : list or str
          Command to be executed, passed to `subprocess.Popen`. If cmd
          is a str, `subprocess.Popen will be called with `shell=True`.
  
      protocol : WitlessProtocol class or subclass which will be
          instantiated for managing communication with the subprocess.
  
          If the protocol is a subclass of
          `datalad.runner.protocol.GeneratorMixIn`, this function will
          return a `Generator` which yields whatever the protocol callback
          fed into `GeneratorMixIn.send_result()`.
  
          If the protocol is not a subclass of
          `datalad.runner.protocol.GeneratorMixIn`, the function will return
          the result created by the protocol method `_generate_result`.
  
      stdin : file-like, bytes, Queue, or None
          If stdin is a file-like, it will be directly used as stdin for the
          subprocess. The caller is responsible for writing to it and closing
          it. If stdin is a bytes, it will be fed to stdin of the subprocess.
          If all data is written, stdin will be closed.
          If stdin is a Queue, all elements (bytes) put into the Queue will
          be passed to stdin until None is read from the queue. If None is
          read, stdin of the subprocess is closed.
          If stdin is None, nothing will be sent to stdin of the subprocess.
          More precisely, `subprocess.Popen` will be called with `stdin=None`.
  
      protocol_kwargs : dict, optional
          Passed to the protocol class constructor.
  
      timeout : float, optional
          If a non-`None` timeout is specified, the `timeout`-method of
          the protocol will be called if:
  
          - stdin-write, stdout-read, or stderr-read time out. In this case
            the file descriptor will be given as argument to the
            timeout-method. If the timeout-method return `True`, the file
            descriptor will be closed.
  
          - process.wait() timeout: if waiting for process completion after
            stdin, stderr, and stdout takes longer than `timeout` seconds,
            the timeout-method will be called with the argument `None`. If
            it returns `True`, the process will be terminated.
  
      exception_on_error : bool, optional
          This argument is only interpreted if the protocol is a subclass
          of `GeneratorMixIn`. If it is `True` (default), a
          `CommandErrorException` is raised by the generator if the
          sub process exited with a return code not equal to zero. If the
          parameter is `False`, no exception is raised. In both cases the
          return code can be read from the attribute `return_code` of
          the generator.
  
      popen_kwargs : dict, optional
          Passed to `subprocess.Popen`, will typically be parameters
          supported by `subprocess.Popen`. Note that `bufsize`, `stdin`,
          `stdout`, `stderr`, and `shell` will be overwritten internally.
      """
  
      self.cmd = cmd
      self.protocol_class = protocol_class
      self.stdin = stdin
      self.protocol_kwargs = protocol_kwargs or {}
      self.timeout = timeout
      self.exception_on_error = exception_on_error
      self.popen_kwargs = popen_kwargs
  
      self.catch_stdout = self.protocol_class.proc_out
      self.catch_stderr = self.protocol_class.proc_err
  
      self.write_stdin: bool = False
      self.stdin_queue: Optional[Queue] = None
      self.process_stdin_fileno: Optional[int] = None
      self.process_stdout_fileno: Optional[int] = None
      self.process_stderr_fileno: Optional[int] = None
      self.stderr_enqueueing_thread: Optional[ReadThread] = None
      self.stdout_enqueueing_thread: Optional[ReadThread] = None
      self.stdin_enqueueing_thread: Optional[WriteThread] = None
      self.process_waiting_thread: Optional[WaitThread] = None
  
      self.process_running: bool = False
      self.output_queue: Queue = Queue()
      self.process_removed: bool = False
      self.generator: Optional[_ResultGenerator] = None
      self.process: Optional[Popen[Any]] = None
      self.return_code: Optional[int] = None
  
      self.last_touched: dict[Optional[int], float] = dict()
      self.active_file_numbers: set[Optional[int]] = set()
      self.stall_check_interval = 10
  
      self.initialization_lock = threading.Lock()
      self.generator_condition = threading.Condition()
      self.owning_thread: Optional[int] = None
  
      # Pure declarations
      self.protocol: WitlessProtocol
      self.fileno_mapping: dict[Optional[int], int]
      self.fileno_to_file: dict[Optional[int], Optional[IO]]
      self.file_to_fileno: dict[IO, int]
      self.result: dict
  
imports: from __future__ import annotations
  import enum
  import logging
  import subprocess
  import threading
  import time
  from collections import deque
  from collections.abc import Generator
  from queue import Empty, Queue
  from subprocess import Popen
  from typing import IO, Any, Optional
  from datalad.utils import on_windows
  from .exception import CommandError
  from .protocol import GeneratorMixIn, WitlessProtocol
  from .runnerthreads import IOState, ReadThread, WaitThread, WriteThread, _try_close
label_desc: Use image node_id 1 to create a new ThreadedRunner object  with example: obj = ThreadedRunner(cmd, protocol_class, stdin, protocol_kwargs, timeout, exception_on_error)