393def serve_devices(devices, options: DeviceServerOptions, exit_event=None):
394
395
396
397
398 devices = copy.deepcopy(devices)
399
400 root_logger = logging.getLogger()
401
402 log_handler = RotatingFileHandler("__MAIN__.log")
403 log_handler.setFormatter(_create_log_formatter("device-server"))
404 root_logger.addHandler(log_handler)
405
406
407
408
409 if exit_event is None:
410 exit_event = multiprocessing.Event()
411
412 servers = (
413 []
414 )
415
416
417
418
419
420
421 parent = multiprocessing.current_process()
422
423 def term_func(sig, frame):
424 """Terminate subprocesses cleanly."""
425 if parent == multiprocessing.current_process():
426 _logger.debug("Shutting down all servers.")
427 exit_event.set()
428
429
430 keep_alive_thread.join()
431 for this_server in servers:
432 this_server.join()
433 sys.exit()
434
435 if sys.platform != "win32":
436 signal.signal(signal.SIGTERM, term_func)
437 signal.signal(signal.SIGINT, term_func)
438
439
440 by_class = {}
441 for dev in devices:
442 by_class[dev["cls"]] = by_class.get(dev["cls"], []) + [dev]
443
444 if not by_class:
445 _logger.warning("No valid devices specified. Maybe an empty list?")
446
447 for cls, devs in by_class.items():
448
449
450
451
452
453
454
455
456 uid_to_host = {}
457 uid_to_port = {}
458 if isinstance(cls, type) and issubclass(cls, FloatingDeviceMixin):
459
460
461 count = 0
462 for dev in devs:
463 uid = dev["uid"]
464 uid_to_host[uid] = dev["host"]
465 uid_to_port[uid] = dev["port"]
466
467 dev["conf"]["index"] = count
468 count += 1
469
470 for dev in devs:
471 servers.append(
472 DeviceServer(
473 dev,
474 options,
475 uid_to_host,
476 uid_to_port,
477 exit_event=exit_event,
478 )
479 )
480 servers[-1].start()
481
482
483
484
485 def keep_alive():
486 """Keep DeviceServers alive."""
487 while not exit_event.is_set():
488 for s in servers:
489 if s.is_alive():
490 continue
491 else:
492 _logger.info(
493 "DeviceServer Failure. Process %s is dead with"
494 " exitcode %s. Restarting...",
495 s.pid,
496 s.exitcode,
497 )
498 servers.remove(s)
499 servers.append(s.clone())
500
501 try:
502 s.join(30)
503 except:
504 _logger.error("... could not join PID %s.", s.pid)
505 else:
506 old_pid = s.pid
507 del s
508 servers[-1].start()
509 _logger.info(
510 "... DeviceServer with PID %s restarted"
511 " as PID %s.",
512 old_pid,
513 servers[-1].pid,
514 )
515 if not servers:
516
517
518 _logger.info("No servers running. Exiting.")
519 exit_event.set()
520 else:
521 try:
522 time.sleep(5)
523 except (KeyboardInterrupt, IOError):
524 pass
525
526 keep_alive_thread = Thread(target=keep_alive)
527 keep_alive_thread.start()
528
529 _logger.info("Device Server started. Press Ctrl+C to exit.")
530 while not exit_event.is_set():
531 try:
532 time.sleep(5)
533 except (KeyboardInterrupt, IOError):
534 _logger.debug("KeyboardInterrupt or IOError")
535 exit_event.set()
536
537 _logger.debug("Shutting down servers ...")
538 while servers:
539 for s in servers:
540 if not s.is_alive():
541 servers.remove(s)
542 del s
543 time.sleep(1)
544 _logger.info(" ... No more servers running.")
545 _logger.debug("Joining threads ...")
546 keep_alive_thread.join()
547 _logger.debug("... Threads joined. Exiting.")
548 return
549
550