1# Copyright (C) 2007-2020 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman.  If not, see <https://www.gnu.org/licenses/>.
17
18"""Interface for runners."""
19
20from public import public
21from zope.interface import Attribute, Interface
22
23
24@public
25class RunnerCrashEvent:
26    """Triggered when a runner encounters an exception in _dispose()."""
27
28    def __init__(self, runner, mlist, msg, metadata, error):
29        self.runner = runner
30        self.mailing_list = mlist
31        self.message = msg
32        self.metadata = metadata
33        self.error = error
34
35
36@public
37class RunnerInterrupt(Exception):
38    """A runner received a system call interrupting signal.
39
40    PEP 475 automatically, and at the C layer, retries system calls such as
41    time.sleep().  This can mean runners with long sleeps in their _snooze()
42    method won't actually exit.  This exception is always raised in Mailman's
43    runner signal handlers to prevent this behavior.  Runners that implement
44    their own .run() method must be prepared to handle this, usually by
45    ignoring it.
46    """
47
48
49@public
50class IRunner(Interface):
51    """The runner."""
52
53    def run():
54        """Start the runner."""
55
56    def stop():
57        """Stop the runner on the next iteration through the loop."""
58
59    is_queue_runner = Attribute("""\
60        A boolean variable describing whether the runner is a queue runner.
61        """)
62
63    queue_directory = Attribute(
64        'The queue directory.  Overridden in subclasses.')
65
66    sleep_time = Attribute("""\
67        The number of seconds this runner will sleep between iterations
68        through the main loop.
69        """)
70
71    def set_signals():
72        """Set up the signal handlers necessary to control the runner.
73
74        The runner should catch the following signals:
75        - SIGTERM and SIGINT: treated exactly the same, they cause the runner
76          to exit with no restart from the master.
77        - SIGUSR1: Also causes the runner to exit, but the master watcher will
78          retart it.
79        - SIGHUP: Re-open the log files.
80        """
81
82    def _one_iteration():
83        """The work done in one iteration of the main loop.
84
85        Can be overridden by subclasses.
86
87        :return: The number of files still left to process.
88        :rtype: int
89        """
90
91    def _process_one_file(msg, msgdata):
92        """Process one queue file.
93
94        :param msg: The message object.
95        :type msg: `email.message.Message`
96        :param msgdata: The message metadata.
97        :type msgdata: dict
98        """
99
100    def _clean_up():
101        """Clean up upon exit from the main processing loop.
102
103        Called when the runner's main loop is stopped, this should perform any
104        necessary resource deallocation.
105        """
106
107    def _dispose(mlist, msg, msgdata):
108        """Dispose of a single message destined for a mailing list.
109
110        Called for each message that the runner is responsible for, this is
111        the primary overridable method for processing each message.
112        Subclasses, must provide implementation for this method.
113
114        :param mlist: The mailing list this message is destined for.
115        :type mlist: `IMailingList`
116        :param msg: The message being processed.
117        :type msg: `email.message.Message`
118        :param msgdata: The message metadata.
119        :type msgdata: dict
120        :return: True if the message should continue to be queued, False if
121            the message should be deleted automatically.
122        :rtype: bool
123        """
124
125    def _do_periodic():
126        """Do some arbitrary periodic processing.
127
128        Called every once in a while both from the runner's main loop, and
129        from the runner's hash slice processing loop.  You can do whatever
130        special periodic processing you want here.
131        """
132
133    def _snooze(filecnt):
134        """Sleep for a little while.
135
136        :param filecnt: The number of messages in the queue the last time
137            through.  Runners can decide to continue to do work, or sleep for
138            a while based on this value.  By default, the base runner only
139            snoozes when there was nothing to do last time around.
140        :type filecnt: int
141        """
142
143    def _short_circuit():
144        """Should processing be short-circuited?
145
146        :return: True if the file processing loop should exit before it's
147            finished processing each message in the current slice of hash
148            space.  False tells _one_iteration() to continue processing until
149            the current snapshot of hash space is exhausted.
150        :rtype: bool
151        """
152