Bug #336142 - Use a longer timeout for ebuild-ipc, in case the system is
[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 array
9 import logging
10 import os
11 import pickle
12 import select
13 import signal
14 import sys
15 import time
16
17 def debug_signal(signum, frame):
18         import pdb
19         pdb.set_trace()
20 signal.signal(signal.SIGUSR1, debug_signal)
21
22 # Avoid sandbox violations after python upgrade.
23 pym_path = os.path.join(os.path.dirname(
24         os.path.dirname(os.path.realpath(__file__))), "pym")
25 if os.environ.get("SANDBOX_ON") == "1":
26         sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":")
27         if pym_path not in sandbox_write:
28                 sandbox_write.append(pym_path)
29                 os.environ["SANDBOX_WRITE"] = \
30                         ":".join(filter(None, sandbox_write))
31
32 import portage
33 portage._disable_legacy_globals()
34
35 class EbuildIpc(object):
36
37         # If the system is heavily loaded then the parent process might
38         # be slow to respond, so give it plenty of time (bug #336142).
39         _COMMUNICATE_TIMEOUT_SECONDS = 900 # 15 minutes
40         _BUFSIZE = 4096
41
42         def __init__(self):
43                 self.fifo_dir = os.environ['PORTAGE_BUILDDIR']
44                 self.ipc_in_fifo = os.path.join(self.fifo_dir, '.ipc_in')
45                 self.ipc_out_fifo = os.path.join(self.fifo_dir, '.ipc_out')
46                 self.ipc_lock_file = os.path.join(self.fifo_dir, '.ipc_lock')
47
48         def communicate(self, args):
49
50                 # Make locks quiet since unintended locking messages displayed on
51                 # stdout could corrupt the intended output of this program.
52                 portage.locks._quiet = True
53                 lock_obj = portage.locks.lockfile(self.ipc_lock_file, unlinkfile=True)
54                 start_time = time.time()
55
56                 try:
57                         try:
58                                 portage.exception.AlarmSignal.register(
59                                         self._COMMUNICATE_TIMEOUT_SECONDS)
60                                 returncode = self._communicate(args)
61                                 return returncode
62                         finally:
63                                 portage.exception.AlarmSignal.unregister()
64                                 portage.locks.unlockfile(lock_obj)
65                 except portage.exception.AlarmSignal:
66                         time_elapsed = time.time() - start_time
67                         portage.util.writemsg_level(
68                                 ('ebuild-ipc timed out after %d seconds\n') % \
69                                 (time_elapsed,),
70                                 level=logging.ERROR, noiselevel=-1)
71                         return 1
72
73         def _communicate(self, args):
74                 input_fd = os.open(self.ipc_out_fifo, os.O_RDONLY|os.O_NONBLOCK)
75
76                 # File streams are in unbuffered mode since we do atomic
77                 # read and write of whole pickles.
78                 input_file = os.fdopen(input_fd, 'rb', 0)
79                 output_file = open(self.ipc_in_fifo, 'wb', 0)
80
81                 # Write the whole pickle in a single atomic write() call,
82                 # since the reader is in non-blocking mode and we want
83                 # it to get the whole pickle at once.
84                 output_file.write(pickle.dumps(args))
85                 output_file.flush()
86
87                 events = select.select([input_file], [], [])
88
89                 # Read the whole pickle in a single atomic read() call.
90                 buf = array.array('B')
91                 try:
92                         buf.fromfile(input_file, self._BUFSIZE)
93                 except (EOFError, IOError) as e:
94                         if not buf:
95                                 portage.util.writemsg("%s\n" % (e,), noiselevel=-1)
96
97                 rval = 2
98
99                 if buf:
100
101                         try:
102                                 reply = pickle.loads(buf.tostring())
103                         except (EnvironmentError, EOFError, ValueError,
104                                 pickle.UnpicklingError) as e:
105                                 portage.util.writemsg("%s\n" % (e,), noiselevel=-1)
106
107                         else:
108                                 output_file.close()
109                                 input_file.close()
110
111                                 (out, err, rval) = reply
112
113                                 if out:
114                                         portage.util.writemsg_stdout(out, noiselevel=-1)
115
116                                 if err:
117                                         portage.util.writemsg(err, noiselevel=-1)
118
119                 return rval
120
121 def ebuild_ipc_main(args):
122         ebuild_ipc = EbuildIpc()
123         return ebuild_ipc.communicate(args)
124
125 if __name__ == '__main__':
126         sys.exit(ebuild_ipc_main(sys.argv[1:]))