@Test
  public void testScheduleWithDyingInstances() {
    try {
      Scheduler scheduler = new Scheduler(TestingUtils.defaultExecutionContext());

      Instance i1 = getRandomInstance(2);
      Instance i2 = getRandomInstance(2);
      Instance i3 = getRandomInstance(1);

      scheduler.newInstanceAvailable(i1);
      scheduler.newInstanceAvailable(i2);
      scheduler.newInstanceAvailable(i3);

      List<SimpleSlot> slots = new ArrayList<SimpleSlot>();
      slots.add(scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get());
      slots.add(scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get());
      slots.add(scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get());
      slots.add(scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get());
      slots.add(scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get());

      i2.markDead();

      for (SimpleSlot slot : slots) {
        if (slot.getOwner() == i2) {
          assertTrue(slot.isCanceled());
        } else {
          assertFalse(slot.isCanceled());
        }

        slot.releaseSlot();
      }

      assertEquals(3, scheduler.getNumberOfAvailableSlots());

      i1.markDead();
      i3.markDead();

      // cannot get another slot, since all instances are dead
      try {
        scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
        fail("Scheduler served a slot from a dead instance");
      } catch (NoResourceAvailableException e) {
        // fine
      } catch (Exception e) {
        fail("Wrong exception type.");
      }

      // now the latest, the scheduler should have noticed (through the lazy mechanisms)
      // that all instances have vanished
      assertEquals(0, scheduler.getNumberOfInstancesWithAvailableSlots());
      assertEquals(0, scheduler.getNumberOfAvailableSlots());
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testAddAndRemoveInstance() {
    try {
      Scheduler scheduler = new Scheduler(TestingUtils.defaultExecutionContext());

      Instance i1 = getRandomInstance(2);
      Instance i2 = getRandomInstance(2);
      Instance i3 = getRandomInstance(2);

      assertEquals(0, scheduler.getNumberOfAvailableInstances());
      assertEquals(0, scheduler.getNumberOfAvailableSlots());
      scheduler.newInstanceAvailable(i1);
      assertEquals(1, scheduler.getNumberOfAvailableInstances());
      assertEquals(2, scheduler.getNumberOfAvailableSlots());
      scheduler.newInstanceAvailable(i2);
      assertEquals(2, scheduler.getNumberOfAvailableInstances());
      assertEquals(4, scheduler.getNumberOfAvailableSlots());
      scheduler.newInstanceAvailable(i3);
      assertEquals(3, scheduler.getNumberOfAvailableInstances());
      assertEquals(6, scheduler.getNumberOfAvailableSlots());

      // cannot add available instance again
      try {
        scheduler.newInstanceAvailable(i2);
        fail("Scheduler accepted instance twice");
      } catch (IllegalArgumentException e) {
        // bueno!
      }

      // some instances die
      assertEquals(3, scheduler.getNumberOfAvailableInstances());
      assertEquals(6, scheduler.getNumberOfAvailableSlots());
      scheduler.instanceDied(i2);
      assertEquals(2, scheduler.getNumberOfAvailableInstances());
      assertEquals(4, scheduler.getNumberOfAvailableSlots());

      // try to add a dead instance
      try {
        scheduler.newInstanceAvailable(i2);
        fail("Scheduler accepted dead instance");
      } catch (IllegalArgumentException e) {
        // stimmt

      }

      scheduler.instanceDied(i1);
      assertEquals(1, scheduler.getNumberOfAvailableInstances());
      assertEquals(2, scheduler.getNumberOfAvailableSlots());
      scheduler.instanceDied(i3);
      assertEquals(0, scheduler.getNumberOfAvailableInstances());
      assertEquals(0, scheduler.getNumberOfAvailableSlots());

      assertFalse(i1.isAlive());
      assertFalse(i2.isAlive());
      assertFalse(i3.isAlive());
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testScheduleQueueing() {
    final int NUM_INSTANCES = 50;
    final int NUM_SLOTS_PER_INSTANCE = 3;
    final int NUM_TASKS_TO_SCHEDULE = 2000;

    try {
      // note: since this test asynchronously releases slots, the executor needs release workers.
      // doing the release call synchronous can lead to a deadlock
      Scheduler scheduler = new Scheduler(TestingUtils.defaultExecutionContext());

      for (int i = 0; i < NUM_INSTANCES; i++) {
        scheduler.newInstanceAvailable(
            getRandomInstance((int) (Math.random() * NUM_SLOTS_PER_INSTANCE) + 1));
      }

      assertEquals(NUM_INSTANCES, scheduler.getNumberOfAvailableInstances());
      final int totalSlots = scheduler.getNumberOfAvailableSlots();

      // all slots we ever got.
      List<Future<SimpleSlot>> allAllocatedSlots = new ArrayList<>();

      // slots that need to be released
      final Set<SimpleSlot> toRelease = new HashSet<SimpleSlot>();

      // flag to track errors in the concurrent thread
      final AtomicBoolean errored = new AtomicBoolean(false);

      // thread to asynchronously release slots
      Runnable disposer =
          new Runnable() {

            @Override
            public void run() {
              try {
                int recycled = 0;
                while (recycled < NUM_TASKS_TO_SCHEDULE) {
                  synchronized (toRelease) {
                    while (toRelease.isEmpty()) {
                      toRelease.wait();
                    }

                    Iterator<SimpleSlot> iter = toRelease.iterator();
                    SimpleSlot next = iter.next();
                    iter.remove();

                    next.releaseSlot();
                    recycled++;
                  }
                }
              } catch (Throwable t) {
                errored.set(true);
              }
            }
          };

      Thread disposeThread = new Thread(disposer);
      disposeThread.start();

      for (int i = 0; i < NUM_TASKS_TO_SCHEDULE; i++) {
        Future<SimpleSlot> future = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), true);
        future.thenAcceptAsync(
            new AcceptFunction<SimpleSlot>() {
              @Override
              public void accept(SimpleSlot slot) {
                synchronized (toRelease) {
                  toRelease.add(slot);
                  toRelease.notifyAll();
                }
              }
            },
            TestingUtils.defaultExecutionContext());
        allAllocatedSlots.add(future);
      }

      disposeThread.join();

      assertFalse("The slot releasing thread caused an error.", errored.get());

      List<SimpleSlot> slotsAfter = new ArrayList<SimpleSlot>();
      for (Future<SimpleSlot> future : allAllocatedSlots) {
        slotsAfter.add(future.get());
      }

      assertEquals(
          "All instances should have available slots.",
          NUM_INSTANCES,
          scheduler.getNumberOfInstancesWithAvailableSlots());

      // the slots should all be different
      assertTrue(areAllDistinct(slotsAfter.toArray()));

      assertEquals(
          "All slots should be available.", totalSlots, scheduler.getNumberOfAvailableSlots());
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testScheduleImmediately() {
    try {
      Scheduler scheduler = new Scheduler(TestingUtils.defaultExecutionContext());
      assertEquals(0, scheduler.getNumberOfAvailableSlots());

      scheduler.newInstanceAvailable(getRandomInstance(2));
      scheduler.newInstanceAvailable(getRandomInstance(1));
      scheduler.newInstanceAvailable(getRandomInstance(2));
      assertEquals(5, scheduler.getNumberOfAvailableSlots());

      // schedule something into all slots
      SimpleSlot s1 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
      SimpleSlot s2 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
      SimpleSlot s3 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
      SimpleSlot s4 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
      SimpleSlot s5 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();

      // the slots should all be different
      assertTrue(areAllDistinct(s1, s2, s3, s4, s5));

      try {
        scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false);
        fail("Scheduler accepted scheduling request without available resource.");
      } catch (NoResourceAvailableException e) {
        // pass!
      }

      // release some slots again
      s3.releaseSlot();
      s4.releaseSlot();
      assertEquals(2, scheduler.getNumberOfAvailableSlots());

      // now we can schedule some more slots
      SimpleSlot s6 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();
      SimpleSlot s7 = scheduler.allocateSlot(new ScheduledUnit(getDummyTask()), false).get();

      assertTrue(areAllDistinct(s1, s2, s3, s4, s5, s6, s7));

      // release all

      s1.releaseSlot();
      s2.releaseSlot();
      s5.releaseSlot();
      s6.releaseSlot();
      s7.releaseSlot();

      assertEquals(5, scheduler.getNumberOfAvailableSlots());

      // check that slots that are released twice (accidentally) do not mess things up

      s1.releaseSlot();
      s2.releaseSlot();
      s5.releaseSlot();
      s6.releaseSlot();
      s7.releaseSlot();

      assertEquals(5, scheduler.getNumberOfAvailableSlots());
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }