summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/prserv/serv.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/prserv/serv.py')
-rw-r--r--bitbake/lib/prserv/serv.py590
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
5import os,sys,logging 7import os,sys,logging
6import signal, time 8import signal, time
7from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
8import threading
9import queue
10import socket 9import socket
11import io 10import io
12import sqlite3 11import sqlite3
13import bb.server.xmlrpcclient
14import prserv 12import prserv
15import prserv.db 13import prserv.db
16import errno 14import errno
17import select 15import bb.asyncrpc
18 16
19logger = logging.getLogger("BitBake.PRserv") 17logger = logging.getLogger("BitBake.PRserv")
20 18
21if sys.hexversion < 0x020600F0: 19PIDPREFIX = "/tmp/PRServer_%s_%s.pid"
22 print("Sorry, python 2.6 or later is required.") 20singleton = None
23 sys.exit(1)
24 21
25class Handler(SimpleXMLRPCRequestHandler): 22class 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
35PIDPREFIX = "/tmp/PRServer_%s_%s.pid" 49 async def handle_test_pr(self, request):
36singleton = 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
39class 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 119class 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
302class PRServSingleton(object): 149class 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
318class 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): 166def 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
351def 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
236def 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
376def stop_daemon(host, port): 259def 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
438def is_local_special(host, port): 312def 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):
447def auto_start(d): 321def 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
495def auto_shutdown(): 367def 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
510def ping(host, port): 374def 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
381def 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