diff options
Diffstat (limited to 'bitbake/lib/prserv/serv.py')
-rw-r--r-- | bitbake/lib/prserv/serv.py | 590 |
1 files changed, 235 insertions, 355 deletions
diff --git a/bitbake/lib/prserv/serv.py b/bitbake/lib/prserv/serv.py index 25dcf8a0ee..dc4be5b620 100644 --- a/bitbake/lib/prserv/serv.py +++ b/bitbake/lib/prserv/serv.py | |||
@@ -1,354 +1,239 @@ | |||
1 | # | 1 | # |
2 | # Copyright BitBake Contributors | ||
3 | # | ||
2 | # SPDX-License-Identifier: GPL-2.0-only | 4 | # SPDX-License-Identifier: GPL-2.0-only |
3 | # | 5 | # |
4 | 6 | ||
5 | import os,sys,logging | 7 | import os,sys,logging |
6 | import signal, time | 8 | import signal, time |
7 | from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler | ||
8 | import threading | ||
9 | import queue | ||
10 | import socket | 9 | import socket |
11 | import io | 10 | import io |
12 | import sqlite3 | 11 | import sqlite3 |
13 | import bb.server.xmlrpcclient | ||
14 | import prserv | 12 | import prserv |
15 | import prserv.db | 13 | import prserv.db |
16 | import errno | 14 | import errno |
17 | import select | 15 | import bb.asyncrpc |
18 | 16 | ||
19 | logger = logging.getLogger("BitBake.PRserv") | 17 | logger = logging.getLogger("BitBake.PRserv") |
20 | 18 | ||
21 | if sys.hexversion < 0x020600F0: | 19 | PIDPREFIX = "/tmp/PRServer_%s_%s.pid" |
22 | print("Sorry, python 2.6 or later is required.") | 20 | singleton = None |
23 | sys.exit(1) | ||
24 | 21 | ||
25 | class Handler(SimpleXMLRPCRequestHandler): | 22 | class PRServerClient(bb.asyncrpc.AsyncServerConnection): |
26 | def _dispatch(self,method,params): | 23 | def __init__(self, socket, server): |
24 | super().__init__(socket, "PRSERVICE", server.logger) | ||
25 | self.server = server | ||
26 | |||
27 | self.handlers.update({ | ||
28 | "get-pr": self.handle_get_pr, | ||
29 | "test-pr": self.handle_test_pr, | ||
30 | "test-package": self.handle_test_package, | ||
31 | "max-package-pr": self.handle_max_package_pr, | ||
32 | "import-one": self.handle_import_one, | ||
33 | "export": self.handle_export, | ||
34 | "is-readonly": self.handle_is_readonly, | ||
35 | }) | ||
36 | |||
37 | def validate_proto_version(self): | ||
38 | return (self.proto_version == (1, 0)) | ||
39 | |||
40 | async def dispatch_message(self, msg): | ||
27 | try: | 41 | try: |
28 | value=self.server.funcs[method](*params) | 42 | return await super().dispatch_message(msg) |
29 | except: | 43 | except: |
30 | import traceback | 44 | self.server.table.sync() |
31 | traceback.print_exc() | ||
32 | raise | 45 | raise |
33 | return value | 46 | else: |
47 | self.server.table.sync_if_dirty() | ||
34 | 48 | ||
35 | PIDPREFIX = "/tmp/PRServer_%s_%s.pid" | 49 | async def handle_test_pr(self, request): |
36 | singleton = None | 50 | '''Finds the PR value corresponding to the request. If not found, returns None and doesn't insert a new value''' |
51 | version = request["version"] | ||
52 | pkgarch = request["pkgarch"] | ||
53 | checksum = request["checksum"] | ||
37 | 54 | ||
55 | value = self.server.table.find_value(version, pkgarch, checksum) | ||
56 | return {"value": value} | ||
38 | 57 | ||
39 | class PRServer(SimpleXMLRPCServer): | 58 | async def handle_test_package(self, request): |
40 | def __init__(self, dbfile, logfile, interface, daemon=True): | 59 | '''Tells whether there are entries for (version, pkgarch) in the db. Returns True or False''' |
41 | ''' constructor ''' | 60 | version = request["version"] |
42 | try: | 61 | pkgarch = request["pkgarch"] |
43 | SimpleXMLRPCServer.__init__(self, interface, | ||
44 | logRequests=False, allow_none=True) | ||
45 | except socket.error: | ||
46 | ip=socket.gethostbyname(interface[0]) | ||
47 | port=interface[1] | ||
48 | msg="PR Server unable to bind to %s:%s\n" % (ip, port) | ||
49 | sys.stderr.write(msg) | ||
50 | raise PRServiceConfigError | ||
51 | 62 | ||
52 | self.dbfile=dbfile | 63 | value = self.server.table.test_package(version, pkgarch) |
53 | self.daemon=daemon | 64 | return {"value": value} |
54 | self.logfile=logfile | ||
55 | self.working_thread=None | ||
56 | self.host, self.port = self.socket.getsockname() | ||
57 | self.pidfile=PIDPREFIX % (self.host, self.port) | ||
58 | |||
59 | self.register_function(self.getPR, "getPR") | ||
60 | self.register_function(self.quit, "quit") | ||
61 | self.register_function(self.ping, "ping") | ||
62 | self.register_function(self.export, "export") | ||
63 | self.register_function(self.dump_db, "dump_db") | ||
64 | self.register_function(self.importone, "importone") | ||
65 | self.register_introspection_functions() | ||
66 | |||
67 | self.quitpipein, self.quitpipeout = os.pipe() | ||
68 | |||
69 | self.requestqueue = queue.Queue() | ||
70 | self.handlerthread = threading.Thread(target = self.process_request_thread) | ||
71 | self.handlerthread.daemon = False | ||
72 | |||
73 | def process_request_thread(self): | ||
74 | """Same as in BaseServer but as a thread. | ||
75 | |||
76 | In addition, exception handling is done here. | ||
77 | |||
78 | """ | ||
79 | iter_count = 1 | ||
80 | # 60 iterations between syncs or sync if dirty every ~30 seconds | ||
81 | iterations_between_sync = 60 | ||
82 | |||
83 | bb.utils.set_process_name("PRServ Handler") | ||
84 | |||
85 | while not self.quitflag: | ||
86 | try: | ||
87 | (request, client_address) = self.requestqueue.get(True, 30) | ||
88 | except queue.Empty: | ||
89 | self.table.sync_if_dirty() | ||
90 | continue | ||
91 | if request is None: | ||
92 | continue | ||
93 | try: | ||
94 | self.finish_request(request, client_address) | ||
95 | self.shutdown_request(request) | ||
96 | iter_count = (iter_count + 1) % iterations_between_sync | ||
97 | if iter_count == 0: | ||
98 | self.table.sync_if_dirty() | ||
99 | except: | ||
100 | self.handle_error(request, client_address) | ||
101 | self.shutdown_request(request) | ||
102 | self.table.sync() | ||
103 | self.table.sync_if_dirty() | ||
104 | |||
105 | def sigint_handler(self, signum, stack): | ||
106 | if self.table: | ||
107 | self.table.sync() | ||
108 | 65 | ||
109 | def sigterm_handler(self, signum, stack): | 66 | async def handle_max_package_pr(self, request): |
110 | if self.table: | 67 | '''Finds the greatest PR value for (version, pkgarch) in the db. Returns None if no entry was found''' |
111 | self.table.sync() | 68 | version = request["version"] |
112 | self.quit() | 69 | pkgarch = request["pkgarch"] |
113 | self.requestqueue.put((None, None)) | ||
114 | 70 | ||
115 | def process_request(self, request, client_address): | 71 | value = self.server.table.find_max_value(version, pkgarch) |
116 | self.requestqueue.put((request, client_address)) | 72 | return {"value": value} |
117 | 73 | ||
118 | def export(self, version=None, pkgarch=None, checksum=None, colinfo=True): | 74 | async def handle_get_pr(self, request): |
119 | try: | 75 | version = request["version"] |
120 | return self.table.export(version, pkgarch, checksum, colinfo) | 76 | pkgarch = request["pkgarch"] |
121 | except sqlite3.Error as exc: | 77 | checksum = request["checksum"] |
122 | logger.error(str(exc)) | 78 | |
123 | return None | 79 | response = None |
124 | |||
125 | def dump_db(self): | ||
126 | """ | ||
127 | Returns a script (string) that reconstructs the state of the | ||
128 | entire database at the time this function is called. The script | ||
129 | language is defined by the backing database engine, which is a | ||
130 | function of server configuration. | ||
131 | Returns None if the database engine does not support dumping to | ||
132 | script or if some other error is encountered in processing. | ||
133 | """ | ||
134 | buff = io.StringIO() | ||
135 | try: | 80 | try: |
136 | self.table.sync() | 81 | value = self.server.table.get_value(version, pkgarch, checksum) |
137 | self.table.dump_db(buff) | 82 | response = {"value": value} |
138 | return buff.getvalue() | 83 | except prserv.NotFoundError: |
139 | except Exception as exc: | 84 | self.logger.error("failure storing value in database for (%s, %s)",version, checksum) |
140 | logger.error(str(exc)) | 85 | |
141 | return None | 86 | return response |
142 | finally: | ||
143 | buff.close() | ||
144 | 87 | ||
145 | def importone(self, version, pkgarch, checksum, value): | 88 | async def handle_import_one(self, request): |
146 | return self.table.importone(version, pkgarch, checksum, value) | 89 | response = None |
90 | if not self.server.read_only: | ||
91 | version = request["version"] | ||
92 | pkgarch = request["pkgarch"] | ||
93 | checksum = request["checksum"] | ||
94 | value = request["value"] | ||
147 | 95 | ||
148 | def ping(self): | 96 | value = self.server.table.importone(version, pkgarch, checksum, value) |
149 | return not self.quitflag | 97 | if value is not None: |
98 | response = {"value": value} | ||
150 | 99 | ||
151 | def getinfo(self): | 100 | return response |
152 | return (self.host, self.port) | 101 | |
102 | async def handle_export(self, request): | ||
103 | version = request["version"] | ||
104 | pkgarch = request["pkgarch"] | ||
105 | checksum = request["checksum"] | ||
106 | colinfo = request["colinfo"] | ||
153 | 107 | ||
154 | def getPR(self, version, pkgarch, checksum): | ||
155 | try: | 108 | try: |
156 | return self.table.getValue(version, pkgarch, checksum) | 109 | (metainfo, datainfo) = self.server.table.export(version, pkgarch, checksum, colinfo) |
157 | except prserv.NotFoundError: | ||
158 | logger.error("can not find value for (%s, %s)",version, checksum) | ||
159 | return None | ||
160 | except sqlite3.Error as exc: | 110 | except sqlite3.Error as exc: |
161 | logger.error(str(exc)) | 111 | self.logger.error(str(exc)) |
162 | return None | 112 | metainfo = datainfo = None |
163 | |||
164 | def quit(self): | ||
165 | self.quitflag=True | ||
166 | os.write(self.quitpipeout, b"q") | ||
167 | os.close(self.quitpipeout) | ||
168 | return | ||
169 | |||
170 | def work_forever(self,): | ||
171 | self.quitflag = False | ||
172 | # This timeout applies to the poll in TCPServer, we need the select | ||
173 | # below to wake on our quit pipe closing. We only ever call into handle_request | ||
174 | # if there is data there. | ||
175 | self.timeout = 0.01 | ||
176 | |||
177 | bb.utils.set_process_name("PRServ") | ||
178 | |||
179 | # DB connection must be created after all forks | ||
180 | self.db = prserv.db.PRData(self.dbfile) | ||
181 | self.table = self.db["PRMAIN"] | ||
182 | 113 | ||
183 | logger.info("Started PRServer with DBfile: %s, IP: %s, PORT: %s, PID: %s" % | 114 | return {"metainfo": metainfo, "datainfo": datainfo} |
184 | (self.dbfile, self.host, self.port, str(os.getpid()))) | ||
185 | |||
186 | self.handlerthread.start() | ||
187 | while not self.quitflag: | ||
188 | ready = select.select([self.fileno(), self.quitpipein], [], [], 30) | ||
189 | if self.quitflag: | ||
190 | break | ||
191 | if self.fileno() in ready[0]: | ||
192 | self.handle_request() | ||
193 | self.handlerthread.join() | ||
194 | self.db.disconnect() | ||
195 | logger.info("PRServer: stopping...") | ||
196 | self.server_close() | ||
197 | os.close(self.quitpipein) | ||
198 | return | ||
199 | 115 | ||
200 | def start(self): | 116 | async def handle_is_readonly(self, request): |
201 | if self.daemon: | 117 | return {"readonly": self.server.read_only} |
202 | pid = self.daemonize() | ||
203 | else: | ||
204 | pid = self.fork() | ||
205 | self.pid = pid | ||
206 | 118 | ||
207 | # Ensure both the parent sees this and the child from the work_forever log entry above | 119 | class PRServer(bb.asyncrpc.AsyncServer): |
208 | logger.info("Started PRServer with DBfile: %s, IP: %s, PORT: %s, PID: %s" % | 120 | def __init__(self, dbfile, read_only=False): |
209 | (self.dbfile, self.host, self.port, str(pid))) | 121 | super().__init__(logger) |
122 | self.dbfile = dbfile | ||
123 | self.table = None | ||
124 | self.read_only = read_only | ||
210 | 125 | ||
211 | def delpid(self): | 126 | def accept_client(self, socket): |
212 | os.remove(self.pidfile) | 127 | return PRServerClient(socket, self) |
213 | 128 | ||
214 | def daemonize(self): | 129 | def start(self): |
215 | """ | 130 | tasks = super().start() |
216 | See Advanced Programming in the UNIX, Sec 13.3 | 131 | self.db = prserv.db.PRData(self.dbfile, read_only=self.read_only) |
217 | """ | 132 | self.table = self.db["PRMAIN"] |
218 | try: | ||
219 | pid = os.fork() | ||
220 | if pid > 0: | ||
221 | os.waitpid(pid, 0) | ||
222 | #parent return instead of exit to give control | ||
223 | return pid | ||
224 | except OSError as e: | ||
225 | raise Exception("%s [%d]" % (e.strerror, e.errno)) | ||
226 | |||
227 | os.setsid() | ||
228 | """ | ||
229 | fork again to make sure the daemon is not session leader, | ||
230 | which prevents it from acquiring controlling terminal | ||
231 | """ | ||
232 | try: | ||
233 | pid = os.fork() | ||
234 | if pid > 0: #parent | ||
235 | os._exit(0) | ||
236 | except OSError as e: | ||
237 | raise Exception("%s [%d]" % (e.strerror, e.errno)) | ||
238 | 133 | ||
239 | self.cleanup_handles() | 134 | self.logger.info("Started PRServer with DBfile: %s, Address: %s, PID: %s" % |
240 | os._exit(0) | 135 | (self.dbfile, self.address, str(os.getpid()))) |
241 | 136 | ||
242 | def fork(self): | 137 | return tasks |
243 | try: | 138 | |
244 | pid = os.fork() | 139 | async def stop(self): |
245 | if pid > 0: | 140 | self.table.sync_if_dirty() |
246 | self.socket.close() # avoid ResourceWarning in parent | 141 | self.db.disconnect() |
247 | return pid | 142 | await super().stop() |
248 | except OSError as e: | 143 | |
249 | raise Exception("%s [%d]" % (e.strerror, e.errno)) | 144 | def signal_handler(self): |
250 | 145 | super().signal_handler() | |
251 | bb.utils.signal_on_parent_exit("SIGTERM") | 146 | if self.table: |
252 | self.cleanup_handles() | 147 | self.table.sync() |
253 | os._exit(0) | ||
254 | |||
255 | def cleanup_handles(self): | ||
256 | signal.signal(signal.SIGINT, self.sigint_handler) | ||
257 | signal.signal(signal.SIGTERM, self.sigterm_handler) | ||
258 | os.chdir("/") | ||
259 | |||
260 | sys.stdout.flush() | ||
261 | sys.stderr.flush() | ||
262 | |||
263 | # We could be called from a python thread with io.StringIO as | ||
264 | # stdout/stderr or it could be 'real' unix fd forking where we need | ||
265 | # to physically close the fds to prevent the program launching us from | ||
266 | # potentially hanging on a pipe. Handle both cases. | ||
267 | si = open('/dev/null', 'r') | ||
268 | try: | ||
269 | os.dup2(si.fileno(),sys.stdin.fileno()) | ||
270 | except (AttributeError, io.UnsupportedOperation): | ||
271 | sys.stdin = si | ||
272 | so = open(self.logfile, 'a+') | ||
273 | try: | ||
274 | os.dup2(so.fileno(),sys.stdout.fileno()) | ||
275 | except (AttributeError, io.UnsupportedOperation): | ||
276 | sys.stdout = so | ||
277 | try: | ||
278 | os.dup2(so.fileno(),sys.stderr.fileno()) | ||
279 | except (AttributeError, io.UnsupportedOperation): | ||
280 | sys.stderr = so | ||
281 | |||
282 | # Clear out all log handlers prior to the fork() to avoid calling | ||
283 | # event handlers not part of the PRserver | ||
284 | for logger_iter in logging.Logger.manager.loggerDict.keys(): | ||
285 | logging.getLogger(logger_iter).handlers = [] | ||
286 | |||
287 | # Ensure logging makes it to the logfile | ||
288 | streamhandler = logging.StreamHandler() | ||
289 | streamhandler.setLevel(logging.DEBUG) | ||
290 | formatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") | ||
291 | streamhandler.setFormatter(formatter) | ||
292 | logger.addHandler(streamhandler) | ||
293 | |||
294 | # write pidfile | ||
295 | pid = str(os.getpid()) | ||
296 | with open(self.pidfile, 'w') as pf: | ||
297 | pf.write("%s\n" % pid) | ||
298 | |||
299 | self.work_forever() | ||
300 | self.delpid() | ||
301 | 148 | ||
302 | class PRServSingleton(object): | 149 | class PRServSingleton(object): |
303 | def __init__(self, dbfile, logfile, interface): | 150 | def __init__(self, dbfile, logfile, host, port): |
304 | self.dbfile = dbfile | 151 | self.dbfile = dbfile |
305 | self.logfile = logfile | 152 | self.logfile = logfile |
306 | self.interface = interface | ||
307 | self.host = None | ||
308 | self.port = None | ||
309 | |||
310 | def start(self): | ||
311 | self.prserv = PRServer(self.dbfile, self.logfile, self.interface, daemon=False) | ||
312 | self.prserv.start() | ||
313 | self.host, self.port = self.prserv.getinfo() | ||
314 | |||
315 | def getinfo(self): | ||
316 | return (self.host, self.port) | ||
317 | |||
318 | class PRServerConnection(object): | ||
319 | def __init__(self, host, port): | ||
320 | if is_local_special(host, port): | ||
321 | host, port = singleton.getinfo() | ||
322 | self.host = host | 153 | self.host = host |
323 | self.port = port | 154 | self.port = port |
324 | self.connection, self.transport = bb.server.xmlrpcclient._create_server(self.host, self.port) | ||
325 | |||
326 | def terminate(self): | ||
327 | try: | ||
328 | logger.info("Terminating PRServer...") | ||
329 | self.connection.quit() | ||
330 | except Exception as exc: | ||
331 | sys.stderr.write("%s\n" % str(exc)) | ||
332 | 155 | ||
333 | def getPR(self, version, pkgarch, checksum): | 156 | def start(self): |
334 | return self.connection.getPR(version, pkgarch, checksum) | 157 | self.prserv = PRServer(self.dbfile) |
158 | self.prserv.start_tcp_server(socket.gethostbyname(self.host), self.port) | ||
159 | self.process = self.prserv.serve_as_process(log_level=logging.WARNING) | ||
335 | 160 | ||
336 | def ping(self): | 161 | if not self.prserv.address: |
337 | return self.connection.ping() | 162 | raise PRServiceConfigError |
163 | if not self.port: | ||
164 | self.port = int(self.prserv.address.rsplit(":", 1)[1]) | ||
338 | 165 | ||
339 | def export(self,version=None, pkgarch=None, checksum=None, colinfo=True): | 166 | def run_as_daemon(func, pidfile, logfile): |
340 | return self.connection.export(version, pkgarch, checksum, colinfo) | 167 | """ |
168 | See Advanced Programming in the UNIX, Sec 13.3 | ||
169 | """ | ||
170 | try: | ||
171 | pid = os.fork() | ||
172 | if pid > 0: | ||
173 | os.waitpid(pid, 0) | ||
174 | #parent return instead of exit to give control | ||
175 | return pid | ||
176 | except OSError as e: | ||
177 | raise Exception("%s [%d]" % (e.strerror, e.errno)) | ||
341 | 178 | ||
342 | def dump_db(self): | 179 | os.setsid() |
343 | return self.connection.dump_db() | 180 | """ |
181 | fork again to make sure the daemon is not session leader, | ||
182 | which prevents it from acquiring controlling terminal | ||
183 | """ | ||
184 | try: | ||
185 | pid = os.fork() | ||
186 | if pid > 0: #parent | ||
187 | os._exit(0) | ||
188 | except OSError as e: | ||
189 | raise Exception("%s [%d]" % (e.strerror, e.errno)) | ||
344 | 190 | ||
345 | def importone(self, version, pkgarch, checksum, value): | 191 | os.chdir("/") |
346 | return self.connection.importone(version, pkgarch, checksum, value) | ||
347 | 192 | ||
348 | def getinfo(self): | 193 | sys.stdout.flush() |
349 | return self.host, self.port | 194 | sys.stderr.flush() |
350 | 195 | ||
351 | def start_daemon(dbfile, host, port, logfile): | 196 | # We could be called from a python thread with io.StringIO as |
197 | # stdout/stderr or it could be 'real' unix fd forking where we need | ||
198 | # to physically close the fds to prevent the program launching us from | ||
199 | # potentially hanging on a pipe. Handle both cases. | ||
200 | si = open("/dev/null", "r") | ||
201 | try: | ||
202 | os.dup2(si.fileno(), sys.stdin.fileno()) | ||
203 | except (AttributeError, io.UnsupportedOperation): | ||
204 | sys.stdin = si | ||
205 | so = open(logfile, "a+") | ||
206 | try: | ||
207 | os.dup2(so.fileno(), sys.stdout.fileno()) | ||
208 | except (AttributeError, io.UnsupportedOperation): | ||
209 | sys.stdout = so | ||
210 | try: | ||
211 | os.dup2(so.fileno(), sys.stderr.fileno()) | ||
212 | except (AttributeError, io.UnsupportedOperation): | ||
213 | sys.stderr = so | ||
214 | |||
215 | # Clear out all log handlers prior to the fork() to avoid calling | ||
216 | # event handlers not part of the PRserver | ||
217 | for logger_iter in logging.Logger.manager.loggerDict.keys(): | ||
218 | logging.getLogger(logger_iter).handlers = [] | ||
219 | |||
220 | # Ensure logging makes it to the logfile | ||
221 | streamhandler = logging.StreamHandler() | ||
222 | streamhandler.setLevel(logging.DEBUG) | ||
223 | formatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") | ||
224 | streamhandler.setFormatter(formatter) | ||
225 | logger.addHandler(streamhandler) | ||
226 | |||
227 | # write pidfile | ||
228 | pid = str(os.getpid()) | ||
229 | with open(pidfile, "w") as pf: | ||
230 | pf.write("%s\n" % pid) | ||
231 | |||
232 | func() | ||
233 | os.remove(pidfile) | ||
234 | os._exit(0) | ||
235 | |||
236 | def start_daemon(dbfile, host, port, logfile, read_only=False): | ||
352 | ip = socket.gethostbyname(host) | 237 | ip = socket.gethostbyname(host) |
353 | pidfile = PIDPREFIX % (ip, port) | 238 | pidfile = PIDPREFIX % (ip, port) |
354 | try: | 239 | try: |
@@ -362,15 +247,13 @@ def start_daemon(dbfile, host, port, logfile): | |||
362 | % pidfile) | 247 | % pidfile) |
363 | return 1 | 248 | return 1 |
364 | 249 | ||
365 | server = PRServer(os.path.abspath(dbfile), os.path.abspath(logfile), (ip,port)) | 250 | dbfile = os.path.abspath(dbfile) |
366 | server.start() | 251 | def daemon_main(): |
252 | server = PRServer(dbfile, read_only=read_only) | ||
253 | server.start_tcp_server(ip, port) | ||
254 | server.serve_forever() | ||
367 | 255 | ||
368 | # Sometimes, the port (i.e. localhost:0) indicated by the user does not match with | 256 | run_as_daemon(daemon_main, pidfile, os.path.abspath(logfile)) |
369 | # the one the server actually is listening, so at least warn the user about it | ||
370 | _,rport = server.getinfo() | ||
371 | if port != rport: | ||
372 | sys.stdout.write("Server is listening at port %s instead of %s\n" | ||
373 | % (rport,port)) | ||
374 | return 0 | 257 | return 0 |
375 | 258 | ||
376 | def stop_daemon(host, port): | 259 | def stop_daemon(host, port): |
@@ -388,37 +271,28 @@ def stop_daemon(host, port): | |||
388 | # so at least advise the user which ports the corresponding server is listening | 271 | # so at least advise the user which ports the corresponding server is listening |
389 | ports = [] | 272 | ports = [] |
390 | portstr = "" | 273 | portstr = "" |
391 | for pf in glob.glob(PIDPREFIX % (ip,'*')): | 274 | for pf in glob.glob(PIDPREFIX % (ip, "*")): |
392 | bn = os.path.basename(pf) | 275 | bn = os.path.basename(pf) |
393 | root, _ = os.path.splitext(bn) | 276 | root, _ = os.path.splitext(bn) |
394 | ports.append(root.split('_')[-1]) | 277 | ports.append(root.split("_")[-1]) |
395 | if len(ports): | 278 | if len(ports): |
396 | portstr = "Wrong port? Other ports listening at %s: %s" % (host, ' '.join(ports)) | 279 | portstr = "Wrong port? Other ports listening at %s: %s" % (host, " ".join(ports)) |
397 | 280 | ||
398 | sys.stderr.write("pidfile %s does not exist. Daemon not running? %s\n" | 281 | sys.stderr.write("pidfile %s does not exist. Daemon not running? %s\n" |
399 | % (pidfile,portstr)) | 282 | % (pidfile, portstr)) |
400 | return 1 | 283 | return 1 |
401 | 284 | ||
402 | try: | 285 | try: |
403 | PRServerConnection(ip, port).terminate() | 286 | if is_running(pid): |
404 | except: | 287 | print("Sending SIGTERM to pr-server.") |
405 | logger.critical("Stop PRService %s:%d failed" % (host,port)) | 288 | os.kill(pid, signal.SIGTERM) |
406 | 289 | time.sleep(0.1) | |
407 | try: | ||
408 | if pid: | ||
409 | wait_timeout = 0 | ||
410 | print("Waiting for pr-server to exit.") | ||
411 | while is_running(pid) and wait_timeout < 50: | ||
412 | time.sleep(0.1) | ||
413 | wait_timeout += 1 | ||
414 | |||
415 | if is_running(pid): | ||
416 | print("Sending SIGTERM to pr-server.") | ||
417 | os.kill(pid,signal.SIGTERM) | ||
418 | time.sleep(0.1) | ||
419 | 290 | ||
420 | if os.path.exists(pidfile): | 291 | try: |
421 | os.remove(pidfile) | 292 | os.remove(pidfile) |
293 | except FileNotFoundError: | ||
294 | # The PID file might have been removed by the exiting process | ||
295 | pass | ||
422 | 296 | ||
423 | except OSError as e: | 297 | except OSError as e: |
424 | err = str(e) | 298 | err = str(e) |
@@ -436,7 +310,7 @@ def is_running(pid): | |||
436 | return True | 310 | return True |
437 | 311 | ||
438 | def is_local_special(host, port): | 312 | def is_local_special(host, port): |
439 | if host.strip().upper() == 'localhost'.upper() and (not port): | 313 | if (host == "localhost" or host == "127.0.0.1") and not port: |
440 | return True | 314 | return True |
441 | else: | 315 | else: |
442 | return False | 316 | return False |
@@ -447,7 +321,7 @@ class PRServiceConfigError(Exception): | |||
447 | def auto_start(d): | 321 | def auto_start(d): |
448 | global singleton | 322 | global singleton |
449 | 323 | ||
450 | host_params = list(filter(None, (d.getVar('PRSERV_HOST') or '').split(':'))) | 324 | host_params = list(filter(None, (d.getVar("PRSERV_HOST") or "").split(":"))) |
451 | if not host_params: | 325 | if not host_params: |
452 | # Shutdown any existing PR Server | 326 | # Shutdown any existing PR Server |
453 | auto_shutdown() | 327 | auto_shutdown() |
@@ -456,11 +330,13 @@ def auto_start(d): | |||
456 | if len(host_params) != 2: | 330 | if len(host_params) != 2: |
457 | # Shutdown any existing PR Server | 331 | # Shutdown any existing PR Server |
458 | auto_shutdown() | 332 | auto_shutdown() |
459 | logger.critical('\n'.join(['PRSERV_HOST: incorrect format', | 333 | logger.critical("\n".join(["PRSERV_HOST: incorrect format", |
460 | 'Usage: PRSERV_HOST = "<hostname>:<port>"'])) | 334 | 'Usage: PRSERV_HOST = "<hostname>:<port>"'])) |
461 | raise PRServiceConfigError | 335 | raise PRServiceConfigError |
462 | 336 | ||
463 | if is_local_special(host_params[0], int(host_params[1])): | 337 | host = host_params[0].strip().lower() |
338 | port = int(host_params[1]) | ||
339 | if is_local_special(host, port): | ||
464 | import bb.utils | 340 | import bb.utils |
465 | cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE")) | 341 | cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE")) |
466 | if not cachedir: | 342 | if not cachedir: |
@@ -474,39 +350,43 @@ def auto_start(d): | |||
474 | auto_shutdown() | 350 | auto_shutdown() |
475 | if not singleton: | 351 | if not singleton: |
476 | bb.utils.mkdirhier(cachedir) | 352 | bb.utils.mkdirhier(cachedir) |
477 | singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), ("localhost",0)) | 353 | singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port) |
478 | singleton.start() | 354 | singleton.start() |
479 | if singleton: | 355 | if singleton: |
480 | host, port = singleton.getinfo() | 356 | host = singleton.host |
481 | else: | 357 | port = singleton.port |
482 | host = host_params[0] | ||
483 | port = int(host_params[1]) | ||
484 | 358 | ||
485 | try: | 359 | try: |
486 | connection = PRServerConnection(host,port) | 360 | ping(host, port) |
487 | connection.ping() | 361 | return str(host) + ":" + str(port) |
488 | realhost, realport = connection.getinfo() | 362 | |
489 | return str(realhost) + ":" + str(realport) | ||
490 | |||
491 | except Exception: | 363 | except Exception: |
492 | logger.critical("PRservice %s:%d not available" % (host, port)) | 364 | logger.critical("PRservice %s:%d not available" % (host, port)) |
493 | raise PRServiceConfigError | 365 | raise PRServiceConfigError |
494 | 366 | ||
495 | def auto_shutdown(): | 367 | def auto_shutdown(): |
496 | global singleton | 368 | global singleton |
497 | if singleton: | 369 | if singleton and singleton.process: |
498 | host, port = singleton.getinfo() | 370 | singleton.process.terminate() |
499 | try: | 371 | singleton.process.join() |
500 | PRServerConnection(host, port).terminate() | ||
501 | except: | ||
502 | logger.critical("Stop PRService %s:%d failed" % (host,port)) | ||
503 | |||
504 | try: | ||
505 | os.waitpid(singleton.prserv.pid, 0) | ||
506 | except ChildProcessError: | ||
507 | pass | ||
508 | singleton = None | 372 | singleton = None |
509 | 373 | ||
510 | def ping(host, port): | 374 | def ping(host, port): |
511 | conn=PRServerConnection(host, port) | 375 | from . import client |
512 | return conn.ping() | 376 | |
377 | with client.PRClient() as conn: | ||
378 | conn.connect_tcp(host, port) | ||
379 | return conn.ping() | ||
380 | |||
381 | def connect(host, port): | ||
382 | from . import client | ||
383 | |||
384 | global singleton | ||
385 | |||
386 | if host.strip().lower() == "localhost" and not port: | ||
387 | host = "localhost" | ||
388 | port = singleton.port | ||
389 | |||
390 | conn = client.PRClient() | ||
391 | conn.connect_tcp(host, port) | ||
392 | return conn | ||