SIGCSE 2010 Mathias Ricken and Robert Cartwright Rice University March 12, 2009
Two Trends
Test-driven development Concurrent programming
Automated grading
Part graded automatically, part by hand
Concurrency Is Difficult
Dont ensure that other threads terminate Tests that should fail may succeed
7
Main thread
Child thread
Main thread
spawns
ConcJUnit
Backward compatible replacement for JUnit Detects exceptions in all threads
Exception handler for all child threads and the event thread
Ensures that child threads have terminated and event thread is done
Enumerate live threads after test Inspect event queue
11
12
ConcJUnit Demo
13
Concurrency Examples
In-class discussion
Multi-threaded counter: data races Multi-threaded bank: deadlock
Homework
Bounded buffer Readers-writer lock Test suite handed out to help students
Multi-threaded Breakout
14
Example: Counter
Class that can increment an integer variable N times Write test first
public class CounterTest extends TestCase { final long PER_THREAD = 1000000; public void testSingle() { Counter c = new Counter(); c.incrementNTimes(PER_THREAD); assertEquals(PER_THREAD, c.getCount()); } }
15
Counter: Implementation
Write implementation
public class Counter { private long count = 0;
public long getCount() { return count; } public void incrementNTimes(long n) { for(long i=0; i<n; ++i) { ++count; } } }
Test passes!
16
17
Shared Data
Why does the multi-threaded counter test fail?
The count field is shared among threads The ++count operation is not atomic Thread may be interrupted after reading count, but before writing back to count
count=0 regA=? regB=? 0 0 ? 0 0 0 0 1 0 1 1 0 1 1 1 1 1 1
18
A1
regA = count; B1 regB = count; A2 regA = regA + 1; A3 count = regA; B2 regB = regB + 1; B3 count = regB;
Data Races
Definition
Two threads access the same data At least one access is a write Nothing prevents the order from changing
19
Thread must compete for ownership of lock object before entering synchronized block Synchronized block is not atomic But once a thread has a lock object, no other thread can execute code protected by the same lock object
20
Counter: Re-Write
Rewrite implementation
// ... private Object lock = new Object();
Test passes!
21
Multi-threaded Breakout
Uses ACM Java Task Force material
Based on Breakout - Nifty Assignment by Eric Roberts, SIGCSE 2006
Other problems
X,Y coordinate changes not atomic X,Y coordinates not volatile or synchronized, event thread may never see the updates
Future Work
Testing all schedules is intractable
Conclusion
Unit testing has important benefits in industry and in the classroom Concurrent programming is becoming more important, and its difficult ConcJUnit helps
www.concutest.org www.drjava.org
25
Notes
Notes
1. Also cannot detect uncaught exceptions in a programs uncaught exception handler (JLS limitation) 2. Only add edge if joined thread is really dead; do not add if join ended spuriously. 3. Have not studied probabilities or durations for sleeps/yields: One inserted delay may negatively impact a second inserted delay Example: If both notify() and wait() are delayed.
27
Spurious Wakeup
4.
public class Test extends TestCase { public void testException() { Thread t = new Thread(new Runnable() { public void run() { throw new RuntimeException("booh!"); } Loop since }); join() may end t.start(); spuriously while(t.isAlive()) { try { t.join(); } catch(InterruptedException ie) { } } } }
28
Image Attribution
Image Attribution
1.
2.
3. 4.
Left image on Two Trends: Test Driven Development, Damian Cugley. Right image on Two Trends: adapted from Brian Goetz et al. 2006, Addison Wesley. Graph on Moores Law: Adapted from Herb Sutter 2009 Image on Concurrency Is Difficult: Caption Fridays
30
Extra Slides
Changes to JUnit (1 of 3)
Thread group with exception handler
JUnit test runs in a separate thread, not main thread Child threads are created in same thread group When test ends, check if handler was invoked
32
failure!
34
success!
Too late!
uncaught!
Changes to JUnit (2 of 3)
Check for living child threads after test ends
Reasoning: Uncaught exceptions in all threads must cause failure If the test is declared a success before all child threads have ended, failures may go unnoticed Therefore, all child threads must terminate before test ends
36
failure!
uncaught!
37
success!
*4
38
Changes to JUnit (3 of 3)
Check if any child threads were not joined
Reasoning: All child threads must terminate before test ends Without join() operation, a test may get lucky Require all child threads to be joined
39
Fork/Join Model
Parent thread joins with each of its child threads
Main thread
Child thread 1
Child thread 2
43
Child thread 1
Child thread 2 Main thread Child thread 1 Child thread 2
CT1 CT2
MT CT1 CT2
45
Unreachable Nodes
An unreachable node has not been joined
Child thread may outlive the test
MT CT1 CT2
46
main Thread
MT
child Thread
CT
47
child Thread
CT
*2
48
Test hangs!
49
Deadlock
Thread A transfers from account 0 to 1 Thread B transfers from account 1 to 0 Thread A gets interrupted after acquiring locks[0]
// thread A synchronized(locks[0]) { // thread B synchronized(locks[1]) { synchronized(locks[0]) // cant continue, locks[0] // is owned by thread A */
50
Homework Assignment
Common structures students will see time and again
Bounded buffer Readers-writer lock
Many Thanks To
My advisor
Corky Cartwright
My committee members
Walid Taha David Scott Bill Scherer
53