Section notes for CS162 Section #5, February 25, 2003 Hakim Weatherspoon ADMINISTRIVIA - Office hours changed to M 12-1, Tu 3:30-4:30 Location 651 Soda (6th floor alcove) - Project 1 code due Thursday, March 6th, 2003, 11:59pm - Midterm on Thursday, March 13th, 2003, 7:00-9:00pm Location 1 Pimentel - Take picures of all groups today - Anonymous feedback cards Write about what you like and do not like about section, what you would like to see more of and less of in section, what is effective and what is not, and any other general comments. QUIZ - A glass box test is one which is written with some knowledge of the implementation of a piece of code. - A block box test is one which only tests the given interface to some functionality. Write down some glass box tests for the producer/consumer code. set the Alarm timeout to 1 unit so that yield is called after _every_ instruction. Write down some black box tests for the producer/consumer code. Create varying number of producers and consumers and verify that the correct number of consumptions happen and correct number of thread blockings happen. For example, create x producers and x consumers and all x threads should finish. Create x - 2 producers and x consumers and x-2 consumers and all producers should finish. etc. What are the corner cases for the producer/consumer code? How can you devise a test to test the corner cases? No consumers. Create a test with multiple producers and no consumers. No producers. Create a test with mutliple producers and no consumers. Give an example of how the producer/consumer code can be modified so that deadlock is possible. (you can create other resources or add to code if needed). Create a second lock, lock2 and have the produce first aquire lock then lock2 and the consumer first aquire lock2 then lock. In this situation locks cannot be taken away (no preemption), there are only two locks (limited resource), it is possible to hold one lock while waiting on another (hold and wait), and there is a possible cycle in the lock graph (T1: lock->lock2 and T2: lock2->lock). PRODUCER/CONSUMER Lock lock; Condition wantToAdd = new Condition(lock); Condition wantToTake = new Condition(lock); Producer() { lock.acquire(); while (numCokes == MAXCOKES) { wantToAdd.sleep(); } numCokes++; wantToTake.wake(); lock.release(); } Consumer() { lock.acquire(); while (numCokes == 0) { wantToTake.sleep(); } numCokes--; wantToAdd.wake(); lock.release(); } TESTING STRATEGIES "Unexpexcted Behavior" When a behavior is unspecified, that basically means that you can do whatever you want. The behavior is unspecified for the caller of the method. In join for example, suppose you have threads A, B, and C. Thread A calls B.join(), then thread C calls B.join(). The behavior for C is irrelevant because we said that the second call to join is undefined/unspecified. In other words, you could have C's call to join() return after B finishes or immediately or never. It doesn't matter. Test Cases - You're required to put test cases in your design document. - Attempt for 2-4 test cases for each part, but more is ok if you feel you need it. - Make sure that your tests cases are distinct. For example, consider the following tests for the priority scheduler: 1. Run three threads, A, B, and C with priorities 1, 2, and 3. Make sure they run in order. 2. Run five threads, A, B, C, D, and E with priorities 2, 3, 5, 6, and 7. Make sure they run in order. These cases, while they might be useful in your testing, are not very different. They have a different number of threads and different priorities, but almost any scheduler that passes test 1 will pass test 2. On your design document, identify similar cases like these and reduce them to one case. The following test description covers both of the above cases: Run some number of threads (< maxPriority) with distinct priorities and ensure that they run in the correct order. - Give the expected result of your test. For example, it would not be sufficient to say "Run some number of threads (< maxPriority) with distinct priorities." It's not clear that you expect them to run in a particular order. - Attempt to find both black box and glass box tests. * A test is one which only tests the given interface to some functionality. For example, part III of the first assignment states "A thread calls waitUntil to suspend its own execution until time has advanced to at least now + x." A black box test might check the time, have a thread call waitUntil(), and then check the time when the thread wakes up again. If the time is what you expected, then the test passes. We designed this test entirely based upon the specification of waitUntil(). * A test is one which is written with some knowledge of the implementation of a piece of code. Suppose you use some queue while implementing waitUntil(). A glass box test might check that the queue is correctly ordered whenever a thread calls waitUntil(). - Check for corner cases. A is a situation where the input is extreme. For example, a corner case while testing the priority scheduler might be with 0 or 1 threads running. Another corner case might have a very large number of threads. These cases tend to be ones that you overlook while coding, so they are often great to test. - Attempt to cover all possibilities in your tests. Sit down and attempt to specify all possible inputs to your code. Figure out what the correct behavior should be when your code gets that input. Group the inputs into similar cases. These are your test cases. DEADLOCK - Some words on MESA style monitors (used in Nachos!) - signaler keeps the lock and cpu, the waiter is put on the ready Q ( so use wait() inside a while loop and recheck the condition! ) - waiter releases the lock and goes to sleep atomically, when it wakes it is on the ready Q - Types of resources - preemptable resources can be taken away with little or no problem (eg cpu, memory), and can be scheduled - non-preemtable resources cause a problem if they are taken away (eg disk, semaphores, terminal, printer), and must be allocated - Deadlock is concerned with allocating resources. - Deadlock is a situation in which each thread in a set is waiting for something from some other thread waiting in the set. Since each is waiting, non can proceed. - Conditions for deadlock 1. limited access (mutex, bounded buffer) 2. no preemption allowed 3. multiple independent requests wait while holding a resource 4. circular chain requests - Approaches to deadlock problem - detection, detect deadlock and fix it, database solution - avoidance, OS solution 1. must eliminate one of the four deadlock conditions (hard!) 2. only make allocations that are "safe" (Banker's Algorithm) - A safe state is one in which deadlock is not inevitable (ie there exists a sequence of task completions which lead to all tasks completed). A safe allocation is one that leads to a safe state. - An unsafe state leads to deadlock. - The Banker's Algorithm is used to compute a safe sequence of tasks. -------------------------------------------------------------------------- Banker's Algorithm -------------------------------------------------------------------------- k is a resource for each thread x max(x,k) is max #k to be used by x alloc(x,k) is #k currently allocated to x need(x,k) is #k still needed by x avail(k) is #k currently available If all threads can finish with no additional resources, need(x) <= avail(k) for all k and x state = safe Else for all threads do Find a thread x, such that need(x,k) <= avail(k) If not found state = unsafe If found release resources mark x = finished avail(k) = avail(k) + alloc(x) - Important stuff; might be a midterm question! - Source of name: Can be used by banks so they don't allocate their available cache without being able to satisfy the needs of all customers. - Process enters system: Declares maximum resources it may need Available[0..r] : Number of available resources of each type Max[0..p][0..r] : Max demand of process P for resource R Alloc[0..p][0..r] : Amount of R allocated to P Need[0..p][0..r] : Amount of R *might be* needed by P; Need[p,r] = Max[p,r] - Alloc[p,r] - Define notation: Vector X <= Y if X[i] <= Y[i] forall i - Making a resource request: 1) Process P states that it needs resources: Request[p][0..r] 2) If Request[p][0..r] > Need[p][0..r], ERROR Process is requesting more than its stated maximum 3) If Request[p][0..r] > Available[p][0..r], WAIT All resources not available, so must wait 4) Set: Available[0..r] = Available[0..r] - Request[p][0..r] Alloc[p][0..r] = Alloc[p][0..r] + Request[p][0..r] Need[p][0..r] = Need[p][0..r] - Request[p][0..r] Now test if this resource-allocation state is "safe". If safe, proceed, otherwise revert to previous values of vectors and have P wait. - Safety test: Available resources >= Max needed by ANY process Implementation: Work[0..r] = Available[0..r] Finish[0..p] = false 1) Find a process P such that Finish[P] = false && Need[p][0..r] <0 Work[0..r] If no such P exists, goto 3 2) Work[0..r] = Work[0..r] + Alloc[p][0..r] Finish[p] = true Goto 1 3) If Finish[p] = true for all procs P, system is safe - Complexity: O(r * p^2) - Dining lawyers with the Banker's Algorithm 4 lawyers (P1, P2, P3, P4) 4 chopsticks - single resource with Available[r] = 4 Each lawyer needs 2 chopsticks: Need[0..p][r] = 2 - Lawyer P1 has 2 chopsticks already Alloc Max Avail P1 2 2 2 P2 0 2 P3 0 2 P4 0 2 - P2 asks for a chopstick Go ahead and "allocate" it: Alloc Max Avail P1 2 2 1 P2 1 2 P3 0 2 P4 0 2 Test for safe state: 1) Initialize: Work = 1 Alloc Max Need Finish P1 2 2 0 F P2 1 2 1 F P3 0 2 2 F P4 0 2 2 F 2) Need <= Work for P1, so... Work = 3 Alloc Max Need Finish P1 2 2 0 T P2 1 2 1 F P3 0 2 2 F P4 0 2 2 F 3) Need <= Work for P2, so... Work = 4 Alloc Max Need Finish P1 2 2 0 T P2 1 2 1 T P3 0 2 2 F P4 0 2 2 F 3) Need <= Work for P3, so... Work = 4 (no change!) Alloc Max Need Finish P1 2 2 0 T P2 1 2 1 T P3 0 2 2 T P4 0 2 2 F 4) Need <= Work for P4, so... Work = 4 (no change!) Alloc Max Need Finish P1 2 2 0 T P2 1 2 1 T P3 0 2 2 T P4 0 2 2 T We are in a safe state! - What about a different setup? P1, P2, and P3 have grabbed one chopstick and P4 is about to grab one. What happens? Alloc Max Avail P1 1 2 1 P2 1 2 P3 1 2 P4 0 2 - P4 asks for a chopstick Go ahead and "allocate" it: Alloc Max Avail P1 1 2 0 P2 1 2 P3 1 2 P4 1 2 Test for safe state: 1) Initialize: Work = 0 Alloc Max Need Finish P1 1 2 1 F P2 1 2 1 F P3 1 2 1 F P4 1 2 1 F 2) No proc exists which has Need <= Work! So we are not in a safe state. - What if P1 had asked for a second chopstick instead? 1) Initialize: Work = 0 Alloc Max Need Finish P1 2 2 0 F P2 1 2 1 F P3 1 2 1 F P4 0 2 2 F 2) P1's need is <= Work, so Work = 2 (+ 2) Alloc Max Need Finish P1 2 2 0 T P2 1 2 1 F P3 1 2 1 F P4 0 2 2 F Now you can see that any order of P2, P3, or P4 will satisfy the safe state.