ArticlePDF Available

Roll Forward, Not Back: A Case for Deterministic Conflict Resolution

Authors:

Abstract

Enabling applications to execute various tasks in parallel is difficult if those tasks exhibit read and write conflicts. In recent work, we developed a programming model based on concurrent revisions that addresses this challenge: each forked task gets a conceptual copy of all locations that are declared to be shared. Each such location has a specific isolation type; on joins, state changes to each location are merged deterministically based on its isolation type. In this paper, we study how to specify isolation types abstractly using operation-based compensation functions rather than state based merge functions. Using several examples including a list with insert, delete and modify operations, we propose compensation tables as a concise, general and intuitively accessible mechanism for determining how to merge arbitrary operation sequences. Finally, we provide sufficient conditions to verify that a state-based merge function correctly implements a compensation table.
Roll Forward, Not Back
A Case for Deterministic Conflict Resolution
Sebastian Burckhardt, Manuel F
¨
ahndrich, and Daan Leijen
Microsoft Research
{sburckha,maf,daan}@microsoft.com
Abstract
Enabling applications to execute various tasks in parallel
is difficult if those tasks exhibit read and write conflicts.
In recent work, we developed a programming model based
on concurrent revisions that addresses this challenge: each
forked task gets a conceptual copy of all locations that are
declared to be shared. Each such location has a specific isola-
tion type; on joins, state changes to each location are merged
deterministically based on its isolation type. In this paper,
we study how to specify isolation types abstractly using
operation-based compensation functions rather than state-
based merge functions. Using several examples including a
list with insert, delete and modify operations, we propose
compensation tables as a concise, general and intuitively ac-
cessible mechanism for determining how to merge arbitrary
operation sequences. Finally, we provide sufficient condi-
tions to verify that a state-based merge function correctly
implements a compensation table.
1. Introduction
With the recent broad availability of shared-memory multi-
processors, many more application developers now have a
strong motivation to tap into the potential performance ben-
efits of parallel execution. However, dealing with conflicts
between parallel tasks can be quite challenging with tradi-
tional synchronization models. In fact, many programmers
are deterred by the engineering complexity of performing
explicit, manual synchronization or replication.
Our vision is that programmers instead use the program-
ming model of concurrent revisions [2], which simplifies
parallelization of conflicting tasks by (conceptually) copying
shared state automatically on a fork, and merging changes
back at joins using custom merge functions. What is exciting
about this model is the potential to simplify programming
by using isolation types, shared higher-level data types that
have suitable merge functions defined for them. For exam-
ple, instead of sharing an integer to count events using read
and writes, two concurrent tasks would share a counter ab-
straction using increment operations on the counter instead.
Although this seems like a trivial shift in perspective, it is the
higher-level semantics of an increment as opposed to a read-
/write pair that permits the definition of “sensible” merge
functions and reasoning about their behavior.
We found that even simple data types expose subtle cor-
rectness issues when trying to specify and verify them. In
this paper we study a simple integer with both add and set
operations in detail. We also provide merge specifications
for lists with insert, modify, and delete operations. The work
presented here lays the foundation for dealing with more
complicated types such as maps.
In our previous work we have shown that as long as merge
functions are deterministic, the entire execution model of
concurrent revisions is deterministic. One particular ques-
tion left unadressed, however, is what additional properties
merge functions should satisfy in order to be “sensible” for
particular data types. For example, what is a sensible merge
for a list data type supporting inserts, deletes, and changes
to a list? We address this question in this paper by intro-
ducing compensation functions as an abstract specfication
mechanism. Unlike state-based merge functions, compensa-
tion functions are defined in terms of sequences of opera-
tions of the underlying data type. We make the following
contributions:
1. We introduce a operation-based view of merge functions,
based on compensation functions that resolve two con-
flicting operation sequences by appending compensa-
tions.
2. We propose compensation tables as a concise yet trans-
parent way to specify compensation functions pairwise.
3. We show that compensation tables naturally define how
to resolve arbitrary operation sequences by “tiling”.
4. We give sufficient conditions for verifying that a state-
based merge function satisfies a compensation table.
5. We present compensation tables for a number of example
data types, including a list, and a concrete implementa-
tion along with a detailed proof of correctness.
2. Concurrent Revisions
The context for our work is the recently proposed determin-
istic concurrent programming model called concurrent revi-
sions [2, 3]. Its key design principles are:
Explicit Join. The programmer forks and joins revisions,
which can execute concurrently. All revisions must be
joined explicitly.
Declarative Data Sharing. The programmer uses special
isolation types to declare what data may be shared, and
how individual data should be merged.
Effect Isolation. All changes made to shared data within
a revision are only locally visible until that revision is
joined.
Conceptually, the runtime copies all shared data when a
new revision is forked. Therefore, the runtime can schedule
concurrent revisions for parallel execution without creating
data races. At the time of the join, the runtime calls a merge
function f for each location that was modified by the joined
revision, and assigns the computed value to the location
(locations that were not modified retain their current value).
The function called depends on the isolation type. It is called
with three values v
current
, v
joined
and v
original
representing the
current value in the joining revision, the current value in the
joined revision, and the value at the time the revision was
originally forked, respectively.
For example, a cumulative integer type may employ the
merge function f
CumulativeInt
(v
c
, v
j
, v
o
) = v
c
+ (v
j
v
o
) as
shown in Fig. 1. This function computes the relative effect
v
j
v
o
of the modifications in the joined revision and adds
it to the current value v
c
. The effect is that modifications by
all revisions are cumulative.
Another isolation type may give priority to the writes in
the child revision by specifying f(v
c
, v
j
, v
o
) = v
j
, as we do
for the versioned integers in Fig. 2. We found these versioned
types to be very useful in practice [2] as they allows us to
precisely control the order of writes: all writes to a versioned
variable appear to take effect atomically at the time of the
join (and thus simply overwrite earlier writes).
A key benefit of the “concurrent revisions + isolation
types” model is that it resolves conflicts between tasks in
a deterministic and programmable way. Even if tasks exhibit
conflicts, they can still be efficiently executed in parallel,
without issues caused by unnecessary rollbacks and retries.
Moreover, the computation is determinate, meaning that it is
fully specified by the program and does not depend on the
relative timing or scheduling of tasks
1
[3].
2.1 Revision Diagrams
It is often helpful to visualize computations using revision
diagrams (see Fig. 1(b) and the bottom row of Fig. 2). Such
diagrams show each revision as a vertically aligned sequence
of points, each point representing one (dynamic) instance of
a statement. We use curved arrows to show precisely where
1
Note further that the determinacy also does not depend on the merge
function being associative or commutative (or being “sensible” in any other
way, for that matter); the only requirement is that it be a function in the
mathematical sense.
(a) (b)
CumulativeInt x = 0 ;
r = fork { x = x + 2 ; }
x = x + 3 ;
join r ; //x = 3 + (2 0)
assert(x
5) ;
x = 0
x = x + 3
x = x + 2
nn
assert
(x
5)
Figure 1. Example illustrating parallel aggregation with
the isolation type CumulativeInt with the merge function
f
CumulativeInt
(v
c
, v
j
, v
o
) = v
c
+ (v
j
v
o
).
new revisions branch out (on a fork) and where they merge
in (on a join). As shown in Fig. 2(e), revisions can be nested.
Revision diagrams are not equivalent to other graphs
commonly used to depict concurrent computations: unlike
DAGs, they are semilattices [3], and unlike in SP-graphs,
children may be joined after their parent is joined (Fig. 2(e)).
3. Data Types and Compensations
We now consider some fundamental definitions of sequential
data types, and show how to use compensation operations
to generalize sequential semantics to a concurrent semantics
appropriate for use with the concurrent revisions model.
First, let Val be the universe of values. We consider values
of all types to be part of this set, and the type to be implicitly
and uniquely determined by each value.
DEFINITION 1. We define a sequential data type to be a
tuple of the form (S, R, M, I, ρ, µ) where S is a set of states,
R is a set of read operations, M is a set of modify operations,
I S is an initial state, ρ : R × S Val is a read function
(which returns for a given read operation and state the value
returned by the read operation), and µ : M × S S is a
modify function (returning for a given write operation and
state the updated state). For convenience, we assume that
every data type always includes an empty operation such
that µ(, s) = s.
EXAMPLE 2. We can define an integer register (a location
holding an integer value and supporting read and write
operations) as a sequential data type
IntReg = (Z, {get}, {set(k) | k Z}, ρ, µ)
where ρ(get, k) = k and µ(set(k), k
0
) = k.
Note that the state of a sequential data type is completely
determined by the sequence of modifications. For a sequence
of modifications w = w
1
. . . w
n
M
and a state s S,
we write µ(w, s) short for µ(w
n
, . . . µ(w
1
, s)) . . . ).
We consider operation sequences equivalent that are
equivalent state transformers: we write w
1
=
D
w
2
for some
data type D = (S, R, M, I, ρ, µ) if µ(w
1
, s) = µ(w
2
, s) for
all s S. For example, set(1)
=
IntReg
set(0)add(1).
(a) (b) (c) (d) (e)
versionedhinti x = 0 ;
r = fork { x = 2 ; }
x = 1 ;
join r ;
assert(x
2) ;
versionedhinti x = 0 ;
r = fork { x = x ; }
x = 1 ;
join t ;
assert(x
0) ;
versionedhinti x = 0 ;
r = fork { }
x = 1 ;
join t ;
assert(x
1) ;
versionedhinti x = 0 ;
r1 = fork { x = 2 ; }
r2 = fork { x = 3 ; }
join r2 ;
join r1 ;
assert(x
2) ;
versionedhinti x = 0 ;
r1 = fork { r2 = fork { x = 1 ; } }
join r1 ;
assert(x
0) ;
join r2 ;
assert(x
1) ;
x = 0
x = 1
x = 2
rr
assert
(x
2)
x = 0
x = 1
x = x
rr
assert
(x
0)
x = 0
x = 1
rr
assert
(x
1)
x = 0
x = 3
00
x = 2
ww
assert
(x
2)
x = 0
nn
assert
(x
0)
x = 1
mm
assert
(x
1)
Figure 2. Examples illustrating the semantics of revisions and versioned types with the merge function f(v
c
, v
j
, v
o
) = v
j
. (a)
The write x=2 takes effect on join; (b,c) the assignment x=x counts as a write, thus removing it leads to a different result; (d)
the order of joins determines the order of writes; (e) a nested revision may be joined after its parent.
3.1 Constructing Merge Functions
The intention behind the concurrent revisions programming
model is to behave as if all modifications performed by a
revision are isolated while the revision is still running, but
take effect atomically at the moment it is joined (i.e. the
modifications are applied to the current state of the joining
revision). As we have demonstrated in prior work [2] and
in the examples earlier in this paper, we can usually achieve
this with a state merge function f : S × S × S S that is
invoked during join if there were write-write conflicts, i.e. if
both the parent and the child performed modifications.
For example, the versioned integer type illustrated in
Fig.2 is intended to merges modifications that represent
absolute changes to the value. On the other hand, the cu-
mulative integer type shown in Fig. 1 is intended to merge
modifications that are interpreted as relative changes. But
what if some changes are absolute and some are relative? In
that case, we need more information about what operations
were performed to perform a proper merge.
EXAMPLE 3. We can define a sequential data type Int that
offers two different modify operations, an absolute one (set
it to specific value), and a relative one (add a value) as
follows:
S = Z
R = {get}
M = {set(k) | k Z} {add(k) | k Z}
ρ(get, k) = k
µ(set(k), k
0
) = k
µ(add(k), k
0
) = k
0
+ k
This example raises two questions: how do we specify
what should happen if operations add(k) and set(k) are
called concurrently? And how can we implement a state
merge function that follows that specification? We give gen-
eral answers to these questions in the remainder of this paper,
with specific solutions for this Example.
3.2 Compensation Functions
It is not always clear how to devise state-based merge func-
tions that achieve the intended semantics. Reasoning about
operations can often provide more insight.
We could describe an operation-based merge function as
taking two sequences of modify operations and returning a
new sequence, i.e. f : M
× M
M
. Unfortunately,
such arbitrary sequences do not permit compositional rea-
soning about merge behavior on nested revision diagrams
(such as the one in Fig. 2(e)). Thus we use compensation
specification instead.
DEFINITION 4. A compensation specification c
for a se-
quential data type (S, R, M, I, ρ, µ) is a function that, given
two sequences, returns two compensation sequences:
c
: M
× M
(M
× M
)
We write c
l
, c
r
to denote the left and right components of c
,
respectively.
Given two operation sequences that we wish to merge,
say w
l
(happening “on the left”, i.e. in the joining revision)
and w
r
(happening “on the right”, i.e. in the joined revi-
sion), we consult c
to obtain the compensating operation
sequences, say c
(w
l
, w
r
) = (v
l
, v
r
). The meaning of these
·
m
1
m
3
>
>
>
>
>
>
>
>
>
·
m
2
c
l
(m
1
,m
3
)
·
c
r
(m
1
,m
3
)
m
4
>
>
>
>
>
>
>
>
>
·
c
l
(m
2
,c
l
(m
1
,m
3
))
·
v
1
v
2
·
c
r
(c
r
(m
1
,m
3
),m
4
)
·
c
l
(v
1
,v
2
)
·
c
r
(v
1
,v
2
)
·
Figure 3. Tiling compensation tables on single operations
constructs a compensation function on sequences. In the
above diagram, we have v
1
= c
r
(m
2
, c
l
(m
1
, m
3
)) and v
2
=
c
l
(c
r
(m
1
, m
3
), m
4
).
operations is that the effect of the merge must be equivalent
to both (1) applying the compensating operations on the left,
i.e. applying v
l
after m
l
, and (2) applying the compensating
operations on the right, i.e. applying v
r
after m
r
.
DEFINITION 5. A compensation specification c
is con-
sistent if the operations composed with their compensa-
tions are equivalent: w
l
, w
r
M
: w
l
c
l
(w
l
, w
r
)
=
D
w
r
c
r
(w
l
, w
r
).
3.3 Compensation Tables
To define compensation functions in practice, it is sensible
to first define a compensation function for pairs of opera-
tions c : M × M (M × M ). We call such a func-
tion a compensation table. Compensation tables (if consis-
tent) have the nice property that they uniquely define a (con-
sistent) compensation function for arbitrary finite operation
sequences because we can tile pairwise compensations as
illustrated in Fig. 3, where we compute the compensation
function c(m
1
m
2
, m
3
m
4
) for merging two instruction se-
quences of two operations each. Starting at the top, we first
compute the compensating actions for merging just m
1
and
m
3
. From that new point we can keep merging on single op-
erations until we fill out the 4 tiles of the diamond. Clearly,
this procedure easily generalizes to sequences of more than
two operations, thus defining a complete compensation func-
tion. While elegant, the tiling method may however not be
efficient in practice (spending time quadratic in the number
of operations). We discuss in Section 4 how to build more
efficient implementations.
EXAMPLE 6. Consider an integer data type as defined in
Example 3, but with M restricted to contain only add op-
erations. We can then define the compensation table
c(add(i), add(j)) = (add(j), add(i)).
In this case, the compensating action is exactly the other
action. It is not hard to see that the merge we define in this
way is equivalent to the state-based merge function f
CumInt
defined earlier.
In general, if the operations commute we can always con-
struct a consistent merge specification by using exactly
the other operation as the compensating action, where
c(m
l
, m
r
) = (m
r
, m
l
). Due to commutativity, if follows
directlty that m
l
m
r
=
m
r
m
l
.
The next example shows that operations do not always
need to commute to be mergeable, nor does the merge func-
tion need to be symmetric.
EXAMPLE 7. Consider the integer register defined in Exam-
ple 2. We can then define the compensation table as
c(set(i), set(j)) = (set(j), ).
This specification is consistent since set(i)set(j)
=
set(j).
In this case, we want the write on the right to overwrite
the write on the left, thus the compensating action on the
left is set(j), while the compensating action on the right is
(none needed). Again, the merge we defined in this way
is equivalent to the state-based merge function f
VersionedInt
defined earlier.
EXAMPLE 8. Consider the integer data type from Exam-
ple 3. Then we can give a compensation table:
c(add(i), add(j)) = (add(j), add(i))
c(set(i), add(j)) = (add(j), set(i + j))
c(add(i), set(j)) = (set(j), )
c(set(i), set(j)) = (set(j), )
EXAMPLE 9. Consider a list data type (S, R, M, I, ρ, µ)
with S being the set of lists of some type (left unspecified
for now), I being the empty list, R = {get(i) | i Z,
M = {set(i, x) | i, x Z}∪{ins(i, x) | i, x Z}{del(i) |
i Z}. The insertion ins(i, x) inserts x right before the
element at index i. We write idx(m) to get the index from
an operation, and adj(m, j) to add j to the index of an
operation m.
Since there are so many cases to consider, we define the
merge table first just for the right-side compensation. First,
we consider all pairs of operations where the indices are
equal and thus work on the same element:
c
r
(del(i), del(i)) = (double deletion)
c
r
(set(i, x), del(i)) = (set is deleted)
c
r
(set(i, x), set(i, y)) = (right side wins)
c
r
(ins(i, x), ins(i, y)) = ins(i, x) if x < y
c
r
(ins(i, x), ins(i, y)) = ins(i + 1, x) if x y
c
r
(del(i), set(i, x)) = del(i)
c
r
(ins(i, x), del(i)) = ins(i, x)
c
r
(del(i), ins(i, x)) = del(i + 1)
c
r
(set(i, x), ins(i, y)) = set(i + 1, x)
Secondly, we consider the cases where the indices differ:
c
r
(m
1
, m
2
) = m
1
if idx(m
1
) < idx(m
2
)
c
r
(m
1
, del(i)) = adj(m
1
, 1) if idx(m
1
) > i
c
r
(m
1
, ins(i, x)) = adj(m
1
, +1) if idx(m
1
) > i
c
r
(m
1
, set(i, x)) = m
1
if idx(m
1
) > i
Finally, we define the left compensation in terms of the right
compensation:
c
l
(set(i, x), set(i, y)) = set(i, y) (right side wins)
c
l
(m
1
, m
2
) = c
r
(m
2
, m
1
) otherwise
This defines the merge semantics of mutable lists. Note that
the cases for conflicting set’s prefer the set of the right side.
One could define lists over mergeable types and use the
apropiate merge specification for that type in such cases. For
double insertions c
r
(ins(i, x), ins(i, y)), we use the order
between the elements x and y to determine the final order:
this ensures that for example a sorted list stays sorted after
merging. Another choice could be to use ‘right side first’, to
only preserve the relative order of insertions in each branch.
4. Concrete Implementations
In practice, we often try to construct concrete implementa-
tions of a data type where state forking and merging is repre-
sented by a replication function (representing the operation
of copying the state upon a fork for use in the forked revi-
sion) and a state merge function f as described in Section 2.
DEFINITION 10. We define a concrete implementation to be
a tuple (S, R, M, I, ρ, µ, r, f ) where (S, R, M, I, ρ, µ) is a
sequential data type, r : S S is a replication function,
and f : S × S × S S is a merge function.
EXAMPLE 11. We can define a concrete integer implemen-
tation for the Int type from Example 3 that satisfies the com-
pensation tables given in Example 8. We augment the state
to store not just a simple integer k, but A(k) if the inte-
ger should be interpreted as an absolute value, or R(k) if
it should be interpreted as a relative value. To simplify the
notation below, we use
as a pattern that matches an ar-
bitrary state, and X(k) as a pattern that matches A(k) or
R(k).
S = {A(i), R(i) | i Z}
R = {get}
M = {set(i), add(i) | i Z}
I = A(0)
ρ(get, A(i)) = ρ(get, R(i)) = i
µ(set(i), X(j)) = A(i)
µ(add(i), X(j)) = X(j + i)
f( , A(m), ) = A(m)
r(X(i)) = R(i)
f(A(n), R(m), X(i)) = A(n + m i)
f(R(n), R(m), X(i)) = X(n + m i)
4.1 Verifying Concrete Implementations
As soon as we present a concrete implementation, we would
like to know whether it correctly represents the the in-
tended sequential semantics (specified by some sequential
data type) and the intended merge semantics (specified by
a compensation table). This question is not academic, but
very important in practice; without a clear idea on how
to relate the implementation to the specification, humans
are certain to make mistakes. We now elaborate how we
can break the verification of some concrete implementation
(S, R, M, I, ρ, µ, r, f ) into three conditions and give suffi-
cient subconditions for each.
(Condition 1) To show that the implementation general-
izes a sequential data type (S
0
, R, M, I
0
, ρ
0
, µ
0
) we can give
an abstraction function ψ : S S
0
that satisfies the follow-
ing conditions:
r R : s S : ρ(r, s) = ρ
0
(r, ψ(s))
m M : s S : ψ(µ(m, s)) = µ
0
(m, ψ(s))
ψ(I) = I
0
s S : ψ(r(s)) = ψ(s)
EXAMPLE 12. We can show that the implementation in
Example 11 generalizes the sequential data type Int from
Example3 by defining the map ψ : S Z as ψ(X(k)) = k,
that is, to “erase” the extra information. The cases are then
easily verified.
(Condition 2). We can show that the concrete implemen-
tation correctly merges revisions in cases where there is at
most one operation, by enumerating all cases. Specifically,
for all s S and m
1
, m
2
M, we can show:
f(µ(m
1
, s), µ(m
2
, r(s)), s) = µ(m
1
c
l
(m
1
, m
2
), s)
= µ(m
2
c
r
(m
2
, m
1
), s)
EXAMPLE 13. For the implementation in Example 11 and
the compensation table in Example 8, we can discharge this
condition by going through all the cases for m
1
and m
2
,
each case being relatively simple.
(Condition 3). We can show that the concrete implemen-
tation merges states correctly even if multiple operations
need to be reconciled, by showing that there exists a “smash”
function ξ : M × M M that satisfies the following con-
ditions:
1. ξ is associative.
2. m M : ξ(m, ) = ξ(, m) = m.
3. ξ is consistent with µ: for all s S and m
1
, m
2
M,
we have µ(ξ(m
1
, m
2
), s) = µ(m
2
, µ(m
1
, s)).
4. ξ is consistent with tiling of compensation functions (as
in Fig. 3): for all m
1
, m
3
, m
4
M, we have
c
l
(m
1
, ξ(m
3
, m
4
)) = ξ(c
l
(m
1
, m
3
), c
l
(c
r
(m
1
, m
3
), m
4
))
c
r
(m
1
, ξ(m
3
, m
4
)) = c
r
(c
r
(m
1
, m
3
), m
4
))
and for all m
1
, m
2
, m
3
M, we have
c
l
(ξ(m
1
, m
2
), m
3
) = c
l
(m
2
, c
l
(m
1
, m
3
)))
c
r
(ξ(m
1
, m
2
), m
3
) = ξ(c
r
(m
1
, m
3
), c
r
(m
2
, c
l
(m
1
, m
3
)))
EXAMPLE 14. For the implementation in Example 11 and
the compensation table in Example 8, we define the smash
function as follows:
ξ(add(i), add(j)) = add(i + j)
ξ(add(i), set(j)) = set(j)
ξ(set(i), add(j)) = set(i + j)
ξ(set(i), set(j)) = set(j)
Again, we can then discharge the conditions by going
through all the cases. The first three are easy. Consis-
tency with the compensation functions is abit more work.
Listing all 32 cases appeared overwhelming at first, but us-
ing diagrams simplified the task reasonably; we drew and
filled in one diagram for each of the 8 combinations of
m
1
, m
3
, m
4
, and one diagram for each of the 8 combina-
tions of m
1
, m
2
, m
3
, then checked 2 conditions per diagram.
Clearly, for more complex data types we would automate this
process.
5. Related Work
Recently, researchers have proposed programming models
for deterministic concurrency [1, 5, 11, 14]. These models all
guarantee that the execution is equivalent to some sequential
execution and do not resolve true conflicts. Cilk++ hyperob-
jects [6] are similar to isolation types, but are deterministic
only for fully commutative operations, and Cilk tasks follow
a more restricted concurrency model [7, 12]. Isolation types
are also similar to the idea of coarse-grained transactions [8]
and semantic commutativity [9] insofar they eliminate false
conflicts by raising the abstraction level.
Conflict resolution schemes have also been studied in the
context of collaborative editing and eventual consistency.
Most similar to our compensation function idea is the op-
erational transformations approach [4], but it requires more
complicated consistency conditions often violated by actual
implementations [10]. Alternatively, conflict resolution can
be simplified by making all operations commutative [13].
6. Conclusion
We believe that the concurrent revisions framework is a great
foundation to study mergeable datatypes. Using compensa-
tion tables we can concisely specify the semantics of concur-
rent data types, and we are working to specify more complex
data types like graphs and dictionaries.
References
[1] E. Berger, T. Yang, T. Liu, and G. Novark. Grace: Safe mul-
tithreaded programming for C/C++. In Object-Oriented Pro-
gramming, Systems, Languages, and Applications (OOPSLA),
2009.
[2] S. Burckhardt, A. Baldassin, and D. Leijen. Concurrent pro-
gramming with revisions and isolation types. In Object-
Oriented Programming, Systems, Languages, and Applica-
tions (OOPSLA), 2010.
[3] S. Burckhardt and D. Leijen. Semantics of concurrent re-
visions. In European Symposium on Programming (ESOP),
2011.
[4] C. A. Ellis and S. J. Gibbs. Concurrency control in groupware
systems. SIGMOD Rec., 18:399–407, June 1989.
[5] R. B. et al. A type and effect system for Deterministic Parallel
Java. In Object-Oriented Programming, Systems, Languages,
and Applications (OOPSLA), 2009.
[6] M. Frigo, P. Halpern, C. E. Leiserson, and S. Lewin-Berlin.
Reducers and other Cilk++ hyperobjects. In Symposium on
Parallel Algorithms and Architectures (SPAA), 2009.
[7] M. Frigo, C. Leiserson, and K. Randall. The implementation
of the Cilk-5 multithreaded language. In Programming Lan-
guage Design and Impl. (PLDI), pages 212–223, 1998.
[8] E. Koskinen, M. Parkinson, and M. Herlihy. Coarse-grained
transactions. In Principles of Programming Languages
(POPL), 2010.
[9] M. Kulkarni, K. Pingali, B. Walter, G. Ramanarayanan,
K. Bala, and L. Chew. Optimistic parallelism requires abstrac-
tions. In Programming Language Design and Implementation
(PLDI), 2007.
[10] G. Oster, P. Urso, P. Molli, and A. Imine. Proving correctness
of transformation functions in collaborative editing systems.
Rapport de Recherche 5795, LORIA – INRIA Lorraine, Dec.
2005.
[11] P. Pratikakis, J. Spacco, and M. Hicks. Transparent proxies
for java futures. SIGPLAN Not., 39(10):206–223, 2004.
[12] K. Randall. Cilk: Efficient Multithreaded Computing. PhD
thesis, Department of Electrical Engineering and Computer
Science, Massachusetts Institute of Technology, May 1998.
[13] M. Shapiro, N. Preguic¸a, C. Baquero, and M. Zawirski. A
comprehensive study of Convergent and Commutative Repli-
cated Data Types. Rapport de Recherche 7506, INRIA, Jan.
2011.
[14] A. Welc, S. Jagannathan, and A. Hosking. Safe futures for
java. In Object-Oriented Programming, Systems, Languages,
and Applications (OOPSLA), pages 439–453, 2005.
... Specifying a semantics for such type in a concurrent setting is not entirely straightforward; for example, what should happen on the join if one revision sets the register while another adds to it concurrently? One way of specifying semantics for such types is through so-called compensation functions [4]. A compensation function comp specifies for every pair of possible operations (m1, m2) what their compensating actions (n1, n2) are, such that composing m1; n1 is equivalent to composing m2; n2. ...
... It turns out that from these one-operation compensation tables, we can always generalize them to compensation functions that work over arbitrary sequences of operations using tiling techniques [4]. ...
... Note that other choices are possible: compensation tables are only a way to concisely describe a particular concurrent semantics which lends itself to validation. This becomes especially important with more complex data types like mutable lists for example [4]. ...
Conference Paper
Full-text available
This article presents an extension to the work of Launchbury and Peyton-Jones on the ST monad. Using a novel model for concurrency, called concurrent revisions [3,5], we show how we can use concurrency together with imperative mutable variables, while still being able to safely convert such computations (in the Rev monad) into pure values again. In contrast to many other transaction models, like software transactional memory (STM), concurrent revisions never use rollback and always deterministically resolve conflicts. As a consequence, concurrent revisions integrate well with side-effecting I/O operations. Using deterministic conflict resolution, concurrent revisions can deal well with situations where there are many conflicts between different threads that modify a shared data structure. We demonstrate this by describing a concurrent game with conflicting concurrent tasks.
... Previous work on revisions [2, 5, 3, 4] introduces revision diagrams and conflict resolution. In this paper we feature a simpler, more direct definition using graph construction rules. ...
Article
Full-text available
When distributed clients query or update shared data, eventual consistency can provide better availability than strong consistency models. However, programming and implementing such systems can be difficult unless we establish a reasonable consistency model, i.e. some minimal guarantees that programmers can understand and systems can provide effectively. To this end, we propose a novel consistency model based on eventually consistent transactions. Unlike serializable transactions, eventually consistent transactions are ordered by two order relations (visibility and arbitration) rather than a single order relation. To demonstrate that eventually consistent transactions can be effectively implemented, we establish a handful of simple operational rules for managing replicas, versions and updates, based on graphs called revision diagrams. We prove that these rules are sufficient to guarantee correct implementation of eventually consistent transactions. Finally, we present two operational models (single server and server pool) of systems that provide eventually consistent transactions.
... Previous work on revisions [2,6,3,5] introduces revision diagrams and conflict resolution . In this paper we feature a simpler, more direct definition using graph construction rules. ...
Conference Paper
Full-text available
When distributed clients query or update shared data, eventual consistency can provide better availability than strong consistency models. However, programming and implementing such systems can be difficult unless we establish a reasonable consistency model, i.e. some minimal guarantees that programmers can understand and systems can provide effectively. To this end, we propose a novel consistency model based on eventually consistent transactions. Unlike serializable transactions, eventually consistent transactions are ordered by two order relations (visibility and arbitration) rather than a single order relation. To demonstrate that eventually consistent transactions can be effectively implemented, we establish a handful of simple operational rules for managing replicas, versions and updates, based on graphs called revision diagrams. We prove that these rules are sufficient to guarantee correct implementation of eventually consistent transactions. Finally, we present two operational models (single server and server pool) of systems that provide eventually consistent transactions.
Conference Paper
This article presents an extension to the work of Launchbury and Peyton-Jones on the ST monad. Using a novel model for concurrency, called concurrent revisions [3,5], we show how we can use concurrency together with imperative mutable variables, while still being able to safely convert such computations (in the Rev monad) into pure values again. In contrast to many other transaction models, like software transactional memory (STM), concurrent revisions never use rollback and always deterministically resolve conflicts. As a consequence, concurrent revisions integrate well with side-effecting I/O operations. Using deterministic conflict resolution, concurrent revisions can deal well with situations where there are many conflicts between different threads that modify a shared data structure. We demonstrate this by describing a concurrent game with conflicting concurrent tasks.
Article
Full-text available
Operational transformation (OT) is an approach which allows to build real-time groupware tools. This approach requires correct transformation functions regarding two conditions called TP1 and TP2. Proving correctness of these transformation functions is very complex and error prone. In this paper, we show how a theorem prover can address this serious bottleneck. To validate our approach, we verifed correctness of state-of-art transformation functions de ned on strings of characters with surprising results. Counter-examples provided by the theorem prover helped us to design the tombstone transformation functions. These functions verify TP1 and TP2, preserve intentions and ensure multi-effect relationships.
Conference Paper
Full-text available
Enabling applications to execute various tasks in parallel is difficult if those tasks exhibit read and write conflicts. We recently developed a programming model based on concurrent revisions that addresses this challenge in a novel way: each forked task gets a conceptual copy of all the shared state, and state changes are integrated only when tasks are joined, at which time write-write conflicts are deterministically resolved. In this paper, we study the precise semantics of this model, in particular its guarantees for determinacy and consistency. First, we introduce a revision calculus that concisely captures the programming model. Despite allowing concurrent execution and locally nondeterministic scheduling, we prove that the calculus is confluent and guarantees determinacy. We show that the consistency guarantees of our calculus are a logical extension of snapshot isolation with support for conflict resolution and nesting. Moreover, we discuss how custom merge functions can provide stronger guarantees for particular data types that are tailored to the needs of the application. Finally, we show we can visualize the nonlinear history of state in our computations using revision diagrams that clarify the synchronization between tasks and allow local reasoning about state updates.
Conference Paper
Full-text available
A future is a simple and elegant abstraction that allows concurrency to be expressed often through a relatively small rewrite of a sequential program. In the absence of side-effects, futures serve as benign annotations that mark potentially concurrent regions of code. Unfortunately, when computation relies heavily on mutation as is the case in Java, its meaning is less clear, and much of its intended simplicity lost.This paper explores the definition and implementation of futures for Java. One can think of safe futures as truly transparent annotations on method calls, which designate opportunities for concurrency. Serial programs can be made concurrent simply by replacing standard method calls with future invocations. Most significantly, even though some parts of the program are executed concurrently and may indeed operate on shared data, the semblance of serial execution is nonetheless preserved. Thus, program reasoning is simplified since data dependencies present in a sequential program are not violated in a version augmented with safe futures.Besides presenting a programming model and API for safe futures, we formalize the safety conditions that must be satisfied to ensure equivalence between a sequential Java program and its future-annotated counterpart. A detailed implementation study is also provided. Our implementation exploits techniques such as object versioning and task revocation to guarantee necessary safety conditions. We also present an extensive experimental evaluation of our implementation to quantify overheads and limitations. Our experiments indicate that for programs with modest mutation rates on shared data, applications can use futures to profitably exploit parallelism, without sacrificing safety.
Conference Paper
Full-text available
Building applications that are responsive and can exploit parallel hardware while remaining simple to write, understand, test, and maintain, poses an important challenge for developers. In particular, it is often desirable to enable various tasks to read or modify shared data concurrently without requiring complicated locking schemes that may throttle concurrency and introduce bugs. We introduce a mechanism that simplifies the parallel execution of different application tasks. Programmers declare what data they wish to share between tasks by using isolation types, and execute tasks concurrently by forking and joining revisions . These revisions are isolated: they read and modify their own private copy of the shared data only. A runtime creates and merges copies automatically, and resolves conflicts deterministically, in a manner declared by the chosen isolation type. To demonstrate the practical viability of our approach, we developed an efficient algorithm and an implementation in the form of a C# library, and used it to parallelize an interactive game application. Our results show that the parallelized game, while simple and very similar to the original sequential game, achieves satisfactory speedups on a multicore processor.
Conference Paper
Full-text available
Traditional transactional memory systems suffer from overly conservative conflict detection, yielding so-called false conflicts, because they are based on fine-grained, low-level read/write conflicts. In response, the recent trend has been toward integrating various abstract data-type libraries using ad-hoc methods of high-level conflict detection. These proposals have led to improved performance but a lack of a unified theory has led to confusion in the literature. We clarify these recent proposals by defining a generalization of transactional memory in which a transaction consists of coarse-grained (abstract data-type) operations rather than simple memory read/write operations. We provide semantics for both pessimistic (e.g. transactional boosting) and optimistic (e.g. traditional TMs and recent alternatives) execution. We show that both are included in the standard atomic semantics, yet find that the choice imposes different requirements on the coarse-grained operations: pessimistic requires operations be left-movers, optimistic requires right-movers. Finally, we discuss how the semantics applies to numerous TM implementation details discussed widely in the literature.
Article
Full-text available
Eventual consistency aims to ensure that replicas of some mutable shared object converge without foreground synchronisation. Previous approaches to eventual consistency are ad-hoc and error-prone. We study a principled approach: to base the design of shared data types on some simple formal conditions that are sufficient to guarantee eventual consistency. We call these types Convergent or Commutative Replicated Data Types (CRDTs). This paper formalises asynchronous object replication, either state based or operation based, and provides a sufficient condition appropriate for each case. It describes several useful CRDTs, including container data types supporting both \add and \remove operations with clean semantics, and more complex types such as graphs, montonic DAGs, and sequences. It discusses some properties needed to implement non-trivial CRDTs.
Conference Paper
The shift from single to multiple core architectures means that programmers must write concurrent, multithreaded programs in order to increase application performance. Unfortunately, multithreaded applications are susceptible to numerous errors, including deadlocks, race conditions, atomicity violations, and order violations. These errors are notoriously difficult for programmers to debug. This paper presents Grace, a software-only runtime system that eliminates concurrency errors for a class of multithreaded programs: those based on fork-join parallelism. By turning threads into processes, leveraging virtual memory protection, and imposing a sequential commit protocol, Grace provides programmers with the appearance of deterministic, sequential execution, while taking advantage of available processing cores to run code concurrently and efficiently. Experimental results demonstrate Grace's effectiveness: with modest code changes across a suite of computationally-intensive benchmarks (1-16 lines), Grace can achieve high scalability and performance while preventing concurrency errors.
Conference Paper
This paper introduces hyperobjects, a linguistic mechanism that allows different branches of a multithreaded program to maintain coordinated local views of the same nonlocal variable. We have identified three kinds of hyperobjects that seem to be useful -- reducers, holders, and splitters -- and we have implemented reducers and holders in Cilk++, a set of extensions to the C++ programming language that enables "dynamic" multithreaded programming in the style of MIT Cilk. We analyze a randomized locking methodology for reducers and show that a work-stealing scheduler can support reducers without incurring significant overhead.
Conference Paper
Groupware systems are computer-based systems that support two or more users engaged in a common task, and that provide an interface to a shared environment. These systems frequently require fine-granularity sharing of data and fast response times. This paper distinguishes real-time groupware systems from other multi-user systems and discusses their concurrency control requirements. An algorithm for concurrency control in real-time groupware systems is then presented. The advantages of this algorithm are its simplicity of use and its responsiveness: users can operate directly on the data without obtaining locks. The algorithm must know some semantics of the operations. However the algorithm's overall structure is independent of the semantic information, allowing the algorithm to be adapted to many situations. An example application of the algorithm to group text editing is given, along with a sketch of its proof of correctness in this particular case. We note that the behavior desired in many of these systems is non-serializable.
Article
The problem of writing software for multicore processors is greatly simplified if we could automatically parallelize sequential programs. Although auto-parallelization has been studied for many decades, it has succeeded only in a few application areas such as dense matrix computations. In particular, auto-parallelization of irregular programs, which are organized around large, pointer-based data structures like graphs, has seemed intractable. The Galois project is taking a fresh look at autoparallelization. Rather than attempt to parallelize all programs no matter how obscurely they are written, we are designing programming abstractions that permit programmers to highlight opportunities for exploiting parallelism in sequential programs, and building a runtime system that uses these hints to execute the program in parallel. In this paper, we describe the design and implementation of a system based on these ideas. Experimental results for two real-world irregular applications, a Delaunay mesh refinement application and a graphics application that performs agglomerative clustering, demonstrate that this approach is promising.