blob: 982fcf71c3249e6830b2043ee3ba30cfe283c608 [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
Patrick Williamsc0f7c042017-02-23 20:41:17 -060033from queue import Empty
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034from 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600140 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600148 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600216 multiprocessing.queues.Queue.__init__(self, maxsize, ctx=multiprocessing.get_context())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500217 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:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600225 return self.getEvent()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500226 try:
227 if not self.server.is_alive():
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600228 return self.getEvent()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229 return self.get(True, timeout)
230 except Empty:
231 return None
232
233 def getEvent(self):
234 try:
235 if not self.server.is_alive():
236 self.setexit()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237 return self.get(False)
238 except Empty:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600239 if self.exit:
240 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500241 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