Skip to content
Snippets Groups Projects
Commit 871d1ab3 authored by Anael Beutot's avatar Anael Beutot
Browse files

Implemented BaseIOJob.

Uses a fork and permits to set ionice on child.

/!\ Don't use logging for logging as it would (maybe) end in a deadlock for the
child.
parent ef42c95f
No related branches found
No related tags found
No related merge requests found
...@@ -36,3 +36,7 @@ class ConsoleAlreadyOpened(CCNodeError): ...@@ -36,3 +36,7 @@ class ConsoleAlreadyOpened(CCNodeError):
class VMMigrationError(CCNodeError): class VMMigrationError(CCNodeError):
pass pass
class JobError(CCNodeError):
pass
import errno
import logging import logging
from threading import Thread import os
import resource
import signal
import subprocess
import sys
from threading import Thread, Event
from cloudcontrol.node.exc import JobError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -130,3 +138,155 @@ class BaseThreadedJob(Thread): ...@@ -130,3 +138,155 @@ class BaseThreadedJob(Thread):
def stop(self): def stop(self):
self.running = False self.running = False
class BaseIOJob(object):
"""Job that can set ionice and executes in a fork.
When inherit, you must define open_fds property that list file descriptors
that must be kept in the child and closed in the parent.
.. warning::
logging should not be used in the child as this would cause a deadlock
to occur, see http://bugs.python.org/issue6721
"""
IO_NICE = 7
def __init__(self, job_manager):
self.job_manager = job_manager
#: job id
self.id = job_manager.job_id.next()
self.running = False
# event for other thread to wait for job termination
self.job_done = Event()
self.fork_pid = None
def pre_job(self):
# overide in sub class
pass
def run_job(self):
# overide in sub class
pass
def set_io_nice(self):
try:
subprocess.check_call(['ionice', '-n%d' % self.IO_NICE,
'-p%d' % os.getpid()], close_fds=True)
except subprocess.CalledProcessError as exc:
sys.stderr.write('Cannot set ionice, return code %s\n' % exc.returncode)
def close_fds(self):
"""Close all fds uneeded fds in children."""
# get max fd
limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if limit == resource.RLIM_INFINITY:
max_fd = 2048
else:
max_fd = limit
exclude_fd = self.open_fds
if self.job_manager.main.config.debug:
exclude_fd += [0, 1, 2] # debug
for fd in xrange(max_fd, -1, -1):
if fd in exclude_fd:
continue
try:
os.close(fd)
except OSError as exc:
if exc.errno != errno.EBADF:
raise
# wasn't open
if not self.job_manager.main.config.debug:
sys.stdin = open(os.devnull)
sys.stdout = open(os.devnull)
sys.stderr = open(os.devnull)
assert sys.stdin.fileno() == 0
assert sys.stdout.fileno() == 1
assert sys.stderr.fileno() == 2
def reset_signal_mask(self):
signal.signal(signal.SIGTERM, lambda *args: os._exit(1))
signal.signal(signal.SIGUSR1, signal.SIG_IGN)
signal.signal(signal.SIGINT, signal.SIG_IGN)
def run(self):
try:
self.fork_pid = os.fork()
except OSError as exc:
logger.error('Cannot fork: %s', os.strerror(exc.errno))
raise
self.running = True
if self.fork_pid == 0:
# child
self.reset_signal_mask()
self.close_fds()
self.set_io_nice()
try:
self.run_job()
except:
sys.stderr.write('Error during job\n')
os._exit(1)
else:
sys.stderr.write('Job execution went well\n')
os._exit(0)
else:
# close child fds
for fd in self.open_fds:
try:
os.close(fd)
except OSError:
logger.error('Error while closing fds in parent')
raise
def start(self):
self.pre_job()
self.run()
def stop(self):
try:
os.kill(self.fork_pid, signal.SIGKILL)
except OSError as exc:
if exc.errno == errno.ESRCH:
logger.debug('Child already killed')
return
logger.error('Cannot kill child for IO job: %s',
os.strerror(exc.errno))
raise
def notify(self):
self.job_manager.notify(self)
def wait(self):
if self.fork_pid is None:
return
try:
while True:
try:
pid, return_status = os.waitpid(self.fork_pid, 0)
except OSError as exc:
if exc.errno == errno.EINTR:
continue
logger.error('Error while waiting for child to terminate: %s',
os.strerror(exc.errno))
raise
else:
break
assert pid == self.fork_pid
if return_status >> 8 != 0:
raise JobError('Exception during job, returned %s',
return_status)
finally:
self.fork_pid = None
self.job_done.set()
self.notify()
def join(self):
self.job_done.wait()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment