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()