Repository: rwth-i6/returnn

Issue:
Unused code in `task_system.py`
With #1164 there is probably some unused code now in the task system. We should check what can be remove and what not.


Discussion:
I did a look again, and it seems that mainly the `Pickler` is still used, but most of the rest is not, like `AsyncTask` and everything it depends on.
Yes if it is not used, it can be removed.

Resolving patch:
diff --git a/returnn/__old_mod_loader__.py b/returnn/__old_mod_loader__.py
--- a/returnn/__old_mod_loader__.py
+++ b/returnn/__old_mod_loader__.py
@@ -54,7 +54,6 @@
   "SprintInterface": "sprint.interface",
   "StereoDataset": "datasets.stereo",
   "TaskSystem": "util.task_system",
-  "TaskSystem_example": "util.task_system_example",
   "TFCompat": "tf.compat",
   "TFDataPipeline": "tf.data_pipeline",
   "TFDistributed": "tf.distributed",
diff --git a/returnn/util/task_system.py b/returnn/util/task_system.py
--- a/returnn/util/task_system.py
+++ b/returnn/util/task_system.py
@@ -5,149 +5,32 @@
 """
 
 from __future__ import print_function
-from threading import Lock, currentThread
+from threading import Lock
 import sys
-PY3 = sys.version_info[0] >= 3
 
 import os
 import io
-
-if PY3:
-  from io import BytesIO
-else:
-  # noinspection PyUnresolvedReferences,PyCompatibility
-  from StringIO import StringIO as BytesIO
-
-from contextlib import contextmanager
 import pickle
 import types
 import struct
 import marshal
 from importlib import import_module
-import errno
-import time
 import numpy
 try:
   from _multiprocessing import Connection
 except ImportError:
   from multiprocessing.connection import Connection
 
-_abs_mod_file = os.path.abspath(__file__)
-
-
-def execInMainProc(func):
-  global isMainProcess
-  if isMainProcess:
-    return func()
-  else:
-    assert _AsyncCallQueue.Self, "works only if called via asyncCall"
-    return _AsyncCallQueue.Self.asyncExecClient(func)
-
-
-def ExecInMainProcDecorator(func):
-  def decoratedFunc(*args, **kwargs):
-    return execInMainProc(lambda: func(*args, **kwargs))
-  return decoratedFunc
-
-
-class AsyncInterrupt(BaseException):
-  pass
-
-
-class ForwardedKeyboardInterrupt(Exception):
-  pass
-
-
-class _AsyncCallQueue:
-  Self = None
-
-  class Types:
-    result = 0
-    exception = 1
-    asyncExec = 2
-
-  def __init__(self, queue):
-    assert not self.Self
-    self.__class__.Self = self
-    self.mutex = Lock()
-    self.queue = queue
-
-  def put(self, type, value):
-    self.queue.put((type, value))
-
-  def asyncExecClient(self, func):
-    with self.mutex:
-      self.put(self.Types.asyncExec, func)
-      t, value = self.queue.get()
-      if t == self.Types.result:
-        return value
-      elif t == self.Types.exception:
-        raise value
-      else:
-        assert False, "bad behavior of asyncCall in asyncExec (%r)" % t
-
-  @classmethod
-  def asyncExecHost(clazz, task, func):
-    q = task
-    name = "<unknown>"
-    try:
-      name = repr(func)
-      res = func()
-    except Exception as exc:
-      print("Exception in asyncExecHost %s %s" % (name, exc))
-      q.put((clazz.Types.exception, exc))
-    else:
-      try:
-        q.put((clazz.Types.result, res))
-      except IOError:
-        # broken pipe or so. parent quit. treat like a SIGINT
-        raise KeyboardInterrupt
-
+PY3 = sys.version_info[0] >= 3
 
-def asyncCall(func, name=None, mustExec=False):
-  """
-  This executes func() in another process and waits/blocks until
-  it is finished. The returned value is passed back to this process
-  and returned. Exceptions are passed back as well and will be
-  reraised here.
+if PY3:
+  from io import BytesIO
+else:
+  # noinspection PyUnresolvedReferences,PyCompatibility
+  from StringIO import StringIO as BytesIO
 
-  If `mustExec` is set, the other process must `exec()` after the `fork()`.
-  If it is not set, it might omit the `exec()`, depending on the platform.
-  """
 
-  def doCall(queue):
-    q = _AsyncCallQueue(queue)
-    try:
-      try:
-        res = func()
-      except KeyboardInterrupt as exc:
-        print("Exception in asyncCall %s: KeyboardInterrupt" % name)
-        q.put(q.Types.exception, ForwardedKeyboardInterrupt(exc))
-      except BaseException as exc:
-        print("Exception in asyncCall %s" % name)
-        sys.excepthook(*sys.exc_info())
-        q.put(q.Types.exception, exc)
-      else:
-        q.put(q.Types.result, res)
-    except (KeyboardInterrupt, ForwardedKeyboardInterrupt):
-      print("asyncCall: SIGINT in put, probably the parent died")
-    # ignore
-
-  task = AsyncTask(func=doCall, name=name, mustExec=mustExec)
-
-  while True:
-    # If there is an unhandled exception in doCall or the process got killed/segfaulted or so,
-    # this will raise an EOFError here.
-    # However, normally, we should catch all exceptions and just reraise them here.
-    t,value = task.get()
-    if t == _AsyncCallQueue.Types.result:
-      return value
-    elif t == _AsyncCallQueue.Types.exception:
-      raise value
-    elif t == _AsyncCallQueue.Types.asyncExec:
-      _AsyncCallQueue.asyncExecHost(task, value)
-    else:
-      assert False, "unknown _AsyncCallQueue type %r" % t
+_abs_mod_file = os.path.abspath(__file__)
 
 
 class SharedMem:
@@ -873,535 +756,3 @@ def save_class(self, cls):
   # however, also doesn't break. it mostly makes sense to just ignore.
   def __getstate__(self): return None
   def __setstate__(self, state): pass
-
-
-class ExecingProcess:
-  """
-  This is a replacement for multiprocessing.Process which always
-  uses fork+exec, not just fork.
-  This ensures that you have a separate independent process.
-  This can avoid many types of bugs, such as:
-    https://stackoverflow.com/questions/24509650
-    https://bugs.python.org/issue6721
-    https://stackoverflow.com/questions/8110920
-    https://stackoverflow.com/questions/23963997
-    https://github.com/numpy/numpy/issues/654
-    https://comments.gmane.org/gmane.comp.python.numeric.general/60204
-  """
-
-  def __init__(self, target, args, name, env_update):
-    self.target = target
-    self.args = args
-    self.name = name
-    self.env_update = env_update
-    self.daemon = True
-    self.pid = None
-    self.exit_status = None
-
-  def start(self):
-    assert self.pid is None
-    assert self.exit_status is None
-    def pipeOpen():
-      readend, writeend = os.pipe()
-      if hasattr(os, "set_inheritable"):
-        # Python 3 by default will close all fds in subprocesses. This will avoid that.
-        os.set_inheritable(readend, True)
-        os.set_inheritable(writeend, True)
-      readend = os.fdopen(readend, "rb")
-      writeend = os.fdopen(writeend, "wb")
-      return readend, writeend
-    self.pipe_c2p = pipeOpen()
-    self.pipe_p2c = pipeOpen()
-    self.parent_pid = os.getpid()
-    pid = os.fork()
-    flags = {key: value for (key, value) in [s.split("=", 1) for s in os.environ.get("THEANO_FLAGS", "").split(",") if s]}
-    if 'base_compiledir' in flags:
-      offset = flags['base_compiledir'].find("_-_", 1)
-      if offset > 1:
-        flags['base_compiledir'] = flags['base_compiledir'][:offset]
-      flags['base_compiledir'] += '_-_' + self.name.replace(' ','_')
-    else:
-      flags['base_compiledir'] = '/tmp/theano/' + self.name.replace(' ','_')
-    os.environ["THEANO_FLAGS"] = ",".join(["=".join(x) for x in flags.items()])
-    if pid == 0:  # child
-      try:
-        sys.stdin.close()  # Force no tty stdin.
-        self.pipe_c2p[0].close()
-        self.pipe_p2c[1].close()
-        py_mod_file = os.path.splitext(_abs_mod_file)[0] + ".py"
-        assert os.path.exists(py_mod_file)
-        args = [sys.executable,
-                py_mod_file,
-                "--forkExecProc",
-                str(self.pipe_c2p[1].fileno()),
-                str(self.pipe_p2c[0].fileno())]
-        if self.env_update:
-          os.environ.update(self.env_update)
-        os.execv(args[0], args)  # Does not return if successful.
-      except BaseException:
-        print("ExecingProcess: Error at initialization.")
-        sys.excepthook(*sys.exc_info())
-        sys.exit(1)
-      finally:
-        sys.exit()
-    else:  # parent
-      self.pipe_c2p[1].close()
-      self.pipe_p2c[0].close()
-      self.pid = pid
-      self.pickler = Pickler(self.pipe_p2c[1])
-      self.pickler.dump(self.name)
-      self.pickler.dump(self.target)
-      self.pickler.dump(self.args)
-      self.pipe_p2c[1].flush()
-
-  def _wait(self, options=0):
-    assert self.parent_pid == os.getpid()
-    assert self.pid
-    assert self.exit_status is None
-    pid, exit_status = os.waitpid(self.pid, options)
-    if pid != self.pid:
-      assert pid == 0
-      # It's still alive, otherwise we would have get the same pid.
-      return
-    self.exit_status = exit_status
-    self.pid = None
-
-  def is_alive(self):
-    if self.pid is None:
-      return False
-    self._wait(os.WNOHANG)
-    return self.pid is not None
-
-  def join(self, timeout=None):
-    if not self.is_alive():
-      return
-    if timeout:
-      # Simple and stupid implementation.
-      while self.is_alive():
-        if timeout < 0:
-          break
-        if timeout < 1.0:
-          time.sleep(timeout)
-          break
-        else:
-          time.sleep(1)
-          timeout -= 1
-      return
-    self._wait()
-
-  Verbose = False
-
-  @staticmethod
-  def checkExec():
-    if "--forkExecProc" in sys.argv:
-      try:
-        from returnn.util import better_exchook
-      except ImportError:
-        pass  # Doesn't matter.
-      else:
-        better_exchook.install()
-      argidx = sys.argv.index("--forkExecProc")
-      writeFileNo = int(sys.argv[argidx + 1])
-      readFileNo = int(sys.argv[argidx + 2])
-      readend = os.fdopen(readFileNo, "rb")
-      writeend = os.fdopen(writeFileNo, "wb")
-      unpickler = Unpickler(readend)
-      name = unpickler.load()
-      if ExecingProcess.Verbose: print("ExecingProcess child %s (pid %i)" % (name, os.getpid()))
-      try:
-        target = unpickler.load()
-        args = unpickler.load()
-      except EOFError:
-        print("Error: unpickle incomplete")
-        raise SystemExit
-      ret = target(*args)
-      sys.exited = True
-      # IOError is probably broken pipe. That probably means that the parent died.
-      try: Pickler(writeend).dump(ret)
-      except IOError: pass
-      try: readend.close()
-      except IOError: pass
-      try: writeend.close()
-      except IOError: pass
-      if ExecingProcess.Verbose: print("ExecingProcess child %s (pid %i) finished" % (name, os.getpid()))
-      raise SystemExit
-
-
-class ProcConnectionDied(Exception):
-  pass
-
-
-class ExecingProcess_ConnectionWrapper(object):
-  """
-  Wrapper around multiprocessing.connection.Connection.
-  This is needed to use our own Pickler.
-  """
-
-  def __init__(self, fd=None, conn=None):
-    self.fd = fd
-    if self.fd:
-      self.conn = Connection(fd)
-    elif conn:
-      self.conn = conn
-    else:
-      self.conn = None
-
-  def __repr__(self):
-    if self.conn is not None:
-      return "<ExecingProcess_ConnectionWrapper fileno=%r>" % self.conn.fileno()
-    else:
-      return "<ExecingProcess_ConnectionWrapper None>"
-
-  def __getstate__(self):
-    if self.fd is not None:
-      return {"fd": self.fd}
-    elif self.conn is not None:
-      return {"conn": self.conn}  # Try to pickle the connection.
-    else:
-      return {}
-
-  def __setstate__(self, state):
-    self.__init__(**state)
-
-  def __getattr__(self, attr):
-    return getattr(self.conn, attr)
-
-  def _check_closed(self):
-    if self.conn.closed:
-      raise ProcConnectionDied("connection closed")
-
-  def _check_writable(self):
-    if not self.conn.writable:
-      raise ProcConnectionDied("connection not writeable")
-
-  def _check_readable(self):
-    if not self.conn.readable:
-      raise ProcConnectionDied("connection not readable")
-
-  def poll(self, *args, **kwargs):
-    while True:
-      try:
-        return self.conn.poll(*args, **kwargs)
-      except IOError as e:
-        if e.errno == errno.EINTR:
-          # https://stackoverflow.com/questions/14136195
-          # We can just keep trying.
-          continue
-        raise ProcConnectionDied("poll IOError: %s" % e)
-      except EOFError as e:
-        raise ProcConnectionDied("poll EOFError: %s" % e)
-
-  def send_bytes(self, value):
-    try:
-      self.conn.send_bytes(value)
-    except (EOFError, IOError) as e:
-      raise ProcConnectionDied("send_bytes EOFError/IOError: %s" % e)
-
-  def send(self, value):
-    self._check_closed()
-    self._check_writable()
-    buf = BytesIO()
-    Pickler(buf).dump(value)
-    self.send_bytes(buf.getvalue())
-
-  def recv_bytes(self):
-    while True:
-      try:
-        return self.conn.recv_bytes()
-      except IOError as e:
-        if e.errno == errno.EINTR:
-          # https://stackoverflow.com/questions/14136195
-          # We can just keep trying.
-          continue
-        raise ProcConnectionDied("recv_bytes IOError: %s" % e)
-      except EOFError as e:
-        raise ProcConnectionDied("recv_bytes EOFError: %s" % e)
-
-  def recv(self):
-    self._check_closed()
-    self._check_readable()
-    buf = self.recv_bytes()
-    f = BytesIO(buf)
-    res = Unpickler(f).load()
-    return res
-
-
-def ExecingProcess_Pipe():
-  """
-  This is like multiprocessing.Pipe(duplex=True).
-  It uses our own ExecingProcess_ConnectionWrapper.
-  """
-  import socket
-  s1, s2 = socket.socketpair()
-  # We duplicate the fds because the socket objects will close the fds after they go out of scope.
-  fd1 = os.dup(s1.fileno())
-  fd2 = os.dup(s2.fileno())
-  s1.close()
-  s2.close()
-  if hasattr(os, "set_inheritable"):
-    # Python 3 by default will close all fds in subprocesses. This will avoid that.
-    os.set_inheritable(fd1, True)
-    os.set_inheritable(fd2, True)
-  c1 = ExecingProcess_ConnectionWrapper(fd1)
-  c2 = ExecingProcess_ConnectionWrapper(fd2)
-  return c1, c2
-
-
-def Pipe_ConnectionWrapper(*args, **kwargs):
-  from multiprocessing import Pipe
-  c1, c2 = Pipe(*args, **kwargs)
-  c1 = ExecingProcess_ConnectionWrapper(conn=c1)
-  c2 = ExecingProcess_ConnectionWrapper(conn=c2)
-  return c1, c2
-
-
-if sys.platform == "win32":
-  try:
-    from multiprocessing.forking import Popen as mp_Popen
-  except ImportError:
-    from multiprocessing.popen_spawn_win32 import Popen as mp_Popen
-
-  class Win32_mp_Popen_wrapper:
-    def __init__(self, env_update):
-      self.env = os.environ.copy()
-      self.env.update(env_update)
-
-    class Popen(mp_Popen):
-      # noinspection PyMissingConstructor
-      def __init__(self, process_obj, env):
-        # No super init call by intention!
-
-        from multiprocessing.forking import duplicate, get_command_line, _python_exe, close, get_preparation_data, HIGHEST_PROTOCOL, dump
-        import msvcrt
-        import _subprocess
-
-        # create pipe for communication with child
-        rfd, wfd = os.pipe()
-
-        # get handle for read end of the pipe and make it inheritable
-        rhandle = duplicate(msvcrt.get_osfhandle(rfd), inheritable=True)
-        os.close(rfd)
-
-        # start process
-        cmd = get_command_line() + [rhandle]
-        cmd = ' '.join('"%s"' % x for x in cmd)
-        hp, ht, pid, tid = _subprocess.CreateProcess(
-          _python_exe, cmd, None, None, 1, 0, env, None, None
-        )
-        ht.Close()
-        close(rhandle)
-
-        # set attributes of self
-        self.pid = pid
-        self.returncode = None
-        self._handle = hp
-
-        # send information to child
-        prep_data = get_preparation_data(process_obj._name)
-        to_child = os.fdopen(wfd, 'wb')
-        mp_Popen._tls.process_handle = int(hp)
-        try:
-          dump(prep_data, to_child, HIGHEST_PROTOCOL)
-          dump(process_obj, to_child, HIGHEST_PROTOCOL)
-        finally:
-          del mp_Popen._tls.process_handle
-          to_child.close()
-
-    def __call__(self, process_obj):
-      return self.Popen(process_obj, self.env)
-
-
-isFork = False  # fork() without exec()
-isMainProcess = True
-
-
-class AsyncTask:
-  """
-  This uses multiprocessing.Process or ExecingProcess to execute some function.
-  In addition, it provides a duplex pipe for communication. This is either
-  multiprocessing.Pipe or ExecingProcess_Pipe.
-  """
-
-  def __init__(self, func, name=None, mustExec=False, env_update=None):
-    """
-    :param func: a function which gets a single parameter,
-      which will be a reference to our instance in the fork,
-      so that it can use our communication methods put/get.
-    :type str name: name for the sub process
-    :param bool mustExec: if True, we do fork+exec, not just fork
-    :param dict[str,str] env_update: for mustExec, also update these env vars
-    """
-    self.name = name or "unnamed"
-    self.func = func
-    self.mustExec = mustExec
-    self.env_update = env_update
-    self.parent_pid = os.getpid()
-    self.SharedMemNumpyConfig = SharedMemNumpyConfig
-    proc_args = {
-      "target": funcCall,
-      "args": ((AsyncTask, "_asyncCall"), (self,)),
-      "name": self.name + " worker process"
-    }
-    if mustExec and sys.platform != "win32":
-      self.Process = ExecingProcess
-      self.Pipe = ExecingProcess_Pipe
-      proc_args["env_update"] = env_update
-    else:
-      from multiprocessing import Process, Pipe
-      self.Process = Process
-      self.Pipe = Pipe_ConnectionWrapper
-    self.parent_conn, self.child_conn = self.Pipe()
-    self.proc = self.Process(**proc_args)
-    self.proc.daemon = True
-    if sys.platform == 'win32':
-      self.proc._Popen = Win32_mp_Popen_wrapper(env_update=env_update)
-    self.proc.start()
-    self.child_conn.close()
-    self.child_pid = self.proc.pid
-    assert self.child_pid
-    self.conn = self.parent_conn
-
-  @staticmethod
-  def _asyncCall(self):
-    assert self.isChild
-    assert isinstance(self.parent_conn, (ExecingProcess_ConnectionWrapper, Connection))
-    parent_conn_handle = self.parent_conn.fileno()
-    try:
-      self.parent_conn.close()
-    except Exception:
-      # Print this here because in the backtrace, the handle will likely be reset already.
-      print("parent connection close failed; file handle was: %r" % parent_conn_handle)
-      raise
-    self.conn = self.child_conn  # we are the child
-    if not self.mustExec and sys.platform != "win32":
-      global isFork
-      isFork = True
-    global isMainProcess
-    isMainProcess = False
-    global SharedMemNumpyConfig
-    SharedMemNumpyConfig = self.SharedMemNumpyConfig
-    try:
-      self.func(self)
-    except KeyboardInterrupt:
-      print("Exception in AsyncTask %s: KeyboardInterrupt" % self.name)
-      sys.exit(1)
-    except SystemExit:
-      raise
-    except BaseException:
-      print("Exception in AsyncTask %s" % self.name)
-      sys.excepthook(*sys.exc_info())
-      sys.exit(1)
-    finally:
-      self.conn.close()
-
-  def put(self, value):
-    self.conn.send(value)
-
-  def get(self):
-    thread = currentThread()
-    try:
-      thread.waitQueue = self
-      res = self.conn.recv()
-    except EOFError: # this happens when the child died
-      raise ForwardedKeyboardInterrupt()
-    except Exception:
-      raise
-    finally:
-      thread.waitQueue = None
-    return res
-
-  @property
-  def isParent(self):
-    return self.parent_pid == os.getpid()
-
-  @property
-  def isChild(self):
-    if self.isParent: return False
-    # Note: self.parent_pid != os.getppid() if the parent died.
-    return True
-
-  # This might be called from the module code.
-  # See OnRequestQueue which implements the same interface.
-  def setCancel(self):
-    self.conn.close()
-    if self.isParent and self.child_pid:
-      import signal
-      try:
-        os.kill(self.child_pid, signal.SIGINT)
-      except OSError:
-        # Could be that the process already died or so. Just ignore and assume it is dead.
-        pass
-      self.child_pid = None
-
-  terminate = setCancel  # alias
-
-  def join(self, timeout=None):
-    return self.proc.join(timeout=timeout)
-
-  def is_alive(self):
-    return self.proc.is_alive()
-
-
-def WarnMustNotBeInForkDecorator(func):
-  class Ctx:
-    didWarn = False
-  def decoratedFunc(*args, **kwargs):
-    global isFork
-    if isFork:
-      if not Ctx.didWarn:
-        print("Must not be in fork!")
-        Ctx.didWarn = True
-      return None
-    return func(*args, **kwargs)
-  return decoratedFunc
-
-
-class ReadWriteLock(object):
-  """Classic implementation of ReadWriteLock.
-  Note that this partly supports recursive lock usage:
-  - Inside a readlock, a writelock will always block!
-  - Inside a readlock, another readlock is fine.
-  - Inside a writelock, any other writelock or readlock is fine.
-  """
-  def __init__(self):
-    import threading
-    self.lock = threading.RLock()
-    self.writeReadyCond = threading.Condition(self.lock)
-    self.readerCount = 0
-  @property
-  @contextmanager
-  def readlock(self):
-    with self.lock:
-      self.readerCount += 1
-    try: yield
-    finally:
-      with self.lock:
-        self.readerCount -= 1
-        if self.readerCount == 0:
-          self.writeReadyCond.notifyAll()
-  @property
-  @contextmanager
-  def writelock(self):
-    with self.lock:
-      while self.readerCount > 0:
-        self.writeReadyCond.wait()
-      yield
-
-
-if __name__ == "__main__":
-  # Make this the right module package.
-  sys.path.insert(0, os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + "/../.."))
-  import returnn.util  # make sure parent package exists
-  __package__ = "returnn.util"
-  __name__ = "returnn.util.task_system"
-  mod = sys.modules["__main__"]
-  sys.modules[__name__] = mod
-  returnn.util.task_system = mod  # need to set this
-  import returnn.util.task_system as mod_  # make sure this works now
-  assert mod is mod_
-  try:
-    ExecingProcess.checkExec()  # Never returns if this proc is called via ExecingProcess.
-  except KeyboardInterrupt:
-    sys.exit(1)
-  print("You are not expected to call this. This is for ExecingProcess.")
-  sys.exit(1)
diff --git a/returnn/util/task_system_example.py b/returnn/util/task_system_example.py
deleted file mode 100644
--- a/returnn/util/task_system_example.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-
-import sys
-from .task_system import AsyncTask
-
-
-def start():
-  # Create a real subprocess, not just a fork.
-  async_task = AsyncTask(func=process, name="My sub process", mustExec=True)
-
-  # We have a connection (duplex pipe) to communicate.
-  async_task.conn.send(print_action)
-  assert async_task.conn.recv() == 42
-  async_task.conn.send(sys.exit)
-
-  async_task.join()
-
-
-def print_action():
-  print("Hello")
-  return 42
-
-
-def process(async_task):
-  while True:
-    action = async_task.conn.recv()
-    res = action()
-    async_task.conn.send(res)
-
-
-if __name__ == "__main__":
-  start()


Test patch:
diff --git a/tests/lint_common.py b/tests/lint_common.py
--- a/tests/lint_common.py
+++ b/tests/lint_common.py
@@ -103,7 +103,6 @@
 ignore_count_for_files = {
   'returnn/util/fsa.py',
   'returnn/util/task_system.py',
-  'returnn/util/task_system_example.py',
   'returnn/datasets/bundle_file.py',
   'returnn/datasets/cached.py',
   'returnn/datasets/normalization_data.py',
diff --git a/tests/test_TaskSystem.py b/tests/test_TaskSystem.py
--- a/tests/test_TaskSystem.py
+++ b/tests/test_TaskSystem.py
@@ -95,40 +95,3 @@ def test_pickle():
   s = pickle_dumps(obj.method)
   inst = pickle_loads(s)
   assert_equal(inst(), 42)
-
-
-def test_AsyncTask():
-  def func(asyncTask):
-    """
-    :type asyncTask: AsyncTask
-    """
-    print("Hello Async")
-    asyncTask.conn.send("hello c2p")
-    assert_equal(asyncTask.conn.recv(), "hello p2c")
-  proc = AsyncTask(
-    func=func,
-    name="AsyncTask proc",
-    mustExec=True,
-    env_update={})
-  assert_equal(proc.conn.recv(), "hello c2p")
-  proc.conn.send("hello p2c")
-  proc.join()
-
-
-def test_AsyncTask_chdir():
-  os.chdir("/")
-  def func(asyncTask):
-    """
-    :type asyncTask: AsyncTask
-    """
-    print("Hello Async")
-    asyncTask.conn.send("hello c2p")
-    assert_equal(asyncTask.conn.recv(), "hello p2c")
-  proc = AsyncTask(
-    func=func,
-    name="AsyncTask proc",
-    mustExec=True,
-    env_update={})
-  assert_equal(proc.conn.recv(), "hello c2p")
-  proc.conn.send("hello p2c")
-  proc.join()