diff options
author | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-07-18 22:28:40 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-07-21 08:41:11 +0100 |
commit | 4602408c69132315c3784718fe4ce155b12464cf (patch) | |
tree | 2262f6d8b5e89930b4da4efd72e226ae8f740611 /bitbake/lib/bb/main.py | |
parent | 21a19e0e0bf1b39969f6f2ec37a5784d0069715b (diff) | |
download | poky-4602408c69132315c3784718fe4ce155b12464cf.tar.gz |
bitbake: server: Rework the server API so process and xmlrpc servers coexist
This changes the way bitbake server works quite radically. Now, the
server is always a process based server with the option of starting
an XMLRPC listener on a specific inferface/port.
Behind the scenes this is done with a "bitbake.sock" file alongside
the bitbake.lock file. If we can obtain the lock, we know we need
to start a server. The server always listens on the socket and UIs
can then connect to this. UIs connect by sending a set of three file
descriptors over the domain socket, one for sending commands, one for
receiving command results and the other for receiving events.
These changes meant we can throw away all the horrid server abstraction
code, the plugable transport option to bitbake and the code becomes
much more readable and debuggable. It also likely removes a ton of
ways you could hang the UI/cooker in weird ways due to all the race
conditions that existed with previous processes.
Changes:
* The foreground option for bitbake-server was dropped. Just tail
the log if you really want this, the codepaths were complicated enough
without adding one for this.
* BBSERVER="autodetect" was dropped. The server will autostart and
autoconnect in process mode. You have to specify an xmlrpc server
address since that can't be autodetected. I can't see a use case
for autodetect now.
* The transport/servetype option to bitbake was dropped.
* A BB_SERVER_TIMEOUT variable is added which allows the server
to stay resident for a period of time after the last client
disconnects before unloading. This is used if the -T/--idle-timeout
option is not passed to bitbake.
This change is invasive and may well introduce new issues however I
believe the codebase is in a much better position for further
development and debugging.
(Bitbake rev: 72a3dbe13a23588e24c0baca6d58c35cdeba3f63)
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/main.py')
-rwxr-xr-x | bitbake/lib/bb/main.py | 230 |
1 files changed, 77 insertions, 153 deletions
diff --git a/bitbake/lib/bb/main.py b/bitbake/lib/bb/main.py index 29e391162e..1edf56f41b 100755 --- a/bitbake/lib/bb/main.py +++ b/bitbake/lib/bb/main.py | |||
@@ -28,6 +28,8 @@ import logging | |||
28 | import optparse | 28 | import optparse |
29 | import warnings | 29 | import warnings |
30 | import fcntl | 30 | import fcntl |
31 | import time | ||
32 | import traceback | ||
31 | 33 | ||
32 | import bb | 34 | import bb |
33 | from bb import event | 35 | from bb import event |
@@ -37,6 +39,9 @@ from bb import ui | |||
37 | from bb import server | 39 | from bb import server |
38 | from bb import cookerdata | 40 | from bb import cookerdata |
39 | 41 | ||
42 | import bb.server.process | ||
43 | import bb.server.xmlrpcclient | ||
44 | |||
40 | logger = logging.getLogger("BitBake") | 45 | logger = logging.getLogger("BitBake") |
41 | 46 | ||
42 | class BBMainException(Exception): | 47 | class BBMainException(Exception): |
@@ -58,9 +63,6 @@ class BitbakeHelpFormatter(optparse.IndentedHelpFormatter): | |||
58 | if option.dest == 'ui': | 63 | if option.dest == 'ui': |
59 | valid_uis = list_extension_modules(bb.ui, 'main') | 64 | valid_uis = list_extension_modules(bb.ui, 'main') |
60 | option.help = option.help.replace('@CHOICES@', present_options(valid_uis)) | 65 | option.help = option.help.replace('@CHOICES@', present_options(valid_uis)) |
61 | elif option.dest == 'servertype': | ||
62 | valid_server_types = list_extension_modules(bb.server, 'BitBakeServer') | ||
63 | option.help = option.help.replace('@CHOICES@', present_options(valid_server_types)) | ||
64 | 66 | ||
65 | return optparse.IndentedHelpFormatter.format_option(self, option) | 67 | return optparse.IndentedHelpFormatter.format_option(self, option) |
66 | 68 | ||
@@ -238,11 +240,6 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): | |||
238 | default=os.environ.get('BITBAKE_UI', 'knotty'), | 240 | default=os.environ.get('BITBAKE_UI', 'knotty'), |
239 | help="The user interface to use (@CHOICES@ - default %default).") | 241 | help="The user interface to use (@CHOICES@ - default %default).") |
240 | 242 | ||
241 | # @CHOICES@ is substituted out by BitbakeHelpFormatter above | ||
242 | parser.add_option("-t", "--servertype", action="store", dest="servertype", | ||
243 | default=["process", "xmlrpc"]["BBSERVER" in os.environ], | ||
244 | help="Choose which server type to use (@CHOICES@ - default %default).") | ||
245 | |||
246 | parser.add_option("", "--token", action="store", dest="xmlrpctoken", | 243 | parser.add_option("", "--token", action="store", dest="xmlrpctoken", |
247 | default=os.environ.get("BBTOKEN"), | 244 | default=os.environ.get("BBTOKEN"), |
248 | help="Specify the connection token to be used when connecting " | 245 | help="Specify the connection token to be used when connecting " |
@@ -258,14 +255,11 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): | |||
258 | help="Run bitbake without a UI, only starting a server " | 255 | help="Run bitbake without a UI, only starting a server " |
259 | "(cooker) process.") | 256 | "(cooker) process.") |
260 | 257 | ||
261 | parser.add_option("", "--foreground", action="store_true", | ||
262 | help="Run bitbake server in foreground.") | ||
263 | |||
264 | parser.add_option("-B", "--bind", action="store", dest="bind", default=False, | 258 | parser.add_option("-B", "--bind", action="store", dest="bind", default=False, |
265 | help="The name/address for the bitbake server to bind to.") | 259 | help="The name/address for the bitbake xmlrpc server to bind to.") |
266 | 260 | ||
267 | parser.add_option("-T", "--idle-timeout", type=int, | 261 | parser.add_option("-T", "--idle-timeout", type=float, dest="server_timeout", |
268 | default=int(os.environ.get("BBTIMEOUT", "0")), | 262 | default=float(os.environ.get("BB_SERVER_TIMEOUT", 0)) or None, |
269 | help="Set timeout to unload bitbake server due to inactivity") | 263 | help="Set timeout to unload bitbake server due to inactivity") |
270 | 264 | ||
271 | parser.add_option("", "--no-setscene", action="store_true", | 265 | parser.add_option("", "--no-setscene", action="store_true", |
@@ -283,7 +277,7 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): | |||
283 | 277 | ||
284 | parser.add_option("-m", "--kill-server", action="store_true", | 278 | parser.add_option("-m", "--kill-server", action="store_true", |
285 | dest="kill_server", default=False, | 279 | dest="kill_server", default=False, |
286 | help="Terminate the remote server.") | 280 | help="Terminate the bitbake server.") |
287 | 281 | ||
288 | parser.add_option("", "--observe-only", action="store_true", | 282 | parser.add_option("", "--observe-only", action="store_true", |
289 | dest="observe_only", default=False, | 283 | dest="observe_only", default=False, |
@@ -322,70 +316,20 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): | |||
322 | eventlog = "bitbake_eventlog_%s.json" % datetime.now().strftime("%Y%m%d%H%M%S") | 316 | eventlog = "bitbake_eventlog_%s.json" % datetime.now().strftime("%Y%m%d%H%M%S") |
323 | options.writeeventlog = eventlog | 317 | options.writeeventlog = eventlog |
324 | 318 | ||
325 | # if BBSERVER says to autodetect, let's do that | 319 | if options.bind: |
326 | if options.remote_server: | 320 | try: |
327 | port = -1 | 321 | #Checking that the port is a number and is a ':' delimited value |
328 | if options.remote_server != 'autostart': | 322 | (host, port) = options.bind.split(':') |
329 | host, port = options.remote_server.split(":", 2) | ||
330 | port = int(port) | 323 | port = int(port) |
331 | # use automatic port if port set to -1, means read it from | 324 | except (ValueError,IndexError): |
332 | # the bitbake.lock file; this is a bit tricky, but we always expect | 325 | raise BBMainException("FATAL: Malformed host:port bind parameter") |
333 | # to be in the base of the build directory if we need to have a | 326 | options.xmlrpcinterface = (host, port) |
334 | # chance to start the server later, anyway | 327 | else: |
335 | if port == -1: | 328 | options.xmlrpcinterface = (None, 0) |
336 | lock_location = "./bitbake.lock" | ||
337 | # we try to read the address at all times; if the server is not started, | ||
338 | # we'll try to start it after the first connect fails, below | ||
339 | try: | ||
340 | lf = open(lock_location, 'r') | ||
341 | remotedef = lf.readline() | ||
342 | [host, port] = remotedef.split(":") | ||
343 | port = int(port) | ||
344 | lf.close() | ||
345 | options.remote_server = remotedef | ||
346 | except Exception as e: | ||
347 | if options.remote_server != 'autostart': | ||
348 | raise BBMainException("Failed to read bitbake.lock (%s), invalid port" % str(e)) | ||
349 | 329 | ||
350 | return options, targets[1:] | 330 | return options, targets[1:] |
351 | 331 | ||
352 | 332 | ||
353 | def start_server(servermodule, configParams, configuration, features): | ||
354 | server = servermodule.BitBakeServer() | ||
355 | single_use = not configParams.server_only and os.getenv('BBSERVER') != 'autostart' | ||
356 | if configParams.bind: | ||
357 | (host, port) = configParams.bind.split(':') | ||
358 | server.initServer((host, int(port)), single_use=single_use, | ||
359 | idle_timeout=configParams.idle_timeout) | ||
360 | configuration.interface = [server.serverImpl.host, server.serverImpl.port] | ||
361 | else: | ||
362 | server.initServer(single_use=single_use) | ||
363 | configuration.interface = [] | ||
364 | |||
365 | try: | ||
366 | configuration.setServerRegIdleCallback(server.getServerIdleCB()) | ||
367 | |||
368 | cooker = bb.cooker.BBCooker(configuration, features) | ||
369 | |||
370 | server.addcooker(cooker) | ||
371 | server.saveConnectionDetails() | ||
372 | except Exception as e: | ||
373 | while hasattr(server, "event_queue"): | ||
374 | import queue | ||
375 | try: | ||
376 | event = server.event_queue.get(block=False) | ||
377 | except (queue.Empty, IOError): | ||
378 | break | ||
379 | if isinstance(event, logging.LogRecord): | ||
380 | logger.handle(event) | ||
381 | raise | ||
382 | if not configParams.foreground: | ||
383 | server.detach() | ||
384 | cooker.shutdown() | ||
385 | cooker.lock.close() | ||
386 | return server | ||
387 | |||
388 | |||
389 | def bitbake_main(configParams, configuration): | 333 | def bitbake_main(configParams, configuration): |
390 | 334 | ||
391 | # Python multiprocessing requires /dev/shm on Linux | 335 | # Python multiprocessing requires /dev/shm on Linux |
@@ -406,45 +350,15 @@ def bitbake_main(configParams, configuration): | |||
406 | 350 | ||
407 | configuration.setConfigParameters(configParams) | 351 | configuration.setConfigParameters(configParams) |
408 | 352 | ||
409 | if configParams.server_only: | 353 | if configParams.server_only and configParams.remote_server: |
410 | if configParams.servertype != "xmlrpc": | ||
411 | raise BBMainException("FATAL: If '--server-only' is defined, we must set the " | ||
412 | "servertype as 'xmlrpc'.\n") | ||
413 | if not configParams.bind: | ||
414 | raise BBMainException("FATAL: The '--server-only' option requires a name/address " | ||
415 | "to bind to with the -B option.\n") | ||
416 | else: | ||
417 | try: | ||
418 | #Checking that the port is a number | ||
419 | int(configParams.bind.split(":")[1]) | ||
420 | except (ValueError,IndexError): | ||
421 | raise BBMainException( | ||
422 | "FATAL: Malformed host:port bind parameter") | ||
423 | if configParams.remote_server: | ||
424 | raise BBMainException("FATAL: The '--server-only' option conflicts with %s.\n" % | 354 | raise BBMainException("FATAL: The '--server-only' option conflicts with %s.\n" % |
425 | ("the BBSERVER environment variable" if "BBSERVER" in os.environ \ | 355 | ("the BBSERVER environment variable" if "BBSERVER" in os.environ \ |
426 | else "the '--remote-server' option")) | 356 | else "the '--remote-server' option")) |
427 | 357 | ||
428 | elif configParams.foreground: | ||
429 | raise BBMainException("FATAL: The '--foreground' option can only be used " | ||
430 | "with --server-only.\n") | ||
431 | |||
432 | if configParams.bind and configParams.servertype != "xmlrpc": | ||
433 | raise BBMainException("FATAL: If '-B' or '--bind' is defined, we must " | ||
434 | "set the servertype as 'xmlrpc'.\n") | ||
435 | |||
436 | if configParams.remote_server and configParams.servertype != "xmlrpc": | ||
437 | raise BBMainException("FATAL: If '--remote-server' is defined, we must " | ||
438 | "set the servertype as 'xmlrpc'.\n") | ||
439 | |||
440 | if configParams.observe_only and (not configParams.remote_server or configParams.bind): | 358 | if configParams.observe_only and (not configParams.remote_server or configParams.bind): |
441 | raise BBMainException("FATAL: '--observe-only' can only be used by UI clients " | 359 | raise BBMainException("FATAL: '--observe-only' can only be used by UI clients " |
442 | "connecting to a server.\n") | 360 | "connecting to a server.\n") |
443 | 361 | ||
444 | if configParams.kill_server and not configParams.remote_server: | ||
445 | raise BBMainException("FATAL: '--kill-server' can only be used to " | ||
446 | "terminate a remote server") | ||
447 | |||
448 | if "BBDEBUG" in os.environ: | 362 | if "BBDEBUG" in os.environ: |
449 | level = int(os.environ["BBDEBUG"]) | 363 | level = int(os.environ["BBDEBUG"]) |
450 | if level > configuration.debug: | 364 | if level > configuration.debug: |
@@ -453,7 +367,7 @@ def bitbake_main(configParams, configuration): | |||
453 | bb.msg.init_msgconfig(configParams.verbose, configuration.debug, | 367 | bb.msg.init_msgconfig(configParams.verbose, configuration.debug, |
454 | configuration.debug_domains) | 368 | configuration.debug_domains) |
455 | 369 | ||
456 | server, server_connection, ui_module = setup_bitbake(configParams, configuration) | 370 | server_connection, ui_module = setup_bitbake(configParams, configuration) |
457 | if server_connection is None and configParams.kill_server: | 371 | if server_connection is None and configParams.kill_server: |
458 | return 0 | 372 | return 0 |
459 | 373 | ||
@@ -463,16 +377,15 @@ def bitbake_main(configParams, configuration): | |||
463 | return 0 | 377 | return 0 |
464 | 378 | ||
465 | try: | 379 | try: |
380 | for event in bb.event.ui_queue: | ||
381 | server_connection.events.queue_event(event) | ||
382 | bb.event.ui_queue = [] | ||
383 | |||
466 | return ui_module.main(server_connection.connection, server_connection.events, | 384 | return ui_module.main(server_connection.connection, server_connection.events, |
467 | configParams) | 385 | configParams) |
468 | finally: | 386 | finally: |
469 | bb.event.ui_queue = [] | ||
470 | server_connection.terminate() | 387 | server_connection.terminate() |
471 | else: | 388 | else: |
472 | print("Bitbake server address: %s, server port: %s" % (server.serverImpl.host, | ||
473 | server.serverImpl.port)) | ||
474 | if configParams.foreground: | ||
475 | server.serverImpl.serve_forever() | ||
476 | return 0 | 389 | return 0 |
477 | 390 | ||
478 | return 1 | 391 | return 1 |
@@ -495,58 +408,69 @@ def setup_bitbake(configParams, configuration, extrafeatures=None, setup_logging | |||
495 | # Collect the feature set for the UI | 408 | # Collect the feature set for the UI |
496 | featureset = getattr(ui_module, "featureSet", []) | 409 | featureset = getattr(ui_module, "featureSet", []) |
497 | 410 | ||
498 | if configParams.server_only: | ||
499 | for param in ('prefile', 'postfile'): | ||
500 | value = getattr(configParams, param) | ||
501 | if value: | ||
502 | setattr(configuration, "%s_server" % param, value) | ||
503 | param = "%s_server" % param | ||
504 | |||
505 | if extrafeatures: | 411 | if extrafeatures: |
506 | for feature in extrafeatures: | 412 | for feature in extrafeatures: |
507 | if not feature in featureset: | 413 | if not feature in featureset: |
508 | featureset.append(feature) | 414 | featureset.append(feature) |
509 | 415 | ||
510 | servermodule = import_extension_module(bb.server, | 416 | server_connection = None |
511 | configParams.servertype, | 417 | |
512 | 'BitBakeServer') | ||
513 | if configParams.remote_server: | 418 | if configParams.remote_server: |
514 | if os.getenv('BBSERVER') == 'autostart': | 419 | # Connect to a remote XMLRPC server |
515 | if configParams.remote_server == 'autostart' or \ | 420 | server_connection = bb.server.xmlrpcclient.connectXMLRPC(configParams.remote_server, featureset, |
516 | not servermodule.check_connection(configParams.remote_server, timeout=2): | 421 | configParams.observe_only, configParams.xmlrpctoken) |
517 | configParams.bind = 'localhost:0' | ||
518 | srv = start_server(servermodule, configParams, configuration, featureset) | ||
519 | configParams.remote_server = '%s:%d' % tuple(configuration.interface) | ||
520 | bb.event.ui_queue = [] | ||
521 | # we start a stub server that is actually a XMLRPClient that connects to a real server | ||
522 | from bb.server.xmlrpc import BitBakeXMLRPCClient | ||
523 | server = servermodule.BitBakeXMLRPCClient(configParams.observe_only, | ||
524 | configParams.xmlrpctoken) | ||
525 | server.saveConnectionDetails(configParams.remote_server) | ||
526 | else: | 422 | else: |
527 | # we start a server with a given configuration | 423 | retries = 8 |
528 | server = start_server(servermodule, configParams, configuration, featureset) | 424 | while retries: |
425 | try: | ||
426 | topdir, lock = lockBitbake() | ||
427 | sockname = topdir + "/bitbake.sock" | ||
428 | if lock: | ||
429 | # we start a server with a given configuration | ||
430 | logger.info("Starting bitbake server...") | ||
431 | server = bb.server.process.BitBakeServer(lock, sockname, configuration, featureset) | ||
432 | # The server will handle any events already in the queue | ||
433 | bb.event.ui_queue = [] | ||
434 | else: | ||
435 | logger.info("Reconnecting to bitbake server...") | ||
436 | if not os.path.exists(sockname): | ||
437 | print("Previous bitbake instance shutting down?, waiting to retry...") | ||
438 | time.sleep(5) | ||
439 | raise bb.server.process.ProcessTimeout("Bitbake still shutting down as socket exists but no lock?") | ||
440 | if not configParams.server_only: | ||
441 | server_connection = bb.server.process.connectProcessServer(sockname, featureset) | ||
442 | if server_connection: | ||
443 | break | ||
444 | except (Exception, bb.server.process.ProcessTimeout) as e: | ||
445 | if not retries: | ||
446 | raise | ||
447 | retries -= 1 | ||
448 | if isinstance(e, (bb.server.process.ProcessTimeout, BrokenPipeError)): | ||
449 | logger.info("Retrying server connection...") | ||
450 | else: | ||
451 | logger.info("Retrying server connection... (%s)" % traceback.format_exc()) | ||
452 | if not retries: | ||
453 | bb.fatal("Unable to connect to bitbake server, or start one") | ||
454 | if retries < 5: | ||
455 | time.sleep(5) | ||
456 | |||
457 | if configParams.kill_server: | ||
458 | server_connection.connection.terminateServer() | ||
459 | server_connection.terminate() | ||
529 | bb.event.ui_queue = [] | 460 | bb.event.ui_queue = [] |
461 | logger.info("Terminated bitbake server.") | ||
462 | return None, None | ||
530 | 463 | ||
531 | if configParams.server_only: | 464 | # Restore the environment in case the UI needs it |
532 | server_connection = None | 465 | for k in cleanedvars: |
533 | else: | 466 | os.environ[k] = cleanedvars[k] |
534 | try: | ||
535 | server_connection = server.establishConnection(featureset) | ||
536 | except Exception as e: | ||
537 | bb.fatal("Could not connect to server %s: %s" % (configParams.remote_server, str(e))) | ||
538 | |||
539 | if configParams.kill_server: | ||
540 | server_connection.connection.terminateServer() | ||
541 | bb.event.ui_queue = [] | ||
542 | return None, None, None | ||
543 | 467 | ||
544 | server_connection.setupEventQueue() | 468 | logger.removeHandler(handler) |
545 | 469 | ||
546 | # Restore the environment in case the UI needs it | 470 | return server_connection, ui_module |
547 | for k in cleanedvars: | ||
548 | os.environ[k] = cleanedvars[k] | ||
549 | 471 | ||
550 | logger.removeHandler(handler) | 472 | def lockBitbake(): |
473 | topdir = bb.cookerdata.findTopdir() | ||
474 | lockfile = topdir + "/bitbake.lock" | ||
475 | return topdir, bb.utils.lockfile(lockfile, False, False) | ||
551 | 476 | ||
552 | return server, server_connection, ui_module | ||