File: procctrl.py

package info (click to toggle)
pyqonsole 0.2.0-2
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 448 kB
  • ctags: 647
  • sloc: python: 4,383; ansic: 111; makefile: 52; sh: 2
file content (245 lines) | stat: -rw-r--r-- 8,698 bytes parent folder | download | duplicates (3)
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE).
# Copyright (c) 2005-2006 CEA Grenoble 
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the CECILL license, available at
# http://www.inria.fr/valorisation/logiciels/Licence.CeCILL-V2.pdf
#
"""KPROCESSCONTROLLER -- A helper class for KProcess


using a struct which contains both the pid and the status makes it
easier to write and read the data into the pipe.
especially this solves a problem which appeared on my box where
slotDoHouseKeeping() received only 4 bytes (with some debug output
around the write()'s it received all 8 bytes). don't know why this
happened, but when writing all 8 bytes at once it works here, aleXXX

struct waitdata
{
  pid_t pid
  int status
}

Based on the konsole code from Lars Doelle.

@author: Lars Doelle
@author: Sylvain Thenault
@copyright: 2003, 2005, 2006
@organization: CEA-Grenoble
@organization: Logilab
@license: CECILL

XXX review singleton aspect
"""
__revision__ = '$Id: procctrl.py,v 1.10 2006-02-15 10:24:01 alf Exp $'

import os
import errno
import fcntl
import select
import signal
import struct
import sys

import qt

def waitChildren():
    """wait for all children process, yield (pid, status) each time one
    is existing
    """
    while 1:
        try:
            yield os.waitpid(-1, os.WNOHANG)
        except OSError, ex:
            if ex.errno == errno.ECHILD:
                break
            raise

theProcessController = None

class ProcessController(qt.QObject):
    """ A class for internal use by Process only. -- Exactly one instance
    of this class is generated by the first instance of Process that is
    created (a pointer to it gets stored in @ref theProcessController ).
    
    This class takes care of the actual (UN*X) signal handling.
    """

    def __init__(self):
        super(ProcessController, self).__init__()        
        global theProcessController
        assert theProcessController is None
        self.old_sigCHLDHandler = None
        self.handler_set = False
        self.process_list = []
        self.fd = os.pipe()
        # delayed children cleanup timer
        self._dcc_timer = qt.QTimer()
        fcntl.fcntl(self.fd[0], fcntl.F_SETFL, os.O_NONBLOCK)
        notifier = qt.QSocketNotifier(self.fd[0], qt.QSocketNotifier.Read, self)
        self.connect(notifier, qt.SIGNAL('activated(int)'),
                     self.slotDoHousekeeping)
        self.connect(self._dcc_timer, qt.SIGNAL('timeout()'),
                                self.delayedChildrenCleanup)
        theProcessController = self
        self.setupHandlers()

##     def __del__(self):
##         global theProcessController
##         assert theProcessController is self
##         self.resetHandlers()
##         self.notifier.setEnabled(False)
##         os.close(self.fd[0])
##         os.close(self.fd[1])
##         del self.notifier
##         theProcessController = None

    def setupHandlers(self):
        if self.handler_set:
            return
        self.old_sigCHLDHandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.sigCHLDHandler)
        #sigaction( SIGCHLD, &act, &self.old_sigCHLDHandler )
        #act.sa_handler=SIG_IGN
        #sigemptyset(&(act.sa_mask))
        #sigaddset(&(act.sa_mask), SIGPIPE)
        #act.sa_flags = 0
        #sigaction( SIGPIPE, &act, 0L)
        signal.signal(signal.SIGPIPE, signal.SIG_IGN)
        self.handler_set = True

    def resetHandlers(self):
        if not self.handler_set:
            return
        signal.signal(signal.SIGCHLD, self.old_sigCHLDHandler)
        # there should be no problem with SIGPIPE staying SIG_IGN
        self.handler_set = False

    def addProcess(self, process):
        # XXX block SIGCHLD handler, because it accesses self.process_list
        #sigset_t newset, oldset
        #sigemptyset( &newset )
        #sigaddset( &newset, SIGCHLD )
        #sigprocmask( SIG_BLOCK, &newset, &oldset )
        self.process_list.append(process)
        #sigprocmask( SIG_SETMASK, &oldset, 0 )

    def removeProcess(self, process):
        # XXX block SIGCHLD handler, because it accesses self.process_list
        #sigset_t newset, oldset
        #sigemptyset( &newset )
        #sigaddset( &newset, SIGCHLD )
        #sigprocmask( SIG_BLOCK, &newset, &oldset )
        self.process_list.remove(process)
        #sigprocmask( SIG_SETMASK, &oldset, 0 )


    def sigCHLDHandler(self, sig, frame):
        """SIGCHLD handler
        
        :signal: int
        :frame: frame object

        Automatically called upon SIGCHLD.
        
        Normally you do not need to do anything with this function but
        if your application needs to disable SIGCHLD for some time for
        reasons beyond your control, you should call this function afterwards
        to make sure that no SIGCHLDs where missed.
        """
        found = False
        # iterating the list doesn't perform any system call
        for process in self.process_list:
            if process.pid is None:
                continue
            if not process.running:
                continue
            try:
                wpid, status = os.waitpid(process.pid, os.WNOHANG)
            except OSError:
                # [Errno 10] No child processes
                # XXX: bug in process.py ?
                continue
            if wpid > 0:
                os.write(self.fd[1], struct.pack('II', wpid, status))
                found = True
        if (not found and
            not self.old_sigCHLDHandler in (signal.SIG_IGN, signal.SIG_DFL)):
            self.old_sigCHLDHandler(sig) # call the old handler
        # handle the rest
        # XXX
        os.write(self.fd[1], struct.pack('II', 0, 0)) # delayed waitpid()

    def slotDoHousekeeping(self, _):
        """NOTE: It can happen that QSocketNotifier fires while
        we have already read from the socket. Deal with it.
        
        read pid and status from the pipe.
        """
        bytes_read = ''
        while not bytes_read:
            try:
                bytes_read = os.read(self.fd[0], struct.calcsize('II'))
            except OSError, ex:
                if ex.errno == errno.EAGAIN:
                    return
                if ex.errno == errno.EINTR:
                    msg = ("Error: pipe read returned errno=%d "
                           "in ProcessController::slotDoHousekeeping")
                    print >> sys.stderr, msg % ex.errno
                    return
        if len(bytes_read) != struct.calcsize('II'):
            msg = "Error: Could not read info from signal handler %d <> %d!"
            print >> sys.stderr, msg % (len(bytes_read), struct.calcsize('II'))
            return
        pid, status = struct.unpack('II', bytes_read)
        if pid == 0:
            self._dcc_timer.start(100, True)
            return
        for process in self.process_list:
            if process.pid == pid:
                process.processHasExited(status)
                return

    def delayedChildrenCleanup(self):
        """this is needed e.g. for popen(), which calls waitpid() checking
        // for its forked child, if we did waitpid() directly in the SIGCHLD
        // handler, popen()'s waitpid() call would fail
        """
        for wpid, status in waitChildren():
            for process in self.process_list:
                if not process.running or process.pid != wpid:
                    continue
                # it's Process, handle it
                os.write(self.fd[1], struct.pack('II', wpid, status))
                break

    def waitForProcessExit(self, timeout):
        """
        * Wait for any process to exit and handle their exit without
        * starting an event loop.
        * This function may cause Process to emit any of its signals.
        *
        * return True if a process exited, False if no process exited within
          @p timeout seconds.
        // Due to a race condition the signal handler may have
        // failed to detect that a pid belonged to a Process
        // and defered handling to delayedChildrenCleanup()
        // Make sure to handle that first.
        """
        if self._dcc_timer.isActive():
            self._dcc_timer.stop()
            self.delayedChildrenCleanup()
        while True:
            rlist, wlist, xlist  = select.select([self.fd[0]], [], [], timeout)
            if not rlist:
                return False
            else:
                self.slotDoHousekeeping(self.fd[0])
                break
        return True


theProcessController = ProcessController()