PowerPoint - OSCAR Lab

A Program Logic for
Concurrent Objects under
Fair Scheduling
Hongjin Liang and Xinyu Feng
University of Science and Technology of China (USTC)
To appear at POPL 2016
Concurrent Object O
Client code C
…
void push(int v) {
…
}
int pop() {
…
}
java.util.concurrent
…
push(7);
x = pop();
…
…
push(6);
…
Whole program C[O]
Correctness of O
• Linearizability
• Correctness w.r.t. functionality
• Not talk about termination/liveness properties
• Progress properties
• Lock-freedom (LF)
• Wait-freedom (WF)
• Obstruction-freedom (OF)
• Deadlock-freedom (DF)
• Starvation-freedom (SF)
Non-blocking synchronization
- Program Logics:
Gotsman et al. POPL’09,
Hoffmann et al. LICS’13, …
Blocking synchronization
- Program Logics: ???
DF and SF as Progress Properties
[Herlihy and Shavit 2011]
• DF: in each fair execution, there always exists some
method call that can finish
• Fair sched: every active thread gets eventually executed
• Disallow non-termination of critical section
f() {
lock L;
while (true) {};
unlock L;
}

DF and SF as Progress Properties
[Herlihy and Shavit 2011]
• DF: in each fair execution, there always exists some
method call that can finish
• Fair sched: every active thread gets eventually executed
• Disallow non-termination of critical section
• Disallow live-lock
g1() {
lock L1;
while (!available(L2)) {
unlock L1;
lock L1;
}
unlock L1;
}
g2() {
lock L2;
while (!available(L1)) {
unlock L2;
lock L2;
}
unlock L2;
}

DF and SF as Progress Properties
[Herlihy and Shavit 2011]
• DF: in each fair execution, there always exists some
method call that can finish
• Fair sched: every active thread gets eventually executed
• Disallow non-termination of critical section
• Disallow live-lock
• Possible ad-hoc synchronization
h1() {
x := 1;
while (y != 0) {};
x := 0;
}
h2() {
y := 1;
while (x != 0) {};
y := 0;
}

DF and SF as Progress Properties
[Herlihy and Shavit 2011]
• DF: in each fair execution, there always exists some
method call that can finish
• Disallow non-termination of critical section
• Disallow live-lock
• Possible ad-hoc synchronization
 Not verified in existing work which views DF as a safety
property
• SF: in each fair execution, every method call can
finish
Our Contributions
• Program Logic LiLi for Linearizability & Liveness
• Identify two challenges in progress verification,
addressed by separate mechanisms
• Unify thread-local reasoning about DF and SF
• One set of inference rules (also applied to verifying LF and WF)
• Examples:
• Ticket locks, queue locks, TAS locks, two-lock queues, …
• We’re the first to verify SF of lock-coupling lists and DF of
optimistic lists and lazy lists
Challenges in Progress Verification
• In sequential settings, no infinite loops  termination
• In concurrent settings, env interference may affect
termination of a method
• Blocking
• Delay
Challenge 1: Blocking
• Caused by absence of env’s certain actions (e.g. unlock)
f() {
lock L;
f(); || g();
unlock L;
}
g() {
f may never terminate because it keeps
waiting for env’s unlock action
lock L;
}
Not acceptable.
Challenge 1: Blocking
• Caused by absence of env’s certain actions (e.g. unlock)
• Sometimes bad: the certain action never happens
• Sometimes acceptable: the certain action eventually
happens under fair scheduling
• SF and DF assume fair scheduling
f() {
f(); || f();
lock L;
unlock L;
}
Left f may wait for env’s unlock.
But it will terminate under fair
scheduling.
Challenge 1: Blocking
• Caused by absence of env’s certain actions (e.g. unlock)
• Sometimes bad: the certain action never happens
• Sometimes acceptable: the certain action eventually
happens under fair scheduling
How to support “acceptable” blocking but
prevent “bad” blocking?
Our idea: introduce definite actions D to
specify the certain actions that eventually
happen
Our idea: definite actions D
Q the thread has released lock
D
P the thread has acquired lock Enabled
• D is a special action in the form of P  Q
• Enabled(D) is defined as P
assume fair scheduling
• D should be “definite”: Q should eventually be reached
(regardless of env) once P holds
Using D to verify counter with ticket lock
the next available ticket
inc() {
currently being served
local i, r;
i := getAndInc( next );
while( i != owner ) {} ; // acquire lock
r := cnt; cnt := r + 1; // critical section
owner := i + 1;
// release lock
i
}
owner
next
Queue management in banks
Using D to verify counter with ticket lock
Lock
Lock
acquiredacquired
inc() {
local i, r;
i := getAndInc( next );
while( i != owner ) {} ; // acquire lock
r := cnt; cnt := r + 1; // critical section
owner := i + 1;
// release lock
}
It’s SF, because
• critical section is straight-line code
• threads requesting the lock form a queue
next
0
1
2
3
owner
T1: request lock
T2: request lock
T3: request lock
T1: release lock
Using D to verify counter with ticket lock
T is blocked: it is waiting
for env threads ahead of
it in the queue to finish D
Lock
acquired
T1
D
the thread acquires lock
T2
T
next
0
the thread has released lock
T is blocked
1
2
3
owner
How to establish D (prove D is
indeed “definite”) ?
Prove critical section terminates
no matter what env does
DProgress: T will be unblocked after env finishes
a finite number of Ds
which form a queue to ensure SF
T2 releases lock
T will get lock
D
T1 releases lock
fair sched
Enable
T2 gets lock
D
T1 gets lock
Enabled
initially
Queue length is a decreasing metric
Lock
acquired
T1
T is blocked
T2
T
next
0
owner
1
2
3
Summary of the definite action idea
 Support “good” blocking
• D will definitely happen under fair sched, regardless of env
• DProgress: any blocked thread will be unblocked after env finishes a
finite queue of Ds
 SF: every method can finish in any fair execution
 More examples: queue locks, lock-coupling lists
 DF: blocking and delay are intertwined
Challenge 2: Delay
• Caused by occurrence of env’s certain actions (e.g. object
state updates via cas)
Example: counter with cas lock
incDF() {
local b, r;
b := false;
incDF();
current thread ID
while( !b ) {
while(true)
incDF();
Left incDF may never terminate due to
env’s infinite num of successful cases.
b := cas(&L, 0, T);
}
r := cnt; cnt := r + 1; // critical section
L := 0;
}
Delay! Acceptable,
since DF requires only
whole-system progress.
D & DProgress idea for blocking does not
allow delay in the DF counter
T is blocked
T1 releases lock
D
T1 is holding lock
T will get lock
?
Queue jumps!
T2 gets lock
DProgress: T will be unblocked after env finishes a finite
queue of Ds, where queue length is a decreasing metric

Queue jumps delay the progress of the unblocked T.
Allowed, since DF requires only whole-system progress.
Relax DProgress to allow queue jumps
T1 releases lock
D
T1 is holding lock
T will get lock
?
Queue jumps!
T2 gets lock
DProgress:
T will be unblocked after env finishes a finite queue of Ds,
where queue length can be increased when delayed by env.
Problem: How to prevent infinite delays without
whole-system progress?
Tokens ensure no infinite delays before
whole-system progress
• Our idea: assign tokens
• Consume a token for a delaying action
• Similar ideas have been used to verify LF (i.e.
whole-system progress under any scheduling)
[Hoffmann et al LICS’13, Liang et al CSL-LICS’14]
Tokens ensure no infinite delays before
whole-system progress
inc() {
{p * }
local b, r;
pay  at successful cas (the only action
that can jump other threads’ queues)
b := false;
while( !b ) {
b := cas(&L, 0, T);
}
r := cnt; cnt := r + 1;
L := 0;
{q}
}
Summary for DF verification
• Blocking and delay are intertwined
• Blocking:
• D will definitely happen under fair sched, regardless of env
• DProgress: a blocked thread waits for env to finish a finite queue of Ds
• Allow queue jumps (queue length increases) when delayed by env
• Delay:
• Each method is assigned a finite number of tokens
• Delaying action: pay one token
 DF: whole system progress under fair scheduling
By turning on/off mechanisms for blocking & delay,
we can support all the four progress properties.
non-delay
non-blocking
wait-freedom

blocking
delay

lock-freedom

starvation-freedom  deadlock-freedom
Trickier: Blocking + Delay + Rollback
Examples: optimistic lists and lazy lists
add(e) { // all locks are CAS locks
local b := false, p, c;
while (!b) {
(p, c) := find(e);
lock p; lock c;
b := validate(p, c);
if (!b) { unlock c; unlock p; }
}
… // insert e between p and c
unlock c; unlock p;
}
It’s deadlock-free.
Problem:
A thread may lock a node for
an unbounded num of times.
Need infinite tokens?
Our solution:
stratify tokens (see the paper)
Our logic LiLi
• Judgment
D, R, G
{p}O:A
• O : concrete method impl (e.g. cas-lock counter)
• A : abstract atomic spec (e.g. <cnt++>)
• D : definite actions
• R, G : rely/guarantee to describe env interference (also
label delaying actions)
• p : concrete & abstract states & tokens
Soundness Theorem
for
LiLi
non-blocking
WF
non-delay
If
D, R, G
{p} O : A ,
delay


then
we have:
blocking
SF
LF


DF
a) O is linearizable w.r.t. A
b) O is deadlock-free
c) if R & G do not have delaying actions, then
O is starvation-free
d) if D is not used, then O is lock-free
e) if D is not used and R & G do not have delaying
actions, then O is wait-free
Summary:
LiLi for Linearzability & Liveness
• Blocking: definite actions
• Delay: tokens
• Unified: By ignoring either or both features, LiLi can be
instantiated to verify all the four progress properties
non-delay
non-blocking
wait-freedom
delay


blocking
starvation-freedom
lock-freedom


deadlock-freedom
Backup Slides
Obstruction-Freedom
• Progress when the thread executes in isolation
(without interference from env)
non-delay
nonblocking
waitfreedom

“good”
blocking
“good”
delay

lockfreedom

starvation-  deadlockfreedom
freedom
unlimited
delay
 obstructionfreedom
Obstruction-Freedom
• Progress when the thread executes in isolation
(without interference from env)
g1() {
while (x > 0) {
x--;
}
}
g2() {
while (x < 10) {
x++;
}
}
Comparisons with earlier token-based work
for LF verification
[Hoffmann et al LICS’13, Liang et al CSL-LICS’14]
• We all assign -tokens to loops (pay  at each round)
• But LiLi also assigns -tokens for delaying actions
• Earlier work assumes each method has only one delaying
action, which is at the linearization point (LP)
incLF(){
local done, r;
done := false;
while (!done) {
r := cnt;
done := cas(cnt, r, r + 1); // LP
}
}
Stratify tokens and delaying actions
Level 2: for add/remove
Level 1: for lock p & lock c
add(e) {
{ (1, 2)  … }
local b := false, p, c;
while (!b) {
(p, c) := find(e);
Could reset level-1 tokens when
delayed by env’s level-2 action
c)  (1, 4)  … }
{ valid(p, c)  (1, 2)  …  …invalid(p,
}
lock p; lock c;
{ valid(p, c)  (1, 0)  …  invalid(p, c)  (1, 2)  … }
b := validate(p, c); if (!b) { unlock c; unlock p; }
}
… // insert e between p and c
unlock c; unlock p;
}
Prevent Live-Lock by stratification of
-tokens & delaying actions
g1() {
lock L1;
while (available(L2)) {
unlock L1;
lock L1;
}
unlock L1;
}
g2() {
lock L2;
while (available(L1)) {
unlock L2;
lock L2;
}
unlock L2;
}
• Level 2: lock L2
• Level 1: lock L1
• When g2 locks L2, g1 gets more 1-level -tokens
• But 2-level -tokens do not increase!
g1(); || g2();
Establish D
• Termination of loop (“critical section”) when D is
enabled
p  B  Enabled(D)  p’ * 
D, R, G
D, R, G
{p’} C {p}
{p} while B do C {p  B}
• p’ has one less -token than p
• one token is consumed to start the new iteration
• -tokens do not increase, unless delayed by env
…
Establish D
• Termination of loop (“critical section”) when D is
enabled
p  B  Enabled(D)  p’ * 
D, R, G
{p’} C {p}
…
{p} while B do C {p  B}
D, R, G
• Global constraints (at TOP rule)
D, R, G
{p  arem(A)  (E)} C {p  arem(skip)}
p  Enabled(D)
G  D  (Enabled(D)  Enabled(D))
D, R, G
{p} C : A
…
DProgress queue for blocking
p  DProgress(n, D, Q)
DProgress(n, D, Q) stable
p  B  (Enabled(D)  Q)  p’ * 
D, R, G
D, R, G
{p} while B do C {p  B}
{p’} C {p}
The full rule for while
p  DProgress(n, D, Q)
DProgress(n, D, Q) stable
p  B  (Enabled(D)  Q)  p’ * 
D, R, G
D, R, G
{p} while B do C {p  B}
{p’} C {p}
Tokens for delay
• ATOM rule: Consume -tokens
SL
{p} C {q’}
D, R, G
q’ k q
(p k q)  G
{p} atomic{C} {q}
• q’ k q : k-level -tokens decrease
• Stable(p, R)
• Reset j-level -tokens for k-level env actions where j < k
• Reset -tokens to loop more rounds
Why linearizability and progress together?
• Progress-aware abstractions for concurrent objects

Linearizability O lin A
Progress P(O)

Contextual refinement O  AP

D, R, G
Progress-aware spec
{p}O:A
Progress-aware specs ASF and ADF
• O  AP:
• Assume fair scheduling
• Preserve termination behaviors
• ASF : atomic spec A

P(O)

• ADF : wrap A with delaying code
O lin A
O  AP

D, R, G
{p}O:A

DF(O)

DF-aware spec
O lin A
O  wr(A)
D, R, G

{p}O:A