docs: def subprocess_func(func, pipe, logger, mem_in_mb, cpu_time_limit_in_s, wall_time_limit_in_s, num_procs, grace_period_in_s, tmp_dir, *args, **kwargs):
  
  	# simple signal handler to catch the signals for time limits
  	def handler(signum, frame):
  		# logs message with level debug on this logger 
  		logger.debug("signal handler: %i"%signum)
  		if (signum == signal.SIGXCPU):
  			# when process reaches soft limit --> a SIGXCPU signal is sent (it normally terminats the process)
  			raise(CpuTimeoutException)
  		elif (signum == signal.SIGALRM):
  			# SIGALRM is sent to process when the specified time limit to an alarm function elapses (when real or clock time elapses)
  			logger.debug("timeout")
  			raise(TimeoutException)
  		raise AnythingException
  
  	# temporary directory to store stdout and stderr
  	if not tmp_dir is None:
  		logger.debug('Redirecting output of the function to files. Access them via the stdout and stderr attributes of the wrapped function.')
  		
  		stdout = open(os.path.join(tmp_dir, 'std.out'), 'a', buffering=1)
  		sys.stdout=stdout
  
  		stderr = open(os.path.join(tmp_dir, 'std.err'), 'a', buffering=1)
  		sys.stderr=stderr
  
  
  	# catching all signals at this point turned out to interfer with the subprocess (e.g. using ROS)
  	signal.signal(signal.SIGALRM, handler)
  	signal.signal(signal.SIGXCPU, handler)
  	signal.signal(signal.SIGQUIT, handler)
  
  	# code to catch EVERY catchable signal (even X11 related ones ... )
  	# only use for debugging/testing as this seems to be too intrusive.
  	"""
  	for i in [x for x in dir(signal) if x.startswith("SIG")]:
  		try:
  			signum = getattr(signal,i)
  			print("register {}, {}".format(signum, i))
  			signal.signal(signum, handler)
  		except:
  			print("Skipping %s"%i)
  	"""
  
  	# set the memory limit
  	if mem_in_mb is not None:
  		# byte --> megabyte
  		mem_in_b = mem_in_mb*1024*1024
  		# the maximum area (in bytes) of address space which may be taken by the process.
  		resource.setrlimit(resource.RLIMIT_AS, (mem_in_b, mem_in_b))
  
  	# for now: don't allow the function to spawn subprocesses itself.
  	#resource.setrlimit(resource.RLIMIT_NPROC, (1, 1))
  	# Turns out, this is quite restrictive, so we don't use this option by default
  	if num_procs is not None:
  		resource.setrlimit(resource.RLIMIT_NPROC, (num_procs, num_procs))
  
  
  	# schedule an alarm in specified number of seconds
  	if wall_time_limit_in_s is not None:
  		signal.alarm(wall_time_limit_in_s)
  	
  	if cpu_time_limit_in_s is not None:
  		# From the Linux man page:
  		# When the process reaches the soft limit, it is sent a SIGXCPU signal.
  		# The default action for this signal is to terminate the process.
  		# However, the signal can be caught, and the handler can return control 
  		# to the main program. If the process continues to consume CPU time,
  		# it will be sent SIGXCPU once per second until the hard limit is reached,
  		# at which time it is sent SIGKILL.
  		resource.setrlimit(resource.RLIMIT_CPU, (cpu_time_limit_in_s,cpu_time_limit_in_s+grace_period_in_s))
  
  	# the actual function call
  	try:
  		logger.debug("call function")
  		return_value = ((func(*args, **kwargs), 0))
  		logger.debug("function returned properly: {}".format(return_value))
  	except MemoryError:
  		return_value = (None, MemorylimitException)
  
  	except OSError as e:
  		if (e.errno == 11):
  			return_value = (None, SubprocessException)
  		else:
  			return_value = (None, AnythingException)
  
  	except CpuTimeoutException:
  		return_value = (None, CpuTimeoutException)
  
  	except TimeoutException:
  		return_value = (None, TimeoutException)
  
  	except AnythingException as e:
  		return_value = (None, AnythingException)
  	except:
  		raise
  		logger.debug("Some wired exception occured!")
  		
  	finally:
  		try:
  			logger.debug("return value: {}".format(return_value))
  			
  			pipe.send(return_value)
  			pipe.close()
  
  		except:
  			# this part should only fail if the parent process is alread dead, so there is not much to do anymore :)
  			pass
  		finally:
  			# recursively kill all children
  			p = psutil.Process()
  			for child in p.children(recursive=True):
  				child.kill()