blob: a3078a873dcaa7f45c8c14b49fe96f8ef96cae27 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# BitBake Process based server.
3#
4# Copyright (C) 2010 Bob Foerster <robert@erafx.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19"""
20 This module implements a multiprocessing.Process based server for bitbake.
21"""
22
23import bb
24import bb.event
25import itertools
26import logging
27import multiprocessing
28import os
29import signal
30import sys
31import time
32import select
33from Queue import Empty
34from multiprocessing import Event, Process, util, Queue, Pipe, queues, Manager
35
36from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer
37
38logger = logging.getLogger('BitBake')
39
40class ServerCommunicator():
41 def __init__(self, connection, event_handle, server):
42 self.connection = connection
43 self.event_handle = event_handle
44 self.server = server
45
46 def runCommand(self, command):
47 # @todo try/except
48 self.connection.send(command)
49
50 if not self.server.is_alive():
51 raise SystemExit
52
53 while True:
54 # don't let the user ctrl-c while we're waiting for a response
55 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050056 for idx in range(0,4): # 0, 1, 2, 3
57 if self.connection.poll(5):
58 return self.connection.recv()
59 else:
60 bb.warn("Timeout while attempting to communicate with bitbake server")
61 bb.fatal("Gave up; Too many tries: timeout while attempting to communicate with bitbake server")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062 except KeyboardInterrupt:
63 pass
64
65 def getEventHandle(self):
66 return self.event_handle.value
67
68class EventAdapter():
69 """
70 Adapter to wrap our event queue since the caller (bb.event) expects to
71 call a send() method, but our actual queue only has put()
72 """
73 def __init__(self, queue):
74 self.queue = queue
75
76 def send(self, event):
77 try:
78 self.queue.put(event)
79 except Exception as err:
80 print("EventAdapter puked: %s" % str(err))
81
82
83class ProcessServer(Process, BaseImplServer):
84 profile_filename = "profile.log"
85 profile_processed_filename = "profile.log.processed"
86
87 def __init__(self, command_channel, event_queue, featurelist):
88 BaseImplServer.__init__(self)
89 Process.__init__(self)
90 self.command_channel = command_channel
91 self.event_queue = event_queue
92 self.event = EventAdapter(event_queue)
93 self.featurelist = featurelist
94 self.quit = False
95
96 self.quitin, self.quitout = Pipe()
97 self.event_handle = multiprocessing.Value("i")
98
99 def run(self):
100 for event in bb.event.ui_queue:
101 self.event_queue.put(event)
102 self.event_handle.value = bb.event.register_UIHhandler(self, True)
103
104 bb.cooker.server_main(self.cooker, self.main)
105
106 def main(self):
107 # Ignore SIGINT within the server, as all SIGINT handling is done by
108 # the UI and communicated to us
109 self.quitin.close()
110 signal.signal(signal.SIGINT, signal.SIG_IGN)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500111 bb.utils.set_process_name("Cooker")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500112 while not self.quit:
113 try:
114 if self.command_channel.poll():
115 command = self.command_channel.recv()
116 self.runCommand(command)
117 if self.quitout.poll():
118 self.quitout.recv()
119 self.quit = True
120 try:
121 self.runCommand(["stateForceShutdown"])
122 except:
123 pass
124
125 self.idle_commands(.1, [self.command_channel, self.quitout])
126 except Exception:
127 logger.exception('Running command %s', command)
128
129 self.event_queue.close()
130 bb.event.unregister_UIHhandler(self.event_handle.value)
131 self.command_channel.close()
132 self.cooker.shutdown(True)
133 self.quitout.close()
134
135 def idle_commands(self, delay, fds=None):
136 nextsleep = delay
137 if not fds:
138 fds = []
139
140 for function, data in self._idlefuns.items():
141 try:
142 retval = function(self, data, False)
143 if retval is False:
144 del self._idlefuns[function]
145 nextsleep = None
146 elif retval is True:
147 nextsleep = None
148 elif isinstance(retval, float):
149 if (retval < nextsleep):
150 nextsleep = retval
151 elif nextsleep is None:
152 continue
153 else:
154 fds = fds + retval
155 except SystemExit:
156 raise
157 except Exception as exc:
158 if not isinstance(exc, bb.BBHandledException):
159 logger.exception('Running idle function')
160 del self._idlefuns[function]
161 self.quit = True
162
163 if nextsleep is not None:
164 select.select(fds,[],[],nextsleep)
165
166 def runCommand(self, command):
167 """
168 Run a cooker command on the server
169 """
170 self.command_channel.send(self.cooker.command.runCommand(command))
171
172 def stop(self):
173 self.quitin.send("quit")
174 self.quitin.close()
175
176class BitBakeProcessServerConnection(BitBakeBaseServerConnection):
177 def __init__(self, serverImpl, ui_channel, event_queue):
178 self.procserver = serverImpl
179 self.ui_channel = ui_channel
180 self.event_queue = event_queue
181 self.connection = ServerCommunicator(self.ui_channel, self.procserver.event_handle, self.procserver)
182 self.events = self.event_queue
183 self.terminated = False
184
185 def sigterm_terminate(self):
186 bb.error("UI received SIGTERM")
187 self.terminate()
188
189 def terminate(self):
190 if self.terminated:
191 return
192 self.terminated = True
193 def flushevents():
194 while True:
195 try:
196 event = self.event_queue.get(block=False)
197 except (Empty, IOError):
198 break
199 if isinstance(event, logging.LogRecord):
200 logger.handle(event)
201
202 signal.signal(signal.SIGINT, signal.SIG_IGN)
203 self.procserver.stop()
204
205 while self.procserver.is_alive():
206 flushevents()
207 self.procserver.join(0.1)
208
209 self.ui_channel.close()
210 self.event_queue.close()
211 self.event_queue.setexit()
212
213# Wrap Queue to provide API which isn't server implementation specific
214class ProcessEventQueue(multiprocessing.queues.Queue):
215 def __init__(self, maxsize):
216 multiprocessing.queues.Queue.__init__(self, maxsize)
217 self.exit = False
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500218 bb.utils.set_process_name("ProcessEQueue")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500219
220 def setexit(self):
221 self.exit = True
222
223 def waitEvent(self, timeout):
224 if self.exit:
225 sys.exit(1)
226 try:
227 if not self.server.is_alive():
228 self.setexit()
229 return None
230 return self.get(True, timeout)
231 except Empty:
232 return None
233
234 def getEvent(self):
235 try:
236 if not self.server.is_alive():
237 self.setexit()
238 return None
239 return self.get(False)
240 except Empty:
241 return None
242
243
244class BitBakeServer(BitBakeBaseServer):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500245 def initServer(self, single_use=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 # establish communication channels. We use bidirectional pipes for
247 # ui <--> server command/response pairs
248 # and a queue for server -> ui event notifications
249 #
250 self.ui_channel, self.server_channel = Pipe()
251 self.event_queue = ProcessEventQueue(0)
252 self.serverImpl = ProcessServer(self.server_channel, self.event_queue, None)
253 self.event_queue.server = self.serverImpl
254
255 def detach(self):
256 self.serverImpl.start()
257 return
258
259 def establishConnection(self, featureset):
260
261 self.connection = BitBakeProcessServerConnection(self.serverImpl, self.ui_channel, self.event_queue)
262
263 _, error = self.connection.connection.runCommand(["setFeatures", featureset])
264 if error:
265 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
266 raise BaseException(error)
267 signal.signal(signal.SIGTERM, lambda i, s: self.connection.sigterm_terminate())
268 return self.connection