Revision 2d6b5414

b/lib/daemon.py
457 457
      self.out_socket.send("\0")
458 458

  
459 459

  
460
class _ShutdownCheck:
461
  """Logic for L{Mainloop} shutdown.
462

  
463
  """
464
  def __init__(self, fn):
465
    """Initializes this class.
466

  
467
    @type fn: callable
468
    @param fn: Function returning C{None} if mainloop can be stopped or a
469
      duration in seconds after which the function should be called again
470
    @see: L{Mainloop.Run}
471

  
472
    """
473
    assert callable(fn)
474

  
475
    self._fn = fn
476
    self._defer = None
477

  
478
  def CanShutdown(self):
479
    """Checks whether mainloop can be stopped.
480

  
481
    @rtype: bool
482

  
483
    """
484
    if self._defer and self._defer.Remaining() > 0:
485
      # A deferred check has already been scheduled
486
      return False
487

  
488
    # Ask mainloop driver whether we can stop or should check again
489
    timeout = self._fn()
490

  
491
    if timeout is None:
492
      # Yes, can stop mainloop
493
      return True
494

  
495
    # Schedule another check in the future
496
    self._defer = utils.RunningTimeout(timeout, True)
497

  
498
    return False
499

  
500

  
460 501
class Mainloop(object):
461 502
  """Generic mainloop for daemons
462 503

  
......
464 505
    timed events
465 506

  
466 507
  """
508
  _SHUTDOWN_TIMEOUT_PRIORITY = -(sys.maxint - 1)
509

  
467 510
  def __init__(self):
468 511
    """Constructs a new Mainloop instance.
469 512

  
......
477 520
  @utils.SignalHandled([signal.SIGCHLD])
478 521
  @utils.SignalHandled([signal.SIGTERM])
479 522
  @utils.SignalHandled([signal.SIGINT])
480
  def Run(self, signal_handlers=None):
523
  def Run(self, shutdown_wait_fn=None, signal_handlers=None):
481 524
    """Runs the mainloop.
482 525

  
526
    @type shutdown_wait_fn: callable
527
    @param shutdown_wait_fn: Function to check whether loop can be terminated;
528
      B{important}: function must be idempotent and must return either None
529
      for shutting down or a timeout for another call
483 530
    @type signal_handlers: dict
484 531
    @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
485 532

  
......
491 538
    # Counter for received signals
492 539
    shutdown_signals = 0
493 540

  
541
    # Logic to wait for shutdown
542
    shutdown_waiter = None
543

  
494 544
    # Start actual main loop
495
    while shutdown_signals < 1:
496
      if not self.scheduler.empty():
545
    while True:
546
      if shutdown_signals == 1 and shutdown_wait_fn is not None:
547
        if shutdown_waiter is None:
548
          shutdown_waiter = _ShutdownCheck(shutdown_wait_fn)
549

  
550
        # Let mainloop driver decide if we can already abort
551
        if shutdown_waiter.CanShutdown():
552
          break
553

  
554
        # Re-evaluate in a second
555
        timeout = 1.0
556

  
557
      elif shutdown_signals >= 1:
558
        # Abort loop if more than one signal has been sent or no callback has
559
        # been given
560
        break
561

  
562
      else:
563
        # Wait forever on I/O events
564
        timeout = None
565

  
566
      if self.scheduler.empty():
567
        asyncore.loop(count=1, timeout=timeout, use_poll=True)
568
      else:
497 569
        try:
498
          self.scheduler.run()
570
          self.scheduler.run(max_delay=timeout)
499 571
        except SchedulerBreakout:
500 572
          pass
501
      else:
502
        asyncore.loop(count=1, use_poll=True)
503 573

  
504 574
      # Check whether a signal was raised
505 575
      for (sig, handler) in signal_handlers.items():

Also available in: Unified diff