def testCreateSwapControllerAndDeleteIt(self, **_kwargs):
        # Instantiates, then deletes SwapController
        sc = SwapController(concurrency=3)

        self.assertEqual(len(sc._slotAgents), 3)

        del sc
Esempio n. 2
0
  def testProgramAbortOnInputReaderThreadCrash(
    self, osExitMock, _slotAgentClassMock, modelSwapperInterfaceClassMock):
    # Verify that a crash in notification reader thread results in a call
    # to os._exit()

    # Configure ModelSwapperInterface mock
    swapperMock = modelSwapperInterfaceClassMock.return_value
    swapperMock.initSchedulerNotification.side_effect = Exception(
      "testProgramAbortOnInputReaderThreadCrash")

    osExitArgQ = Queue.Queue()
    osExitMock.side_effect = osExitArgQ.put

    sc = SwapController(concurrency=1)
    self.assertEqual(len(sc._slotAgents), 1)

    # Request stop, so that the main loop will exit ASAP
    sc.requestStopTS()

    # Run SwapController in a thread
    def runSwapControllerThread(sc, runResultQ):
      try:
        g_logger.info("Swap Controller run-thread is running")
        try:
          r = sc.run()
        except Exception as e:
          runResultQ.put(e)
        else:
          runResultQ.put(r)
      finally:
        g_logger.info("Swap Controller run-thread is exiting")


    runResultQ = Queue.Queue()
    scThread = threading.Thread(
      target=runSwapControllerThread,
      name="runSwapControllerThread",
      args=(sc, runResultQ))
    scThread.setDaemon(True)
    scThread.start()

    # Wait for os._exit to be called
    self.assertEqual(
      osExitArgQ.get(timeout=5),
      SwapController._EXIT_CODE_ON_FAILURE_OF_NOTIFICATION_READER_THREAD)

    # Wait for the run-thread to stop
    g_logger.info("Waiting for SwapController run-thread to stop")
    scThread.join(timeout=5)
    self.assertFalse(scThread.isAlive())
    g_logger.info("SwapController run-thread stopped")

    runResult = runResultQ.get_nowait()
    self.assertIsNone(runResult)
Esempio n. 3
0
  def testDetectNotificationReaderThreadTargetCallFailed(
    self, _slotAgentClassMock, _modelSwapperInterfaceClassMock):
    # Reproduce failure to invoke the SwapController's input-reader thread
    # target and verify that SwapController's event loop raises the expected
    # exception

    sc = SwapController(concurrency=1)
    self.assertEqual(len(sc._slotAgents), 1)

    # Patch SwapController's input-thread object with one that will exhibit a
    # failure while trying to call the thread target
    def expectTwoArgs(_a, _b):
      pass
    t = threading.Thread(target=expectTwoArgs)
    t.setDaemon(True)
    patch.multiple(sc, _notificationReaderThread=t).start()

    # Attempt to run it in a thread
    def runSwapControllerThread(sc, runResultQ):
      try:
        g_logger.info("Swap Controller run-thread is running")
        try:
          r = sc.run()
        except Exception as e:
          runResultQ.put(e)
        else:
          runResultQ.put(r)
      finally:
        g_logger.info("Swap Controller run-thread is exiting")


    runResultQ = Queue.Queue()
    scThread = threading.Thread(
      target=runSwapControllerThread,
      name="runSwapControllerThread",
      args=(sc, runResultQ))
    scThread.setDaemon(True)
    scThread.start()

    # Wait for the run-thread to stop
    g_logger.info("Waiting for SwapController run-thread to stop")
    scThread.join(timeout=5)
    self.assertFalse(scThread.isAlive())
    g_logger.info("SwapController run-thread stopped")

    # Confirm the expected exception
    runResult = runResultQ.get_nowait()
    self.assertIsInstance(runResult, AssertionError)
    self.assertIn("Notification-reader thread failed to start in time",
                  runResult.args[0])
Esempio n. 4
0
    def __init__(self, concurrency):
        self._logger = _getLogger()
        self._concurrency = concurrency

        self._signalPipeReadFD, self._signalPipeWriteFD = os.pipe()
        # Make the write end non-blocking to prevent accidental deadlocking of the
        # signal dispatcher
        fcntl.fcntl(
            self._signalPipeWriteFD, fcntl.F_SETFL,
            fcntl.fcntl(self._signalPipeWriteFD, fcntl.F_GETFL)
            | os.O_NONBLOCK)

        # Register for signals of interest
        self._signalsOfInterest = [
            signal.SIGHUP, signal.SIGTERM, signal.SIGINT
        ]
        for sig in self._signalsOfInterest:
            signal.signal(sig, self._handleSignal)

        # Create the slot agents and swap controller.
        self._swapController = SwapController(concurrency=concurrency)
Esempio n. 5
0
  def testRunSwapControllerAndStopIt(
    self, _slotAgentClassMock, modelSwapperInterfaceClassMock):
    # Instantiate SwapController instance, run it in a separate thread,
    # then stop SwapController.

    # Configure ModelSwapperInterface instance mock
    swapperMock = modelSwapperInterfaceClassMock.return_value
    notificationConsumer = DummyConsumer()
    swapperMock.consumeModelSchedulerNotifications.return_value = (
      notificationConsumer)

    sc = SwapController(concurrency=3)
    self.assertEqual(len(sc._slotAgents), 3)

    # Run it in a thread
    def runSwapControllerThread(sc, runResultQ):
      r = sc.run()
      runResultQ.put(r)

    runResultQ = Queue.Queue()
    scThread = threading.Thread(
      target=runSwapControllerThread,
      name="runSwapControllerThread",
      args=(sc, runResultQ))
    scThread.setDaemon(True)
    scThread.start()

    # Now stop it
    sc.requestStopTS()
    # Prod notification reader's consumer loop to detect the stop request and
    # exit gracefully by adding a dummy message
    notificationConsumer.q.put(None)

    # There isn't much going on, it should stop immediately
    scThread.join(timeout=5)
    self.assertFalse(scThread.isAlive())

    runResult = runResultQ.get_nowait()
    self.assertIsNone(runResult)
    def testModelPreemptionAndStop(self, slotAgentClassMock,
                                   modelSwapperInterfaceClassMock):
        # Test preemption of slots in SwapController

        # Configure ModelSwapperInterface instance mock
        swapperMock = modelSwapperInterfaceClassMock.return_value
        notificationConsumer = DummyConsumer()
        swapperMock.consumeModelSchedulerNotifications.return_value = (
            notificationConsumer)
        swapperMock.modelInputPending.side_effect = (
            lambda modelID: not modelInputDescriptors[
                modelID].requestBatchesQ.empty())

        # Configure SlotAgent class mock to create dummy slot agent instances and
        # add them to our list so that we can introspect them later
        concurrency = 3
        multiplier = 3
        numModels = concurrency * multiplier

        # Generate model IDs
        modelIDs = [hex(i) for i in xrange(numModels)]

        requestBatches = (
            "firstBatch",
            "secondBatch",
        )

        modelInputDescriptors = dict(
            (modelID,
             _ModelInputDescriptor(requestBatches=requestBatches,
                                   consumeSizes=[1, 1]))
            for modelID in modelIDs)

        slotAgents = []
        slotAgentClassMock.side_effect = (lambda slotID: slotAgents.append(
            _DummySlotAgent(slotID, modelInputDescriptors.__getitem__)) or
                                          slotAgents[-1])

        # Run SwapController in a thread
        sc = SwapController(concurrency=concurrency)
        self.assertEqual(len(sc._slotAgents), concurrency)
        self.assertEqual(len(slotAgents), concurrency)

        def runSwapControllerThread(sc, runResultQ):
            try:
                g_logger.info("Swap Controller run-thread is running")
                r = sc.run()
                runResultQ.put(r)
            finally:
                g_logger.info("Swap Controller run-thread is exiting")

        runResultQ = Queue.Queue()
        scThread = threading.Thread(target=runSwapControllerThread,
                                    name="runSwapControllerThread",
                                    args=(sc, runResultQ))
        scThread.setDaemon(True)
        scThread.start()

        # Prod SwapController to process all models
        for modelID in modelIDs:
            notificationConsumer.q.put(_createModelInputNotification(modelID))

        # Wait for model input queues to drain
        for modelID, desc in modelInputDescriptors.iteritems():
            g_logger.info("Waiting for model=%s inputQ to be empty", modelID)
            desc.requestBatchesQ.waitUntilEmpty(timeout=5)
            self.assertTrue(desc.requestBatchesQ.empty())
            g_logger.info("model=%s inputQ is empty", modelID)

        # Verify that all SlotAgents are occupied
        for sa in slotAgents:
            self.assertIsNotNone(sa.modelID)

        # Now stop SwapController
        g_logger.info("Requesting SwapController to stop")
        sc.requestStopTS()

        # So that the notification reader thread detects stop request and exits:
        notificationConsumer.q.put(_createModelInputNotification(modelID))

        g_logger.info("Waiting for SwapController run-thread to stop")
        scThread.join(timeout=5)
        self.assertFalse(scThread.isAlive())
        g_logger.info("SwapController run-thread stopped")

        # Verify that SwapController.run() returned without error
        self.assertIsNone(runResultQ.get_nowait())

        # Verify that all slot agents were closed
        for sa in slotAgents:
            self.assertEqual(sa.numCloseCalls, 1)

        # Verify that input data of all models was drained
        for modelID, desc in modelInputDescriptors.iteritems():
            g_logger.info("Verify empty input for model=%s", modelID)
            self.assertEqual(desc.requestBatchesQ.qsize(), 0)
            self.assertEqual(desc.requestBatchesProcessedQ.qsize(),
                             len(requestBatches))

        # Verify that all slot agents did work and were closed
        for sa in slotAgents:
            g_logger.info(
                "sa=%s: closeCalls=%s; startCalls=%s; stopCalls=%s; releaseCalls=%s",
                sa.slotID, sa.numCloseCalls, sa.numStartModelCalls,
                sa.numStopModelCalls, sa.numReleaseSlotCalls)

            self.assertEqual(sa.numCloseCalls, 1)

            self.assertEqual(sa.numStartModelCalls,
                             multiplier * len(requestBatches))
            self.assertEqual(sa.numStopModelCalls,
                             multiplier * len(requestBatches))
            self.assertEqual(sa.numReleaseSlotCalls,
                             multiplier * len(requestBatches))
    def testSimpleSingleSuccessfulModelInputAndStop(
            self, slotAgentClassMock, modelSwapperInterfaceClassMock):
        # Instantiate SwapController instance, run it in a separate thread,
        # feed some data for one model, then stop SwapController.

        # Configure ModelSwapperInterface instance mock
        swapperMock = modelSwapperInterfaceClassMock.return_value
        notificationConsumer = DummyConsumer()
        swapperMock.consumeModelSchedulerNotifications.return_value = (
            notificationConsumer)
        swapperMock.modelInputPending.return_value = False

        # Configure SlotAgent class mock to create dummy slot agent instances and
        # add them to our list so that we can introspect them later
        modelID = "abcd"
        requestBatches = (
            "firstBatch",
            "secondBatch",
        )
        modelInputDesc = _ModelInputDescriptor(requestBatches=requestBatches,
                                               consumeSizes=[
                                                   2,
                                               ])
        modelInputDescriptors = {modelID: modelInputDesc}
        slotAgents = []
        slotAgentClassMock.side_effect = (lambda slotID: slotAgents.append(
            _DummySlotAgent(slotID, modelInputDescriptors.__getitem__)) or
                                          slotAgents[-1])

        # Run SwapController in a thread
        concurrency = 3
        sc = SwapController(concurrency=concurrency)
        self.assertEqual(len(sc._slotAgents), concurrency)
        self.assertEqual(len(slotAgents), concurrency)

        def runSwapControllerThread(sc, runResultQ):
            try:
                g_logger.info("Swap Controller run-thread is running")
                r = sc.run()
                runResultQ.put(r)
            except:
                runResultQ.put(sys.exc_info()[1])
                raise
            finally:
                g_logger.info("Swap Controller run-thread is exiting")

        runResultQ = Queue.Queue()
        scThread = threading.Thread(target=runSwapControllerThread,
                                    name="runSwapControllerThread",
                                    args=(sc, runResultQ))
        scThread.setDaemon(True)
        scThread.start()

        # Prod SwapController to process model input
        notificationConsumer.q.put(_createModelInputNotification(modelID))

        # Wait for the model input queue to drain
        g_logger.info("Waiting for model inputQ to be empty")
        modelInputDesc.requestBatchesQ.waitUntilEmpty(timeout=5)
        self.assertTrue(modelInputDesc.requestBatchesQ.empty())
        g_logger.info("model inputQ is empty")

        # Now stop SwapController
        g_logger.info("Requesting SwapController to stop")
        sc.requestStopTS()

        # So that the notification reader thread detects stop request and exits:
        notificationConsumer.q.put(_createModelInputNotification(modelID))

        g_logger.info("Waiting for SwapController run-thread to stop")
        scThread.join(timeout=5)
        self.assertFalse(scThread.isAlive())
        g_logger.info("SwapController run-thread stopped")

        # Verify that SwapController.run() returned without error
        self.assertIsNone(runResultQ.get_nowait())

        # Verify that all slot agents were closed
        for sa in slotAgents:
            self.assertEqual(sa.numCloseCalls, 1)

        # Verify that a single slot agent handled all the input data
        targetSA = None
        for sa in slotAgents:
            if sa.numStartModelCalls > 0:
                self.assertIsNone(targetSA)
                targetSA = sa
                self.assertEqual(sa.numStartModelCalls, 1)
                self.assertEqual(sa.numStopModelCalls, 1)
                self.assertEqual(
                    modelInputDesc.requestBatchesProcessedQ.qsize(),
                    len(requestBatches))
            else:
                self.assertEqual(sa.numStartModelCalls, 0)
                self.assertEqual(sa.numStopModelCalls, 0)

        self.assertIsNotNone(targetSA)