Skip to content
Snippets Groups Projects
Part4Test.scala 8.32 KiB
Newer Older
Matt Bovel's avatar
Matt Bovel committed
package midterm22

import instrumentation.Monitor
import instrumentation.MockedMonitor

import org.junit.*
import org.junit.Assert.*
import instrumentation.*
import java.util.concurrent.atomic.AtomicInteger

class Part4Test:
Matt Bovel's avatar
Matt Bovel committed

Matt Bovel's avatar
Matt Bovel committed
  // This test can result in a deadlock because locks can be called in any
  // order. Here, Thread 1 locks Node 3 first and then Node 2, whereas Thread 2
Matt Bovel's avatar
Matt Bovel committed
  // locks Node 2 first and then Node 3. This will lead to a deadlock.
Matt Bovel's avatar
Matt Bovel committed
  @Test
  def testQuestion9() =
    TestUtils.assertDeadlock(
      TestHelper.testManySchedules(
        2,
        scheduler =>
Matt Bovel's avatar
Matt Bovel committed
          val allNodes =
            (for i <- 0 to 6 yield ScheduledNode(i, scheduler)).toList
Matt Bovel's avatar
Matt Bovel committed

          // Shared by all threads
          var sum: Int = 0
          def increment(e: Int) = sum += e

          (
            List(
              () =>
                // Thread 1
Matt Bovel's avatar
Matt Bovel committed
                var nodes: List[Node] =
                  List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
                nodes = nodes
Matt Bovel's avatar
Matt Bovel committed
                lockFun(nodes, increment)
              ,
Matt Bovel's avatar
Matt Bovel committed
              () =>
                // Thread 2
Matt Bovel's avatar
Matt Bovel committed
                var nodes: List[Node] =
                  List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
                nodes = nodes
                lockFun(nodes, increment),
            ),
            results => (true, "")
          )
      )
    )
Matt Bovel's avatar
Matt Bovel committed

Matt Bovel's avatar
Matt Bovel committed
  // This will not lead to a deadlock because the lock acquire happens in a
  // particular order. Thread 1 acquires locks in order 1->2->3->4, whereas
  // Thread 2 acquires locks in order 2->3->5.
  @Test
  def testQuestion10() =
    TestHelper.testManySchedules(
      2,
      scheduler =>
Matt Bovel's avatar
Matt Bovel committed
        val allNodes =
          (for i <- 0 to 6 yield ScheduledNode(i, scheduler)).toList
Matt Bovel's avatar
Matt Bovel committed

        // Shared by all threads
        var sum: Int = 0
        def increment(e: Int) = sum += e

        (
          List(
            () =>
              // Thread 1
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid > y.guid)
Matt Bovel's avatar
Matt Bovel committed
              lockFun(nodes, increment)
            ,
Matt Bovel's avatar
Matt Bovel committed
            () =>
              // Thread 2
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid > y.guid)
              lockFun(nodes, increment),
          ),
          results => (true, "")
        )
    )

  // This will not lead to a deadlock because the lock acquire happens in a
  // particular order. Thread 1 acquires locks in order 4->3->2->1, whereas
Matt Bovel's avatar
Matt Bovel committed
  // Thread 2 acquires locks in order 5->3->2.
Matt Bovel's avatar
Matt Bovel committed
  @Test
  def testQuestion11() =
    TestHelper.testManySchedules(
      2,
      scheduler =>
Matt Bovel's avatar
Matt Bovel committed
        val allNodes =
          (for i <- 0 to 6 yield ScheduledNode(i, scheduler)).toList
Matt Bovel's avatar
Matt Bovel committed

        // Shared by all threads
        var sum: Int = 0
        def increment(e: Int) = sum += e

        (
          List(
            () =>
              // Thread 1
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
Matt Bovel's avatar
Matt Bovel committed
              lockFun(nodes, increment)
            ,
Matt Bovel's avatar
Matt Bovel committed
            () =>
              // Thread 2
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
              lockFun(nodes, increment),
          ),
          results => (true, "")
        )
    )

  // This test can result in a deadlock because locks are not called in any
  // order. Thread 1 acquire order (3->2->4->1), Thread 2 acquire order
  // (2->3->5). Thread 1 locks Node3 first and then Node2, whereas Thread 2
Matt Bovel's avatar
Matt Bovel committed
  // locks Node 2 first and then Node3. This will lead to a deadlock.
Matt Bovel's avatar
Matt Bovel committed
  @Test
  def testQuestion12() =
    TestUtils.assertDeadlock(
      TestHelper.testManySchedules(
        2,
        scheduler =>
Matt Bovel's avatar
Matt Bovel committed
          val allNodes =
            (for i <- 0 to 6 yield ScheduledNode(i, scheduler)).toList
Matt Bovel's avatar
Matt Bovel committed

          // Shared by all threads
          var sum: Int = 0
          def increment(e: Int) = sum += e

          (
            List(
              () =>
                // Thread 1
Matt Bovel's avatar
Matt Bovel committed
                var nodes: List[Node] =
                  List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
                nodes = nodes.tail.appended(nodes(0))
Matt Bovel's avatar
Matt Bovel committed
                lockFun(nodes, increment)
              ,
Matt Bovel's avatar
Matt Bovel committed
              () =>
                // Thread 2
Matt Bovel's avatar
Matt Bovel committed
                var nodes: List[Node] =
                  List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
                nodes = nodes.tail.appended(nodes(0))
                lockFun(nodes, increment),
            ),
            results => (true, "")
          )
      )
    )
Matt Bovel's avatar
Matt Bovel committed

  // sum returns wrong answer because there is a data race on the sum variable.
Matt Bovel's avatar
Matt Bovel committed
  @Test(expected = classOf[AssertionError])
  def testQuestion13() =
    TestHelper.testManySchedules(
      2,
      scheduler =>
Matt Bovel's avatar
Matt Bovel committed
        val allNodes =
          (for i <- 0 to 6 yield ScheduledNode(i, scheduler)).toList
Matt Bovel's avatar
Matt Bovel committed

        // Shared by all threads
        var sum: Int = 0
        def increment(e: Int) =
Matt Bovel's avatar
Matt Bovel committed
          val previousSum = scheduler.exec { sum }("Get sum")
          scheduler.exec { sum = previousSum + e }("Write sum")
Matt Bovel's avatar
Matt Bovel committed

        (
          List(
            () =>
              // Thread 1
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
Matt Bovel's avatar
Matt Bovel committed
              lockFun(nodes, increment)
            ,
Matt Bovel's avatar
Matt Bovel committed
            () =>
              // Thread 2
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
              lockFun(nodes, increment),
          ),
          results =>
            if sum != 20 then
              (false, f"Wrong sum: expected 20 but got $sum")
            else
              (true, "")
        )
    )

  // sum value will be correct here because "sum += e" is protected by a lock.
  @Test
  def testQuestion14() =
    TestHelper.testManySchedules(
      2,
      sched =>
        val allNodes = (for i <- 0 to 6 yield ScheduledNode(i, sched)).toList

        val monitor = new MockedMonitor: // Monitor is a type of a lock.
Matt Bovel's avatar
Matt Bovel committed
          def scheduler = sched
Matt Bovel's avatar
Matt Bovel committed

        // Shared by all threads
        var sum: Int = 0
        def increment(e: Int) =
          monitor.synchronized { sum += e }

        (
          List(
            () =>
              // Thread 1
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
Matt Bovel's avatar
Matt Bovel committed
              lockFun(nodes, increment)
            ,
Matt Bovel's avatar
Matt Bovel committed
            () =>
              // Thread 2
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
              lockFun(nodes, increment),
          ),
          results =>
            if sum != 20 then
              (false, f"Wrong sum: expected 20 but got $sum")
            else
              (true, "")
        )
    )
Matt Bovel's avatar
Matt Bovel committed

Matt Bovel's avatar
Matt Bovel committed
  // total will give correct output here as it is an atomic instruction.
  @Test
  def testQuestion15() =
    TestHelper.testManySchedules(
      2,
      sched =>
        val allNodes = (for i <- 0 to 6 yield ScheduledNode(i, sched)).toList

        // Shared by all threads
        var total: AtomicInteger = new AtomicInteger(0)
        def increment(e: Int) =
          total.addAndGet(e)

        (
          List(
            () =>
              // Thread 1
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(1), allNodes(3), allNodes(2), allNodes(4))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
Matt Bovel's avatar
Matt Bovel committed
              lockFun(nodes, increment)
            ,
Matt Bovel's avatar
Matt Bovel committed
            () =>
              // Thread 2
Matt Bovel's avatar
Matt Bovel committed
              var nodes: List[Node] =
                List(allNodes(5), allNodes(2), allNodes(3))
Matt Bovel's avatar
Matt Bovel committed
              nodes = nodes.sortWith((x, y) => x.guid < y.guid)
              lockFun(nodes, increment),
          ),
          results =>
            if total.get != 20 then
              (false, f"Wrong total: expected 20 but got $total")
            else
              (true, "")
        )
    )
Matt Bovel's avatar
Matt Bovel committed

  class ScheduledNode(value: Int, val scheduler: Scheduler) extends Node(value)
      with MockedMonitor