Bug #335777 - Add a 40 second timeout in ebuild-ipc.py, so that if an
[portage.git] / bin / ebuild-ipc.py
1 #!/usr/bin/python
2 # Copyright 2010 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 #
5 # This is a helper which ebuild processes can use
6 # to communicate with portage's main python process.
7
8 import logging
9 import os
10 import pickle
11 import select
12 import signal
13 import sys
14 import time
15
16 def debug_signal(signum, frame):
17         import pdb
18         pdb.set_trace()
19 signal.signal(signal.SIGUSR1, debug_signal)
20
21 # Avoid sandbox violations after python upgrade.
22 pym_path = os.path.join(os.path.dirname(
23         os.path.dirname(os.path.realpath(__file__))), "pym")
24 if os.environ.get("SANDBOX_ON") == "1":
25         sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":")
26         if pym_path not in sandbox_write:
27                 sandbox_write.append(pym_path)
28                 os.environ["SANDBOX_WRITE"] = \
29                         ":".join(filter(None, sandbox_write))
30
31 import portage
32
33 class EbuildIpc(object):
34
35         _COMMUNICATE_TIMEOUT_SECONDS = 40
36
37         def __init__(self):
38                 self.fifo_dir = os.environ['PORTAGE_BUILDDIR']
39                 self.ipc_in_fifo = os.path.join(self.fifo_dir, '.ipc_in')
40                 self.ipc_out_fifo = os.path.join(self.fifo_dir, '.ipc_out')
41                 self.ipc_lock_file = os.path.join(self.fifo_dir, '.ipc_lock')
42
43         def communicate(self, args):
44
45                 # Make locks quiet since unintended locking messages displayed on
46                 # stdout could corrupt the intended output of this program.
47                 portage.locks._quiet = True
48                 lock_obj = portage.locks.lockfile(self.ipc_lock_file, unlinkfile=True)
49                 start_time = time.time()
50
51                 try:
52                         signal.signal(signal.SIGALRM, portage.exception.AlarmSignal.signal_handler)
53                         signal.alarm(self._COMMUNICATE_TIMEOUT_SECONDS)
54                         returncode = self._communicate(args)
55                         signal.alarm(0)
56                         return returncode
57                 except portage.exception.AlarmSignal:
58                         time_elapsed = time.time() - start_time
59                         portage.util.writemsg_level(
60                                 ('ebuild-ipc timed out after %d seconds\n') % \
61                                 (time_elapsed,),
62                                 level=logging.ERROR, noiselevel=-1)
63                         return 1
64                 finally:
65                         signal.alarm(0)
66                         portage.locks.unlockfile(lock_obj)
67
68         def _communicate(self, args):
69                 input_fd = os.open(self.ipc_out_fifo, os.O_RDONLY|os.O_NONBLOCK)
70
71                 # File streams are in unbuffered mode since we do atomic
72                 # read and write of whole pickles.
73                 input_file = os.fdopen(input_fd, 'rb', 0)
74                 output_file = open(self.ipc_in_fifo, 'wb', 0)
75
76                 # Write the whole pickle in a single atomic write() call,
77                 # since the reader is in non-blocking mode and we want
78                 # it to get the whole pickle at once.
79                 output_file.write(pickle.dumps(args))
80                 output_file.flush()
81
82                 events = select.select([input_file], [], [])
83
84                 # Read the whole pickle in a single read() call since
85                 # this stream is in non-blocking mode and pickle.load()
86                 # has been known to raise the following exception when
87                 # reading from a non-blocking stream:
88                 #
89                 #   File "/usr/lib64/python2.6/pickle.py", line 1370, in load
90                 #     return Unpickler(file).load()
91                 #   File "/usr/lib64/python2.6/pickle.py", line 858, in load
92                 #     dispatch[key](self)
93                 #   File "/usr/lib64/python2.6/pickle.py", line 1195, in load_setitem
94                 #     value = stack.pop()
95                 # IndexError: pop from empty list
96
97                 pickle_str = input_file.read()
98                 reply = pickle.loads(pickle_str)
99                 output_file.close()
100                 input_file.close()
101
102                 (out, err, rval) = reply
103
104                 if out:
105                         portage.util.writemsg_stdout(out, noiselevel=-1)
106
107                 if err:
108                         portage.util.writemsg(err, noiselevel=-1)
109
110                 return rval
111
112 def ebuild_ipc_main(args):
113         ebuild_ipc = EbuildIpc()
114         return ebuild_ipc.communicate(args)
115
116 if __name__ == '__main__':
117         sys.exit(ebuild_ipc_main(sys.argv[1:]))