Polyphonic C# Nick Benton Luca Cardelli Cédric Fournet

advertisement
Polyphonic C#
Nick Benton
Luca Cardelli
Cédric Fournet
Microsoft Research
1
Asynchrony is where its at



Distribution => concurrency + latency
=> asynchrony
=> more concurrency
Message-passing, event-based
programming, dataflow models
For programming languages, coordination
(orchestration) languages & frameworks,
workflow
2
Language support for
concurrency




Make invariants and intentions more
apparent (part of the interface)
Good software engineering
Allows the compiler much more freedom to
choose different implementations
Also helps other tools
3
.NET today




Java-style “monitors”
OS shared memory primitives
Clunky delegate-based asynchronous calling model
Hard to understand, use and get right
 Different models at different scales
 Support for asynchrony all on the caller side – little
help building code to handle messages (must be
thread-safe, reactive, and deadlock-free)
4
Polyphonic C#


An extension of the C# language with new
concurrency constructs
Based on the join calculus


A single model which works both for




A foundational process calculus like the p-calculus but
better suited to asynchronous, distributed systems
local concurrency (multiple threads on a single machine)
distributed concurrency (asynchronous messaging over
LAN or WAN)
It is different
But it’s also simple – if Mort can do any kind of
concurrency, he can do this
5
In one slide:



Objects have both synchronous and asynchronous methods.
Values are passed by ordinary method calls:
 If the method is synchronous, the caller blocks until the method returns
some result (as usual).
 If the method is async, the call completes at once and returns void.
A class defines a collection of chords (synchronization patterns),
which define what happens once a particular set of methods have
been invoked. One method may appear in several chords.
 When pending method calls match a pattern, its body runs.
 If there is no match, the invocations are queued up.
 If there are several matches, an unspecified pattern is selected.
 If a pattern containing only async methods fires, the body runs in a
new thread.
6
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
}
7
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
}
•An ordinary (synchronous) method with no
arguments, returning a string
8
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
}
•An ordinary (synchronous) method with no
arguments, returning a string
•An asynchronous method (hence returning no
result), with a string argument
9
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
}
•An ordinary (synchronous) method with no
arguments, returning a string
•An asynchronous method (hence returning no
result), with a string argument
•Joined together in a chord
10
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
}
•Calls to put() return immediately (but are internally queued if there’s
no waiting get()).
•Calls to get() block until/unless there’s a matching put()
•When there’s a match the body runs, returning the argument of the
put() to the caller of get().
•Exactly which pairs of calls are matched up is unspecified.
11
A simple buffer
class Buffer {
String get() & async put(String s) {
return s;
}
•Does example this involve spawning any threads?
}
•No. Though the calls will usually come from different preexisting threads.
•So is it thread-safe? You don’t seem to have locked anything…
•Yes. The chord compiles into code which uses locks. (And that
doesn’t mean everything is synchronized on the object.)
•Which method gets the returned result?
•The synchronous one. And there can be at most one of those
in
12
a chord.
Reader/Writer
…using threads and mutexes in Modula 3
An introduction to programming with threads.
Andrew D. Birrell, January 1989.
13
Reader/Writer in five chords
public class ReaderWriter {
public void Exclusive() & async Idle() {}
public void ReleaseExclusive() { Idle(); }
public void Shared() & async Idle()
{ S(1); }
public void Shared() & async S(int n) { S(n+1); }
public void ReleaseShared() & async S(int n) {
if (n == 1) Idle(); else S(n-1);
}
public ReaderWriter() { Idle(); }
}
A single private message represents the state:
none  Idle()  S(1)  S(2)  S(3) …
14
Asynchronous requests and
responses

Service exposes an async method which takes
parameters and somewhere to put the result:


a buffer, or a channel, or
a delegate
public delegate async IntCB(int v);
public class Service {
public async request(String arg, IntCB callback) {
int result;
// do something interesting…
callback(result);
}
}
15
Asynchronous requests and
responses - Join
class Join2 {
void wait(out int i, out int j)
& async first(int r1)
& async second(int r2) {
i = r1; j = r2; return;
}
}
// client code:
int i,j;
Join2 x = new Join2();
service1.request(arg1, new IntCB(x.first));
service2.request(arg2, new IntCB(x.second));
// do something useful
// now wait until both results have come back
x.wait(out i,out j);
// do something with i and j
16
Asynchronous requests and
responses - Select
class Select {
int wait()
& async reply(int r) {
return r;
}
}
// client code:
int i;
Select x = new Select();
service1.request(arg1, new IntCB(x.reply));
service2.request(arg2, new IntCB(x.reply));
// do something useful
// now wait until one result has come back
i = x.wait();
// do something with i
17
Active Objects
public abstract class ActiveObject : MarshalByRefObject
{
protected bool done;
abstract protected void processmessage();
public ActiveObject () {
done = false; mainloop();
}
async mainloop() {
while (!done) { processmessage(); }
}
}
18
…continued
class Stock : ActiveObject {
override protected void processmessage()
& public async bid(BidOffer thebid) {
// process bid messages
}
override protected void processmessage()
& public async register(Client who) {
// process registration requests
}
…
}
19
Extending C# with chords
Classes can declare methods using generalized
chord-declarations instead of method-declarations.

chord-declaration ::= method-header [ & method-header ]* body
method-header ::= attributes modifiers [return-type | async] name (parms)
Interesting well-formedness conditions:

1.
2.
3.
At most one header can have a return type (i.e. be synchronous).
The inheritance restriction.
“ref” and “out” parameters cannot appear in async headers.
20
Why only one synchronous
method in a chord?

JoCaml allows multiple synchronous methods to be joined, as in
the following rendezvous
int f(int x) & int g(int y) {
return y to f;
return x to g;
}

But in which thread does the body run? In C#, thread identity is
“very” observable, since threads are the holders of particular reentrant locks. So we rule this out in the interests of keeping &
commutative. (Of course, it’s still easy to code up an asymmetric
rendezvous in Polyphonic C#.)
21
The problem with inheritance
class C {
virtual void f() & virtual async g() {…}
virtual void f() & virtual async h() {…}
}
class D : C {
override async g() { …}
}


We’ve “half” overridden f
Too easy to create deadlock or async leakage
void m(C x) { x.g(); x.f();}
…
m(new D());
22
The inheritance restriction

Two methods are co-declared if they appear together
in a chord declaration.
Whenever a method is overridden,
every co-declared method must also be
overridden.

Hence, the compiler rejects patterns such as
public virtual void f() & private async g() {…}

In general, inheritance and concurrency do not mix well.
Our restriction is simple; it could be made less restrictive.
23
Types etc.


async is a subtype of void
Allow covariant return types on those two:




An async method may override a void one
A void delegate may be created from an async method
An async method may implement a void method in an
interface
async methods are given the [OneWay] attribute,
so remote calls are non-blocking
24
Demo
25
Coming Soon:
C
26
C





Integrates Polyphonic C# with “the language
formerly known as Xen”
Erik Meijer, Wolfram Shulte and others in WebData
Claudio Russo, Gavin Bierman
Data integration: unifying objects with relational and
XML data
Features:





Stream types
Tuples
Unions
XML literals
Generalized member access
27
Simple streams
public static int* FromTo(int s, int e) {
for (i = s; i <= e; i++) yield i;
}
int* OneToTen = FromTo(1,10);
foreach(int j in OneToTen) {
Console.WriteLine(j);
};
28
Stream typing
>= 0
T*
Possibly
null
>= 1
T?
T+
T
Don't care
T!
Never null
S <: T
S* <: T*
T** ≈ T*
29
Tuple typing (simplified)
covariance
S <: T
[…,S,…] <: […,T,…]
forget
labels
[…, T m,…] <: […,T,…]
[…, T,…] <: (…|T|…)*
forget
ordering
30
Typing simple queries
type Sales =
[ int Sale, int Employee, string Customer,
string Item, string Supplier, decimal Price ]
Sales*
Sale
Employee
Customer
Item
Supplier
1
1 Simpson
Sofa
Harrison
…
… …
…
…
Price
$235.67
…
[int Sale, string Item, decimal Price]* s =
select Sale, Item, Price
from Sales;
31
Unions
XSD-style
content
model
class Address {
sequence{
choice {
string Street; int POBox;
};
string City; int ZipCode;
string Country;
};
};
Address! a = …;
string? street = a.Street; // maybe null
choice{string; int;}* m = a.*;
32
XML literals (with holes)
class Email {…}
static Email OOF (Date d, TimeSpan s, string to){
return
<Email>
<Header>
<To>{to}</To><From>Wolfram</From>
<Subject>OOF</Subject>
</Header>
<Body>
<P>I am OOF from {d} until {d+s}.</P>
</Body>
</Email>;
}
33
Conclusions

A clean, simple, new model for
asynchronous concurrency in C#




Model good for both local and distributed
settings
Efficiently compiled to queues and automata
Works well in practice
Plus relational, XML, object integration

streams, tuples, unions, XML, select, …
http://research.microsoft.com/~nick/polyphony/
34
Fairer reader/writer lock
class ReaderWriterFair
{
ReaderWriter() { idle(); }
private int n = 0; // protected by s() or t()
public void Shared() & async idle() { n=1; s(); }
public void Shared() & async s() { n++; s(); }
public void ReleaseShared() & async s() {
if (--n == 0) idle(); else s();
}
public void Exclusive() & async idle() {}
public void ReleaseExclusive() { idle(); }
public void ReleaseShared() & async t() {
if (--n == 0) idleExclusive(); else t();
}
public void Exclusive() & async s() {
t(); wait();
}
void wait() & async idleExclusive() {}
}
35
Predictable Demo:
Dining Philosophers
waiting
to eat
eating
waiting
to eat
thinking
eating
36
Code extract
class Room {
public Room (int size) { hasspaces(size); }
public void enter() & private async hasspaces(int n) {
if (n > 1)
hasspaces(n-1);
else
isfull();
}
public void leave() & private async hasspaces(int n) {
hasspaces(n+1);
}
public void leave() & private async isfull() {
hasspaces(1); }
}
37
A Better Syntax?
class ReaderWriter {
private async Idle();
private async S(int n);
// declare asyncs
public void Exclusive()
when Idle() {}
public void ReleaseExclusive() {
Idle();
}
public void Shared()
when Idle() {S(1);}
| when S(int n) {S(n+1);}
// syncs can have sequence of
// “when” patterns involving
// asyncs
public void ReleaseShared()
when S(int n) {
if (n==1) Idle(); else S(n-1);
}
}
Could even allow when
patterns as general statements,
though this seems in dubious
38
taste…
Santa Claus problem (Trono,
Ben-Ari)






Santa sleeps until awakened by either all 9 reindeer or by 3 of
the 10 elves.
If woken by reindeer he harnesses them all up, they deliver
presents together, he unharnesses them, they go off on holiday
and he goes back to sleep.
If woken by a group of elves, he shows them into his office,
consults with them on toy R&D then shows them all out and goes
back to sleep.
Surprisingly tricky to avoid bugs such as Santa going off without
the reindeer, queue-jumping elves
Trono posed problem and gave incorrect solution using
semaphores
Ben-Ari gave a non-trivial solution using Ada primitives and ugly,
inefficient and unsatisfactory solution in Java
39
public class nway {
public async produce(int n) & public void
consume() {
if (n==1) {
alldone();
} else {
produce(n-1);
}
}
public void waitforalldone() & async alldone() {
return;
}
}
40
class santa {
static nway harness = new nway();
static nway unharness = new nway();
static nway roomin = new nway();
static nway roomout = new nway();
static void santalife() {
while (true) {
waittobewoken();
// get back here when dealt with elves or reindeer
}
}
static void waittobewoken() & static async elvesready() {
roomin.produce(3);
roomin.waitforalldone();
elveswaiting(0);
// all elves in the room, consult
roomout.produce(3);
roomout.waitforalldone();
// all elves shown out, go back to bed
}
static void waittobewoken() & static async reindeerready() {
// similar to elvesready chord
}
41
static async elflife(int
while (true) {
// work
elfqueue();
//
roomin.consume(); //
// consult with santa
roomout.consume(); //
}
}
elfid) {
wait to join group of 3
wait to be shown in
wait to be shown out again
static void elfqueue() & static async elveswaiting(int e)
{
if (e==2) {
elvesready();
// last elf in a group so wake santa
}
else {
elveswaiting(e+1);
}
}
42
Pattern Matching
async Sell(string item, Client seller)
& async Buy (string item, Client buyer) {
... // match them up
}
Very useful, but hard to compile efficiently
43
Ordered processing, currently
class SequenceProcessor : ActiveObject {
private SortedList pending = new SortedList();
private int next = 0;
public async Message(int stamp, string contents)
& override protected void ProcessMessage() {
if (stamp == next) {
DealWith(contents);
while (pending.ContainsKey(++next)) {
DealWith((string)pending[next]);
pending.Remove(next);
}
} else {
pending.Add(stamp,contents);
}
}
...
}
44
with matching
class SequenceProcessor : ActiveObject {
public async Message(int stamp, string contents)
& override protected void ProcessMessage()
& async waitingfor(int stamp) {
DealWith(contents);
waitingfor(stamp++);
}
SequenceProcessor() {
waitingfor(0);
}
...
}
45
Implementation




Translate Polyphonic C# -> C#
Introduce queues for pending calls (holding blocked
threads for sync methods, arguments for asyncs)
Generated code (using brief lock to protect queue
state) looks for matches and then either
 Enqueues args (async no match)
 Enqueues thread and blocks (sync no match)
 Dequeues other args and continues (sync match)
 Wakes up blocked thread (async match with sync)
 Spawns new thread (async match all async)
Efficient – bitmasks to look for matches, no
PulseAlls,…
46
Samples





animated dining philosophers
web service combinators (Cardelli & Davies)
adaptive scheduler (cf. Larus & Parkes),
accessing web services (Terraserver),
active objects and remoting (stock trader)
47
Current and future work




Direct syntactic support for timeouts
Limited pattern-matching on message
contents
Adding joinable transactions with explicit
compensations
Behavioural types?
48
Download