Source code for pylada.process.process
from abc import ABCMeta, abstractmethod
[docs]class Process(object):
""" Abstract base class of all processes.
This class defines the interface for processes. Derived classes should
overload :py:meth:`start`, :py:meth:`poll`, and :py:meth:`wait`. The
first is called to actually launch the sub-process (for instance, an
actual call to vasp in :py:class:`~pylada.process.program.ProgramProcess`).
It receives a dictionary or :py:class:`Communicator` instance with a
description of how the process should be launched, eg the number of
processors, nodes, and so forth. At this point, an external child program
will generally be running. The second function, :py:meth:`poll`, is
called to check whether the sub-process, say VASP, is still running. It
returns True if the process is finished. The last function is equivalent
to calling :py:meth:`poll` until it returns True.
Futhermore, a process can be :py:meth:`terminated <terminate>`,
:py:meth:`killed <kill>` and :py:meth:`cleaned up <_cleanup>`.
In general, a process is used as follows:
.. code-block:: python
# initialized
process = SomeProcess()
# started on a number of processors
process.start(comm)
# checked
try:
if process.poll():
# finished, do something
except Fail as e:
# error, do something
"""
__metaclass__ = ABCMeta
[docs] def __init__(self, maxtrials=1, **kwargs):
""" Initializes a process. """
super(Process, self).__init__()
self.nberrors = 0
""" Number of times process was restarted.
Some derived instances may well restart a failed sub-process. This is
how often it has been restarted.
"""
self.maxtrials = maxtrials
""" Maximum number of restarts. """
self.process = None
""" Currently running process.
This is the sub-process handled by this instance. At the lowest
level, it is likely an instance of `subprocess.Popen`__. It may,
however, be a further abstraction, such as a
:py:class:`~pylada.process.program.ProgramProcess` instance.
.. __ : http://docs.python.org/library/subprocess.html#subprocess.Popen
"""
self.started = False
""" Whether the process was ever started.
Whether :py:meth:`start` was called. It may only be called once.
"""
@abstractmethod
[docs] def poll(self):
""" Polls current job.
:return: True if the process is finished.
:raise NotStarted: If the process was never launched.
"""
from . import NotStarted
if not self.started: raise NotStarted("Process was never started.")
return self.nbrunning_processes == 0
@property
[docs] def done(self):
""" True if job already finished. """
return self.started and self.process is None
@property
[docs] def nbrunning_processes(self):
""" Number of running processes.
For simple processes, this will be one or zero.
For multitasking processes this may be something more.
"""
return 0 if (not self.started) or self.process is None else 1
@abstractmethod
[docs] def start(self, comm):
""" Starts current job.
:param comm:
Holds information about how to launch an mpi-aware process.
:type comm: :py:class:`~process.mpi.Communicator`
:returns: True if process is already finished.
:raises MPISizeError:
if no communicator is not None and `comm['n'] == 0`. Assumes that
running on 0 processors is an error in resource allocation.
:raise AlreadyStarted:
When called for a second time. Each process should be unique: we
do not want to run the VASP program twice in the same location,
especially not simultaneously.
"""
from . import AlreadyStarted
from .mpi import MPISizeError, Communicator
if self.done: return True
if self.started: raise AlreadyStarted('start cannot be called twice.')
self.started = True
if comm is not None:
if comm['n'] == 0: raise MPISizeError('Empty communicator passed to process.')
self._comm = comm if hasattr(comm, 'machines') else Communicator(**comm)
return False
[docs] def _cleanup(self):
""" Cleans up behind process instance.
This may mean closing standard input/output file, removing temporary
files.. By default, calls cleanup of :py:attr:`process`, and sets
:py:attr:`process` to None.
"""
try:
if hasattr(self.process, '_cleanup'): self.process._cleanup()
finally:
self.process = None
if hasattr(self, '_comm'):
try: self._comm.cleanup()
finally: del self._comm
def __del__(self):
""" Kills process if it exists.
Tries and cleans up this instance cleanly.
If a process exists, it is cleaned. The existence of this function
implies that the reference to this instance should best not be lost
until whatever it is supposed to oversee is finished doing its stuff.
"""
if self.process is None: return
try: self.process.kill()
except: pass
try: self._cleanup()
except: pass
[docs] def terminate(self):
""" Terminates current process. """
if self.process is None: return
try: self.process.terminate()
except: pass
self._cleanup()
[docs] def kill(self):
""" Kills current process. """
if self.process is None: return
try: self.process.kill()
except: pass
self._cleanup()
@abstractmethod
[docs] def wait(self):
""" Waits for process to end, then cleanup. """
from . import NotStarted
if not self.started: raise NotStarted("Process was never started.")
if self.nbrunning_processes == 0: return True