summaryrefslogtreecommitdiff
path: root/distbuild/subprocess_eventsrc.py
blob: e025161e7ad63b061fb3df643664d4ea3fd78713 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# distbuild/subprocess_eventsrc.py -- for managing subprocesses
#
# Copyright (C) 2014-2015  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.


import logging
import os
import signal

import distbuild


class FileReadable(object):

    def __init__(self, request_id, p, f):
        self.request_id = request_id
        self.process = p
        self.file = f


class FileWriteable(object):

    def __init__(self, request_id, p, f):
        self.request_id = request_id
        self.process = p
        self.file = f


class SubprocessEventSource(distbuild.EventSource):
    '''Event source for monitoring one or more subprocesses.

    This will send FileReadable and FileWritable events based on the
    stdin and stdout and stderr handles of each subprocesses.

    When the subprocess terminates, you'll receive final FileReadable events
    for stdout and for stderr. At that point, reading from those file
    descriptors will return None, at which point you can be sure that the
    subprocess is no longer running.

    '''

    def __init__(self):
        self.procs = []
        self.closed = False

    def get_select_params(self):
        r = []
        w = []
        for requst_id, p in self.procs:
            if p.stdin_contents is not None:
                w.append(p.stdin)
            if p.stdout is not None:
                r.append(p.stdout)
            if p.stderr is not None:
                r.append(p.stderr)
        return r, w, [], None

    def get_events(self, r, w, x):
        events = []

        for request_id, p in self.procs:
            if p.stdin in w:
                events.append(FileWriteable(request_id, p, p.stdin))
            if p.stdout in r:
                events.append(FileReadable(request_id, p, p.stdout))
            if p.stderr in r:
                events.append(FileReadable(request_id, p, p.stderr))

        return events

    def add(self, request_id, process):

        self.procs.append((request_id, process))
        distbuild.set_nonblocking(process.stdin)
        distbuild.set_nonblocking(process.stdout)
        distbuild.set_nonblocking(process.stderr)

    def remove(self, process):
        self.procs = [t for t in self.procs if t[1] != process]

    def kill_by_id(self, request_id):
        logging.debug('SES: Killing all processes for %s', request_id)
        for id, process in self.procs:
            if id == request_id:
                logging.debug('SES: killing process group of %r', process)
                os.killpg(process.pid, signal.SIGKILL)

    def close(self):
        self.procs = []
        self.closed = True

    def is_finished(self):
        return self.closed