From 9be42ea820c1b3f623ebeebaedfc3cd81107b4c9 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 28 Oct 2016 09:56:46 -0400 Subject: Create temp.md --- chapter/2/temp.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 chapter/2/temp.md diff --git a/chapter/2/temp.md b/chapter/2/temp.md new file mode 100644 index 0000000..fcefc09 --- /dev/null +++ b/chapter/2/temp.md @@ -0,0 +1,23 @@ +# What are promises ? + +- Future, promise, delay, or deferred. +- Definition + +# Historical Background + +- Algol thunk +- Incremental garbage collection of Processes - 1977 +- 1995 Joule channels +- 1997 Mark Miller - E + +# Current state of things + +- Lot of work done in Javascript +- Scala +- Finagle +- Java8 +- ? + +# Future Work + +- ? -- cgit v1.2.3 From cedc03d63afc7f837062e4b66a3bcbcc34516b56 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 28 Oct 2016 11:25:57 -0400 Subject: Update temp.md --- chapter/2/temp.md | 1 + 1 file changed, 1 insertion(+) diff --git a/chapter/2/temp.md b/chapter/2/temp.md index fcefc09..0506ded 100644 --- a/chapter/2/temp.md +++ b/chapter/2/temp.md @@ -2,6 +2,7 @@ - Future, promise, delay, or deferred. - Definition +- States of promises # Historical Background -- cgit v1.2.3 From d4e0f859d700cd6a3c8a2d1b41821d0fa5da70cb Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Thu, 17 Nov 2016 16:33:14 -0500 Subject: Basic structure for introducing CAP --- ...dic-to-basic-how-the-database-ph-has-changed.md | 157 +++++++++++++++++++++ chapter/6/consistency-crdts.md | 11 -- 2 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md delete mode 100644 chapter/6/consistency-crdts.md diff --git a/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md new file mode 100644 index 0000000..99b12d0 --- /dev/null +++ b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md @@ -0,0 +1,157 @@ +--- +layout: page +title: "ACIDic to BASEic: How the database pH has changed" +by: "Aviral Goel" +--- + +## 1. The **ACID**ic Database Systems + +Relational Database Management Systems are the most ubiquitous database systems for persisting state. Their properties are defined in terms of transactions on their data. A database transaction can be either a single operation or a sequence of operations, but is treated as a single logical operation on the data by the database. The properties of these transactions provide certain guarantees to the application developer. The acronym **ACID** was coined by Andreas Reuter and Theo Härder in 1983 to describe them. + +* **Atomicity** guarantees that any transaction will either complete or leave the database unchanged. If any operation of the transaction fails, the entire transaction fails. Thus, a transaction is perceived as an atomic operation on the database. This property is guaranteed even during power failures, system crashes and other erroneous situations. + +* **Consistency** guarantees that any transaction will always result in a valid database state, i.e., the transaction preserves all database rules, such as unique keys, etc. + +* **Isolation** guarantees that concurrent transactions do not interfere with each other. No transaction views the effects of other transactions prematurely. In other words, they execute on the database as if they were invoked serially (though a read and write can still be executed in parallel). + +* **Durability** guarantees that upon the completion of a transaction, the effects are applied permanently on the database and cannot be undone. They remain visible even in the event of power failures or crashes. This is done by ensuring that the changes are committed to disk (non-volatile memory). + +

ACIDity implies that if a transaction is complete, the database state is structurally consistent (adhering to the rules of the schema) and stored on disk to prevent any loss.

+ +Because of the strong guarantees this model simplifies the life of the developer and has been traditionally the go to approach in application development. It is instructive to examine how these properties are enforced. + +Single node databases can simply rely upon locking to ensure *ACID*ity. Each transaction marks the data it operates upon, thus enabling the database to block other concurrent transactions from modifying the same data. The lock has to be acquired both while reading and writing data. The locking mechanism enforces a strict linearizable consistency. An alternative, *multiversioning* allows a read and write operation to execute in parallel. Each transaction which reads data from the database is provided the earlier unmodified version of the data that is being modified by a write operation. This means that read operations don't have to acquire locks on the database. This enables read operations to execute without blocking write operations and write operations to execute without blocking read operations. + +This model works well on a single node. But it exposes a serious limitation when too many concurrent transactions are performed. A single node database server will only be able to process so many concurrent read operations. The situation worsens when many concurrent write operations are performed. To guarantee *ACID*ity, the write operations will be performed in sequence. The last write request will have to wait for an arbitrary amount of time, a totally unacceptable situation for many real time systems. This requires the application developer to decide on a **Scaling** strategy. + +## 2. Transaction Volume + +To increase the volume of transactions against a database, two scaling strategies can be considered + +**Vertical Scaling** is the easiest approach to scale a relational database. The database is simply moved to a larger computer which provides more transactional capacity. Unfortunately, its far too easy to outgrow the capacity of the largest system available and it is costly to purchase a bigger system each time that happens. Since its not commodity hardware, vendor lock-in will add to further costs. + +**Horizontal Scaling** is a more viable option and can be implemented in two ways. Data can be segregated into functional groups spread across databases. This is called *Functional Scaling*. Data within a functional group can be further split across multiple databases, enabling functional areas to be scaled independently of one another for even more transactional capacity. This is called *sharding*. + +Horizontal Scaling through functional partitioning enables high degree of scalability. However, the functionally separate tables employ constraints such as foreign keys. For these constraints to be enforced by the database itself, all tables have to reside on a single database server. This limits horizontal scaling. To work around this limitation the tables in a functional group have to be stored on different database servers. But now, a single database server can no longer enforce constraints between the tables. In order to ensure *ACID*ity of distributed transactions, distributed databases employ a two-phase commit (2PC) protocol. + +* In the first phase, a coordinator node interrogates all other nodes to ensure that a commit is possible. If all databases agree then the next phase begins, else the transaction is canceled. + +* In the second phase, the coordinator asks each database to commit the data. + +2PC is a blocking protocol and is usually employed for updates which can take from a few milliseconds up to a few minutes to commit. This means that while a transaction is being processed, other transactions will be blocked. So the application that initiated the transaction will be blocked. Another option is to handle the consistency across databases at the application level. This only complicates the situation for the application developer who is likely to implement a similar strategy if *ACID*ity is to be maintained. + +# The part below is in bits and pieces. A lot of details need to be filled in. + +## 3. A Distributed Concoction + +**I am a cute diagram for the paragraph below.** + +In the network above, all messages between the node set G1 and G2 are lost due to a network issue. The system as a whole detects this situation. There are two options - + +* The system allows any application to read and write to data objects on these nodes as they are **available**. The application writes to a data object. This write operation completes in one of the nodes of G1. Due to **network partition**, this change is not propagated to replicas of the data object in G2. Subsequently the application tries to read the value of that data object and the read operation executes in one of the nodes of G2. The read operation returns the older value of the data object, thus making the application state not **consistent**. + +## 4. The volatile network + +Network Partition is a contentious subject among distributed database architects. While some maintain that network partitions are rare, other point to their. 9 + + +## 4. The spicy ingredients + + +This simple observation shows a tension between three issues concerning distributed systems - + +**Consistency** is the guarantee of total ordering of all operations on a data object such that each operation appears indivisible. This means that any read operation must return the most recently written value. This provides a very convenient invariant to the client application that uses the distributed data store. This definition of consistency is the same as the **Atomic**ity guarantee provided by relational database transactions. + +**Availability** is the guarantee that every request to a distributed system must result in a response. However, this is too vague a definition. Whether a node failed in the process of responding or it ran a really long computation to generate a response or whether the request or the response got lost due to network issues is generally impossible to determine by the client and willHence, for all practical purposes, availability can be defined as the service responding to a request in a timely fashion, the amount of delay an application can bear depends on the application domain. + +**Partitioning** is the loss of messages between the nodes of a distributed system. + + +This observation led Eric Brewer to conjecture in an invited talk at PODC 2000 - + +
It is impossible for a web service to provide the following three guarantees: +Consistency +Availability +Partition Tolerance
+ +It is clear that the prime culprit here is network partition. If there are no network partitions, any distributed service will be both highly available and provide strong consistency of shared data objects. Unfortunately, network partitions cannot be remedied in a distributed system. + + + + + +## 3. Strong Consistency + + + + + + + + + +We observed how in the event of a network partition, we could not have both availability and consistency at the same time. Let's study their pairwise interaction - + + +For many applications *ACID*ic datastores impose a more severe consistency guarantee than is actually needed and this reduces their availability. By relaxing the constraints on data consistency one can achieve higher scalability and availability. + +### 2. The **BASE**ic distributed state + +When viewed through the lens of CAP theorem and its consequences on distributed applications we realize that we cannot commit to perfect availability and strong consistency. But surely we can explore the middle ground. We can guarantee availability most of the time with sometimes inconsistent view of the data. The consistency is eventually achieved when the communication between the nodes resumes. This leads to the following properties of the current distributed applications, referred to by the acronym BASE. + +**Basically Available** services are those which are partially available when partitions happen. Thus, they appear to work most of the time. +**Soft State** services provide no strong consistency guarantees. They are not write consistent. Since replicas may not be mutually consistent, applications have to accept stale data. +**Eventually Consistent** services try to make application state consistent whenever possible. + + +### What's the right pH for my distributed solution? + +Whether an application chooses to an *ACID*ic or *BASE*ic service depends on the domain. An application developer has to consider the consistency-availability tradeoff on a case by case basis. *ACID*ic databases provide a very simple and strong consistency model making application development easy for domains where data inconsistency cannot be tolerated. *BASE*ic databases provide a very loose consistency model, placing more burden on the application developer to understand the limitations of the database and work around that, retaining sane application behavior. + +## References + +https://neo4j.com/blog/acid-vs-base-consistency-models-explained/ +https://en.wikipedia.org/wiki/Eventual_consistency/ +https://en.wikipedia.org/wiki/Distributed_transaction +https://en.wikipedia.org/wiki/Distributed_database +https://en.wikipedia.org/wiki/ACID +http://searchstorage.techtarget.com/definition/data-availability +https://aphyr.com/posts/288-the-network-is-reliable +http://research.microsoft.com/en-us/um/people/navendu/papers/sigcomm11netwiser.pdf +http://web.archive.org/web/20140327023856/http://voltdb.com/clarifications-cap-theorem-and-data-related-errors/ +http://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf +http://www.hpl.hp.com/techreports/2012/HPL-2012-101.pdf +http://research.microsoft.com/en-us/um/people/navendu/papers/sigcomm11netwiser.pdf +http://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf +http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf +https://people.mpi-sws.org/~druschel/courses/ds/papers/cooper-pnuts.pdf +http://blog.gigaspaces.com/nocap-part-ii-availability-and-partition-tolerance/ +http://stackoverflow.com/questions/39664619/what-if-we-partition-a-ca-distributed-system +https://people.eecs.berkeley.edu/~istoica/classes/cs268/06/notes/20-BFTx2.pdf +http://ivoroshilin.com/2012/12/13/brewers-cap-theorem-explained-base-versus-acid/ +https://www.quora.com/What-is-the-difference-between-CAP-and-BASE-and-how-are-they-related-with-each-other +http://berb.github.io/diploma-thesis/original/061_challenge.html +http://dssresources.com/faq/index.php?action=artikel&id=281 +https://saipraveenblog.wordpress.com/2015/12/25/cap-theorem-for-distributed-systems-explained/ +https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed +https://dzone.com/articles/better-explaining-cap-theorem +http://www.julianbrowne.com/article/viewer/brewers-cap-theorem +http://delivery.acm.org/10.1145/1400000/1394128/p48-pritchett.pdf?ip=73.69.60.168&id=1394128&acc=OPEN&key=4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E6D218144511F3437&CFID=694281010&CFTOKEN=94478194&__acm__=1479326744_f7b98c8bf4e23bdfe8f17b43e4f14231 +http://dl.acm.org/citation.cfm?doid=1394127.1394128 +https://en.wikipedia.org/wiki/Eventual_consistency +https://en.wikipedia.org/wiki/Two-phase_commit_protocol +https://en.wikipedia.org/wiki/ACID +https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf +http://www.johndcook.com/blog/2009/07/06/brewer-cap-theorem-base/ +http://searchsqlserver.techtarget.com/definition/ACID +http://queue.acm.org/detail.cfm?id=1394128 +http://www.dataversity.net/acid-vs-base-the-shifting-ph-of-database-transaction-processing/ +https://neo4j.com/developer/graph-db-vs-nosql/#_navigate_document_stores_with_graph_databases +https://neo4j.com/blog/aggregate-stores-tour/ +https://en.wikipedia.org/wiki/Eventual_consistency +https://en.wikipedia.org/wiki/Distributed_transaction +https://en.wikipedia.org/wiki/Distributed_database +https://en.wikipedia.org/wiki/ACID +http://searchstorage.techtarget.com/definition/data-availability +https://datatechnologytoday.wordpress.com/2013/06/24/defining-database-availability/ +{% bibliography --file rpc %} diff --git a/chapter/6/consistency-crdts.md b/chapter/6/consistency-crdts.md deleted file mode 100644 index fcb49e7..0000000 --- a/chapter/6/consistency-crdts.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: page -title: "Consistency & CRDTs" -by: "Joe Schmoe and Mary Jane" ---- - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file consistency-crdts %} - -## References - -{% bibliography --file consistency-crdts %} \ No newline at end of file -- cgit v1.2.3 From b2870df267d95cf93754165ced84e2be4cbfe50a Mon Sep 17 00:00:00 2001 From: James Larisch Date: Thu, 17 Nov 2016 17:30:00 -0500 Subject: FIRST DRAFT --- chapter/7/langs-consistency.md | 115 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 3ac6ceb..6eddfc5 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -1,11 +1,118 @@ --- layout: page -title: "Languages for Consistency" -by: "Joe Schmoe and Mary Jane" +title: "Languages Built For Consistency" +by: "James Larisch" --- +# Languages Built For Consistency + +## What's the problem? + As processors become expensive and the limits of Moore's Law are pushed, programmers lately find themselves in situations where they need to connect multiple computers together using a network cable. Perhaps it's not even due to cost or performance constraints; perhaps your company has servers in New York and San Fransisco, and there is some global state that requires synchronization across the country. Problems requiring solutions of this nature can be described as "distributed systems" problems. Your data / processing power / entry points are distributed for some reason. In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination is required. + + As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network simply is not reliable - and if we wait for it to be reliable, we sacrifice things like timeliness. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the properties of our systems. As stated, we can't perfectly emulate a single computer, but once we accept that fact... there are plenty of things we *can* do! + +## The Shopping Cart + Let's bring all these theorem talk back to reality. Let's say you're working at a new e-commerce startup, and you'd like to revolutionize the electronic shopping cart. You'd like to give the customer the ability to do the following: + * Log in to the site and add a candle to the cart while traveling Beijing. + * Take a HyperLoop train (3 hours) from Beijing to Los Angeles. + * Log back into the site, remove the candle from their cart, and add a skateboard to their cart. + * Take another HyperLoop train from Los Angeles to Paris (5 hours). + * Log back into the site, add another skateboard, and checkout. + + Let's assume you have a server in every single country, and customers connect to the geographically closest server. + + If you only had 1 user of your website, this wouldn't be too hard. You could constantly send out messages to all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay i-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. + + But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which server she asks. + + If you're confident that the servers' state will eventually converge, you could present the user with an error message until the states have converged. That way, you know the user is looking at consistent state. [I may be overlapping too much with Aviral's section here. will wait until I see his draft before continuing. + + Mention Amazon's Dynamo + shopping cart. + +### Example + + Let's take a look at the following Javascript. For simplicity's sake, let's pretend users can only add things to their shopping cart. + + ```javascript + class Cart { + constructor(peers, socket) { + this.mySocket = socket; + this.peers = peers; + this.items = new Set(); + } + + addItem(item) { + this.items.add(item); + } + + synchronize() { + peers.forEach(function(peer) { + peer.send(items); + }); + } + + receiveState(items) { + this.items = this.items.union(items); + } + + run() { + var clientAddition = Interface.receiveInput(); // contrived + this.addItem(clientAddition); + var receivedState = mySocket.nonBlockingRead(); // contrived + if (receivedState !== undefined) { + this.receiveState(receivedState); + } + synchronize(); + sleep(10); + run(); + } + } + + // theoretical usage + + var socket = new UDPSocket(); // contrived + var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets + cart.run(); + ``` + + Here is an (almost) fully functional shopping cart program. You can imagine this code running across multiple nodes scattered over the world. The meat of the program lies in the `run()` method. Let's walk through that: + 1. Program receives an addition to the cart from the user. + 2. Program adds that item to the current local state. + 3. Program checks its UDP socket for any messages. + 4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. + 5. Synchronize our current state by sending our state to every peer that we know about. + 6. Sleep for 10 seconds. + 7. Repeat! + + Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. + + This is the *Strong Eventual Consistency* concept that Aviral introduced in Section 6. It's *eventual* because given a long enough timeline the clients' states will sync up: they are constantly trying to synchronize. [mention you can't remove things trivially, this is actually a CRDT, union is a monotonic operation] + +### The Intern + Unfortunately Jerry, the intern, has found your code. He'd like to make a few changes. He messes it up somehow. I'm not entirely sure how yet. + +### Guarantees + The original Javascript we wrote down exhibits the property from Section 6 known as *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. + + But where should these guarantees live? In the above Javascript example, the guarantees aren't guarantees at all, really. There's no restriction on what the programmer is allowed to do - the programmer has simply constructed a program that mirrors guarantees that she has modeled in her brain. In order to maintain properties such as *monotonicity*, she must constantly check the model in her brain against the code. We haven't really helped the programmer out that much - she has a lot of thinking to do. + + At the disk hardward level, there are certain mechanisms in place to ensure that data does not become corrupted when multiple things attempt to write bits to the same physical location. This is considered a type of IO-consistency. It doesn't help much with our shopping cart, but it's certainly necessary. These important guarantees facilitate the higher level abstractions by ensuring low-level safety. It would be unreasonable to expect our disks to enforce monotonicity, for example, since this would restrict usage of disks to monotonic programs only (more on this later!). But on the other hand, as we've seen, pushing the consistency to the application/programmer level is also unreasonable. Our tools should work for us. + + Why not push the consistency guarantees in between? Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. + + Wouldn't it be great if tools like this existed? + +### Bloom + The dudes/dudettes at Berkeley seem to think so too. + +#### Restriction & Danger + [Bloom restricts you, it's different, and it's dangerous] + +### Lasp + [Library not language, embeddable, not dangerous] + Instead of trying to do it all (and accepting danger), it tries to be embeddable (and truly restrictive.) + -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file langs-consistency %} ## References -{% bibliography --file langs-consistency %} \ No newline at end of file +{% bibliography --file langs-consistency %} -- cgit v1.2.3 From fc4363ffb32d2f0a25e572c6f0598d0c4ffeae09 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 18 Nov 2016 11:28:39 -0500 Subject: Start --- chapter/1/rpc.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index b4bce84..392d9ab 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -1,9 +1,11 @@ --- layout: page -title: "Remote Procedure Call" -by: "Joe Schmoe and Mary Jane" +title: "RPC is Not Dead: Rise, Fall and Rise of RPC" +by: "Muzammil Abdul Rehman and Paul Grosu" --- +## + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file rpc %} ## References -- cgit v1.2.3 From 92c6d66ba7e4c8c837004932974264411130b979 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 18 Nov 2016 12:59:45 -0500 Subject: Outline --- chapter/1/rpc.md | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 237 insertions(+), 1 deletion(-) diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index 392d9ab..d1d48ed 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -4,7 +4,243 @@ title: "RPC is Not Dead: Rise, Fall and Rise of RPC" by: "Muzammil Abdul Rehman and Paul Grosu" --- -## +## Introduction + +*Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build + +* Define what RPC is. +* The main idea of our paper: +* RPC was initially built as a tool for outsourcing computing. +* RPC is relevant to this day as a language for building and connecting scalable modularized, language-agnostic systems. +* It is the design and idea of remote computation, the driving force behind RPC, gave rise to truly distributed systems and different communication schemes between different entities. +* Why is RPC relevant? +* Microservices +* Asynchronous Bidirectional communication for connecting services and devices +* GRPC, Finagle, Thrift, SOAP, CORBA, RMI +* It has influenced other programming designs. +* Evolved with Time +* REST, HTTP + + +* The main idea of our paper: + + * RPC was initially built as a tool for outsourcing computing. + + * RPC is relevant to this day as a language for building and connecting scalable modularized, language-agnostic systems. + + * It is the design and idea of remote computation, the driving force behind RPC, gave rise to truly distributed systems and different communication schemes between different entities. + +* Why is RPC relevant? + + * Microservices + + * Asynchronous Bidirectional communication for connecting services and devices + + * GRPC, Finagle, Thrift, SOAP, CORBA, RMI + + * It has influenced other programming designs. + + * Evolved with Time + + * REST, HTTP + +## Remote Procedure Calls: + +* Local and remote endpoints, communication protocol. + + * Diagram. + +* Initially: there was a registry involved(now they’ve moved), kept an open connection,. + +* Now: + + * Security(Authentication and authorization) + + * Fault tolerance. + + * Asynchronously + + * Load Balancing + +* Examples: + + * One could view the internet as example of RPC.e.g TCP handshake(both act as server and client). + + * First: Google Maps API(REST) + + * SSL Handshake. + +Suggestions from Heather: + +* Be aware of Chris's thing: https://christophermeiklejohn.com/pl/2016/04/12/rpc.html + +* Thrift vs gRPC. + +## Evolution of RPC: + +* RPC has evolved from what it was originally proposed. + +* Chris’s thing: https://christophermeiklejohn.com/pl/2016/04/12/rpc.html + +* 1980’s + + * RPC origin. + + * Implementing RPC: [https://dl.acm.org/citation.cfm?id=357392](https://dl.acm.org/citation.cfm?id=357392) + + * The RPC thesis(Nelson) + + * More examples + +* 1990’s + + * The fall of RPC/Criticism of RPC + + * Limitations + + * [http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf](http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf) + + * Systems that use message passing. + +* 2000-* + +## Remote Method Invocation: + +* Pros and Cons + +## CORBA: + +* Pros and Cons + +## XML-RPC and SOAP: + +* Pros and Cons + +## Thrift: + +* Pros and Cons + +## Finagle: + +* Pros and Cons + +## gRPC: + +## Discussion 1(change heading): + +* gRPC vs Thrift (maybe also Finagle) + +## Applications: + +* RPC and shared state (Persistence Layer): + + * [http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1](http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1) + + * http://ieeexplore.ieee.org/document/918991/?arnumber=918991 + +* Grid computing: + + * https://link.springer.com/article/10.1023/A:1024083511032 + +* Mobile Systems(offloading and battery requirements): [https://link.springer.com/article/10.1007/s11036-012-0368-0](https://link.springer.com/article/10.1007/s11036-012-0368-0) + +* Embedded RPC: + + * https://dl.acm.org/citation.cfm?id=1127840 + +* Micro services architecture(ecosystem) + +* Streaming + +* RPC can be async + +* Shared State + +* microservices + +## RPC in Streaming Protocols: + +* Streaming requests and buffered responses + +## RPC in microservices ecosystem: + +* Creating new services. + +* Bootstrapping + +* Load balancing + + * Creating new services in Actor-Like model + + * Fault tolerance + + * Self-recovery + +* Business and Persistence Layer were combined and the Persistence layer is not shared anymore, where each endpoints has its own persistent state: + + * [https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm](https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm) + +## Security in RPC: + +* Initially it was separate. + + * Authentication, authorization issues have been resolved + +* Now embedded in the protocol + +* Security and Privacy in RPC + + * Bugs in the libraries. + + * Trust Issues between client and the server. + + * [http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/](http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/) + + * Brewer’s view: https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf + + * E programming language: distributed object model/VAT + +## Discussion: + +* RPC vs REST and other services. RPC influence. + +* The future of RPC + + * Where it shines. Not in message passing. + +## Conclusions: + + Some conclusion. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Class: Functional Programming for Distributed Computing + +Theme: The idea of communicating and invoking remote functions for distributed computation. + +Target Audience: Networks background, and wants to learn RPC. + +-> RPC is not XYZ (HTTP, REST, …) though it has influenced. The + +RPC influence in XYZ design, though + +* RPC started in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. + +* RPC started as a separate implements of REST, Streaming RPC, and now made possible of integration of all these implementations as a single abstraction for a user endpoint service. + + * (subsection) How RPC influenced other models of communication. + +* RPC Models: + + * One Server Model + +* Methods of invoking remote function. + +* Discuss the evolution and pitfalls as they developed to an optimized + +* Software-As-A-Service: End-User focused. + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file rpc %} -- cgit v1.2.3 From 4d364efe97868d268ed190e003d764571c537252 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Mon, 21 Nov 2016 11:06:27 -0500 Subject: RPC: Commit 1 --- _bibliography/rpc.bib | 122 +++++++++++++++++++++++++----- chapter/1/rpc.md | 201 ++++++++++++++++---------------------------------- 2 files changed, 169 insertions(+), 154 deletions(-) diff --git a/_bibliography/rpc.bib b/_bibliography/rpc.bib index 416b697..b0e8932 100644 --- a/_bibliography/rpc.bib +++ b/_bibliography/rpc.bib @@ -7,20 +7,108 @@ year = {2010}, } -@inproceedings{Elsman2005, - author = {Martin Elsman}, - title = {Type-specialized serialization with sharing}, - booktitle = {Trends in Functional Programming}, - year = {2005}, - pages = {47-62}, -} - -@article{Kennedy2004, - author = {Andrew Kennedy}, - title = {Pickler combinators}, - journal = {J. Funct. Program.}, - volume = {14}, - number = {6}, - year = {2004}, - pages = {727-739}, -} \ No newline at end of file +@article{implementingrpc, + title={Implementing remote procedure calls}, + author={Birrell, Andrew D and Nelson, Bruce Jay}, + journal={ACM Transactions on Computer Systems (TOCS)}, + volume={2}, + number={1}, + pages={39--59}, + year={1984}, + publisher={ACM} +} + +@article{rmipaper, + title={A Distributed Object Model for the Java\^{} T\^{} M System}, + author={Wollrath, Ann and Riggs, Roger and Waldo, Jim}, + year={1996} +} + +@book{rmibook, + title={Java. rmi: The Remote Method Invocation Guide}, + author={Pitt, Esmond and McNiff, Kathy}, + year={2001}, + publisher={Addison-Wesley Longman Publishing Co., Inc.} +} + +@book{critiqueofrpc, + title={A critique of the remote procedure call paradigm}, + author={Tanenbaum, Andrew Stuart and van Renesse, Robbert}, + year={1987} +} + +@inproceedings{rpcoverrdma, + title={FaSST: Fast, Scalable and Simple Distributed Transactions with Two-Sided (RDMA) Datagram RPCs}, + author={Kalia, Anuj and Kaminsky, Michael and Andersen, David G}, + booktitle={12th USENIX Symposium on Operating Systems Design and Implementation (OSDI 16)}, + pages={185--201}, + organization={USENIX Association} +} + +@inproceedings{sunnfs, + title={Design and implementation of the Sun network filesystem}, + author={Sandberg, Russel and Goldberg, David and Kleiman, Steve and Walsh, Dan and Lyon, Bob}, + booktitle={Proceedings of the Summer USENIX conference}, + pages={119--130}, + year={1985} +} + +@misc{thrift, + title={Apache Thrift}, + author={Prunicki, Andrew}, + year={2009} +} + +@book{corba, + title={CORBA 3 fundamentals and programming}, + author={Siegel, Jon and Ph. D.}, + volume={2}, + year={2000}, + publisher={John Wiley \& Sons New York, NY, USA:} +} + +@misc{grpc, + title = {gRPC}, + author={Google}, + url = {http://www.grpc.io/}, + note = {Accessed: 2016-11-11}, +} + +@misc{soaparticle1, + title = {Exclusive .NET Developer's Journal "Indigo" Interview with Microsoft's Don Box}, + author={Derek Ferguson}, + url = {http://dotnet.sys-con.com/node/45908}, + note = {Accessed: 2016-11-11}, +} + +@misc{corbasite, + title = {CORBA-OMG}, + author={CORBA}, + url = {http://www.corba.org/}, + note = {Accessed: 2016-11-11}, +} + + +@inproceedings{finagle, + title={Your server as a function}, + author={Eriksen, Marius}, + booktitle={Proceedings of the Seventh Workshop on Programming Languages and Operating Systems}, + pages={5}, + year={2013}, + organization={ACM} +} + +@inproceedings{anycastrpc, + title={Anycast-RPC for Wireless Sensor Networks}, + author={Bergstrom, Eric and Pandey, Raju}, + booktitle={2007 IEEE International Conference on Mobile Adhoc and Sensor Systems}, + pages={1--8}, + year={2007}, + organization={IEEE} +} + +@article{rpcrfc, + title={RFC 1831 - RPC: Remote procedure call protocol specification version 2}, + author={Srinivasan, Raj}, + year={1995} +} diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index d1d48ed..a05022f 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -1,248 +1,175 @@ --- layout: page -title: "RPC is Not Dead: Rise, Fall and Rise of RPC" +title: "RPC is Not Dead: Rise, Fall and the Rise of RPC" by: "Muzammil Abdul Rehman and Paul Grosu" --- ## Introduction -*Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build +*Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build modular, scalable, distributed, language-agnostic ecosystem of applications. This RPC *paradigm* has been part of the driving force in creating truly revolutionizing distributed systems and giving rise to various communication schemes and protocols between diverse systems. -* Define what RPC is. -* The main idea of our paper: -* RPC was initially built as a tool for outsourcing computing. -* RPC is relevant to this day as a language for building and connecting scalable modularized, language-agnostic systems. -* It is the design and idea of remote computation, the driving force behind RPC, gave rise to truly distributed systems and different communication schemes between different entities. -* Why is RPC relevant? -* Microservices -* Asynchronous Bidirectional communication for connecting services and devices -* GRPC, Finagle, Thrift, SOAP, CORBA, RMI -* It has influenced other programming designs. -* Evolved with Time -* REST, HTTP +RPC *paradigm* has been implemented in various forms in our every-day systems. From lower level applications like Network File Systems{% cite sunnfs --file rpc %} and Remote Direct Memory Access{% cite rpcoverrdma --file rpc %} to access protocols to developing an ecosystem of microservices, RPC has been used everywhere. Some of the major examples of RPC include SunNFS{% cite sunnfs --file rpc %}, Twitter's Finagle{% cite finalge --file rpc %}, Apache Thrift{% cite thrift --file rpc %}, Java RMI{% cite rmipaper --file rpc %}, SOAP, CORBA{% cite corba --file rpc %}, Google's gRPC{% cite grpc --file rpc %}. +* adds paragraph about rise and fall -* The main idea of our paper: +RPC has evolved over the years. Starting off as a synchronous, insecure, request-response system, RPC has evolved into a secure, asynchronous, fault-tolerant, resilient *paradigm* that has influenced protocols and programming designs, like, HTTP, REST, and just about anything with a request-response system. It has transitioned to an asynchronous bidirectional communication for connecting services and devices across the internet. RPC has influenced various design paradigms and communication protocols. - * RPC was initially built as a tool for outsourcing computing. - - * RPC is relevant to this day as a language for building and connecting scalable modularized, language-agnostic systems. - - * It is the design and idea of remote computation, the driving force behind RPC, gave rise to truly distributed systems and different communication schemes between different entities. - -* Why is RPC relevant? - - * Microservices - - * Asynchronous Bidirectional communication for connecting services and devices - - * GRPC, Finagle, Thrift, SOAP, CORBA, RMI - - * It has influenced other programming designs. +## Remote Procedure Calls: - * Evolved with Time +* Diagram of RPC: Local and remote endpoints, communication protocol. - * REST, HTTP +*Remote Procedure Call paradigm* can be defined, at a high level, as a set of two language-agnostic communication *endpoints* connected over a network with one endpoint sending a request and the other endpoint generating a response based on that request. In the simplest terms, it's a request-response paradigm where the two *endpoints*/hosts have different *address space*. The host that requests a remote procedure can be referred to as *caller* and the host that responds to this can be referred to as *callee*. -## Remote Procedure Calls: +The *endpoints* in the RPC can either be a client and a server, two nodes in a peer-to-peer network, two hosts in a grid computation system, or even two microservices. The RPC communcation is not limited to two hosts, rather could have multiple hosts or *endpoints* involved {% cite anycastrpc --file rpc %}. -* Local and remote endpoints, communication protocol. +* explain the diagram here. - * Diagram. +One important feature of RPC is different *address space* {% cite implementingrpc --file rpc %} for all the endpoints, however, passing the locations to a global storage(Amazon S3, Microsoft Azure, Google Cloud Store) is not impossible.In RPC, all the hosts have separate *address spaces*. They can't share pointers or references to a memory location in one host. This *address space* isolation means that all the information is passed in the messages between the host communicating as a value (objects or variables) but not by reference. Since RPC is a *remote* procedure call, the values sent to the *remote* host cannot be pointers or references to a *local* memory. However, passing links to a global shared memory location is not impossible but rather dependent on the type of system(see *Applications* section for detail). -* Initially: there was a registry involved(now they’ve moved), kept an open connection,. +Originally, RPC was developed as a synchronous, language-specific marshalling service with a custom network protocol to outsource computation{% cite implementingrpc --file rpc %}. It had registry-system to register all the servers. One of the earliest RPC-based system{% cite implementingrpc --file rpc %} was implemented in the Cedar programming language in early 1980's. The goal of this system was to provide similar progamming semantics as local procedure calls. Developed for a LAN network with an inefficient network protocol and a *serialization* scheme to transfer information using the said network protocol, this system aimed at executing a *procedure*(also referred as *method* or a *function*) in a remote *address space*. The single-thread synchronous client and the server were written in an old *Cedar* programming language with a registry system used by the servers to *bind*(or register) their procedures. The clients used this registry system to find a specific server to execute their *remote* procedures. -* Now: +Modern RPC-based systems are language-agnostic, fault-tolerant, asynchronous, load-balanced systems. Authenticaiton and authorization to these systems have been added as needed along with other security features. - * Security(Authentication and authorization) +RPC programs have a network, therefore, they need to handle remote errors and be able to communication information successfully. Error handling generally varies and is categorized as *remote-host* or *network* failure handling. Depending on the type of the system, and the error, the caller(or the callee) return an error and these errors can be handled accordingly. For asynchronous RPC calls, it's possible to specify events to ensure progress. - * Fault tolerance. +RPC implementations use a *serialization*(also referred to as *marshalling* or *pickling*) scheme on top of an underlying communication protocol(traditionally TCP over IP). These *serialization* schemes allow both the caller *caller* and *callee* to become language agnostic allowing both these systems to be developed in parallel without any language restrictions. Some examples of serialization schemes are JSON, XML, or Protocol Buffers{% cite grpc --file rpc %}. - * Asynchronously +RPC allows different components of a larger system to be developed independtly of one another. The language-agnostic nature combined with a decoupling of some parts of the system allows the two components(caller and callee) to scale separately and add new functionalities. - * Load Balancing +Some RPC implementations have moved from a one-server model to a dynamically-created, load-balanced microservices. * Examples: - * One could view the internet as example of RPC.e.g TCP handshake(both act as server and client). - * First: Google Maps API(REST) - * SSL Handshake. -Suggestions from Heather: - -* Be aware of Chris's thing: https://christophermeiklejohn.com/pl/2016/04/12/rpc.html - -* Thrift vs gRPC. ## Evolution of RPC: -* RPC has evolved from what it was originally proposed. +RPC started in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. +* RPC has evolved from what it was originally proposed. * Chris’s thing: https://christophermeiklejohn.com/pl/2016/04/12/rpc.html +* diagram(maybe not): 4 lines, (y-axis: -1 to 1, x-axis 1980's 2016) -* 1980’s +### The Rise: All Hail RPC - * RPC origin. +* RPC origin. - * Implementing RPC: [https://dl.acm.org/citation.cfm?id=357392](https://dl.acm.org/citation.cfm?id=357392) + * Implementing RPC: [https://dl.acm.org/citation.cfm?id=357392](https://dl.acm.org/citation.cfm?id=357392) + * The RPC thesis(Nelson) + * More examples - * The RPC thesis(Nelson) +### The Fall: RPC is Dead - * More examples +* The fall of RPC/Criticism of RPC + * Limitations + * http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf + * Systems that use message passing. -* 1990’s +### The Rise, Again: Long Live RPC - * The fall of RPC/Criticism of RPC +* gRPC +* XML SOAP +* Java RMI +* Finagle +* Thrift +* Apache Etch +* Sun RPC(ONC RPC) - * Limitations - * [http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf](http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf) +#### Java Remote Method Invocation: +Java RMI (Java Remote Method Invocation){% cite rmibook --file rpc %} is a Java implementation for performing RPC (Remote Procedure Calls) between a client and a server. The client using a stub passes via a socket connection the information over the network to the server. The Remote Object Registry (ROR){% cite rmipaper --file rpc %} on the server contains the references to objects that can be accessed remotely and through which the client will connect to. The client then can request of the invocation of methods on the server for processing the requested call and then responds with the answer. RMI provides some security by being encoded but not encrypted, though that can be augmented by tunneling over a secure connection or other methods. - * Systems that use message passing. -* 2000-* -## Remote Method Invocation: +#### CORBA: +CORBA (Common Object Request Broker Architecture){% cite corba --file rpc %} was created by the Object Management Group {% cite corbasite --file rpc %} to allow for language-agnostic communication among multiple computers. It is an object-oriented model defined via an Interface Definition Language (IDL) and the communication is managed through an Object Request Broker (ORB). Each client and server have an ORB by which they communicate. The benefits of CORBA is that it allows for multi-language implementations that can communicate with each other, but much of the criticism around CORBA relates to poor consistency among implementations. -* Pros and Cons - -## CORBA: - -* Pros and Cons +#### XML-RPC and SOAP: -## XML-RPC and SOAP: +SOAP (Simple Object Access Protocol) is a successor of XML-RPC as a web-services protocol for communicating between a client and server. It was initially designed by a group at Microsoft {% cite soaparticle1 --file rpc %}. The SOAP message is a XML-formatted message composed of an envelope inside which a header and a body is provided. The body of the message contains the request and response of the message, which is transmitted over HTTP or SMTP. The benefits of such a protocol is that provides the flexibility for transmission of multiple tranport protocol, though parsing such messages could become a bottleneck. -* Pros and Cons - -## Thrift: -* Pros and Cons +#### Thrift: +Thrift is a RPC created by Facebook and now part of the Apache Foundation {% cite thrift --file rpc %}. It is a language-agnostic IDL by which one generates the code for the client and server. It provides the opportunity for compressed serialization by customizing the protocol and the transport after the description file has been processed. -## Finagle: +#### Finagle: +Finagle was generated by Twitter and is an RPC written in Scala and can run on an JVM. It is based on three object types: Service objects, Filter objects and Future objects{% cite finagle --file rpc %}. The Future objects acts by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. +#### Open Network Computing RPC: * Pros and Cons -## gRPC: +#### gRPC: -## Discussion 1(change heading): +### The Contenders for the Throne: gRPC, Thrift or RMI * gRPC vs Thrift (maybe also Finagle) ## Applications: * RPC and shared state (Persistence Layer): - - * [http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1](http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1) - + * http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1 * http://ieeexplore.ieee.org/document/918991/?arnumber=918991 * Grid computing: - * https://link.springer.com/article/10.1023/A:1024083511032 -* Mobile Systems(offloading and battery requirements): [https://link.springer.com/article/10.1007/s11036-012-0368-0](https://link.springer.com/article/10.1007/s11036-012-0368-0) +* Mobile Systems(offloading and battery requirements): + * https://link.springer.com/article/10.1007/s11036-012-0368-0 * Embedded RPC: - * https://dl.acm.org/citation.cfm?id=1127840 * Micro services architecture(ecosystem) -* Streaming - * RPC can be async * Shared State * microservices -## RPC in Streaming Protocols: +* Futures and promises: RPC? -* Streaming requests and buffered responses +### Streaming requests and buffered responses -## RPC in microservices ecosystem: +### RPC in microservices ecosystem: + +RPC started as a separate implements of REST, Streaming RPC, and now made possible of integration of all these implementations as a single abstraction for a user endpoint service. * Creating new services. * Bootstrapping * Load balancing - * Creating new services in Actor-Like model - * Fault tolerance - * Self-recovery * Business and Persistence Layer were combined and the Persistence layer is not shared anymore, where each endpoints has its own persistent state: - - * [https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm](https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm) + * https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm ## Security in RPC: - * Initially it was separate. - * Authentication, authorization issues have been resolved - * Now embedded in the protocol - * Security and Privacy in RPC - * Bugs in the libraries. - * Trust Issues between client and the server. - - * [http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/](http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/) - + * http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/ * Brewer’s view: https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf - * E programming language: distributed object model/VAT ## Discussion: - * RPC vs REST and other services. RPC influence. - * The future of RPC - * Where it shines. Not in message passing. + * RPC is not XYZ (HTTP, REST, …) though it has influenced. -## Conclusions: - - Some conclusion. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Class: Functional Programming for Distributed Computing - -Theme: The idea of communicating and invoking remote functions for distributed computation. - -Target Audience: Networks background, and wants to learn RPC. - --> RPC is not XYZ (HTTP, REST, …) though it has influenced. The - -RPC influence in XYZ design, though - -* RPC started in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. - -* RPC started as a separate implements of REST, Streaming RPC, and now made possible of integration of all these implementations as a single abstraction for a user endpoint service. - - * (subsection) How RPC influenced other models of communication. - -* RPC Models: - - * One Server Model - -* Methods of invoking remote function. - -* Discuss the evolution and pitfalls as they developed to an optimized - -* Software-As-A-Service: End-User focused. - +## Conclusions(maybe not a heading): +RPC is not dead: long live the Remote Procedure calls. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file rpc %} ## References -- cgit v1.2.3 From fed89cf607de9094be69968bc20e8c7896c5ddb4 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Sat, 3 Dec 2016 14:37:19 -0500 Subject: Initial very rough partial draft --- chapter/3/message-passing.md | 102 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 5898e23..dcb6f28 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -1,11 +1,103 @@ --- layout: page -title: "Message Passing" -by: "Joe Schmoe and Mary Jane" +title: "Message Passing and the Actor Model" +by: "Nathaniel Dempkowski" --- -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file message-passing %} +# Introduction -## References +In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. Message passing programming models have strong roots in computer science, and have essentially been discussed since the advent of object-oriented programming with Smalltalk in the 1980's. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, including those focused on the actor model of programming and organizing units of computation. -{% bibliography --file message-passing %} \ No newline at end of file +Message passing programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. Orleans gives an example of this, detailing not just a programming model, but a runtime system that is a quite advanced implementation of a message passing and actor model to solve real world problems. +The important question to ask about these sources is “Why message passing?” There are a number of distributed programming models, so why was this one so important when it was initially proposed. What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? + +# Original Proposal of the Actor Model + +# Classic Actor Model + +The classic actor model came about with the formalization of an actor as a unit of computation that implements the following primitives: +* `create`: create an actor from a behavior description and a set of parameters, including other existing actors +* `send`: send a message to another actor +* `become`: have an actor replace their behavior with a new one + +"The sequential subset of actor systems that implement this model is typically functional. Changes to the state of an actor are aggregated in a single become statement. Actors have a flexible interface that can similarly be changed by switching the behaviour of that actor." (43 Years of Actors) + +If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. + +This + +## Concurrent Object-Oriented Programming (1990) + +This is a seminal paper for the classic actor model, as it offers classic actors as a natural solution to solving problems at the intersection of two trends of computing: increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. + +This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to actually identify some of the programmer-centric advantages of actors. The author claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." (Agha, September, 1990) Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. “It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.” (Agha, September, 1990) This flexibility turns out to be a highly discussed concern that many of the later papers make a point to mention. + +## Rosette + +## Akka + +# Process-based Actor + +The process-based actor model is essentially an actor modelled as a process that runs from start to completion. These actors use a `receive` primitive to specify messages that an actor can receive during a given state. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. + +## Erlang + +Erlang was the primary driver of the process-based actor model, originally developing it to program large highly-reliable fault-tolerant telecommunications switching systems. This model was essentially developed independently from other actor systems and research. (It would be nice to have more details here, to emphasize how this actor model independently organically arose from some of the core needs of distributed systems) + +Erlang actors run as lightweight isolated processes. They do not have visibility into one another, and pass around pure messages, which are immutable. These have no dangling points or data references between objects, and really enforce the idea of immutable separated data between actors unlike many of the classic actor implementations in which references to actors and data can be passed around freely. + +TODO: mention disadvantages of Erlang's `receive` + +Erlang also seeks to build failure into the programming model, as one of the core assumptions of a distributed system is that things are going to fail. Erlang provides the ability for processes to monitor one another through two primitives: + +* `monitor`: one-way unobtrusive notification of process failure/shutdown +* `link`: two-way notification of process failure/shutdown allowing for coordinated termination + +These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. (Example of Erlang reliability or something would be good here) + +## Scala Actors + +Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." The authors say that VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors say that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” + +The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. (I feel like there needs to be a better concluding point to this) + +In addition to the more natural abstraction, the Erlang model is further enhanced with Scala's type system and advanced pattern-matching capabilities. + +# Communicating event-loops + +The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. + +## E Language + +The E language implements a model that is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. + +## AmbientTalk + +# Active Objects + +Active object actors draw a distinction between two different types of objects: active and passive objects. Every active object has a single entry point defining a fixed set of messages that are understood. Passive objects are the objects that are actually sent between actors, and are copied around to guarantee isolation. + +## ABCL/1 Language + +## Orleans + + + +# Why the actor model? + +The actor programming model offers benefits to programmers of distributed systems by allowing for easier programmer reasoning about behavior, providing a lightweight concurrency primitive that naturally scales across many machines, and enabling looser coupling among components of a system allowing for change without service disruption. Actors enable a programmer to easier reason about their behavior because they are at a fundamental level isolated from other actors. When programming an actor, the programmer only has to worry about the behavior of that actor and the messages it can send and receive. This alleviates the need for the programmer to reason about an entire system. Instead the programmer has a fixed set of concerns, meaning they can ensure behavioral correctness in isolation, rather than having to worry about an interaction they hadn’t anticipated occurring. Actors provide a single means of communication (message-passing), meaning that a lot of concerns a programmer has around concurrent modification of data are alleviated. Data is restricted to the data within a single actor and the messages it has been passed, rather than all of the accessible data in the whole system. + +Actors are lightweight, meaning that the programmer usually does not have to worry about how many actors they are creating. This is a contrast to other fundamental units of concurrency like threads or processes, which a programmer has to be acutely aware of, as they incur high costs of creation, and quickly run into machine resource and performance limitations. Haller (2009) says that without a lightweight process abstraction, burden is increased on the programmer to write their code in an obscured style (Philipp Haller, 2009). Unlike threads and processes, actors can also easily be told to run on other machines as they are functionally isolated. This cannot traditionally be done with threads or processes, as they are unable to be passed over the network to run elsewhere. Messages can be passed over the network, so an actor does not have to care where it is running as long as it can send and receive messages. They are more scalable because of this property, and it means that actors can naturally be distributed across a number of machines to meet the load or availability demands of the system. + +Finally, because actors are loosely coupled, only depending on a set of input and output messages to and from other actors, their behavior can be modified and upgraded without changing the entire system. For example, a single actor could be upgraded to use a more performant algorithm to do its work, and as long as it can process the same input and output messages, nothing else in the system has to change. This isolation is a contrast to methods of concurrent programming like remote procedure calls, futures, and promises. These models emphasize a tighter coupling between units of computation, where a process may call a method directly on another process and expect a specific result. This means that both the caller and callee (receiver of the call) need to have knowledge of the code being run, so you lose the ability to upgrade one without impacting the other. This becomes a problem in practice, as it means that as the complexity of your distributed system grows, more and more pieces become linked together. Agha (1990) states, “It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.” This is not desirable, as a key characteristic of distributed systems is availability, and the more things are linked together, the more of your system you have to take down or halt to make changes/upgrades. Actors compare favorably to other concurrent programming primitives like threads or remote procedure calls due to their low cost and loosely coupled nature. They are also programmer friendly, and ease the programmer burden of reasoning about a distributed system. + +# Modern usage in production + +It is important when reviewing models of programming distributed systems not to look just to academia, but to see which of these systems are actually used in industry to build things. This can give us insight into which features of actor systems are actually useful, and the trends that exist throughout these systems. + +## Module vs. Runtime approaches to tooling + + +# References + +{% bibliography --file message-passing %} -- cgit v1.2.3 From 1b06bef058acd4a96fba8d2e33c67b4c2bcce32f Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Sat, 3 Dec 2016 14:43:01 -0500 Subject: Add more about Akka and production usage of actors --- chapter/3/message-passing.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index dcb6f28..2b56024 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -36,6 +36,8 @@ This paper looks at a lot of systems and languages that are implementing solutio ## Akka +Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It makes an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. + # Process-based Actor The process-based actor model is essentially an actor modelled as a process that runs from start to completion. These actors use a `receive` primitive to specify messages that an actor can receive during a given state. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. @@ -81,8 +83,6 @@ Active object actors draw a distinction between two different types of objects: ## Orleans - - # Why the actor model? The actor programming model offers benefits to programmers of distributed systems by allowing for easier programmer reasoning about behavior, providing a lightweight concurrency primitive that naturally scales across many machines, and enabling looser coupling among components of a system allowing for change without service disruption. Actors enable a programmer to easier reason about their behavior because they are at a fundamental level isolated from other actors. When programming an actor, the programmer only has to worry about the behavior of that actor and the messages it can send and receive. This alleviates the need for the programmer to reason about an entire system. Instead the programmer has a fixed set of concerns, meaning they can ensure behavioral correctness in isolation, rather than having to worry about an interaction they hadn’t anticipated occurring. Actors provide a single means of communication (message-passing), meaning that a lot of concerns a programmer has around concurrent modification of data are alleviated. Data is restricted to the data within a single actor and the messages it has been passed, rather than all of the accessible data in the whole system. @@ -93,9 +93,27 @@ Finally, because actors are loosely coupled, only depending on a set of input an # Modern usage in production -It is important when reviewing models of programming distributed systems not to look just to academia, but to see which of these systems are actually used in industry to build things. This can give us insight into which features of actor systems are actually useful, and the trends that exist throughout these systems. +It is important when reviewing models of programming distributed systems not to look just to academia, but to see which of these systems are actually used in industry to build things. This can give us insight into which features of actor systems are actually useful, and the trends that exist throughout these systems. + +_On the Integration of the Actor Model into Mainstream Technologies_ by Philipp Haller provides some insight into the requirements of an industrial-strength actor implementation on a mainstream platform. These requirements were drawn out of an initial effort with [Scala Actors](#scala-actors) to bring the actor model to mainstream software engineering, as well as lessons learned from the deployment and advancement of production actors in [Akka](#akka). + +* _Library-based implementation_: It is not obvious which concurrency abstraction wins in real world cases, and different concurrency models might be used to solve different problems, so implementing a concurrency model as a library enables flexibility in usage. +* _High-level domain-specific language_: A domain-specific language or something comparable is a requirement to compete with languages that specialize in concurrency, otherwise your abstractions are lacking in idioms and expressiveness. +* _Event-driven implementation_: Actors need to be lightweight, meaning they cannot be mapped to an entire VM thread or process. For most platforms this means an event-driven model. +* _High performance_: Most industrial applications that use actors are highly performance sensitive, and high performance enables more graceful scalability. +* _Flexible remote actors_: Many applications can benefit from remote actors, which can communicate transparently over the network. Flexibility in deployment mechanisms is also very important. + +These attributes give us a good basis for analyzing whether an actor system can be successful in production. These are attributes that are necessary, but not sufficient for an actor system to be useful in production. + +## Actors as a framework + +One trend that seems common among the actor systems we see in production is extensive environments and tooling. I would argue that Akka, Erlang, and Orleans are the primary actor systems that see real production use, and I think the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. + +Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. + +### Module vs. Runtime approaches to tooling -## Module vs. Runtime approaches to tooling +Both Akka and Erlang take a module-based approach to tooling around their actor systems. The Orleans framework goes in another direction, instead providing an TODO: finish this thought # References -- cgit v1.2.3 From 4d373306b89abca4d7eb2204bcd397b364b167f5 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Sat, 3 Dec 2016 15:02:29 -0500 Subject: Ignore .DS_Store files for sanity --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 45c1505..4c548b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ _site .sass-cache .jekyll-metadata + +.DS_Store -- cgit v1.2.3 From b0882145fc373e1d85049be00bdddedd24ed78bd Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Sat, 3 Dec 2016 21:12:51 -0500 Subject: Add Cloud Haskell section heading --- chapter/3/message-passing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 2b56024..bb903b9 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -57,6 +57,8 @@ Erlang also seeks to build failure into the programming model, as one of the cor These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. (Example of Erlang reliability or something would be good here) +## Cloud Haskell + ## Scala Actors Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." The authors say that VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors say that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” -- cgit v1.2.3 From 38e2d93c10fda6881f73730560da53642a9ea7ff Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Sat, 3 Dec 2016 23:25:46 -0500 Subject: Fill in more sections about Akka, Cloud Haskell, and ABCL/1 --- chapter/3/message-passing.md | 49 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index bb903b9..ff67cda 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -13,9 +13,12 @@ The important question to ask about these sources is “Why message passing?” # Original Proposal of the Actor Model + + # Classic Actor Model The classic actor model came about with the formalization of an actor as a unit of computation that implements the following primitives: + * `create`: create an actor from a behavior description and a set of parameters, including other existing actors * `send`: send a message to another actor * `become`: have an actor replace their behavior with a new one @@ -24,7 +27,7 @@ The classic actor model came about with the formalization of an actor as a unit If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. -This +TODO: This ## Concurrent Object-Oriented Programming (1990) @@ -34,13 +37,21 @@ This paper looks at a lot of systems and languages that are implementing solutio ## Rosette + + ## Akka -Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It makes an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. +Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It makes an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. + +Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching patter to handle the message. The message will simply be discarded an an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. -# Process-based Actor +The Akka runtime also provides advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. -The process-based actor model is essentially an actor modelled as a process that runs from start to completion. These actors use a `receive` primitive to specify messages that an actor can receive during a given state. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. +Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. + +# Process-based Actors + +The process-based actor model is essentially an actor modeled as a process that runs from start to completion. These actors use a `receive` primitive to specify messages that an actor can receive during a given state. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. ## Erlang @@ -59,6 +70,8 @@ These primitives can be used to construct complex hierarchies of supervision tha ## Cloud Haskell +Cloud Haskell is an extension/DSL of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. Cloud Haskell processes can use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Monadic types types make it possible for programmers to use an effective style, where they can ensure that pure and effective code are not mixed. Additionally, Cloud Haskell has shared memory within an actor process, which is useful for certain applications, but forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. + ## Scala Actors Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." The authors say that VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors say that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” @@ -75,16 +88,38 @@ The communicating event-loop model was introduced in the E language, and is simi The E language implements a model that is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. +TODO: write more here + ## AmbientTalk # Active Objects Active object actors draw a distinction between two different types of objects: active and passive objects. Every active object has a single entry point defining a fixed set of messages that are understood. Passive objects are the objects that are actually sent between actors, and are copied around to guarantee isolation. +The active object model as initially described in the ABCL/1 language defines objects with a state and three modes: + +* `dormant`: Initial state of no computation, simply waiting for a message to activate the behavior of the actor. +* `active`: A state in which computation is performed that is triggered when a message is received that satisfies the patterns and constraints that the actor has defined it can process. +* `waiting`: A state of blocked execution, where the actor is active, but waiting until a certain type or pattern of message arrives to continue computation. + ## ABCL/1 Language +The ABCL/1 language implements the active object model described above, representing a system as a collection of objects, and the interactions between those objects as concurrent messages being passed around. One interesting aspect of ABCL/1 is the idea of explicitly different modes of message passing. Other actor models generally have a notion of priority around the values, types, or patterns of messages they process, but ABCL/1 implements tow different modes of message passing with different semantics. They have standard queued messages in the `ordinary` mode, but more interestingly they have `express` priority messages. When an object receives an express message it halts any other processing of ordinary messages it is performing, and processes the `express` message immediately. This enables an actor to accept high-priority messages while in `active` mode, and also enables monitoring and interrupting actors. + +The language also offers different models of synchronization around message-passing between actors. Three different message-passing models are given that enable different use cases: + +* `past`: Requests another actor to perform a task, while simultaneously proceeding with computation without waiting for the task to be completed. +* `now`: Waits for a message to be received, and to receive a response. This acts as a basic synchronization barrier across actors. +* `future`: Acts like a typical future, continuing computation until a remote result is needed, and then blocking until that result is received. + +It is interesting to note that all of these modes can be expressed by the `past` style of message-passing, as long as the type of the message and which actor to reply to with results are known. + +TODO: there should be something here to wrap up ABCL/1, and its impact? + ## Orleans + + # Why the actor model? The actor programming model offers benefits to programmers of distributed systems by allowing for easier programmer reasoning about behavior, providing a lightweight concurrency primitive that naturally scales across many machines, and enabling looser coupling among components of a system allowing for change without service disruption. Actors enable a programmer to easier reason about their behavior because they are at a fundamental level isolated from other actors. When programming an actor, the programmer only has to worry about the behavior of that actor and the messages it can send and receive. This alleviates the need for the programmer to reason about an entire system. Instead the programmer has a fixed set of concerns, meaning they can ensure behavioral correctness in isolation, rather than having to worry about an interaction they hadn’t anticipated occurring. Actors provide a single means of communication (message-passing), meaning that a lot of concerns a programmer has around concurrent modification of data are alleviated. Data is restricted to the data within a single actor and the messages it has been passed, rather than all of the accessible data in the whole system. @@ -115,9 +150,13 @@ Akka and Erlang provide modules that you can piece together to build various pie ### Module vs. Runtime approaches to tooling -Both Akka and Erlang take a module-based approach to tooling around their actor systems. The Orleans framework goes in another direction, instead providing an TODO: finish this thought +Both Akka and Erlang take a module-based approach to tooling around their actor systems. The Orleans framework goes in another direction, instead providing an + +TODO: finish this thought # References +TODO: fill these out + {% bibliography --file message-passing %} -- cgit v1.2.3 From 90b5b04b986336b2b0582eaba13f8f3ac34bd973 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Mon, 5 Dec 2016 18:03:14 -0500 Subject: Flesh out some more ideas across the general different types of actors --- chapter/3/message-passing.md | 65 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index ff67cda..14e8e61 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -13,21 +13,33 @@ The important question to ask about these sources is “Why message passing?” # Original Proposal of the Actor Model +The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel communication while safely exploiting distributed concurrency across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. +They define actors as units of computation. These units can send messages to one another, and have a mailbox which contains messages they have received. These messages are of the form `(request: ; reply-to: )`. + +Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a continuation, where the continuation is another message to an actor. These messages are one-way and make no claims about whether a message will ever be received in response. This model is limited, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. + +One interesting thing to note is that this original paper talks about actors in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept reminds me of something like a Lisp machine, but built to specially utilize the actor model of computation for artificial intelligence. # Classic Actor Model -The classic actor model came about with the formalization of an actor as a unit of computation that implements the following primitives: +The classic actor model came about with the formalization of an actor as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor is formalized as the following primitive actions: * `create`: create an actor from a behavior description and a set of parameters, including other existing actors * `send`: send a message to another actor * `become`: have an actor replace their behavior with a new one +As originally described, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are unique addressable, and have their own independent message queues. State changes using the classic actor model are specified using the `become` operation. Each time an actor processes a communication it computes a behavior in response to the next type of communication it expects to process. A `become` operation's argument is another named behavior with some state to pass to that named behavior. + +For purely functional actors the new behavior would be identical to the original. For more complex actors however, this enables the aggregation of state changes at a higher level of granularity than something like a variable assignment. This isolation changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. + +TODO: Not sure where this quote fits in? Maybe worth just pull-quoting it or taking it out as most of the points this hits on are better explained above. + "The sequential subset of actor systems that implement this model is typically functional. Changes to the state of an actor are aggregated in a single become statement. Actors have a flexible interface that can similarly be changed by switching the behaviour of that actor." (43 Years of Actors) -If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. +If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. The focus is on the messaging and designing the interactions and communications between the objects. -TODO: This +TODO: write more here ## Concurrent Object-Oriented Programming (1990) @@ -51,15 +63,35 @@ Akka is the production-ready result of the classic actor model lineage. It is ac # Process-based Actors -The process-based actor model is essentially an actor modeled as a process that runs from start to completion. These actors use a `receive` primitive to specify messages that an actor can receive during a given state. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. +TODO: better opening sentence + +The process-based actor model is essentially an actor modeled as a process that runs from start to completion. + +The first language to explicitly implement this model is Erlang, and they even say in a retrospective that their view of computation is broadly similar to the Agha's classic actor model. However, with process-based actors different mechanics are used. + +Process-based actors are defined by a computation which runs from start to completion, rather than the classic actor model, which defines an actor almost as a state machine of behaviors and the logic to transition between those. Similar state-machine like expressions are possible through recursion, but programming those feels fundamentally different than using the previously described `become` statement. + +These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. + +Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. ## Erlang -Erlang was the primary driver of the process-based actor model, originally developing it to program large highly-reliable fault-tolerant telecommunications switching systems. This model was essentially developed independently from other actor systems and research. (It would be nice to have more details here, to emphasize how this actor model independently organically arose from some of the core needs of distributed systems) +Erlang was the primary driver of the process-based actor model. This model was originally developed to program large highly-reliable fault-tolerant telecommunications switching systems. Erlang's development started in 1985, but its model of programming is still used today. The motivations of the Erlang model were around four key properties that were needed to program fault-tolerant operations: + +* Isolated processes +* Pure message passing between processes +* Detection of errors in remote processes +* The ability to determine what type of error caused a process crash -Erlang actors run as lightweight isolated processes. They do not have visibility into one another, and pass around pure messages, which are immutable. These have no dangling points or data references between objects, and really enforce the idea of immutable separated data between actors unlike many of the classic actor implementations in which references to actors and data can be passed around freely. +The Erlang researchers initially believed that shared-memory was preventing fault-tolerance and they saw message-passing of immutable data between processes as the solution to avoiding shared-memory. This model was essentially developed independently from other actor systems and research, especially as its development was started before Agha's classic actor model formalization was even published, but it ends up with a broadly similar view of computation to Agha's classic actor model. -TODO: mention disadvantages of Erlang's `receive` +Erlang actors run as lightweight isolated processes. They do not have visibility into one another, and pass around pure messages, which are immutable. These have no dangling pointers or data references between objects, and really enforce the idea of immutable separated data between actors unlike many of the early classic actor implementations in which references to actors and data can be passed around freely. + + +TODO: is it really worth mentioning `receive` again here? I think its assumed that the `receive` semantics above apply here? + +Erlang implements a blocking `receive` operation as a means of processing messages from a processes' mailbox. Erlang also seeks to build failure into the programming model, as one of the core assumptions of a distributed system is that things are going to fail. Erlang provides the ability for processes to monitor one another through two primitives: @@ -84,11 +116,26 @@ In addition to the more natural abstraction, the Erlang model is further enhance The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. +TODO: what does that sentence really mean? + ## E Language -The E language implements a model that is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. +The E language implements a model that is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. This vat contains not just objects, but a mailbox for all of the objects inside, as well as a call stack. There is a shared message queue and event-loop that acts as one abstraction barrier for computation. The actual references to objects within a vat which are used for communication and computation across actors operate at a different level of abstraction. -TODO: write more here +When handing out references at a different level of granularity than actor-global, how do you ensure the benefits of isolation that the actor model provides? After all, by handing out references inside of an actor it sounds like we're just reinventing shared-memory problems. The answer is that E's reference-states define many of the isolation guarantees around computation that we expect from actors. Two different reference-states are defined: + +* _Near reference_: This is a reference between two objects in the same vat. These expose both immediate-calls and eventual-sends. +* _Eventual reference_: This is a reference which crosses vat boundaries, and only exposes eventual-sends, not immediate-calls. + +The difference in semantics between the two types of references means that only objects within the same vat are granted synchronous access to one another. The most an eventual reference can do is send and queue a message for processing at some unspecified point in the future. This means that within the execution of a vat, a degree of temporal isolation can be defined between the objects and communications within the vat, and the communications to and from other vats. + +TODO: better transition sentence from reference types -> why we care about references at a less abstract level than the actor. + +Additionally, it some of motivation here comes from wanting to work at a finer-grained level of references than a traditional actor exposes. + +The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. + +TODO: write more here, maybe something around promise pipelining and partial failure? implications of different types of communication? ## AmbientTalk -- cgit v1.2.3 From c54f9005cc314238f1b3ec108099635a1d376063 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Mon, 5 Dec 2016 19:21:13 -0500 Subject: Fill in Orleans info --- chapter/3/message-passing.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 14e8e61..c563aeb 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -11,7 +11,7 @@ In the field of message passing programming models, it is not only important to Message passing programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. Orleans gives an example of this, detailing not just a programming model, but a runtime system that is a quite advanced implementation of a message passing and actor model to solve real world problems. The important question to ask about these sources is “Why message passing?” There are a number of distributed programming models, so why was this one so important when it was initially proposed. What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? -# Original Proposal of the Actor Model +# Original proposal of the actor model The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel communication while safely exploiting distributed concurrency across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. @@ -21,7 +21,7 @@ Actors attempt to process messages from their mailboxes by matching their `reque One interesting thing to note is that this original paper talks about actors in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept reminds me of something like a Lisp machine, but built to specially utilize the actor model of computation for artificial intelligence. -# Classic Actor Model +# Classic actor model The classic actor model came about with the formalization of an actor as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor is formalized as the following primitive actions: @@ -50,6 +50,8 @@ This paper looks at a lot of systems and languages that are implementing solutio ## Rosette +TODO: fill this out + ## Akka @@ -61,7 +63,7 @@ The Akka runtime also provides advantages over Scala Actors. The runtime uses a Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. -# Process-based Actors +# Process-based actors TODO: better opening sentence @@ -116,7 +118,7 @@ In addition to the more natural abstraction, the Erlang model is further enhance The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. -TODO: what does that sentence really mean? +TODO: what does that sentence really mean? need a better introduction to this model. ## E Language @@ -139,6 +141,8 @@ TODO: write more here, maybe something around promise pipelining and partial fai ## AmbientTalk +TODO: fill out + # Active Objects Active object actors draw a distinction between two different types of objects: active and passive objects. Every active object has a single entry point defining a fixed set of messages that are understood. Passive objects are the objects that are actually sent between actors, and are copied around to guarantee isolation. @@ -165,7 +169,19 @@ TODO: there should be something here to wrap up ABCL/1, and its impact? ## Orleans +Orleans takes the concept of lifecycle-less (not sure this is the term I want to use) actors, which are activated in response to asynchronous messages and places them in the context of cloud applications. Orleans does this via actors (called "grains") which are isolated units of computation and behavior that can have multiple instantiations (called "activations") for scalability. These actors also have persistence, meaning they have a persistent state that is kept in durable storage so that it can be used to manage things like user data. + +TODO: something about the notion of identity of an actor here. There are words below, but they could flow better into other points. + +It feels like Orleans uses a different notion of identity than other actor systems. In other systems an "actor" might refer to a behavior and instances of that actor might refer to identities that the actor represents like individual users. In Orleans, an actor represents that persistent identity, and the actual instantiations are in fact reconcilable copies of that identity. + +The programmer essentially assumes that a single entity is handling requests to an actor, but the Orleans runtime actually allows for multiple instantiations for scalability. These instantiations are invoked in response to an RPC-like call from the programmer which immediately returns an asynchronous promise. Multiple instances of an actor can be running and modifying the state of that actor at the same time. The immediate question here is how does that actually work? It doesn't intuitively seem like transparently accessing and changing multiple isolated copies of the same state should produce anything but problems when its time to do something with that state. + +Orleans solves this problem by providing mechanisms to reconcile conflicting changes. If multiple instances of an actor modify persistent state, they need to be reconciled into a consistent state in some meaningful way. The default here is a last-write-wins strategy, but Orleans also exposes the ability to create fine-grained reconciliation policies, as well as a number of common reconcilable data structures. If an application requires a certain reconciliation algorithm, the developer can implement it using Orleans. These reconciliation mechanisms are built upon Orleans' concept of transactions. + +Transactions in Orleans are a way to causally reason about the different instances of actors that are involved in a computation. Because in this model computation happens in response to a single outside request, a given actor's chain of computation via. associated actors always contains a single instantiation of each actor. These causal chain of instantiations is treated as a single transaction. At reconciliation time Orleans uses these transactions, along with current instantiation state to reconcile to a consistent state. +All of this is a longwinded way of saying that Orleans' programmer-centric contributions are that it separates the concerns of running and managing actor lifecycles from the concerns of how data flows throughout your distributed system. It does this is a fault-tolerant way, and for most programming tasks, you likely wouldn't have to worry about scaling and reconciling data in response to requests. It provides many of the benefits of the actor model, through a programming model that attempts to abstract away many of the details that you would have to worry about when using actors in production. # Why the actor model? @@ -195,11 +211,11 @@ One trend that seems common among the actor systems we see in production is exte Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. -### Module vs. Runtime approaches to tooling +## Module vs. "managed" runtime approaches Both Akka and Erlang take a module-based approach to tooling around their actor systems. The Orleans framework goes in another direction, instead providing an -TODO: finish this thought +TODO: finish this thought, avoid the word tooling because that implies like IDEs and stuff # References -- cgit v1.2.3 From 0d0ba3a765e2296a4313d89b368e8abf68d9f932 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 15:27:39 -0500 Subject: gRPC --- chapter/1/figures/grpc-cross-language.png | Bin 0 -> 27394 bytes chapter/1/figures/http2-frame.png | Bin 0 -> 12057 bytes chapter/1/figures/http2-stream-lifecycle.png | Bin 0 -> 49038 bytes chapter/1/gRPC.md | 122 +++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 chapter/1/figures/grpc-cross-language.png create mode 100644 chapter/1/figures/http2-frame.png create mode 100644 chapter/1/figures/http2-stream-lifecycle.png create mode 100644 chapter/1/gRPC.md diff --git a/chapter/1/figures/grpc-cross-language.png b/chapter/1/figures/grpc-cross-language.png new file mode 100644 index 0000000..c600f67 Binary files /dev/null and b/chapter/1/figures/grpc-cross-language.png differ diff --git a/chapter/1/figures/http2-frame.png b/chapter/1/figures/http2-frame.png new file mode 100644 index 0000000..59d6ed5 Binary files /dev/null and b/chapter/1/figures/http2-frame.png differ diff --git a/chapter/1/figures/http2-stream-lifecycle.png b/chapter/1/figures/http2-stream-lifecycle.png new file mode 100644 index 0000000..87333cb Binary files /dev/null and b/chapter/1/figures/http2-stream-lifecycle.png differ diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md new file mode 100644 index 0000000..49bdee5 --- /dev/null +++ b/chapter/1/gRPC.md @@ -0,0 +1,122 @@ +--- +layout: page +title: "gRPC" +by: "Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eric Anderson (Google, Inc.), Vijay Pai (Google, Inc.), and Heather Miller (Northeastern U.)" +--- + +

+

gRPC

+

+ +

+

Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eric Anderson (Google, Inc.), Vijay Pai (Google, Inc.), and Heather Miller (Northeastern U.)

+

+ +
+ +

Abstract

+ +gRPC has been built from a collaboration between Google and Square as a public replacement of Stubby, ARCWire and Sake {% cite Apigee %}. The gRPC framework is a form of an Actor Model based on an IDL (Interface Description Language), which is defined via the Protocol Buffer message format. With the introduction of HTTP/2 the internal Google Stubby and Square Sake frameworks are now been made available to the public. By working on top of the HTTP/2 protocol, gRPC enables messages to be multiplexed and compressed bi-directionally as premptive streams for maximizing capacity of any microservices ecosystem. Google has also a new approach to public projects, where instead of just releasing a paper describing the concepts will now also provide the implementation of how to properly interpret the standard. + + +

Introduction

+ +In order to understand gRPC and the flexibity of enabling a microservices ecosystem to become into a Reactive Actor Model, it is important to appreciate the nuances of the HTTP/2 Protocol upon which it is based. Afterward we will describe the gRPC Framework - focusing specifically on the gRPC-Java implementation - with the scope to expand this chapter over time to all implementations of gRPC. At the end we will cover examples demonstrating these ideas, by taking a user from the initial steps of how to work with the gRPC-Java framework. + +

1 HTTP/2

+ +The HTTP 1.1 protocol has been a success for some time, though there were some key features which began to be requested by the community with the increase of distributed computing, especially in the area of microservices. The phenomenon of creating more modularized functional units that are organically constructed based on a share-nothing model with a bidirectional, high-throughput request and response methodology demands a new protocol for communication and integration. Thus the HTTP/2 was born as a new standard, which is a binary wire protocol providing compressed streams that can be multiplexed for concurrency. As many microservices implementations currently scan header messages before actually processing any payload in order to scale up the processing and routing of messages, HTTP/2 now provides header compression for this purpose. One last important benefit is that the server endpoint can actually push cached resources to the client based on anticipated future communication, dramatically saving client communication time and processing. + +

1.1 HTTP/2 Frames

+ +The HTTP/2 protocol is now a framed protocol, which expands the capability for bidirectional, asynchronous communication. Every message is thus part of a frame that will have a header, frame type and stream identifier aside from the standard frame length for processing. Each stream can have a priority, which allows for dependency between streams to be achieved forming a priority tree. The data can be either a request or response which allows for the bidirectional communication, with the capability of flagging the communication for stream termination, flow control with priority settings, continuation and push responses from the server for client confirmation. Below is the format of the HTTP/2 frame {% cite RFC7540 %}: + +

+
+ Figure 1: The encoding a HTTP/2 frame. +

+ +

1.2 Header Compression

+ +The HTTP header is one of the primary methods of passing information about the state of other endpoints, the request or response and the payload. This enables endpoints to save time when processing a large quantity to streams, with the ability to forward information along without wasting time to inspect the payload. Since the header information can be quite large, it is possible to now compress the them to allow for better throughput and capacity of stored stateful information. + + +

1.3 Multiplexed Streams

+ +As streams are core to the implementation of HTTP/2, it is important to discuss the details of their implemenation in the protocol. As many streams can be open simultanously from many endpoints, each stream will be in one of the following states. Each stream is multiplexed together forming a chain of streams that are transmitted over the wire, allowing for asynchronous bi-directional concurrency to be performed by the receiving endpoint. Below is the lifecycle of a stream {% cite RFC7540 %}: + +

+
+ Figure 2: The lifecycle of a HTTP/2 stream. +

+ +

1.4 Flow Control of Streams

+ +Since many streams will compete for the bandwidth of a connection, in order to prevent bottlenecks and collisions in the transmission. This is done via the WINDOW_UPDATE payload for every stream - and the overall connection as well - to let the sender know how much room the receiving endpoint has for processing new data. + +

2 Protocol Buffers with RPC

+ +Though gRPC was built on top of HTTP/2, an IDL had to be used to perform the communication between endpoints. The natural direction was to use Protocol Buffers is the method of stucturing data for serialization between a server and client. At the time of the start of gRPC development only version 2.0 (proto2) was available, which only implemented data structures without any request/response mechanism. An example of a Protocol Buffer data structure would look something like this: + +``` +// A message containing the user's name. +message Hello { + string name = 1; +} +``` +

+ Figure 3: Protocol Buffer version 2.0 representing a message data-structure. +

+ +Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: + +``` +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} +``` +

+ Figure 4: Protocol Buffer version 3.0 representing a message data-structure with the accompanied RPC definition. +

+ +Notice the addition of a service, where the RPC call would use one of the messages as the structure of a Request with the other being the Response message format. + +Once of these Proto file get generated, one would then use them to compile with gRPC to for generating the Client and Server files representing the classical two endpoints of a RPC implementation. + +

3 gRPC

+ +gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, but expand it to all the implementations with time. gRPC is a framework for + +

+
+ Figure 5: gRPC allows for asynchronous language-agnostic message passing via Protocol Buffers. +

+ + +## References + +[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo +[Authentication]: http://www.grpc.io/docs/guides/auth.html +[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +[CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core +[ErrorModel]: http://www.grpc.io/docs/guides/error.html +[gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md +[gRPC-Companies]: http://www.grpc.io/about/ +[gRPC-Languages]: http://www.grpc.io/docs/ +[gRPC-Protos]: https://github.com/googleapis/googleapis/ +[Netty]: http://netty.io/ +[RFC7540]: http://httpwg.org/specs/rfc7540.html +[HelloWorldProto]: https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto + -- cgit v1.2.3 From 5594d046d3fdf98fc96adf629f0dfc1529b56d68 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 15:30:03 -0500 Subject: gRPC --- chapter/1/gRPC.md | 1 + 1 file changed, 1 insertion(+) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 49bdee5..ae8d2c1 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -12,6 +12,7 @@ by: "Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eri

Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eric Anderson (Google, Inc.), Vijay Pai (Google, Inc.), and Heather Miller (Northeastern U.)

+

Abstract

-- cgit v1.2.3 From 7a4919f1267279165276e13e24fb95b8698ac457 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 15:35:54 -0500 Subject: submit --- chapter/1/gRPC.md | 1 - 1 file changed, 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index ae8d2c1..49bdee5 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -12,7 +12,6 @@ by: "Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eri

Paul Grosu (Northeastern U.), Muzammil Abdul Rehman (Northeastern U.), Eric Anderson (Google, Inc.), Vijay Pai (Google, Inc.), and Heather Miller (Northeastern U.)

-

Abstract

-- cgit v1.2.3 From db03fdb08aedc5125580c7ec8baa7215f08fea92 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 18:24:23 -0500 Subject: submit --- chapter/1/gRPC.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 49bdee5..741f265 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -40,7 +40,6 @@ The HTTP/2 protocol is now a framed protocol, which expands the capability for b The HTTP header is one of the primary methods of passing information about the state of other endpoints, the request or response and the payload. This enables endpoints to save time when processing a large quantity to streams, with the ability to forward information along without wasting time to inspect the payload. Since the header information can be quite large, it is possible to now compress the them to allow for better throughput and capacity of stored stateful information. -

1.3 Multiplexed Streams

As streams are core to the implementation of HTTP/2, it is important to discuss the details of their implemenation in the protocol. As many streams can be open simultanously from many endpoints, each stream will be in one of the following states. Each stream is multiplexed together forming a chain of streams that are transmitted over the wire, allowing for asynchronous bi-directional concurrency to be performed by the receiving endpoint. Below is the lifecycle of a stream {% cite RFC7540 %}: @@ -68,6 +67,18 @@ message Hello { Figure 3: Protocol Buffer version 2.0 representing a message data-structure.

+This message will also be encoded for highest compression when sent over the wire. For example, let us say that the message is the string "Hi". + + + + + + + + + +
TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float
+ Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: ``` -- cgit v1.2.3 From 48ff73298ef9c06266fd031e5eceeb8128df0206 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 18:26:40 -0500 Subject: submit --- chapter/1/gRPC.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 741f265..62a3795 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -69,15 +69,10 @@ message Hello { This message will also be encoded for highest compression when sent over the wire. For example, let us say that the message is the string "Hi". - - - - - - - - -
TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float
+

+
+ Table 1: Tag values for Protocol Buffer types. +

Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: -- cgit v1.2.3 From e9d10f981f3e2d81f913bd59c4d797373c980f6d Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 18:27:35 -0500 Subject: submit --- chapter/1/figures/protobuf-types.png | Bin 0 -> 19941 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 chapter/1/figures/protobuf-types.png diff --git a/chapter/1/figures/protobuf-types.png b/chapter/1/figures/protobuf-types.png new file mode 100644 index 0000000..aaf3a1e Binary files /dev/null and b/chapter/1/figures/protobuf-types.png differ -- cgit v1.2.3 From 2b0e3e9ff9f08b00aed2268853208a6b9926695b Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 18:47:16 -0500 Subject: submit --- chapter/1/gRPC.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 62a3795..fb3c2a0 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -67,13 +67,19 @@ message Hello { Figure 3: Protocol Buffer version 2.0 representing a message data-structure.

-This message will also be encoded for highest compression when sent over the wire. For example, let us say that the message is the string "Hi". +This message will also be encoded for highest compression when sent over the wire. For example, let us say that the message is the string "Hi". Every Protocol Buffer type has a value, and in this case a string has a value of `2`, as noted in the Table 1 {% cite Protobuf-Types %}.


Table 1: Tag values for Protocol Buffer types.

+One will notice that there is a number associated with each field element in the Protocol Buffer definition, which represents its tag. In Figure 3, the field `name` has a tag of `1`. When a message gets encoded each field will start with a one byte value (8 bits), where the least-significant 3-bit value encode the type and the rest the tag. In this case tag which is `1`, with a type of 2. Thus the encoding will be `00001 010`, which has a hexdecimal value of `A`. The following byte is the length of the string which is `2`, followed by the string as `48` and `69` representing `H` and `i`. Thus the whole tranmission will look as follows: + +``` +A 2 48 69 +``` + Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: ``` @@ -125,4 +131,4 @@ gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, b [Netty]: http://netty.io/ [RFC7540]: http://httpwg.org/specs/rfc7540.html [HelloWorldProto]: https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto - +[Protobuf-Types]: https://developers.google.com/protocol-buffers/docs/encoding -- cgit v1.2.3 From 47c8ad4b134cfd11b8cf1b7003d7314fed42b71d Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 19:01:59 -0500 Subject: submit --- chapter/1/gRPC.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index fb3c2a0..622498d 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -105,17 +105,23 @@ service Greeter { Notice the addition of a service, where the RPC call would use one of the messages as the structure of a Request with the other being the Response message format. -Once of these Proto file get generated, one would then use them to compile with gRPC to for generating the Client and Server files representing the classical two endpoints of a RPC implementation. +Once of these Proto file gets generated, one would then use them to compile with gRPC to for generating the Client and Server files representing the classical two endpoints of a RPC implementation.

3 gRPC

-gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, but expand it to all the implementations with time. gRPC is a framework for +gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, but expand it to all the implementations with time. gRPC is a cross-platform framework that allows integration across many languages as denoted in Figure 5 {% cite gRPC-Overview %}.


Figure 5: gRPC allows for asynchronous language-agnostic message passing via Protocol Buffers.

+The officially supported languages are listed in Table 2 {% cite gRPC-Languages %}. + +

+
+ Table 2: Officially supported languages by gRPC. +

## References @@ -132,3 +138,5 @@ gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, b [RFC7540]: http://httpwg.org/specs/rfc7540.html [HelloWorldProto]: https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto [Protobuf-Types]: https://developers.google.com/protocol-buffers/docs/encoding +[gRPC-Overview]: http://www.grpc.io/docs/guides/ +[gRPC-Languages]: http://www.grpc.io/about/#osp -- cgit v1.2.3 From 2c7ba877fbfe365fe8ce74a9c34823ebe84a8eb8 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 19:03:55 -0500 Subject: submit --- chapter/1/figures/grpc-languages.png | Bin 0 -> 47003 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 chapter/1/figures/grpc-languages.png diff --git a/chapter/1/figures/grpc-languages.png b/chapter/1/figures/grpc-languages.png new file mode 100644 index 0000000..1f1c50d Binary files /dev/null and b/chapter/1/figures/grpc-languages.png differ -- cgit v1.2.3 From 66187e549607d715b684b0a4df7e589d057287a0 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 19:31:57 -0500 Subject: submit --- chapter/1/figures/grpc-benchmark.png | Bin 0 -> 17014 bytes chapter/1/gRPC.md | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 chapter/1/figures/grpc-benchmark.png diff --git a/chapter/1/figures/grpc-benchmark.png b/chapter/1/figures/grpc-benchmark.png new file mode 100644 index 0000000..9f39c71 Binary files /dev/null and b/chapter/1/figures/grpc-benchmark.png differ diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 622498d..99a81e0 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -123,6 +123,18 @@ The officially supported languages are listed in Table 2 {% cite gRPC-Languages Table 2: Officially supported languages by gRPC.

+There are benchmarks being performed on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6. + +

+
+ Figure 6: Benchmark showing the queries-per-second on two virtual machines with 32 cores each. +

+ + +

3.1 gRPC Java

+ +The Java implementation of gRPC has been built with Mobile platform in mind and requires support of JDK 6.0 and higher. There are + ## References [Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -- cgit v1.2.3 From 93948d701c8e531c42caeb538b7fcb352e8d19ab Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 19:32:39 -0500 Subject: submit --- chapter/1/gRPC.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 99a81e0..96d4609 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -123,7 +123,7 @@ The officially supported languages are listed in Table 2 {% cite gRPC-Languages Table 2: Officially supported languages by gRPC.

-There are benchmarks being performed on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6. +There are benchmarks being performed on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6 {% cite gRPC-Benchmark %}.


@@ -152,3 +152,4 @@ The Java implementation of gRPC has been built with Mobile platform in mind and [Protobuf-Types]: https://developers.google.com/protocol-buffers/docs/encoding [gRPC-Overview]: http://www.grpc.io/docs/guides/ [gRPC-Languages]: http://www.grpc.io/about/#osp +[gRPC-Benchmark]: http://www.grpc.io/docs/guides/benchmarking.html \ No newline at end of file -- cgit v1.2.3 From b4c160fa2fc1256e700cea1511547b7aab068d13 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Tue, 6 Dec 2016 21:04:35 -0500 Subject: Write about Rosette and AmbientTalk --- chapter/3/message-passing.md | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index c563aeb..69e4f48 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -9,6 +9,7 @@ by: "Nathaniel Dempkowski" In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. Message passing programming models have strong roots in computer science, and have essentially been discussed since the advent of object-oriented programming with Smalltalk in the 1980's. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, including those focused on the actor model of programming and organizing units of computation. Message passing programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. Orleans gives an example of this, detailing not just a programming model, but a runtime system that is a quite advanced implementation of a message passing and actor model to solve real world problems. + The important question to ask about these sources is “Why message passing?” There are a number of distributed programming models, so why was this one so important when it was initially proposed. What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? # Original proposal of the actor model @@ -49,9 +50,22 @@ This paper looks at a lot of systems and languages that are implementing solutio ## Rosette +Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, and the code examples given in the paper are written in Rosette. It is important to mention as it seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. + +The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means of control over how resources are allocated to subcomputations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: + +* _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. +* _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. + +The Rosette language features, many of which we take for granted in object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. I think the more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the machine makes it difficult to re-map a program to another one. I think this idea of using actors as a more flexible and natural abstraction is an important one which is seen in some form within many of the actor systems described here. + +Actors in Rosette are organized into three types of classes which describe different aspects of the actors within the system: -TODO: fill this out +* _Abstract classes_ specify requests, responses, and actions within the system which can be observed. The idea behind these is to expose the higher-level behaviors of the system, but tailor the actual actor implementations to the resource constraints of the system. +* _Representation classes_ specify the resource management characteristics of implementations of abstract classes. +* _Behavior classes_ specify the actual implementations of actors in given abstract and representation classes. +These classes represent a concrete object-oriented abstraction to organize actors which handles the practical constraints of a distributed system. It represents a step in the direction of handling not just the information flow and behavior of the system, but the underlying hardware and resources. Rosette's model feels like a direct expression of those concerns which are something every actor system in production inevitably ends up addressing. ## Akka @@ -118,7 +132,7 @@ In addition to the more natural abstraction, the Erlang model is further enhance The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. -TODO: what does that sentence really mean? need a better introduction to this model. +TODO: what does that sentence really mean? need a better introduction to this model. Could add more about AmbientTalk in the intro? If this is too expanded its going to be repeating the same idea of accessing objects within actors 3 times though. ## E Language @@ -139,9 +153,21 @@ The simplest example is that you want to ensure that another actor in your syste TODO: write more here, maybe something around promise pipelining and partial failure? implications of different types of communication? -## AmbientTalk +## AmbientTalk/2 + +AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between passive objects. + +Actors in AmbientTalk/2 are representations of an event loops. The message queue is the event queue, messages are events, asynchronous message sends are event notifications, and object methods are the event handlers. The event loop serially processes messages from the queue to avoid race conditions. Local objects within an actor are owned by that actor, which is the only entity allowed to directly execute methods on them. Like E, objects within an actor can communicate using synchronous or asynchronous methods of communication. Again similar to E, objects that are referenced outside of an actor can only be communicated to asynchronously by sending messages. Objects can additionally declare themselves serializable, which means they can be copied and sent to other actors for use as local objects. When this happens, there is no maintained relationship between the original object and its copy. + +AmbientTalk/2 uses the event loop model to enforce three essential concurrency control properties: + +* _Serial execution_: Events are processed sequentially from an event queue, so the handling of a single event is atomic with respect to other events. +* _Non-blocking communication_: An event loop doesn't suspend computation to wait for other event loops, instead all communication happens strictly as asynchronous event notifications. +* _Exclusive state access_: Event handlers (object methods) and their associated state belong to a single event loop, which has access to their mutable state. Mutation of other event loop state is only possible indirectly by passing an event notification asking for mutation to occur. + +The end result of all this decoupling and isolation of computation is that it is a natural fit for mobile ad hoc networks. In this domain, connections are volatile with limited range and transient failures. Removing coupling based on time or synchronization is a natural fit for the domain, and the communicating event-loop actor model is a natural model for programming these systems. AmbientTalk/2 provides additional features on top of the communicating event-loop model like service discovery. These enable ad hoc network creation as actors near each other can broadcast their existence and advertise common services that can be used for communication. -TODO: fill out +AmbientTalk/2 is most notable as a reimagining of the communicating event-loops actor model for a modern use case. # Active Objects -- cgit v1.2.3 From 0598f53e1a16ab7731f081a77f15542192c28b07 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:25:40 -0500 Subject: submit --- chapter/1/gRPC.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 96d4609..0dc78c3 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -130,10 +130,17 @@ There are benchmarks being performed on a daily basis to ensure that gRPC perfor Figure 6: Benchmark showing the queries-per-second on two virtual machines with 32 cores each.

+Most of the public Google APIs have been ported to support gRPC and their definitions can be analyzed at the following location: + +

+ https://github.com/googleapis/googleapis/tree/master/google
+

+ +

3.1 gRPC Java

-The Java implementation of gRPC has been built with Mobile platform in mind and requires support of JDK 6.0 and higher. There are +The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built for data centers specifically to support C/C++ for the Linux platform, the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. ## References -- cgit v1.2.3 From c4a9f8305ae1a99721030b9d3009a64453e17121 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:32:44 -0500 Subject: submit --- chapter/1/figures/grpc-googleapis.png | Bin 0 -> 33354 bytes chapter/1/gRPC.md | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 chapter/1/figures/grpc-googleapis.png diff --git a/chapter/1/figures/grpc-googleapis.png b/chapter/1/figures/grpc-googleapis.png new file mode 100644 index 0000000..62718e5 Binary files /dev/null and b/chapter/1/figures/grpc-googleapis.png differ diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 0dc78c3..c3333c2 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -130,12 +130,12 @@ There are benchmarks being performed on a daily basis to ensure that gRPC perfor Figure 6: Benchmark showing the queries-per-second on two virtual machines with 32 cores each.

-Most of the public Google APIs have been ported to support gRPC and their definitions can be analyzed at the following location: +Most of the public Google APIs - including the Speech API, Vision API, BigTable, Pub/Sub, etc. - have been ported to support gRPC, and their definitions can be found at the following location:

- https://github.com/googleapis/googleapis/tree/master/google
-

- +
+ Figure 7: The public Google APIs have been updated for gRPC, and be found at https://github.com/googleapis/googleapis/tree/master/google +

3.1 gRPC Java

-- cgit v1.2.3 From 2d03731c4d9a4d2a0342e613cb8d7ad7e5fc3f7a Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:35:14 -0500 Subject: submit --- chapter/1/gRPC.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index c3333c2..59090d0 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -130,7 +130,7 @@ There are benchmarks being performed on a daily basis to ensure that gRPC perfor Figure 6: Benchmark showing the queries-per-second on two virtual machines with 32 cores each.

-Most of the public Google APIs - including the Speech API, Vision API, BigTable, Pub/Sub, etc. - have been ported to support gRPC, and their definitions can be found at the following location: +Most of the public Google APIs - including the Speech API, Vision API, Bigtable, Pub/Sub, etc. - have been ported to support gRPC, and their definitions can be found at the following location:


@@ -140,13 +140,13 @@ Most of the public Google APIs - including the Speech API, Vision API, BigTable,

3.1 gRPC Java

-The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built for data centers specifically to support C/C++ for the Linux platform, the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. +The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. ## References -[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -[Authentication]: http://www.grpc.io/docs/guides/auth.html -[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +`[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo` [Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo +`[Authentication]: http://www.grpc.io/docs/guides/auth.html` [Authentication]: http://www.grpc.io/docs/guides/auth.html +`[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html` [Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html [CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html [gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md -- cgit v1.2.3 From fc4ac30854c08373e0608260b48265627fc382fe Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:36:00 -0500 Subject: submit --- chapter/1/gRPC.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 59090d0..0bb7ede 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -144,9 +144,9 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References -`[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo` [Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -`[Authentication]: http://www.grpc.io/docs/guides/auth.html` [Authentication]: http://www.grpc.io/docs/guides/auth.html -`[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html` [Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +`` [Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo +`` [Authentication]: http://www.grpc.io/docs/guides/auth.html +`` [Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html [CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html [gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md -- cgit v1.2.3 From 7d22d28bce2beb45907663b8aff9e04aeae2cbbd Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:36:34 -0500 Subject: submit --- chapter/1/gRPC.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 0bb7ede..2afd338 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -144,9 +144,12 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References -`` [Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -`` [Authentication]: http://www.grpc.io/docs/guides/auth.html -`` [Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +``[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo + +``[Authentication]: http://www.grpc.io/docs/guides/auth.html + +``[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html + [CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html [gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md -- cgit v1.2.3 From 7694f5c94f38a35e457649f11d571818d6eec6df Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:36:51 -0500 Subject: submit --- chapter/1/gRPC.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 2afd338..ddac5d7 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -144,11 +144,11 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References -``[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo +`[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -``[Authentication]: http://www.grpc.io/docs/guides/auth.html +`[Authentication]: http://www.grpc.io/docs/guides/auth.html -``[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +`[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html [CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html -- cgit v1.2.3 From 3027b93e08cf101f078b5a1604b302fa6d1ccee5 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:37:17 -0500 Subject: submit --- chapter/1/gRPC.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index ddac5d7..20a0ce0 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -144,11 +144,11 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References -`[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo +` `[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -`[Authentication]: http://www.grpc.io/docs/guides/auth.html +` `[Authentication]: http://www.grpc.io/docs/guides/auth.html -`[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html +` `[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html [CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html -- cgit v1.2.3 From e0f2b97b972a58fff80bf0989729f1aa97bc7fba Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:38:29 -0500 Subject: submit --- chapter/1/gRPC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 20a0ce0..e4eed89 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -134,7 +134,7 @@ Most of the public Google APIs - including the Speech API, Vision API, Bigtable,


- Figure 7: The public Google APIs have been updated for gRPC, and be found at https://github.com/googleapis/googleapis/tree/master/google + Figure 7: The public Google APIs have been updated for gRPC, and be found at https://github.com/googleapis/googleapis/tree/master/google

@@ -150,7 +150,7 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ` `[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html -[CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core +` `[CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html [gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md [gRPC-Companies]: http://www.grpc.io/about/ -- cgit v1.2.3 From bf05b2a7a974fc7af352f7a2e68f08cc610353ac Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:38:48 -0500 Subject: submit --- chapter/1/gRPC.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index e4eed89..af26980 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -145,11 +145,8 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References ` `[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo - ` `[Authentication]: http://www.grpc.io/docs/guides/auth.html - ` `[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html - ` `[CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core [ErrorModel]: http://www.grpc.io/docs/guides/error.html [gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md -- cgit v1.2.3 From 6bf06870225d78ea9457ae87840bae822b50d893 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:39:43 -0500 Subject: submit --- chapter/1/gRPC.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index af26980..20028ea 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -145,18 +145,34 @@ The Java implementation of gRPC been built with Mobile platform in mind and to p ## References ` `[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo + ` `[Authentication]: http://www.grpc.io/docs/guides/auth.html + ` `[Benchmarks]: http://www.grpc.io/docs/guides/benchmarking.html + ` `[CoreSurfaceAPIs]: https://github.com/grpc/grpc/tree/master/src/core -[ErrorModel]: http://www.grpc.io/docs/guides/error.html -[gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md -[gRPC-Companies]: http://www.grpc.io/about/ -[gRPC-Languages]: http://www.grpc.io/docs/ -[gRPC-Protos]: https://github.com/googleapis/googleapis/ -[Netty]: http://netty.io/ -[RFC7540]: http://httpwg.org/specs/rfc7540.html -[HelloWorldProto]: https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto -[Protobuf-Types]: https://developers.google.com/protocol-buffers/docs/encoding -[gRPC-Overview]: http://www.grpc.io/docs/guides/ -[gRPC-Languages]: http://www.grpc.io/about/#osp -[gRPC-Benchmark]: http://www.grpc.io/docs/guides/benchmarking.html \ No newline at end of file + +` `[ErrorModel]: http://www.grpc.io/docs/guides/error.html + +` `[gRPC]: https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md + +` `[gRPC-Companies]: http://www.grpc.io/about/ + +` `[gRPC-Languages]: http://www.grpc.io/docs/ + +` `[gRPC-Protos]: https://github.com/googleapis/googleapis/ + +` `[Netty]: http://netty.io/ + +` `[RFC7540]: http://httpwg.org/specs/rfc7540.html + +` `[HelloWorldProto]: https://github.com/grpc/grpc/blob/master/examples/protos/ +helloworld.proto + +` `[Protobuf-Types]: https://developers.google.com/protocol-buffers/docs/encoding + +` `[gRPC-Overview]: http://www.grpc.io/docs/guides/ + +` `[gRPC-Languages]: http://www.grpc.io/about/#osp + +` `[gRPC-Benchmark]: http://www.grpc.io/docs/guides/benchmarking.html -- cgit v1.2.3 From 880349fca82d4504dde53d0dd706238b803046d9 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:46:21 -0500 Subject: submit --- chapter/1/gRPC.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 20028ea..0063958 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -137,11 +137,28 @@ Most of the public Google APIs - including the Speech API, Vision API, Bigtable, Figure 7: The public Google APIs have been updated for gRPC, and be found at https://github.com/googleapis/googleapis/tree/master/google

+

3.2 Authentication

-

3.1 gRPC Java

+There are two methods of authentication that are available in gRPC: + +* SSL/TLS +* Google Token (via OAuth2) + +gRPC is flexible in that once can implement their custom authentication if that is preferred. + +

3.3 gRPC Java

The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. +

3... Downloading gRPC Java

+ +The easiest way to download the gRPC-Java implemenation is by performing the following command: + + +

3... Hello World Demonstration

+ + + ## References ` `[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -- cgit v1.2.3 From 0125b8bba4eea09ec018db6dfcb529ac71e1f6e2 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:47:59 -0500 Subject: submit --- chapter/1/gRPC.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 0063958..5c13c98 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -116,13 +116,6 @@ gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, b Figure 5: gRPC allows for asynchronous language-agnostic message passing via Protocol Buffers.

-The officially supported languages are listed in Table 2 {% cite gRPC-Languages %}. - -

-
- Table 2: Officially supported languages by gRPC. -

- There are benchmarks being performed on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6 {% cite gRPC-Benchmark %}.

@@ -137,6 +130,16 @@ Most of the public Google APIs - including the Speech API, Vision API, Bigtable, Figure 7: The public Google APIs have been updated for gRPC, and be found at https://github.com/googleapis/googleapis/tree/master/google

+ +

3.1 Supported Languages

+ +The officially supported languages are listed in Table 2 {% cite gRPC-Languages %}. + +

+
+ Table 2: Officially supported languages by gRPC. +

+

3.2 Authentication

There are two methods of authentication that are available in gRPC: @@ -146,6 +149,8 @@ There are two methods of authentication that are available in gRPC: gRPC is flexible in that once can implement their custom authentication if that is preferred. + +

3.3 gRPC Java

The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. -- cgit v1.2.3 From 507b194bd0e1222787127bc902ac022f435784f3 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 21:49:20 -0500 Subject: submit --- chapter/1/gRPC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 5c13c98..4b0c04b 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -116,14 +116,14 @@ gRPC was built on top of HTTP/2, and we will cover the specifics of gRPC-Java, b Figure 5: gRPC allows for asynchronous language-agnostic message passing via Protocol Buffers.

-There are benchmarks being performed on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6 {% cite gRPC-Benchmark %}. +To ensure scalability, benchmarks are run on a daily basis to ensure that gRPC performs optimally under high-throughput conditions as illustrated in Figure 6 {% cite gRPC-Benchmark %}.


Figure 6: Benchmark showing the queries-per-second on two virtual machines with 32 cores each.

-Most of the public Google APIs - including the Speech API, Vision API, Bigtable, Pub/Sub, etc. - have been ported to support gRPC, and their definitions can be found at the following location: +To standardize, most of the public Google APIs - including the Speech API, Vision API, Bigtable, Pub/Sub, etc. - have been ported to support gRPC, and their definitions can be found at the following location:


-- cgit v1.2.3 From b42f4f4f4108ded7f89d80bed9a0c72fdc4ac013 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 22:04:40 -0500 Subject: submit --- chapter/1/gRPC.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 4b0c04b..d4f1601 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -147,9 +147,21 @@ There are two methods of authentication that are available in gRPC: * SSL/TLS * Google Token (via OAuth2) -gRPC is flexible in that once can implement their custom authentication if that is preferred. +gRPC is flexible in that once can plug in their custom authentication system if that is preferred. +

3.3 gRPC Mechanism

+In its simplest form gRPC has a structured set of steps one goes about using it, which has this general flow: + +1. Download gRPC for the language of interest. + +2. Implement the Request and Response definition in a ProtoBuf file. + +3. Compile the ProtoBuf file and run the code-generators for the the specific language. This will generate the Client and Server endpoints. + +4. Customize the Client and Server code for the desired implementation. + +Most of these will require tweaking the Protobuf file and testing the throughput to ensure that the network and CPU capacities are optimally maximized.

3.3 gRPC Java

-- cgit v1.2.3 From f1689419b0626987a53ca9c3e178d47b0067abf3 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 22:07:24 -0500 Subject: submit --- chapter/1/gRPC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index d4f1601..4d06e75 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -149,7 +149,7 @@ There are two methods of authentication that are available in gRPC: gRPC is flexible in that once can plug in their custom authentication system if that is preferred. -

3.3 gRPC Mechanism

+

3.3 Development Cycle

In its simplest form gRPC has a structured set of steps one goes about using it, which has this general flow: -- cgit v1.2.3 From 46d75a8eb88b7edbb30a14ba8834f6b4bc2ed68a Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 22:23:26 -0500 Subject: submit --- chapter/1/gRPC.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 4d06e75..08962b1 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -163,7 +163,18 @@ In its simplest form gRPC has a structured set of steps one goes about using it, Most of these will require tweaking the Protobuf file and testing the throughput to ensure that the network and CPU capacities are optimally maximized. -

3.3 gRPC Java

+

3.4 The gRPC Framework (Stub, Channel and Transport Layer)

+ +One starts by initializing a communication Channel between Client to a Server and storing that as a Stub. The Credentials are provided to the Channel when being initialized. These form a Context for the Client's connection to the Server. Then a Request can be built based on the definition in the Protobuf file. The Request and associated expectedResponse is executed by the service constructed in the Protobuf file. The Response is them parsed for any data coming from the Channel. + +The connection can be asynchronous and bi-directionally streaming so that data is constantly flowing back and available to be read when ready. This allows one to treat the Client and Server as endpoints where one can even adjust not just the flow but also intercept to filter and thus request the data of interest. + +That stub can be referenced later in order + + +The Java implementation of gRPC been built with Mobile platform in mind and to + +

3... gRPC Java

The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. -- cgit v1.2.3 From 3e366732f47c850dd9734dfabfada204e9bef7be Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Tue, 6 Dec 2016 22:28:38 -0500 Subject: submit --- chapter/1/gRPC.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 08962b1..7d9cd34 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -167,12 +167,9 @@ Most of these will require tweaking the Protobuf file and testing the throughput One starts by initializing a communication Channel between Client to a Server and storing that as a Stub. The Credentials are provided to the Channel when being initialized. These form a Context for the Client's connection to the Server. Then a Request can be built based on the definition in the Protobuf file. The Request and associated expectedResponse is executed by the service constructed in the Protobuf file. The Response is them parsed for any data coming from the Channel. -The connection can be asynchronous and bi-directionally streaming so that data is constantly flowing back and available to be read when ready. This allows one to treat the Client and Server as endpoints where one can even adjust not just the flow but also intercept to filter and thus request the data of interest. +The connection can be asynchronous and bi-directionally streaming so that data is constantly flowing back and available to be read when ready. This allows one to treat the Client and Server as endpoints where one can even adjust not just the flow but also intercept and decoration to filter and thus request and retrieve the data of interest. -That stub can be referenced later in order - - -The Java implementation of gRPC been built with Mobile platform in mind and to +The Transport Layer performs the retrieval and placing of binary protocol on the wire. For gRPC-Java has three implementations, though a user can implement their own: Netty, OkHttp, and inProcess.

3... gRPC Java

-- cgit v1.2.3 From 803908d2a8ba43d9f8ec4ddde547541fb4b148b2 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Wed, 7 Dec 2016 00:47:22 -0500 Subject: Fill out actors as a framework, approaches, and CSP sections --- chapter/3/message-passing.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 69e4f48..a32a105 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -52,7 +52,7 @@ This paper looks at a lot of systems and languages that are implementing solutio Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, and the code examples given in the paper are written in Rosette. It is important to mention as it seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. -The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means of control over how resources are allocated to subcomputations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: +The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means of control over how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: * _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. * _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. @@ -118,7 +118,7 @@ These primitives can be used to construct complex hierarchies of supervision tha ## Cloud Haskell -Cloud Haskell is an extension/DSL of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. Cloud Haskell processes can use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Monadic types types make it possible for programmers to use an effective style, where they can ensure that pure and effective code are not mixed. Additionally, Cloud Haskell has shared memory within an actor process, which is useful for certain applications, but forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. +Cloud Haskell is an extension/DSL of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. Cloud Haskell processes can use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Monadic types make it possible for programmers to use an effective style, where they can ensure that pure and effective code are not mixed. Additionally, Cloud Haskell has shared memory within an actor process, which is useful for certain applications, but forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. ## Scala Actors @@ -132,7 +132,7 @@ In addition to the more natural abstraction, the Erlang model is further enhance The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. -TODO: what does that sentence really mean? need a better introduction to this model. Could add more about AmbientTalk in the intro? If this is too expanded its going to be repeating the same idea of accessing objects within actors 3 times though. +TODO: what does that sentence really mean? need a better introduction to this model. Could add more about AmbientTalk in the intro? If this is too expanded its going to be repeating the same idea of accessing objects within actors 3 times though. ## E Language @@ -235,13 +235,25 @@ These attributes give us a good basis for analyzing whether an actor system can One trend that seems common among the actor systems we see in production is extensive environments and tooling. I would argue that Akka, Erlang, and Orleans are the primary actor systems that see real production use, and I think the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. -Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. +Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. Cloud Haskell also provides something analogous to Erlang's OTP called the Cloud Haskell Platform. -## Module vs. "managed" runtime approaches +Orleans is different from these as it is built from the ground up with a more declarative style and runtime. This does a lot of the work of distributing and scaling actors for you, but it is still definitely a framework which handles a lot of the common problems of distribution so that programmers can focus on building the logic of their system. -Both Akka and Erlang take a module-based approach to tooling around their actor systems. The Orleans framework goes in another direction, instead providing an +## Module vs. managed runtime approaches -TODO: finish this thought, avoid the word tooling because that implies like IDEs and stuff +Based on my research there have been two prevalent approaches to frameworks which are actually used to build production actor systems in industry. These are high-level philosophies about the meta-organization of an actor system. They are the design philosophies that aren't even directly considered when just looking at the base actor programming and execution models. I think the easiest way to describe these is are as the "module approach" and the "managed runtime approach". A high-level analogy to describe these is that the module approach is similar to manually managing memory, while the managed runtime approach is similar to garbage collection. In the module approach, you care about the lifecycle and physical allocation of actors within your system, while in the managed runtime approach you care more about the reconciliation behavior and flow of persistent state between automatic instantiations of your actors. + +Both Akka and Erlang take a module approach to building their actor systems. This means that when you build a system using these languages/frameworks, you are using smaller composable components as pieces of the larger system you want to build. You are explicitly dealing with the lifecycles and instantiations of actors within your system, where to distribute them across physical machines, and how to balance actors to scale. Some of these problems might be handled by libraries, but at some level you are specifying how all of the organization of your actors is happening. The JVM or Erlang VM isn't doing it for you. + +Orleans goes in another direction, which I call the managed runtime approach. Instead of providing small components which let you build your own abstractions, they provide a runtime in the cloud that attempts to abstract away a lot of the details of managing actors. It does this to such an extent that you no longer even directly manage actor lifecycles, where they live on machines, or how they are replicated and scaled. Instead you program with actors in a more declarative style. You never explicitly instantiate actors, instead you assume that the runtime will figure it out for you in response to requests to your system. You program in strategies to deal with problems like domain-specific reconciliation of data across instances, but you generally leave it to the runtime to scale and distribute the actor instances within your system. + +I don't have an opinion on which of these is right. Both approaches have been successful in industry. Erlang has the famous use case of a telephone exchange and a successful history since then. Akka has an entire page detailing its usage in giant companies. Orleans has been used as a backend to massive Microsoft-scale games and applications with millions of users. It seems like the module approach is more popular, but there's only really one example of the managed runtime approach out there. There's no equivalent to Orleans on the JVM or Erlang VM, so realistically it doesn't have as much exposure in the distributed programming community. + +## Comparison to Communicating Sequential Processes (CSP) + +TODO: where should this live in the chapter? + +You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. I think a lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. I think this view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. # References -- cgit v1.2.3 From c776ce785f3a35059b6da451cec4d6aac943f185 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 01:08:55 -0500 Subject: submit --- chapter/1/gRPC.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 7d9cd34..76a285b 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -80,7 +80,7 @@ One will notice that there is a number associated with each field element in the A 2 48 69 ``` -Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: +Thus the language had to be updated to support gRPC and the development of a service message with a request and a response definition was added for version version 3.0.0 of Protocol Buffers. The updated implementation would look as follows {% cite HelloWorldProto %}: ``` // The request message containing the user's name. @@ -100,7 +100,7 @@ service Greeter { } ```

- Figure 4: Protocol Buffer version 3.0 representing a message data-structure with the accompanied RPC definition. + Figure 4: Protocol Buffer version 3.0.0 representing a message data-structure with the accompanied RPC definition.

Notice the addition of a service, where the RPC call would use one of the messages as the structure of a Request with the other being the Response message format. @@ -171,13 +171,30 @@ The connection can be asynchronous and bi-directionally streaming so that data i The Transport Layer performs the retrieval and placing of binary protocol on the wire. For gRPC-Java has three implementations, though a user can implement their own: Netty, OkHttp, and inProcess. -

3... gRPC Java

+

3.5 gRPC Java

The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. -

3... Downloading gRPC Java

+

3.5.1 Downloading gRPC Java

-The easiest way to download the gRPC-Java implemenation is by performing the following command: +The easiest way to download the gRPC-Java implementation is by performing the following command: + +``` +git clone -b v1.0.0 https://github.com/grpc/grpc-java.git +``` + +Next compile on a Windows machine using Gradle using the following steps - and if you are using any Firewall software it might be necessary to temporarily disable it while compiling gRPC-Java as sockets are used for the tests: + +``` +cd grpc-java +set GRADLE_OPTS=-Xmx2048m +set JAVA_OPTS=-Xmx2048m +set DEFAULT_JVM_OPTS="-Dfile.encoding=utf-8" +echo skipCodegen=true > gradle.properties +gradlew.bat build -x test +cd examples +gradlew.bat installDist +```

3... Hello World Demonstration

-- cgit v1.2.3 From b8eb0b556606b2ef1ad98771382f99e7df6c20b4 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 01:10:03 -0500 Subject: submit --- chapter/1/gRPC.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 76a285b..ecdaa73 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -196,8 +196,15 @@ cd examples gradlew.bat installDist ``` +If you are having issues with Unicode translation of Git on Windows you can try the following commands after entering the `examples` folder: -

3... Hello World Demonstration

+``` +https://raw.githubusercontent.com/benelot/grpc-java/feb88a96a4bc689631baec11abe989a776230b74/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java + +copy RouteGuideServer.java src\main\java\io\grpc\examples\routeguide\RouteGuideServer.java +``` + +

3 Hello World Demonstration

-- cgit v1.2.3 From 255d5413ffdb8aea348b3d143377a51755f6b701 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 01:21:07 -0500 Subject: submit --- chapter/1/figures/hello-world-client.png | Bin 0 -> 30161 bytes chapter/1/figures/hello-world-server.png | Bin 0 -> 13005 bytes chapter/1/gRPC.md | 28 ++++++++++++++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 chapter/1/figures/hello-world-client.png create mode 100644 chapter/1/figures/hello-world-server.png diff --git a/chapter/1/figures/hello-world-client.png b/chapter/1/figures/hello-world-client.png new file mode 100644 index 0000000..c4cf7d4 Binary files /dev/null and b/chapter/1/figures/hello-world-client.png differ diff --git a/chapter/1/figures/hello-world-server.png b/chapter/1/figures/hello-world-server.png new file mode 100644 index 0000000..a51554b Binary files /dev/null and b/chapter/1/figures/hello-world-server.png differ diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index ecdaa73..d39a4f6 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -199,14 +199,38 @@ gradlew.bat installDist If you are having issues with Unicode translation of Git on Windows you can try the following commands after entering the `examples` folder: ``` -https://raw.githubusercontent.com/benelot/grpc-java/feb88a96a4bc689631baec11abe989a776230b74/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java +wget https://raw.githubusercontent.com/benelot/grpc-java/feb88a96a4bc689631baec11abe989a776230b74/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java copy RouteGuideServer.java src\main\java\io\grpc\examples\routeguide\RouteGuideServer.java ``` -

3 Hello World Demonstration

+

3.5.2 Running the Hello World Demonstration

+Make sure you open two Command (Terminal) windows, each within the `grpc-java\examples\build\install\examples\bin` folder. In the first of the two windows type the following command: +``` +hello-world-server.bat +``` + +You should see the following: + +

+
+ Figure 8: The Hello World gRPC Server. +

+ +In the second of the two windows type the following command: + +``` +hello-world-client.bat +``` + +You should see the following response: + +

+
+ Figure 9: The Hello World gRPC Client and the response from the Server. +

## References -- cgit v1.2.3 From 7669eaecd48e61685361666d97edc7fbbdf56970 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 01:22:18 -0500 Subject: submit --- chapter/1/gRPC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index d39a4f6..3bea7e1 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -196,7 +196,7 @@ cd examples gradlew.bat installDist ``` -If you are having issues with Unicode translation of Git on Windows you can try the following commands after entering the `examples` folder: +If you are having issues with Unicode (UTF-8) translation when using Git on Windows, you can try the following commands after entering the `examples` folder: ``` wget https://raw.githubusercontent.com/benelot/grpc-java/feb88a96a4bc689631baec11abe989a776230b74/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java -- cgit v1.2.3 From 5cbc7c29567b3c893e4069f2a6bc0d5ad27b7489 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 01:22:51 -0500 Subject: submit --- chapter/1/gRPC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 3bea7e1..643edaa 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -216,7 +216,7 @@ You should see the following:


- Figure 8: The Hello World gRPC Server. + Figure 8: The Hello World gRPC Server.

In the second of the two windows type the following command: @@ -229,7 +229,7 @@ You should see the following response:


- Figure 9: The Hello World gRPC Client and the response from the Server. + Figure 9: The Hello World gRPC Client and the response from the Server.

## References -- cgit v1.2.3 From 3dfb4ce876319f12bf257b346b3e55eeb0492df3 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Wed, 7 Dec 2016 01:31:56 -0500 Subject: Add monstrous ACM bibtex citations --- _bibliography/message-passing.bib | 256 ++++++++++++++++++++++++++++++++++---- 1 file changed, 230 insertions(+), 26 deletions(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index 416b697..3a83d2a 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -1,26 +1,230 @@ -@inproceedings{Uniqueness, - author = {Philipp Haller and - Martin Odersky}, - title = {Capabilities for Uniqueness and Borrowing}, - booktitle = {ECOOP 2010, Maribor, Slovenia, June 21-25, 2010.}, - pages = {354--378}, - year = {2010}, -} - -@inproceedings{Elsman2005, - author = {Martin Elsman}, - title = {Type-specialized serialization with sharing}, - booktitle = {Trends in Functional Programming}, - year = {2005}, - pages = {47-62}, -} - -@article{Kennedy2004, - author = {Andrew Kennedy}, - title = {Pickler combinators}, - journal = {J. Funct. Program.}, - volume = {14}, - number = {6}, - year = {2004}, - pages = {727-739}, -} \ No newline at end of file +@inproceedings{DeKoster:2016:YAT:3001886.3001890, + author = {De Koster, Joeri and Van Cutsem, Tom and De Meuter, Wolfgang}, + title = {43 Years of Actors: A Taxonomy of Actor Models and Their Key Properties}, + booktitle = {Proceedings of the 6th International Workshop on Programming Based on Actors, Agents, and Decentralized Control}, + series = {AGERE 2016}, + year = {2016}, + isbn = {978-1-4503-4639-9}, + location = {Amsterdam, Netherlands}, + pages = {31--40}, + numpages = {10}, + url = {http://doi.acm.org/10.1145/3001886.3001890}, + doi = {10.1145/3001886.3001890}, + acmid = {3001890}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {Actor Model, Concurrency}, +} + +@article{Yonezawa:1986:OCP:960112.28722, + author = {Yonezawa, Akinori and Briot, Jean-Pierre and Shibayama, Etsuya}, + title = {Object-oriented Concurrent Programming in ABCL/1}, + journal = {SIGPLAN Not.}, + issue_date = {Nov. 1986}, + volume = {21}, + number = {11}, + month = jun, + year = {1986}, + issn = {0362-1340}, + pages = {258--268}, + numpages = {11}, + url = {http://doi.acm.org/10.1145/960112.28722}, + doi = {10.1145/960112.28722}, + acmid = {28722}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@inproceedings{Dedecker:2006:APA:2171327.2171349, + author = {Dedecker, Jessie and Van Cutsem, Tom and Mostinckx, Stijn and D\&\#39;Hondt, Theo and De Meuter, Wolfgang}, + title = {Ambient-Oriented Programming in Ambienttalk}, + booktitle = {Proceedings of the 20th European Conference on Object-Oriented Programming}, + series = {ECOOP'06}, + year = {2006}, + isbn = {3-540-35726-2, 978-3-540-35726-1}, + location = {Nantes, France}, + pages = {230--254}, + numpages = {25}, + url = {http://dx.doi.org/10.1007/11785477_16}, + doi = {10.1007/11785477_16}, + acmid = {2171349}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + +@inproceedings{Cutsem:2007:AOE:1338443.1338745, + author = {Cutsem, Tom Van and Mostinckx, Stijn and Boix, Elisa Gonzalez and Dedecker, Jessie and Meuter, Wolfgang De}, + title = {AmbientTalk: Object-oriented Event-driven Programming in Mobile Ad Hoc Networks}, + booktitle = {Proceedings of the XXVI International Conference of the Chilean Society of Computer Science}, + series = {SCCC '07}, + year = {2007}, + isbn = {0-7695-3017-6}, + pages = {3--12}, + numpages = {10}, + url = {http://dx.doi.org/10.1109/SCCC.2007.4}, + doi = {10.1109/SCCC.2007.4}, + acmid = {1338745}, + publisher = {IEEE Computer Society}, + address = {Washington, DC, USA}, +} + +@book{ + author = {Hugh McKee}, + title = {Designing Reactive Systems: The Role of Actors in Distributed Architecture}, + year = {2016}, +} + +@inproceedings{Miller:2005:CSP:1986262.1986274, + author = {Miller, Mark S. and Tribble, E. Dean and Shapiro, Jonathan}, + title = {Concurrency Among Strangers: Programming in E As Plan Coordination}, + booktitle = {Proceedings of the 1st International Conference on Trustworthy Global Computing}, + series = {TGC'05}, + year = {2005}, + isbn = {3-540-30007-4, 978-3-540-30007-6}, + location = {Edinburgh, UK}, + pages = {195--229}, + numpages = {35}, + url = {http://dl.acm.org/citation.cfm?id=1986262.1986274}, + acmid = {1986274}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + +@article{Agha:1990:COP:83880.84528, + author = {Agha, Gul}, + title = {Concurrent Object-oriented Programming}, + journal = {Commun. ACM}, + issue_date = {Sept. 1990}, + volume = {33}, + number = {9}, + month = sep, + year = {1990}, + issn = {0001-0782}, + pages = {125--141}, + numpages = {17}, + url = {http://doi.acm.org/10.1145/83880.84528}, + doi = {10.1145/83880.84528}, + acmid = {84528}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@article{Armstrong:2010:ERL:1810891.1810910, + author = {Armstrong, Joe}, + title = {Erlang}, + journal = {Commun. ACM}, + issue_date = {September 2010}, + volume = {53}, + number = {9}, + month = sep, + year = {2010}, + issn = {0001-0782}, + pages = {68--75}, + numpages = {8}, + url = {http://doi.acm.org/10.1145/1810891.1810910}, + doi = {10.1145/1810891.1810910}, + acmid = {1810910}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@inproceedings{Haller:2012:IAM:2414639.2414641, + author = {Haller, Philipp}, + title = {On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective}, + booktitle = {Proceedings of the 2Nd Edition on Programming Systems, Languages and Applications Based on Actors, Agents, and Decentralized Control Abstractions}, + series = {AGERE! 2012}, + year = {2012}, + isbn = {978-1-4503-1630-9}, + location = {Tucson, Arizona, USA}, + pages = {1--6}, + numpages = {6}, + url = {http://doi.acm.org/10.1145/2414639.2414641}, + doi = {10.1145/2414639.2414641}, + acmid = {2414641}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {actors, concurrent programming, distributed programming, scala, threads}, +} + +@inproceedings{Hewitt:1973:UMA:1624775.1624804, + author = {Hewitt, Carl and Bishop, Peter and Steiger, Richard}, + title = {A Universal Modular ACTOR Formalism for Artificial Intelligence}, + booktitle = {Proceedings of the 3rd International Joint Conference on Artificial Intelligence}, + series = {IJCAI'73}, + year = {1973}, + location = {Stanford, USA}, + pages = {235--245}, + numpages = {11}, + url = {http://dl.acm.org/citation.cfm?id=1624775.1624804}, + acmid = {1624804}, + publisher = {Morgan Kaufmann Publishers Inc.}, + address = {San Francisco, CA, USA}, +} + +@article {vantcutsem14ambienttalk, + title = {AmbientTalk: programming responsive mobile peer-to-peer applications with actors}, + journal = {Computer Languages, Systems and Structures, SCI Impact factor in 2013: 0.296, 5 year impact factor 0.329 (to appear)}, + year = {2014}, + publisher = {Elsevier}, + issn = {1477-8424}, + author = {Tom Van Cutsem and Elisa Gonzalez Boix and Christophe Scholliers and Andoni Lombide Carreton and Dries Harnie and Kevin Pinte and Wolfgang De Meuter}, + editor = {Nick Benton} +} + +@inproceedings{Bykov:2011:OCC:2038916.2038932, + author = {Bykov, Sergey and Geller, Alan and Kliot, Gabriel and Larus, James R. and Pandya, Ravi and Thelin, Jorgen}, + title = {Orleans: Cloud Computing for Everyone}, + booktitle = {Proceedings of the 2Nd ACM Symposium on Cloud Computing}, + series = {SOCC '11}, + year = {2011}, + isbn = {978-1-4503-0976-9}, + location = {Cascais, Portugal}, + pages = {16:1--16:14}, + articleno = {16}, + numpages = {14}, + url = {http://doi.acm.org/10.1145/2038916.2038932}, + doi = {10.1145/2038916.2038932}, + acmid = {2038932}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {cloud computing, distributed actors, programming models}, +} + +@article{Tomlinson:1988:ROC:67387.67410, + author = {Tomlinson, C. and Kim, W. and Scheevel, M. and Singh, V. and Will, B. and Agha, G.}, + title = {Rosette: An Object-oriented Concurrent Systems Architecture}, + journal = {SIGPLAN Not.}, + issue_date = {April 1989}, + volume = {24}, + number = {4}, + month = sep, + year = {1988}, + issn = {0362-1340}, + pages = {91--93}, + numpages = {3}, + url = {http://doi.acm.org/10.1145/67387.67410}, + doi = {10.1145/67387.67410}, + acmid = {67410}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@article{Haller:2009:SAU:1496391.1496422, + author = {Haller, Philipp and Odersky, Martin}, + title = {Scala Actors: Unifying Thread-based and Event-based Programming}, + journal = {Theor. Comput. Sci.}, + issue_date = {February, 2009}, + volume = {410}, + number = {2-3}, + month = feb, + year = {2009}, + issn = {0304-3975}, + pages = {202--220}, + numpages = {19}, + url = {http://dx.doi.org/10.1016/j.tcs.2008.09.019}, + doi = {10.1016/j.tcs.2008.09.019}, + acmid = {1496422}, + publisher = {Elsevier Science Publishers Ltd.}, + address = {Essex, UK}, + keywords = {Actors, Concurrent programming, Events, Threads}, +} -- cgit v1.2.3 From 797f9b6f86e732c4f1ce8abd45c78f43182a3995 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Wed, 7 Dec 2016 01:34:37 -0500 Subject: Add cite-key --- _bibliography/message-passing.bib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index 3a83d2a..2700085 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -68,7 +68,7 @@ address = {Washington, DC, USA}, } -@book{ +@book{ReactiveSystems, author = {Hugh McKee}, title = {Designing Reactive Systems: The Role of Actors in Distributed Architecture}, year = {2016}, -- cgit v1.2.3 From be218ecc9ca79c011a3dc266a7831a8aca513bab Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Wed, 7 Dec 2016 01:49:20 -0500 Subject: Update references TODO --- chapter/3/message-passing.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index a32a105..3f35ad8 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -255,9 +255,8 @@ TODO: where should this live in the chapter? You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. I think a lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. I think this view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. - # References -TODO: fill these out +TODO: Add non-journal references {% bibliography --file message-passing %} -- cgit v1.2.3 From 63e77966fd35ce0cbc88a70615069122ba33a1d9 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 16:06:48 -0500 Subject: submit --- .../1/figures/grpc-client-transport-handler.png | Bin 0 -> 67834 bytes .../1/figures/grpc-server-transport-handler.png | Bin 0 -> 60913 bytes chapter/1/gRPC.md | 31 +++++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 chapter/1/figures/grpc-client-transport-handler.png create mode 100644 chapter/1/figures/grpc-server-transport-handler.png diff --git a/chapter/1/figures/grpc-client-transport-handler.png b/chapter/1/figures/grpc-client-transport-handler.png new file mode 100644 index 0000000..edd5236 Binary files /dev/null and b/chapter/1/figures/grpc-client-transport-handler.png differ diff --git a/chapter/1/figures/grpc-server-transport-handler.png b/chapter/1/figures/grpc-server-transport-handler.png new file mode 100644 index 0000000..fe895c0 Binary files /dev/null and b/chapter/1/figures/grpc-server-transport-handler.png differ diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 643edaa..953cfdd 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -175,6 +175,31 @@ The Transport Layer performs the retrieval and placing of binary protoc The Java implementation of gRPC been built with Mobile platform in mind and to provide that capability it requires JDK 6.0 to be supported. Though the core of gRPC is built with data centers in mind - specifically to support C/C++ for the Linux platform - the Java and Go implementations are two very reliable platform to experiment the microservice ecosystem implementations. +There are several moving parts to understanding how gRPC-Java works. The first important step is to ensure that the Client and Server stub inferface code get generated by the Protobuf plugin compiler. This is usually placed in your Gradle build file called `build.gradle` as follows: + +``` + compile 'io.grpc:grpc-netty:1.0.1' + compile 'io.grpc:grpc-protobuf:1.0.1' + compile 'io.grpc:grpc-stub:1.0.1' +``` + +When you build using Gradle, then the appropriate base code gets generated for you, which you can override to build your preferred implementation of the Client and Server. + +Since one has to implement the HTTP/2 protocol, the chosen method was to have a Metadata class that will convert the key-value pairs into HTTP/2 Headers and vice-versa for the Netty implementation via GrpcHttp2HeadersDecoder and GrpcHttp2OutboundHeaders. + +Another key insight is to understand that the code that handles the HTTP/2 conversion for the Client and the Server are being done via the NettyClientHandler.java and NettyServerHandler.java classes shown in Figures 8 and 9. + +

+
+ Figure 8: The Client Tranport Handler for gRPC-Java. +

+ +

+
+ Figure 9: The Server Tranport Handler for gRPC-Java. +

+ +

3.5.1 Downloading gRPC Java

The easiest way to download the gRPC-Java implementation is by performing the following command: @@ -183,7 +208,7 @@ The easiest way to download the gRPC-Java implementation is by performing the fo git clone -b v1.0.0 https://github.com/grpc/grpc-java.git ``` -Next compile on a Windows machine using Gradle using the following steps - and if you are using any Firewall software it might be necessary to temporarily disable it while compiling gRPC-Java as sockets are used for the tests: +Next compile on a Windows machine using Gradle (or Maven) using the following steps - and if you are using any Firewall software it might be necessary to temporarily disable it while compiling gRPC-Java as sockets are used for the tests: ``` cd grpc-java @@ -216,7 +241,7 @@ You should see the following:


- Figure 8: The Hello World gRPC Server. + Figure 10: The Hello World gRPC Server.

In the second of the two windows type the following command: @@ -229,7 +254,7 @@ You should see the following response:


- Figure 9: The Hello World gRPC Client and the response from the Server. + Figure 10: The Hello World gRPC Client and the response from the Server.

## References -- cgit v1.2.3 From 9f7e80b3051e1eedcc8342cf1d497dec1de495ec Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Wed, 7 Dec 2016 16:18:01 -0500 Subject: Broad revisions --- _bibliography/message-passing.bib | 23 +++++++ chapter/3/message-passing.md | 128 +++++++++++++++++--------------------- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index 2700085..acb92a4 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -228,3 +228,26 @@ address = {Essex, UK}, keywords = {Actors, Concurrent programming, Events, Threads}, } + +@inproceedings{epstein2011, + acmid = {2034690}, + added-at = {2014-12-10T16:12:02.000+0100}, + address = {New York, NY, USA}, + author = {Epstein, Jeff and Black, Andrew P. and Peyton-Jones, Simon}, + biburl = {http://www.bibsonomy.org/bibtex/24882f140b6bbca2806c57a22c57b5c5d/chesteve}, + booktitle = {Proceedings of the 4th ACM symposium on Haskell}, + doi = {10.1145/2034675.2034690}, + interhash = {48386ecc60d5772410dec97e267a570b}, + intrahash = {4882f140b6bbca2806c57a22c57b5c5d}, + isbn = {978-1-4503-0860-1}, + keywords = {imported}, + location = {Tokyo, Japan}, + numpages = {12}, + pages = {118--129}, + publisher = {ACM}, + series = {Haskell '11}, + timestamp = {2014-12-10T16:12:02.000+0100}, + title = {Towards Haskell in the cloud}, + xxxurl = {http://doi.acm.org/10.1145/2034675.2034690}, + year = 2011 +} diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 3f35ad8..ee489ff 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -6,58 +6,63 @@ by: "Nathaniel Dempkowski" # Introduction -In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. Message passing programming models have strong roots in computer science, and have essentially been discussed since the advent of object-oriented programming with Smalltalk in the 1980's. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, including those focused on the actor model of programming and organizing units of computation. +Message passing programming models have essentially been discussed since the beginning of distributed computing and as a result message passing can be taken to mean a lot of things. If you look up a broad definition on Wikipedia, it includes things like RPC, CSP, and MPI. In practice when people talk about message passing today they mostly mean the actor model. -Message passing programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. Orleans gives an example of this, detailing not just a programming model, but a runtime system that is a quite advanced implementation of a message passing and actor model to solve real world problems. +In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, especially those focused on the actor model of programming and organizing units of computation. -The important question to ask about these sources is “Why message passing?” There are a number of distributed programming models, so why was this one so important when it was initially proposed. What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? +In this chapter I describe four different actor models: classic actors, process-based actors, communicating event-loops, and active objects. I attempt to highlight historic and modern languages that exemplify these models, as well as the philosophies and tradeoffs that programmers need to be aware of to understand these models. + +Actor programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. There are a few robust industrial-strength actor systems that are being used to power massive scalable distributed systems. There are a couple of different approaches to building actor frameworks that are detailed later in the chapter. + +I think an important framing for the models and sources presented is “Why message passing, and specifically why the actor model?” There are a number of distributed programming models, so why was this one so important when it was initially proposed? What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? The broad advantages of the actor model are around isolation of state, scalability, and simplifying the programmer's ability to reason about their system. # Original proposal of the actor model -The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel communication while safely exploiting distributed concurrency across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. +The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel computation in communication in a way that could be safely distributed concurrently across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. + +They define actors as independent units of computation with isolated state. These units can send messages to one another, and have a mailbox which contains messages they have received. These messages are of the form: -They define actors as units of computation. These units can send messages to one another, and have a mailbox which contains messages they have received. These messages are of the form `(request: ; reply-to: )`. +``` +(request: + reply-to: ) +``` -Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a continuation, where the continuation is another message to an actor. These messages are one-way and make no claims about whether a message will ever be received in response. This model is limited, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. +Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a type of continuation, where the continuation is the message to another actor. These messages are one-way and make no claims about whether a message will ever be received in response. This model is limited compared to many of the others, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. One interesting thing to note is that this original paper talks about actors in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept reminds me of something like a Lisp machine, but built to specially utilize the actor model of computation for artificial intelligence. # Classic actor model -The classic actor model came about with the formalization of an actor as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor is formalized as the following primitive actions: +The classic actor model was formalized as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor expands on the original proposal of actors, keeping the ideas of asynchronous communication through messages between isolated units of computation and state. The classic actor is formalized as the following primitive operations: * `create`: create an actor from a behavior description and a set of parameters, including other existing actors * `send`: send a message to another actor * `become`: have an actor replace their behavior with a new one -As originally described, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are unique addressable, and have their own independent message queues. State changes using the classic actor model are specified using the `become` operation. Each time an actor processes a communication it computes a behavior in response to the next type of communication it expects to process. A `become` operation's argument is another named behavior with some state to pass to that named behavior. - -For purely functional actors the new behavior would be identical to the original. For more complex actors however, this enables the aggregation of state changes at a higher level of granularity than something like a variable assignment. This isolation changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. - -TODO: Not sure where this quote fits in? Maybe worth just pull-quoting it or taking it out as most of the points this hits on are better explained above. +As originally described, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are uniquely addressable, and have their own independent mailboxes or message queues. State changes using the classic actor model are specified and aggregated using the `become` operation. Each time an actor processes a communication it computes a behavior in response to the next type of communication it expects to process. A `become` operation's argument is another named behavior with some state to pass to that named behavior. -"The sequential subset of actor systems that implement this model is typically functional. Changes to the state of an actor are aggregated in a single become statement. Actors have a flexible interface that can similarly be changed by switching the behaviour of that actor." (43 Years of Actors) +For purely functional actors the new behavior would be identical to the original. For more complex actors however, this enables the aggregation of state changes at a higher level of granularity than something like a variable assignment. This enables flexibility in the behavior of an actor over time in response to the actions of other actors in the system. Additionally, this isolation changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. -If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. The focus is on the messaging and designing the interactions and communications between the objects. +If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. Kay's ideas sound closer to what we consider the actor model today, and less like what we consider object-oriented programming. The focus is on designing the messaging and communications that dictate how objects interact. -TODO: write more here +TODO: transition ## Concurrent Object-Oriented Programming (1990) -This is a seminal paper for the classic actor model, as it offers classic actors as a natural solution to solving problems at the intersection of two trends of computing: increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. +This is the seminal paper for the classic actor model, as it offers classic actors as a natural solution to solving problems at the intersection of two trends in computing: increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. -This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to actually identify some of the programmer-centric advantages of actors. The author claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." (Agha, September, 1990) Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. “It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.” (Agha, September, 1990) This flexibility turns out to be a highly discussed concern that many of the later papers make a point to mention. +This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to actually identify some of the programmer-centric advantages of actors. One of the core languages used for examples in the paper is Rosette, but the paper largely focuses on the potential and benefits of the model. Agha claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. "It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs." This flexibility turns out to be a highly discussed advantage which continues to be touted in modern actor systems. ## Rosette -Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, and the code examples given in the paper are written in Rosette. It is important to mention as it seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. +Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, and the code examples given in the paper are written in Rosette. Agha is even an author on the Rosette paper, so its clear that Rosette is foundational to the classic actor model. It seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. -The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means of control over how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: +The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means to control how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined, but rather depends on some heuristic to return results. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: * _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. * _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. -The Rosette language features, many of which we take for granted in object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. I think the more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the machine makes it difficult to re-map a program to another one. I think this idea of using actors as a more flexible and natural abstraction is an important one which is seen in some form within many of the actor systems described here. +The Rosette language has a number of object-oriented features, many of which we take for granted in modern object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. I think the more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. In Rosette, the concurrency is an inherent property of the program structure and resource allocation. This is different from a language like Java, where all of the concurrency is very explicit. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers and architectures have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the specific machine makes it difficult to re-map a program to another one. I think this idea of using actors as a more flexible and natural abstraction over concurrency and distribution of resources is an important one which is seen in some form within many actor systems. Actors in Rosette are organized into three types of classes which describe different aspects of the actors within the system: @@ -69,95 +74,80 @@ These classes represent a concrete object-oriented abstraction to organize actor ## Akka -Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It makes an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. +Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching patter to handle the message. The message will simply be discarded an an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. -The Akka runtime also provides advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. +The Akka runtime also provides performance advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. -Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. +Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. More detail about this is given when describing the production usage of actors. # Process-based actors -TODO: better opening sentence +The process-based actor model is essentially an actor modeled as a process that runs from start to completion. This view is broadly similar to the classic actor, but different mechanics exist around managing the lifecycle and behaviors of actors between the models. The first language to explicitly implement this model is Erlang, and they even say in a retrospective that their view of computation is broadly similar to the Agha's classic actor model. -The process-based actor model is essentially an actor modeled as a process that runs from start to completion. +Process-based actors are defined as a computation which runs from start to completion, rather than the classic actor model, which defines an actor almost as a state machine of behaviors and the logic to transition between those. Similar state-machine like behavior transitions are possible through recursion with process-based actors, but programming them feels fundamentally different than using the previously described `become` statement. -The first language to explicitly implement this model is Erlang, and they even say in a retrospective that their view of computation is broadly similar to the Agha's classic actor model. However, with process-based actors different mechanics are used. - -Process-based actors are defined by a computation which runs from start to completion, rather than the classic actor model, which defines an actor almost as a state machine of behaviors and the logic to transition between those. Similar state-machine like expressions are possible through recursion, but programming those feels fundamentally different than using the previously described `become` statement. - -These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. - -Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. +These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. `receive` statements have some notion of defining acceptable messages, usually based on patterns, conditionals or types. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. ## Erlang -Erlang was the primary driver of the process-based actor model. This model was originally developed to program large highly-reliable fault-tolerant telecommunications switching systems. Erlang's development started in 1985, but its model of programming is still used today. The motivations of the Erlang model were around four key properties that were needed to program fault-tolerant operations: +Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. Erlang was the origin of the process-based actor model. The Ericsson company originally developed this model to program large highly-reliable fault-tolerant telecommunications switching systems. Erlang's development started in 1985, but its model of programming is still used today. The motivations of the Erlang model were around four key properties that were needed to program fault-tolerant operations: * Isolated processes * Pure message passing between processes * Detection of errors in remote processes * The ability to determine what type of error caused a process crash -The Erlang researchers initially believed that shared-memory was preventing fault-tolerance and they saw message-passing of immutable data between processes as the solution to avoiding shared-memory. This model was essentially developed independently from other actor systems and research, especially as its development was started before Agha's classic actor model formalization was even published, but it ends up with a broadly similar view of computation to Agha's classic actor model. +The Erlang researchers initially believed that shared-memory was preventing fault-tolerance and they saw message-passing of immutable data between processes as the solution to avoiding shared-memory. There was a concern that passing around and copying data would be costly, but the Erlang developers saw fault-tolerance as a more important concern than performance. This model was essentially developed independently from other actor systems and research, especially as its development was started before Agha's classic actor model formalization was even published, but it ends up with a broadly similar view of computation to Agha's classic actor model. Erlang actors run as lightweight isolated processes. They do not have visibility into one another, and pass around pure messages, which are immutable. These have no dangling pointers or data references between objects, and really enforce the idea of immutable separated data between actors unlike many of the early classic actor implementations in which references to actors and data can be passed around freely. - -TODO: is it really worth mentioning `receive` again here? I think its assumed that the `receive` semantics above apply here? - -Erlang implements a blocking `receive` operation as a means of processing messages from a processes' mailbox. +Erlang implements a blocking `receive` operation as a means of processing messages from a processes' mailbox. They use value matching on message tuples as a means of describing the types of messages a given actor can accept. Erlang also seeks to build failure into the programming model, as one of the core assumptions of a distributed system is that things are going to fail. Erlang provides the ability for processes to monitor one another through two primitives: * `monitor`: one-way unobtrusive notification of process failure/shutdown * `link`: two-way notification of process failure/shutdown allowing for coordinated termination -These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. (Example of Erlang reliability or something would be good here) +These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. Erlang's philosophies used to build a reliable fault-tolerant telephone exchange seem to be broadly applicable to the fault-tolerance problems of distributed systems. -## Cloud Haskell +## Scala Actors -Cloud Haskell is an extension/DSL of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. Cloud Haskell processes can use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Monadic types make it possible for programmers to use an effective style, where they can ensure that pure and effective code are not mixed. Additionally, Cloud Haskell has shared memory within an actor process, which is useful for certain applications, but forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. +Scala Actors is an example of taking and enhancing the Erlang model while bringing it to a new platform. Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors claim that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” -## Scala Actors +The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. In addition to the more natural abstraction, the Erlang model was further enhanced with Scala's type system and advanced pattern-matching capabilities. -Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." The authors say that VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors say that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” +## Cloud Haskell -The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. (I feel like there needs to be a better concluding point to this) +Cloud Haskell is an extension or domain specific language of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. This alone isn't so different from Erlang, which operates on immutable data in the context of isolated memory. -In addition to the more natural abstraction, the Erlang model is further enhanced with Scala's type system and advanced pattern-matching capabilities. +One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. In Erlang, all you have is dynamic pattern matching based on values patterns, and the hope that the wrong types of message don't get passed around your system. Cloud Haskell processes can also use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Haskell's monadic types make it possible for programmers to use a programming style, where they can ensure that pure and effective code are not mixed. This makes reasoning about where side-effects happen in your system easier. Cloud Haskell has shared memory within an actor process, which is useful for certain applications. This might sound like it could cause problems, but shared-memory structures are forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. This means that as long as a function and its environment are serializable, they can be spun off as a remote computation and seamlessly continued elsewhere. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. Cloud Haskell is currently supported and also has developed the Cloud Haskell Platform, which aims to provide common functionality needed to build and manage a production actor system using Cloud Haskell. # Communicating event-loops -The communicating event-loop model was introduced in the E language, and is similar to process actors, but doesn't make a distinction between passive and active objects. - -TODO: what does that sentence really mean? need a better introduction to this model. Could add more about AmbientTalk in the intro? If this is too expanded its going to be repeating the same idea of accessing objects within actors 3 times though. +The communicating event-loop model was introduced in the E language, and is one that aims to change the level of granularity at which communication happens within an actor-based system. The previously described actor systems organize communication at the actor level, while the communicating event model puts communication between actors in the context of actions on objects within those actors. The overall messages still reference higher-level actors, but those messages refer to more granular actions within an actor's state. ## E Language -The E language implements a model that is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. This vat contains not just objects, but a mailbox for all of the objects inside, as well as a call stack. There is a shared message queue and event-loop that acts as one abstraction barrier for computation. The actual references to objects within a vat which are used for communication and computation across actors operate at a different level of abstraction. - -When handing out references at a different level of granularity than actor-global, how do you ensure the benefits of isolation that the actor model provides? After all, by handing out references inside of an actor it sounds like we're just reinventing shared-memory problems. The answer is that E's reference-states define many of the isolation guarantees around computation that we expect from actors. Two different reference-states are defined: +The E language implements a model which is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. This vat contains not just objects, but a mailbox for all of the objects inside, as well as a call stack for methods on those objects. There is a shared message queue and event-loop that acts as one abstraction barrier for computation across actors. The actual references to objects within a vat are used for addressing communication and computation across actors and operate at a different level of abstraction. -* _Near reference_: This is a reference between two objects in the same vat. These expose both immediate-calls and eventual-sends. -* _Eventual reference_: This is a reference which crosses vat boundaries, and only exposes eventual-sends, not immediate-calls. +When handing out references at a different level of granularity than actor-global, how do you ensure the benefits of isolation that the actor model provides? After all, by referencing objects inside of an actor from many places it sounds like we're just reinventing shared-memory problems. The answer is that E's reference-states define many of the isolation guarantees around computation that we expect from actors. Two different ways to reference objects are defined: -The difference in semantics between the two types of references means that only objects within the same vat are granted synchronous access to one another. The most an eventual reference can do is send and queue a message for processing at some unspecified point in the future. This means that within the execution of a vat, a degree of temporal isolation can be defined between the objects and communications within the vat, and the communications to and from other vats. +* _Near reference_: This is a reference only possible between two objects in the same vat. These expose both synchronous immediate-calls and asynchronous eventual-sends. +* _Eventual reference_: This is a reference which crosses vat boundaries, and only exposes asynchronous eventual-sends, not synchronous immediate-calls. -TODO: better transition sentence from reference types -> why we care about references at a less abstract level than the actor. +The difference in semantics between the two types of references means that only objects within the same vat are granted synchronous access to one another. The most an eventual reference can do is asynchronously send and queue a message for processing at some unspecified point in the future. This means that within the execution of a vat, a degree of temporal isolation can be defined between the objects and communications within the vat, and the communications to and from other vats. -Additionally, it some of motivation here comes from wanting to work at a finer-grained level of references than a traditional actor exposes. +The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. -The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. - -TODO: write more here, maybe something around promise pipelining and partial failure? implications of different types of communication? +TODO: write more here, maybe something around promise pipelining and partial failure? implications of different types of communication? maybe mention some of the points that inspire some aspects of modern actors? ## AmbientTalk/2 -AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between passive objects. +AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between and movement of passive objects. -Actors in AmbientTalk/2 are representations of an event loops. The message queue is the event queue, messages are events, asynchronous message sends are event notifications, and object methods are the event handlers. The event loop serially processes messages from the queue to avoid race conditions. Local objects within an actor are owned by that actor, which is the only entity allowed to directly execute methods on them. Like E, objects within an actor can communicate using synchronous or asynchronous methods of communication. Again similar to E, objects that are referenced outside of an actor can only be communicated to asynchronously by sending messages. Objects can additionally declare themselves serializable, which means they can be copied and sent to other actors for use as local objects. When this happens, there is no maintained relationship between the original object and its copy. +Actors in AmbientTalk/2 are representations of event loops. The message queue is the event queue, messages are events, asynchronous message sends are event notifications, and object methods are the event handlers. The event loop serially processes messages from the queue to avoid race conditions. Local objects within an actor are owned by that actor, which is the only entity allowed to directly execute methods on them. Like E, objects within an actor can communicate using synchronous or asynchronous methods of communication. Again similar to E, objects that are referenced outside of an actor can only be communicated to asynchronously by sending messages. Objects can additionally declare themselves serializable, which means they can be copied and sent to other actors for use as local objects. When this happens, there is no maintained relationship between the original object and its copy. AmbientTalk/2 uses the event loop model to enforce three essential concurrency control properties: @@ -167,11 +157,11 @@ AmbientTalk/2 uses the event loop model to enforce three essential concurrency c The end result of all this decoupling and isolation of computation is that it is a natural fit for mobile ad hoc networks. In this domain, connections are volatile with limited range and transient failures. Removing coupling based on time or synchronization is a natural fit for the domain, and the communicating event-loop actor model is a natural model for programming these systems. AmbientTalk/2 provides additional features on top of the communicating event-loop model like service discovery. These enable ad hoc network creation as actors near each other can broadcast their existence and advertise common services that can be used for communication. -AmbientTalk/2 is most notable as a reimagining of the communicating event-loops actor model for a modern use case. +AmbientTalk/2 is most notable as a reimagining of the communicating event-loops actor model for a modern use case. This again speaks to the broader advantages of actors and their applicability to solving the problems of distributed systems. # Active Objects -Active object actors draw a distinction between two different types of objects: active and passive objects. Every active object has a single entry point defining a fixed set of messages that are understood. Passive objects are the objects that are actually sent between actors, and are copied around to guarantee isolation. +Active object actors draw a distinction between two different types of objects: active and passive objects. Every active object has a single entry point defining a fixed set of messages that are understood. Passive objects are the objects that are actually sent between actors, and are copied around to guarantee isolation. This enables a separation of concerns between data that relates to actor communication and data that relates to actor state and behavior. The active object model as initially described in the ABCL/1 language defines objects with a state and three modes: @@ -181,7 +171,7 @@ The active object model as initially described in the ABCL/1 language defines ob ## ABCL/1 Language -The ABCL/1 language implements the active object model described above, representing a system as a collection of objects, and the interactions between those objects as concurrent messages being passed around. One interesting aspect of ABCL/1 is the idea of explicitly different modes of message passing. Other actor models generally have a notion of priority around the values, types, or patterns of messages they process, but ABCL/1 implements tow different modes of message passing with different semantics. They have standard queued messages in the `ordinary` mode, but more interestingly they have `express` priority messages. When an object receives an express message it halts any other processing of ordinary messages it is performing, and processes the `express` message immediately. This enables an actor to accept high-priority messages while in `active` mode, and also enables monitoring and interrupting actors. +The ABCL/1 language implements the active object model described above, representing a system as a collection of objects, and the interactions between those objects as concurrent messages being passed around. One interesting aspect of ABCL/1 is the idea of explicitly different modes of message passing. Other actor models generally have a notion of priority around the values, types, or patterns of messages they process, usually defined by the ordering of their receive operation, but ABCL/1 implements two different modes of message passing with different semantics. They have standard queued messages in the `ordinary` mode, but more interestingly they have `express` priority messages. When an object receives an express message it halts any other processing of ordinary messages it is performing, and processes the `express` message immediately. This enables an actor to accept high-priority messages while in `active` mode, and also enables monitoring and interrupting actors. The language also offers different models of synchronization around message-passing between actors. Three different message-passing models are given that enable different use cases: @@ -191,15 +181,13 @@ The language also offers different models of synchronization around message-pass It is interesting to note that all of these modes can be expressed by the `past` style of message-passing, as long as the type of the message and which actor to reply to with results are known. -TODO: there should be something here to wrap up ABCL/1, and its impact? +The key difference here is around how this different style of actors relates to managing their lifecycle. In the active object style, lifecycle is a result of messages or requests to actors, but in other styles we see a more explicit notion of lifecycle and creating/destroying actors. ## Orleans -Orleans takes the concept of lifecycle-less (not sure this is the term I want to use) actors, which are activated in response to asynchronous messages and places them in the context of cloud applications. Orleans does this via actors (called "grains") which are isolated units of computation and behavior that can have multiple instantiations (called "activations") for scalability. These actors also have persistence, meaning they have a persistent state that is kept in durable storage so that it can be used to manage things like user data. - -TODO: something about the notion of identity of an actor here. There are words below, but they could flow better into other points. +Orleans takes the concept of actors whose lifecycle is dependent on messaging or requests and places them in the context of cloud applications. Orleans does this via actors (called "grains") which are isolated units of computation and behavior that can have multiple instantiations (called "activations") for scalability. These actors also have persistence, meaning they have a persistent state that is kept in durable storage so that it can be used to manage things like user data. -It feels like Orleans uses a different notion of identity than other actor systems. In other systems an "actor" might refer to a behavior and instances of that actor might refer to identities that the actor represents like individual users. In Orleans, an actor represents that persistent identity, and the actual instantiations are in fact reconcilable copies of that identity. +Orleans uses a different notion of identity than other actor systems. In other systems an "actor" might refer to a behavior and instances of that actor might refer to identities that the actor represents like individual users. In Orleans, an actor represents that persistent identity, and the actual instantiations are in fact reconcilable copies of that identity. The programmer essentially assumes that a single entity is handling requests to an actor, but the Orleans runtime actually allows for multiple instantiations for scalability. These instantiations are invoked in response to an RPC-like call from the programmer which immediately returns an asynchronous promise. Multiple instances of an actor can be running and modifying the state of that actor at the same time. The immediate question here is how does that actually work? It doesn't intuitively seem like transparently accessing and changing multiple isolated copies of the same state should produce anything but problems when its time to do something with that state. @@ -207,7 +195,7 @@ Orleans solves this problem by providing mechanisms to reconcile conflicting cha Transactions in Orleans are a way to causally reason about the different instances of actors that are involved in a computation. Because in this model computation happens in response to a single outside request, a given actor's chain of computation via. associated actors always contains a single instantiation of each actor. These causal chain of instantiations is treated as a single transaction. At reconciliation time Orleans uses these transactions, along with current instantiation state to reconcile to a consistent state. -All of this is a longwinded way of saying that Orleans' programmer-centric contributions are that it separates the concerns of running and managing actor lifecycles from the concerns of how data flows throughout your distributed system. It does this is a fault-tolerant way, and for most programming tasks, you likely wouldn't have to worry about scaling and reconciling data in response to requests. It provides many of the benefits of the actor model, through a programming model that attempts to abstract away many of the details that you would have to worry about when using actors in production. +All of this is a longwinded way of saying that Orleans' programmer-centric contributions are that it separates the concerns of running and managing actor lifecycles from the concerns of how data flows throughout your distributed system. It does this is a fault-tolerant way, and for many programming tasks, you likely wouldn't have to worry about scaling and reconciling data in response to requests. It provides the benefits of the actor model through a programming model that attempts to abstract away details that you would otherwise have to worry about when using actors in production. # Why the actor model? -- cgit v1.2.3 From e25b8418bcd2297d0ecc6a350e9d4c11219c6c5c Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 16:28:15 -0500 Subject: submit --- chapter/1/gRPC.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index 953cfdd..e61e432 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -49,6 +49,30 @@ As streams are core to the implementation of HTTP/2, it is important to discuss Figure 2: The lifecycle of a HTTP/2 stream.

+To better understand this diagram, it is important to define some of the terms in it: + +PUSH_PROMISE - This is being performed by one endpoint to alert another that it will be sending some data over the wire. + +RST_STREAM - This makes termination of a stream possible. + +PRIORITY - This is sent by an endpoint on the priority of a stream. + +END_STREAM - This flag denotes the end of a DATA frame. + +HEADERS - This frame will open a stream. + +Idle - This is a state that a stream can be in when it is opened by receiving a HEADERS frame. + +Reserved (Local) - To be in this state is means that one has sent a PUSH_PROMISE frame. + +Reserved (Remote) - To be in this state is means that it has been reserved by a remote endpoint. + +Open - To be in this state means that both endpoints can send frames. + +Closed - This is a terminal state. + +Half-Closed (Local) - This means that no frames can be sent except for WINDOW_UPDATE, PRIORITY, and RST_STREAM. +

1.4 Flow Control of Streams

Since many streams will compete for the bandwidth of a connection, in order to prevent bottlenecks and collisions in the transmission. This is done via the WINDOW_UPDATE payload for every stream - and the overall connection as well - to let the sender know how much room the receiving endpoint has for processing new data. -- cgit v1.2.3 From e8b2aa3c2fd421f407aa4ad5979cc33ce18e0b77 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 16:31:11 -0500 Subject: submit --- chapter/1/gRPC.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index e61e432..d645046 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -73,6 +73,8 @@ To better understand this diagram, it is important to define some of the terms i Half-Closed (Local) - This means that no frames can be sent except for WINDOW_UPDATE, PRIORITY, and RST_STREAM. +Half-Closed (Remote) - This means that a frame is not used by the remote endpoint to send frames of data. +

1.4 Flow Control of Streams

Since many streams will compete for the bandwidth of a connection, in order to prevent bottlenecks and collisions in the transmission. This is done via the WINDOW_UPDATE payload for every stream - and the overall connection as well - to let the sender know how much room the receiving endpoint has for processing new data. -- cgit v1.2.3 From aedd469d5ec714759ab0a7cf91c426631288be03 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 16:35:14 -0500 Subject: submit --- chapter/1/gRPC.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index d645046..fceeaa6 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -283,6 +283,10 @@ You should see the following response: Figure 10: The Hello World gRPC Client and the response from the Server.

+

4 Conclusion

+ +This chapter presented an overview of the concepts behing gRPC, HTTP/2 and will be expanded in both breadth and language implementations. The area of microservices one can see how a server endpoint can actually spawn more endpoints where the message content is the protobuf definition for new endpoints to be generated for load-balancing like for the classical Actor Model. + ## References ` `[Apigee]: https://www.youtube.com/watch?v=-2sWDr3Z0Wo -- cgit v1.2.3 From 0e9470119ea56635ea096777f4656f75138b5ed1 Mon Sep 17 00:00:00 2001 From: Paul Grosu Date: Wed, 7 Dec 2016 16:36:41 -0500 Subject: submit --- chapter/1/gRPC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/1/gRPC.md b/chapter/1/gRPC.md index fceeaa6..f6c47b7 100644 --- a/chapter/1/gRPC.md +++ b/chapter/1/gRPC.md @@ -81,7 +81,7 @@ Since many streams will compete for the bandwidth of a connection, in order to p

2 Protocol Buffers with RPC

-Though gRPC was built on top of HTTP/2, an IDL had to be used to perform the communication between endpoints. The natural direction was to use Protocol Buffers is the method of stucturing data for serialization between a server and client. At the time of the start of gRPC development only version 2.0 (proto2) was available, which only implemented data structures without any request/response mechanism. An example of a Protocol Buffer data structure would look something like this: +Though gRPC was built on top of HTTP/2, an IDL had to be used to perform the communication between endpoints. The natural direction was to use Protocol Buffers is the method of stucturing key-value-based data for serialization between a server and client. At the time of the start of gRPC development only version 2.0 (proto2) was available, which only implemented data structures without any request/response mechanism. An example of a Protocol Buffer data structure would look something like this: ``` // A message containing the user's name. @@ -100,7 +100,7 @@ This message will also be encoded for highest compression when sent over the wir Table 1: Tag values for Protocol Buffer types.

-One will notice that there is a number associated with each field element in the Protocol Buffer definition, which represents its tag. In Figure 3, the field `name` has a tag of `1`. When a message gets encoded each field will start with a one byte value (8 bits), where the least-significant 3-bit value encode the type and the rest the tag. In this case tag which is `1`, with a type of 2. Thus the encoding will be `00001 010`, which has a hexdecimal value of `A`. The following byte is the length of the string which is `2`, followed by the string as `48` and `69` representing `H` and `i`. Thus the whole tranmission will look as follows: +One will notice that there is a number associated with each field element in the Protocol Buffer definition, which represents its tag. In Figure 3, the field `name` has a tag of `1`. When a message gets encoded each field (key) will start with a one byte value (8 bits), where the least-significant 3-bit value encode the type and the rest the tag. In this case tag which is `1`, with a type of 2. Thus the encoding will be `00001 010`, which has a hexdecimal value of `A`. The following byte is the length of the string which is `2`, followed by the string as `48` and `69` representing `H` and `i`. Thus the whole tranmission will look as follows: ``` A 2 48 69 -- cgit v1.2.3 From 7b757cb2d86cfa2827f2efadbc0e674ca0cbc6cf Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 9 Dec 2016 02:41:38 -0500 Subject: Muzammil-RPC Chapter Complete(except SunRPC) --- _bibliography/rpc.bib | 221 +++++++++++++++++++++++ chapter/1/rpc.md | 249 ++++++++++++++++---------- resources/img/rpc_chapter_1_ycog_10_steps.png | Bin 0 -> 24905 bytes 3 files changed, 372 insertions(+), 98 deletions(-) create mode 100644 resources/img/rpc_chapter_1_ycog_10_steps.png diff --git a/_bibliography/rpc.bib b/_bibliography/rpc.bib index b0e8932..db77c12 100644 --- a/_bibliography/rpc.bib +++ b/_bibliography/rpc.bib @@ -112,3 +112,224 @@ author={Srinivasan, Raj}, year={1995} } + +@misc{microservices1rpc, + title={Delving Into the Microservices Architecture}, + author={Mueller, John}, + year={2015}, + url = {http://blog.smartbear.com/microservices/delving-into-the-microservices-architecture/}, +} + +@article{rpcorigin, + title={High-level framework for network-based resource sharing}, + author={White, James E}, + year={1975} +} + +@inproceedings{interweave1, + title={Integrating remote invocation and distributed shared state}, + author={Tang, Chunqiang and Chen, DeQing and Dwarkadas, Sandhya and Scott, Michael L}, + booktitle={Parallel and Distributed Processing Symposium, 2004. Proceedings. 18th International}, + pages={30}, + year={2004}, + organization={IEEE} +} + + +@inproceedings{interweave2, + title={Interweave: A middleware system for distributed shared state}, + author={Chen, DeQing and Dwarkadas, Sandhya and Parthasarathy, Srinivasan and Pinheiro, Eduardo and Scott, Michael L}, + booktitle={International Workshop on Languages, Compilers, and Run-Time Systems for Scalable Computers}, + pages={207--220}, + year={2000}, + organization={Springer} +} + +@inproceedings{interweave3, + title={Multi-level shared state for distributed systems}, + author={Chen, DeQing and Tang, Chunqiang and Chen, Xiangchuan and Dwarkadas, Sandhya and Scott, Michael L}, + booktitle={Parallel Processing, 2002. Proceedings. International Conference on}, + pages={131--140}, + year={2002}, + organization={IEEE} +} + +@article{offloading1, + title={A survey of computation offloading for mobile systems}, + author={Kumar, Karthik and Liu, Jibang and Lu, Yung-Hsiang and Bhargava, Bharat}, + journal={Mobile Networks and Applications}, + volume={18}, + number={1}, + pages={129--140}, + year={2013}, + publisher={Springer} +} + +@misc{ibis, + title={Ibis Communication middleware}, + author={Ibis}, + url = {https://www.cs.vu.nl/ibis/rmi.html}, +} + +@article{cuckoo, + title={Cuckoo: flexible compute-intensive task offloading in mobile cloud computing}, + author={Zhou, Zhigang and Zhang, Hongli and Ye, Lin and Du, Xiaojiang}, + journal={Wireless Communications and Mobile Computing}, + year={2016}, + publisher={Wiley Online Library} +} + +@inproceedings{maui, + title={MAUI: making smartphones last longer with code offload}, + author={Cuervo, Eduardo and Balasubramanian, Aruna and Cho, Dae-ki and Wolman, Alec and Saroiu, Stefan and Chandra, Ranveer and Bahl, Paramvir}, + booktitle={Proceedings of the 8th international conference on Mobile systems, applications, and services}, + pages={49--62}, + year={2010}, + organization={ACM} +} + +@article{docker, + title={Docker: lightweight linux containers for consistent development and deployment}, + author={Merkel, Dirk}, + journal={Linux Journal}, + volume={2014}, + number={239}, + pages={2}, + year={2014}, + publisher={Belltown Media} +} + +@inproceedings{selfdest, + title={RFID systems and security and privacy implications}, + author={Sarma, Sanjay E and Weis, Stephen A and Engels, Daniel W}, + booktitle={International Workshop on Cryptographic Hardware and Embedded Systems}, + pages={454--469}, + year={2002}, + organization={Springer} +} + +@misc{orcalenfs, + title={Overview of Secure RPC}, + author={Oracle}, + url = {https://docs.oracle.com/cd/E23823_01/html/816-4557/auth-2.html}, +} + +@misc{capnprotosecure, + title={Is Cap'n Proto Secure?}, + author={Kenton}, + url = {https://capnproto.org/faq.html#is-capn-proto-secure}, +} + + +@article{grid1, + title={High performance GridRPC middleware}, + author={Caniou, Yves and Caron, Eddy and Desprez, Fr{\'e}d{\'e}ric and Nakada, Hidemoto and Tanaka, Yoshio and Seymour, Keith}, + journal={Recent Developments in Grid Technology and Applications, Nova Science Publishers}, + pages={141--181}, + year={2008} +} + +@incollection{gridsolve1, + title={Gridsolve: The evolution of a network enabled solver}, + author={YarKhan, Asim and Dongarra, Jack and Seymour, Keith}, + booktitle={Grid-Based Problem Solving Environments}, + pages={215--224}, + year={2007}, + publisher={Springer} +} + +@article{gridsolve2, + title={Interactive Grid-access using Gridsolve and giggle}, + author={Hardt, M and Seymour, Keith and Dongarra, Jack and Zapf, M and Ruitter, NV}, + journal={Computing and Informatics}, + volume={27}, + number={2}, + pages={233--248}, + year={2012} +} + +@article{ninf, + title={Ninf-G: A reference implementation of RPC-based programming middleware for Grid computing}, + author={Tanaka, Yoshio and Nakada, Hidemoto and Sekiguchi, Satoshi and Suzumura, Toyotaro and Matsuoka, Satoshi}, + journal={Journal of Grid computing}, + volume={1}, + number={1}, + pages={41--51}, + year={2003}, + publisher={Springer} +} + +@article{erlang, + title={Concurrent programming in ERLANG}, + author={Armstrong, Joe and Virding, Robert and Wikstr{\"o}m, Claes and Williams, Mike}, + year={1993}, + publisher={Citeseer} +} + +@misc{Apigee, + title={Is Cap'n Proto Secure?}, + author={Surtani, Manick and Ho, Alan}, + url = {https://www.youtube.com/watch?v=-2sWDr3Z0Wo}, +} + +@misc{CoreSurfaceAPIs, + title={gRPC core APIs}, + author={Google}, + url = {https://github.com/grpc/grpc/tree/master/src/core}, +} + +@misc{gRPCCompanies, + title={About gRPC}, + author={Google}, + url = {http://www.grpc.io/about/}, +} + +@misc{gRPCLanguages, + title={gRPC Documentation}, + author={Google}, + url = {http://www.grpc.io/docs/}, +} + +@misc{gRPCProtos, + title={Google APIs}, + author={Google}, + url = {https://github.com/googleapis/googleapis/}, +} + +@misc{rpcimage, + title={Remote Procedure Call (RPC)}, + author={Taing, Nguonly}, + url = {http://lycog.com/distributed-systems/remote-procedure-call/}, + note = {Image URL: http://lycog.com/wp-content/uploads/2011/03/rpc-10-steps.png} +} + +@misc{trendrpcthrift, + title={Remote Procedure Call (RPC)}, + author={Google Trends}, + url = {https://www.google.com/trends/explore?cat=31&date=today%2012-m&q=apache%20thrift,grpc&hl=en-US} +} + +@misc{grpcauth, + title={GRPC Authentication}, + author={Google}, + url = {http://www.grpc.io/docs/guides/auth.html}, +} + +@misc{rfc707, + title={RFC 707: A high-level framework for network-based resource sharing}, + author={White, James E}, + year={1975}, + publisher={December} +} + +@techreport{rfc674, + title={RFC 674: Procedure call documents: Version 2}, + author={Postel, J and White, JE}, + year={1974} +} + +@article{rfc684, + title={RFC 684: Commentary on procedure calling as a network protocol}, + author={Schantz, Richard}, + year={1975} +} diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index a05022f..05a6452 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -1,6 +1,6 @@ --- layout: page -title: "RPC is Not Dead: Rise, Fall and the Rise of RPC" +title: "RPC is Not Dead: Rise, Fall and the Rise of Remote Procedure Calls" by: "Muzammil Abdul Rehman and Paul Grosu" --- @@ -8,169 +8,222 @@ by: "Muzammil Abdul Rehman and Paul Grosu" *Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build modular, scalable, distributed, language-agnostic ecosystem of applications. This RPC *paradigm* has been part of the driving force in creating truly revolutionizing distributed systems and giving rise to various communication schemes and protocols between diverse systems. -RPC *paradigm* has been implemented in various forms in our every-day systems. From lower level applications like Network File Systems{% cite sunnfs --file rpc %} and Remote Direct Memory Access{% cite rpcoverrdma --file rpc %} to access protocols to developing an ecosystem of microservices, RPC has been used everywhere. Some of the major examples of RPC include SunNFS{% cite sunnfs --file rpc %}, Twitter's Finagle{% cite finalge --file rpc %}, Apache Thrift{% cite thrift --file rpc %}, Java RMI{% cite rmipaper --file rpc %}, SOAP, CORBA{% cite corba --file rpc %}, Google's gRPC{% cite grpc --file rpc %}. +RPC *paradigm* has been implemented in various forms in our every-day systems. From lower level applications like Network File Systems{% cite sunnfs --file rpc %} and Remote Direct Memory Access{% cite rpcoverrdma --file rpc %} to access protocols to developing an ecosystem of microservices, RPC has been used everywhere. Some of the major examples of RPC include SunNFS{% cite sunnfs --file rpc %}, Twitter's Finagle{% cite finagle --file rpc %}, Apache Thrift{% cite thrift --file rpc %}, Java RMI{% cite rmipaper --file rpc %}, SOAP, CORBA{% cite corba --file rpc %}, Google's gRPC{% cite grpc --file rpc %}. -* adds paragraph about rise and fall - -RPC has evolved over the years. Starting off as a synchronous, insecure, request-response system, RPC has evolved into a secure, asynchronous, fault-tolerant, resilient *paradigm* that has influenced protocols and programming designs, like, HTTP, REST, and just about anything with a request-response system. It has transitioned to an asynchronous bidirectional communication for connecting services and devices across the internet. RPC has influenced various design paradigms and communication protocols. +RPC has evolved over the years. Starting off as a synchronous, insecure, request-response system, RPC has evolved into a secure, asynchronous, resilient *paradigm* that has influenced protocols and programming designs, like, HTTP, REST, and just about anything with a request-response system. It has transitioned to an asynchronous bidirectional communication for connecting services and devices across the internet. RPC has influenced various design paradigms and communication protocols. ## Remote Procedure Calls: -* Diagram of RPC: Local and remote endpoints, communication protocol. - *Remote Procedure Call paradigm* can be defined, at a high level, as a set of two language-agnostic communication *endpoints* connected over a network with one endpoint sending a request and the other endpoint generating a response based on that request. In the simplest terms, it's a request-response paradigm where the two *endpoints*/hosts have different *address space*. The host that requests a remote procedure can be referred to as *caller* and the host that responds to this can be referred to as *callee*. -The *endpoints* in the RPC can either be a client and a server, two nodes in a peer-to-peer network, two hosts in a grid computation system, or even two microservices. The RPC communcation is not limited to two hosts, rather could have multiple hosts or *endpoints* involved {% cite anycastrpc --file rpc %}. +The *endpoints* in the RPC can either be a client and a server, two nodes in a peer-to-peer network, two hosts in a grid computation system, or even two microservices. The RPC communication is not limited to two hosts, rather could have multiple hosts or *endpoints* involved {% cite anycastrpc --file rpc %}. + +
+ RPC in 10 Steps. +

Fig1. - Remote Procedure Call{% cite rpcimage --file rpc %}.

+
+ +The simplest RPC implementation looks like Fig1. In this case, the *client*(or *caller*) and the *server*(or *callee*) are separated by a physical network. The main components of the system are the client routine/program, the client stub, the server routine/program, the server stub, and the network routines. The client program can only interact with the client stub that provides the interface of the remote server to the client. This stub also provides marshalling/pickling/serialization of the input arguments sent to the stub by the client routine. Similarly, the server stub provides a client interface to the server routines. Whenever a client routine has to perform a *remote procedure*, it calls the client stub, which serializes the input argument. This serialized data is sent to the server using OS network routines (TCP/IP). The data is serialized by the server stub, present to the server routines for the given arguments. The return value from the server routines is serialized again and sent over the network back to the client where it's deserialized by the client stub and presented to the client routine. This *remote procedure* is generally hidden from the client routine and it appears as a *local procedure* to the client. RPC services also require a discovery service/host-resolution mechanism to bootstrap the communication between the client and the server. + +One important feature of RPC is different *address space* {% cite implementingrpc --file rpc %} for all the endpoints, however, passing the locations to a global storage(Amazon S3, Microsoft Azure, Google Cloud Store) is not impossible. In RPC, all the hosts have separate *address spaces*. They can't share pointers or references to a memory location in one host. This *address space* isolation means that all the information is passed in the messages between the host communicating as a value (objects or variables) but not by reference. Since RPC is a *remote* procedure call, the values sent to the *remote* host cannot be pointers or references to a *local* memory. However, passing links to a global shared memory location is not impossible but rather dependent on the type of system (see *Applications* section for detail). + +Originally, RPC was developed as a synchronous, language-specific marshalling service with a custom network protocol to outsource computation {% cite implementingrpc --file rpc %}. It had registry-system to register all the servers. One of the earliest RPC-based system {% cite implementingrpc --file rpc %} was implemented in the Cedar programming language in early 1980's. The goal of this system was to provide similar programming semantics as local procedure calls. Developed for a LAN network with an inefficient network protocol and a *serialization* scheme to transfer information using the said network protocol, this system aimed at executing a *procedure*(also referred as *method* or a *function*) in a remote *address space*. The single-thread synchronous client and the server were written in an old *Cedar* programming language with a registry system used by the servers to *bind*(or register) their procedures. The clients used this registry system to find a specific server to execute their *remote* procedures. + +Modern RPC-based systems are language-agnostic, asynchronous, load-balanced systems. Authentication and authorization to these systems have been added as needed along with other security features. Most of these systems have fault-handling built into them as modules. + +RPC programs have a network (or a communication channel), therefore, they need to handle remote errors and be able to communication information successfully. Error handling generally varies and is categorized as *remote-host* or *network* failure handling. Depending on the type of the system, and the error, the caller (or the callee) return an error and these errors can be handled accordingly. For asynchronous RPC calls, it's possible to specify events to ensure progress. + +RPC implementations use a *serialization*(also referred to as *marshalling* or *pickling*) scheme on top of an underlying communication protocol (traditionally TCP over IP). These *serialization* schemes allow both the caller *caller* and *callee* to become language agnostic allowing both these systems to be developed in parallel without any language restrictions. Some examples of serialization schemes are JSON, XML, or Protocol Buffers {% cite grpc --file rpc %}. -* explain the diagram here. +RPC allows different components of a larger system to be developed independently of one another. The language-agnostic nature combined with a decoupling of some parts of the system allows the two components (caller and callee) to scale separately and add new functionalities. This independent scaling of the system might lead to a mesh of interconnected RPC *services* facilitating one another. -One important feature of RPC is different *address space* {% cite implementingrpc --file rpc %} for all the endpoints, however, passing the locations to a global storage(Amazon S3, Microsoft Azure, Google Cloud Store) is not impossible.In RPC, all the hosts have separate *address spaces*. They can't share pointers or references to a memory location in one host. This *address space* isolation means that all the information is passed in the messages between the host communicating as a value (objects or variables) but not by reference. Since RPC is a *remote* procedure call, the values sent to the *remote* host cannot be pointers or references to a *local* memory. However, passing links to a global shared memory location is not impossible but rather dependent on the type of system(see *Applications* section for detail). +### Examples of RPC -Originally, RPC was developed as a synchronous, language-specific marshalling service with a custom network protocol to outsource computation{% cite implementingrpc --file rpc %}. It had registry-system to register all the servers. One of the earliest RPC-based system{% cite implementingrpc --file rpc %} was implemented in the Cedar programming language in early 1980's. The goal of this system was to provide similar progamming semantics as local procedure calls. Developed for a LAN network with an inefficient network protocol and a *serialization* scheme to transfer information using the said network protocol, this system aimed at executing a *procedure*(also referred as *method* or a *function*) in a remote *address space*. The single-thread synchronous client and the server were written in an old *Cedar* programming language with a registry system used by the servers to *bind*(or register) their procedures. The clients used this registry system to find a specific server to execute their *remote* procedures. +RPC has become very predominant in modern systems. In the simplest RPC systems, a client connects to a server over a network connection and performs a *procedure*. This procedure could be as simple as `return "Hello World"` in your favorite programming language. However, the complexity of the of this remote procedure has no upper bound. -Modern RPC-based systems are language-agnostic, fault-tolerant, asynchronous, load-balanced systems. Authenticaiton and authorization to these systems have been added as needed along with other security features. +Here's the code of this simple RPC server, written in Python3. +```python +from xmlrpc.server import SimpleXMLRPCServer -RPC programs have a network, therefore, they need to handle remote errors and be able to communication information successfully. Error handling generally varies and is categorized as *remote-host* or *network* failure handling. Depending on the type of the system, and the error, the caller(or the callee) return an error and these errors can be handled accordingly. For asynchronous RPC calls, it's possible to specify events to ensure progress. +# a simple RPC function that returns "Hello World!" +def remote_procedure(n): + return "Hello World!" -RPC implementations use a *serialization*(also referred to as *marshalling* or *pickling*) scheme on top of an underlying communication protocol(traditionally TCP over IP). These *serialization* schemes allow both the caller *caller* and *callee* to become language agnostic allowing both these systems to be developed in parallel without any language restrictions. Some examples of serialization schemes are JSON, XML, or Protocol Buffers{% cite grpc --file rpc %}. +server = SimpleXMLRPCServer(("localhost", 8080)) +print("RPC Server listening on port 8080...") +server.register_function(remote_procedure, "remote_procedure") +server.serve_forever() +``` -RPC allows different components of a larger system to be developed independtly of one another. The language-agnostic nature combined with a decoupling of some parts of the system allows the two components(caller and callee) to scale separately and add new functionalities. +This code for a simple RPC client for the above server, written in Python3, is as follows. -Some RPC implementations have moved from a one-server model to a dynamically-created, load-balanced microservices. +```python +import xmlrpc.client -* Examples: - * One could view the internet as example of RPC.e.g TCP handshake(both act as server and client). - * First: Google Maps API(REST) - * SSL Handshake. +with xmlrpc.client.ServerProxy("http://localhost:8080/") as proxy: + print(proxy.remote_procedure()) +``` +In the above example, we create a simple function called `remote_procedure` and *bind* it to port *8080* on *localhost*. The RPC client then connects to the server and *request* the `remote_procedure` with no input arguments. The server then *responds* with a return value of the `remote_procedure`. + +One can even view the *three-way handshake* as an example of RPC paradigm. The *three-way handshake* is most commonly used in establishing a TCP connection. Here, a server-side application *binds* to a port on the server, and adds a hostname resolution entry is added to a DNS server(can be seen as a *registry* in RPC). Now, when the client has to connect to the server, it requests a DNS server to resolve the hostname to an IP address and the client sends a SYN packet. This SYN packet can be seen as a *request* to another *address space*. The server, upon receiving this, returns a SYN-ACK packet. This SYN-ACK packet from the server can be seen as *response* from the server, as well as a *request* to establish the connection. The client then *responds* with an ACK packet. ## Evolution of RPC: -RPC started in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. +RPC paradigm was first proposed in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. It has had a long and arduous journey to its current state. Here are the three main(overlapping) stages that RPC went through. -* RPC has evolved from what it was originally proposed. -* Chris’s thing: https://christophermeiklejohn.com/pl/2016/04/12/rpc.html -* diagram(maybe not): 4 lines, (y-axis: -1 to 1, x-axis 1980's 2016) +### The Rise: All Hail RPC(Early 1970's - Mid 1980's) -### The Rise: All Hail RPC +RPC started off strong. With RFCs{% cite rfc674 rfc707 --file rpc %} coming out and specifying the design of Remote Procedure Calls, followed by Nelson et. al{% cite implementingrpc --file rpc %} coming up with a first implementation for the Cedar programming language, RPC revolutionized systems in general and gave rise to one of the earliest distributed systems(apart from the internet, of course). -* RPC origin. +With these early achievements, people started using RPC as the defacto design choice. It became a Holy Grail in the systems community for a few years after the first implementation. - * Implementing RPC: [https://dl.acm.org/citation.cfm?id=357392](https://dl.acm.org/citation.cfm?id=357392) - * The RPC thesis(Nelson) - * More examples +### The Fall: RPC is Dead(Late 1970's - Late 1980's) -### The Fall: RPC is Dead +RPC, despite being an initial success, wasn't without flaws. Within a year of its inception, the limitation of the RPC started to catch up with it. RFC 684 criticized RPC for latency, failures, and the cost. It also focussed on message-passing systems as an alternative to RPC design. Similarly, a few years down the road, in 1988, Tenenbaum et. al presented similar concerns against RPC {%cite critiqueofrpc --file rpc %}. It talked about problems heterogeneous devices, message passing as an alternative, packet loss, network failure, RPC's synchronous nature, and highlighted that RPC is not a one-size-fits-all model. -* The fall of RPC/Criticism of RPC - * Limitations - * http://www.cs.vu.nl//~ast/afscheid/publications/euteco-1988.pdf - * Systems that use message passing. +### The Rise, Again: Long Live RPC(Early 1990's - Today) -### The Rise, Again: Long Live RPC +Despite facing problems in its early days, RPC withstood the test of time. Researchers realized the limitations of RPC and focussed on rectifying and instead of enforcing RPC, they started to use RPC in applications where it was needed. The designer started adding exception-handling, async, network failure handling and heterogenity between different languages/devices to RPC. -* gRPC -* XML SOAP -* Java RMI -* Finagle -* Thrift -* Apache Etch -* Sun RPC(ONC RPC) +Perhaps, the earliest system in this era was SunRPC {% cite sunnfs --file rpc %} used for the Sun Network File System(NFS). This SunRPC has gone under various additions and is now referred to as Open Network Computing RPC(ONC RPC). +Soon to follow SunRPC was the language-agnostic CORBA{% cite corba --file rpc %} which was followed by Java RMI{% cite rmipaper --file rpc %}. CORBA and RMI have also undergone various modifications as internet standards were set and TCP/IP became the norm. -#### Java Remote Method Invocation: -Java RMI (Java Remote Method Invocation){% cite rmibook --file rpc %} is a Java implementation for performing RPC (Remote Procedure Calls) between a client and a server. The client using a stub passes via a socket connection the information over the network to the server. The Remote Object Registry (ROR){% cite rmipaper --file rpc %} on the server contains the references to objects that can be accessed remotely and through which the client will connect to. The client then can request of the invocation of methods on the server for processing the requested call and then responds with the answer. RMI provides some security by being encoded but not encrypted, though that can be augmented by tunneling over a secure connection or other methods. +A new breed of RPC also started in this era(early 2000's), Async RPC, giving rise to systems that use *futures* and *promises*, like Finagle{% cite finagle --file rpc %} and Cap'n Proto(post-2010). +In the post-2000 era, MAUI{% cite maui --file rpc %}, Cap'n Proto{% cite capnproto --file rpc %}, gRPC{% cite grpc --file rpc %}, Thrift{% cite thrift --file rpc %} and Finagle{% cite finagle --file rpc %} have been released, which have significantly boosted the widespread use of RPC. A level overview of some of the most important RPC implementation is as follows. +#### Java Remote Method Invocation +Java RMI (Java Remote Method Invocation){% cite rmibook --file rpc %} is a Java implementation for performing RPC (Remote Procedure Calls) between a client and a server. The client using a stub passes via a socket connection the information over the network to the server. The Remote Object Registry (ROR){% cite rmipaper --file rpc %} on the server contains the references to objects that can be accessed remotely and through which the client will connect to. The client then can request of the invocation of methods on the server for processing the requested call and then responds with the answer. RMI provides some security by being encoded but not encrypted, though that can be augmented by tunneling over a secure connection or other methods. -#### CORBA: +#### CORBA CORBA (Common Object Request Broker Architecture){% cite corba --file rpc %} was created by the Object Management Group {% cite corbasite --file rpc %} to allow for language-agnostic communication among multiple computers. It is an object-oriented model defined via an Interface Definition Language (IDL) and the communication is managed through an Object Request Broker (ORB). Each client and server have an ORB by which they communicate. The benefits of CORBA is that it allows for multi-language implementations that can communicate with each other, but much of the criticism around CORBA relates to poor consistency among implementations. -#### XML-RPC and SOAP: +#### XML-RPC and SOAP +SOAP (Simple Object Access Protocol) is a successor of XML-RPC as a web-services protocol for communicating between a client and server. It was initially designed by a group at Microsoft {% cite soaparticle1 --file rpc %}. The SOAP message is an XML-formatted message composed of an envelope inside which a header and a body are provided. The body of the message contains the request and response of the message, which is transmitted over HTTP or SMTP. The benefit of such a protocol is that it provides the flexibility for transmission over multiple transport protocol, though parsing such messages could become a bottleneck. -SOAP (Simple Object Access Protocol) is a successor of XML-RPC as a web-services protocol for communicating between a client and server. It was initially designed by a group at Microsoft {% cite soaparticle1 --file rpc %}. The SOAP message is a XML-formatted message composed of an envelope inside which a header and a body is provided. The body of the message contains the request and response of the message, which is transmitted over HTTP or SMTP. The benefits of such a protocol is that provides the flexibility for transmission of multiple tranport protocol, though parsing such messages could become a bottleneck. +#### Thrift +Thrift is an RPC system created by Facebook and now part of the Apache Foundation {% cite thrift --file rpc %}. It is a language-agnostic IDL by which one generates the code for the client and server. It provides the opportunity for compressed serialization by customizing the protocol and the transport after the description file has been processed. +#### Finagle +Finagle was generated by Twitter and is an RPC system written in Scala and can run on a JVM. It is based on three object types: Service objects, Filter objects and Future objects {% cite finagle --file rpc %}. The Future objects act by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. -#### Thrift: -Thrift is a RPC created by Facebook and now part of the Apache Foundation {% cite thrift --file rpc %}. It is a language-agnostic IDL by which one generates the code for the client and server. It provides the opportunity for compressed serialization by customizing the protocol and the transport after the description file has been processed. +#### Open Network Computing RPC +* Pros and Cons -#### Finagle: -Finagle was generated by Twitter and is an RPC written in Scala and can run on an JVM. It is based on three object types: Service objects, Filter objects and Future objects{% cite finagle --file rpc %}. The Future objects acts by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. +#### Mobile Assistance Using Infrastructure(MAUI) -#### Open Network Computing RPC: -* Pros and Cons +The MAUI project {% cite maui --file rpc %}, developed by Microsoft is a computation offloading system for mobile systems. It's an automated system that offloads a mobile code to a dedicated infrastructure in order to increase the battery life of the mobile, minimize the load on the programmer and perform complex computations offsite. MAUI uses RPC as the communication protocol between the mobile and the infrastructure. + +#### gRPC + +gRPC has been built as a collaboration between Google and Square as a public replacement of Stubby, ARCWire, and Sake {% cite Apigee --file rpc %}. The IDL for gRPC is Protocol Buffers(also referred as ProtoBuf). + +gRPC provides a platform for scalable, bi-directional streaming using both synchronized and asynchronous communication. It multiplexes the requests over a single connection using header compression. This makes it possible for gRPC to be used for mobile clients where battery life and data usage are important. +The core library is in C -- except for Java and GO -- and surface APIs are implemented for all the other languages connecting through it{% cite CoreSurfaceAPIs --file rpc %}. + +Since Protocol Buffers has been utilized by many individuals and companies, gRPC makes it natural to extend their RPC ecosystems via gRPC. Companies like Cisco, Juniper and Netflix {% cite gRPCCompanies --file rpc %} have found it practical to adopt it. +A majority of the Google Public APIs, like their places and maps APIs, have been ported to gRPC ProtoBuf {% cite gRPCProtos --file rpc %} as well. -#### gRPC: +#### Cap'n Proto +CapnProto{% cite capnproto --file rpc %} is a data interchange RPC system between that bypasses data-encoding step(like JSON or ProtoBuf) to significantly improve the performance. It's developed by the original author of gRPC's ProtoBuf, but since it uses bytes(binary data) for encoding/decoding, it outperforms gRPC's ProtoBuf. It uses futures and promises to combine various remote operations into a single to save the transportation round-trips. -### The Contenders for the Throne: gRPC, Thrift or RMI +### The Heir to the Throne: gRPC or Thrift -* gRPC vs Thrift (maybe also Finagle) +Although there are many candidates to be considered as top contenders for RPC throne, most of these are targeted for a specific type of application. ONC is generally specific to the Network File System(though it's being pushed as a standard), Cap'n Proto is relatively new and untested, MAUI is specific to mobile systems, the open-source Finagle is primarily being used at Twitter(not widespread), and the Java RMI simply doesn't even close anyways(sorry to burst your bubble Java fans). -## Applications: +Probably, the most powerful, and practical systems out there are Apache Thrift and Google's gRPC, primarily because *variants* of these two systems have been developed and used by Facebook and Google, respectively. This might be considered as biased view against other RPC implementations, however, when one considers Big Data and Internet-scale, only these two companies (and these two systems) come close. -* RPC and shared state (Persistence Layer): - * http://ieeexplore.ieee.org/document/1302942/?arnumber=1302942&tag=1 - * http://ieeexplore.ieee.org/document/918991/?arnumber=918991 +Thrift was actually released a few years ago, while the first stable release for gRPC came out in August 2016. However, despite being 'out there', Thrift is currently less popular than gRPC {%cite trendrpcthrift --file rpc %}. -* Grid computing: - * https://link.springer.com/article/10.1023/A:1024083511032 +gRPC {% cite gRPCLanguages --file rpc %} and Thrift, both, support most of the popular languages, including Java, C/C++, and Python. Thrift supports other languages, like Ruby, Erlang, Perl, Javascript, Node.js and OCaml while gRPC currently supports Node.js and Go. -* Mobile Systems(offloading and battery requirements): - * https://link.springer.com/article/10.1007/s11036-012-0368-0 +Thrift provides exception-handling as a message while the programmer has to handle exceptions in gRPC. -* Embedded RPC: - * https://dl.acm.org/citation.cfm?id=1127840 +Although custom authentication mechanisms can be implemented in both these system, gRPC come with a Google-backed authentication using SSL/TLS and Google Tokens {% cite grpcauth --file rpc %}. -* Micro services architecture(ecosystem) +The major differences between gRPC and Thrift can be summed in this table. -* RPC can be async +| Comparison | Thrift | gRPC | +| ----- | ----- | ----- | +| License | Apache2 | BSD | +| Supported Languages | C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml | C/C++, Python, Go, Java, Ruby, PHP, C#, Node.js, Objective-C | +| Exceptions | Allows being built in the message | Implemented by the programmer | +| Authentication | Custom | Custom + Google Tokens | +| Bi-Directionality | Not straightforward | Core Implementation via HTTP/2 | +| Multiplexing | Some functionality via multiplexed server | Core Implementation via HTTP/2 | -* Shared State +Although, it's difficult to specifically choose one over the other, however, with increasing popularity of gRPC, and the fact that it's still in early stages of development, the general trend{%cite trendrpcthrift --file rpc %} over the past year has started to shift in favor of gRPC and it's giving Thrift a run for its money. -* microservices +**Note:** This study is performed in December 2016 so the results are expected to change with time. -* Futures and promises: RPC? +## Applications -### Streaming requests and buffered responses +Since its inception, various papers have been published in applying RPC paradigm to different domains, as well as using RPC implementations to create new systems. Here are some of applications and systems that incorporated RPC. -### RPC in microservices ecosystem: +#### Shared State and Persistence Layer -RPC started as a separate implements of REST, Streaming RPC, and now made possible of integration of all these implementations as a single abstraction for a user endpoint service. +One major limitation (and the advantage) of RPC is considered the separate *address space* of all the machines in the network. This means that *pointers* or *references* to a data object cannot be passed between the caller and the callee. Therefore, Interweave {% cite interweave2 interweave1 interweave3 --file rpc %} is a middleware system that allows scalable sharing of arbitrary data-types and language-independent processes running heterogeneous hardware. Interweave is specifically designed and is compatible with RPC-based systems and allows easier access to the shared resources between different applications. It even allows passing C *pointers* between the caller and the callee. -* Creating new services. +#### GridRPC -* Bootstrapping +Grid computing is one of the most widely used applications of RPC paradigm. At a high level, it can be seen as a mesh (or a network) of computers connected with each other to for *grid* such each system can leverage resources from any other system in the network. -* Load balancing - * Creating new services in Actor-Like model - * Fault tolerance - * Self-recovery +In the GridRPC paradigm, each computer in the network can act as the *caller* or the *callee* depending on the amount of resources required {% cite grid1 --file rpc %}. It's also possible for the same computer to act as the *caller* as well as the *callee* for *different* computations. -* Business and Persistence Layer were combined and the Persistence layer is not shared anymore, where each endpoints has its own persistent state: - * https://help.sap.com/saphelp_nwmobile711/helpdata/de/7e/d1a40b5bc84868b1606ce0dc72d88b/content.htm +Some of the most popular implementations that allow one to have GridRPC-compliant middleware are GridSolve{% cite gridsolve1 gridsolve2 --file rpc %} and Ninf-G{% cite ninf --file rpc %}. Ninf is relatively older than GridSolve and was first published in the late 1990's. It's a simple RPC layer that also provides authentication and secure communication between the two parties. GridSolve, on the other hand, is relatively complex and provides a middleware for the communications using a client-agent-server model. + +#### Mobile Systems and Computation Offloading + +Mobile systems have become very powerful these days. With multi-core processors and gigabytes of RAM, they can undertake relatively complex computations without a hassle. Due to this advancement, they consume a larger amount of energy and hence, their batteries, despite becoming larger, drain quickly with usage. Moreover, mobile data (network bandwidth) is still limited and expensive. Due to these requirements, it's better to offload mobile computations from mobile systems when possible. RPC plays an important role in the communication for this *computation offloading*. Some of these services use Grid RPC technologies to offload this computation. Whereas, other technologies use an RMI(Remote Method Invocation) system for this. + +The Ibis Project {% cite ibis --file rpc %} builds an RMI and GMI (Group Method Invocation) model to facilitate outsourcing computation. Cuckoo {% cite cuckoo --file rpc %} uses this Ibis communication middleware to offload computation. + +The Microsoft's MAUI Project {% cite maui --file rpc %} uses RPC communication and allows partitioning of .NET applications and "fine-grained code offload to maximize energy savings with minimal burden on the programmer". MAUI decides the methods to offload to the external MAUI server at runtime. + +#### Async RPC, Futures and Promises + +Remote Procedure Calls can be asynchronous. Not only that but these async RPCs play in integral role in the *futures* and *promises*. *Future* and *promises* are programming constructs that where a *future* is seen as variable/data/return type/error while a *promise* is seen as a *future* that doesn't have a value, yet. We follow Finagle's {% cite finagle --file rpc %} definition of *futures* and *promises*, where the *promise* of a *future*(an empty *future*) is considered as a *request* while the async fulfillment of this *promise* by a *future* is seen as the *response*. This construct is primarily used for concurrent programming. + +Perhaps the most renowned systems using this type of RPC model are Twitter's Finagle{% cite finagle --file rpc %} and Cap'n Proto{% cite capnproto --file rpc %}. + +#### RPC in Microservices Ecosystem: + +RPC implementations have moved from a one-server model to multiple servers and on to dynamically-created, load-balanced microservices. RPC started as a separate implementations of REST, Streaming RPC, MAUI, gRPC, Cap'n Proto, and has now made it possible for integration of all these implementations as a single abstraction as a user *endpoint* service. The endpoints are the building blocks of *microservices*. These *microservices* interact with each other and applications and combine to give the feel of one large monolithic service. + +The use of RPC has allowed us to create new microservices on-the-fly. The microservices can not only created and bootstrapped at runtime but also have inherent features like load-balancing and failure-recovery. This bootstrapping might occur on the same machine, adding to a Docker container {% cite docker --file rpc %}, or across a network (using any combination of DNS, NATs or other mechanisms). + +RPC can be defined as the "glue" that holds all the microservices together{% cite microservices1rpc --file rpc %}. This means that RPC is one of the primary communication mechanism between different microservices running on different systems. A microservice requests another microservice to perform an operation/query. The other microservice, upon receiving such request, performs an operation and returns a response. This operation could vary from a simple computation to invoking another microservice creating a series of RPC events to creating new microservices on the fly to dynamically load balance the microservices system. + +An example of a microservices ecosystem that uses futures/promises is Finagle at Twitter. ## Security in RPC: -* Initially it was separate. - * Authentication, authorization issues have been resolved -* Now embedded in the protocol -* Security and Privacy in RPC - * Bugs in the libraries. - * Trust Issues between client and the server. - * http://static.usenix.org/publications/library/proceedings/sec02/full_papers/giffin/giffin_html/ - * Brewer’s view: https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf - * E programming language: distributed object model/VAT + +The initial RPC implementation {% cite implementingrpc --file rpc %} was developed for an isolated LAN network and didn't focus much on security. There're various attack surfaces in that model, from the malicious registry, to a malicious server, to a client targeting for Denial-of-Service to Man-in-the-Middle attack between client and server. + +As time progressed and internet evolved, new standards came along, and RPC implementations became much more secure. Security, in RPC, is generally added as a *module* or a *package*. These modules have libraries for authentication and authorization of the communication services (caller and callee). These modules are not always bug-free and it's possible to gain unauthorized access to the system. Efforts are being made to rectify these situations by the security in general, using code inspection and bug bounty programs to catch these bugs before-hand. However, with time new bugs arise and this cycle continues. It's a vicious cycle between attackers and security experts, both of whom tries to outdo their opponent. + +For example, the Oracle Network File System uses a *Secure RPC*{% cite oraclenfs --file rpc %} to perform authentication in the NFS. This *Secure RPC* uses Diffie-Hellman authentication mechanism with DES encryption to allow only authorized users to access the NFS. Similarly, Cap'n Proto {% cite capnprotosecure --file rpc %} claims that it is resilient to memory leaks, segfaults, and malicious inputs and can be used between mutually untrusting parties. However, in Cap'n Proto "the RPC layer is not robust against resource exhaustion attacks, possibly allowing denials of service", nor has it undergone any formal verification {% cite capnprotosecure --file rpc %}. + +Although, it's possible to come up with a *Threat Model* that would make an RPC implementation insecure to use, however, one has to understand that using any distributed system increases the attack surface anyways and claiming one *paradigm* to be more secure than another would be a biased statement, since *paradigms* are generally an idea and it depends on different system designers to use these *paradigms* to build their systems and take care of features specific to real systems, like security and load-balancing. There's always a possibility of rerouting a request to a malicious server(if the registry gets hacked), or there's no trust between the *caller* and *callee*. However, we maintain that RPC *paradigm* is not secure or insecure(for that matter), and that the most secure systems are the ones that are in an isolated environment, disconnected from the public internet with a self-destruct mechanism{% cite self --file rpc %} in place, in an impenetrable bunker, and guarded by the Knights Templar(*they don't exist! Well, maybe Fort Meade comes close*). ## Discussion: -* RPC vs REST and other services. RPC influence. -* The future of RPC - * Where it shines. Not in message passing. - * RPC is not XYZ (HTTP, REST, …) though it has influenced. -## Conclusions(maybe not a heading): +RPC *paradigm* shines the most in *request-response* mechanisms. Futures and Promises also appear to a new breed of RPC. This leads one to question, as to whether every *request-response* system is a modified implementation to of the RPC *paradigm*, or does it actually bring anything new to the table? These modern communication protocols, like HTTP and REST, might just be a different flavor of RPC. In HTTP, a client *requests* a web page(or some other content), the server then *responds* with the required content. The dynamics of this communication might be slightly different from your traditional RPC, however, an HTTP Stateless server adheres to most of the concepts behind RPC *paradigm*. Similarly, consider sending a request to your favorite Google API. Say, you want to translate your latitude/longitude to an address using their Reverse Geocoding API, or maybe want to find out a good restaurant in your vicinity using their Places API, you'll send a *request* to their server to perform a *procedure* that would take a few input arguments, like the coordinates, and return the result. Even though these APIs follow a RESTful design, it appears to be an extension to the RPC *paradigm*. + +RPC paradigm has evolved over time. It has evolved to the extent that, currently, it's become very difficult differentiate RPC from non-RPC. For the past decades, researchers and industry leaders have tried to come up with *their* definition of RPC. The proponents of RPC paradigm view every *request-response* communication as an implementation the RPC paradigm while those against RPC try to explicitly come up with the bounds of RPC. RPC supporters consider it as the Holy Grail of distributed systems. They view it as the foundation of modern distributed communication. From Apache Thrift and ONC to HTTP and REST, they advocate it all as RPC while REST developers have strong opinions against RPC. + +Moreover, with modern global storage mechanisms, the need for RPC systems to have a separate *address space* seems to be slowly dissolving and disappearing into thin air. So, the question remains what *is* RPC and what * is not* RPC? This is an open-ended question. There is no unanimous agreement about what RPC should look like, except that it has communication between two *endpoints*. What we think of RPC is: -RPC is not dead: long live the Remote Procedure calls. +*"In the world of distributed systems, where every individual component of a system, be it a hard disk, a multi-core processor, or a microservice, is an extension of the RPC, it's difficult to come with a concrete definition of the RPC paradigm. Therefore, anything loosely associated with a request-response mechanism can be considered as RPC".* +
+

+**RPC is not dead, long live RPC!** +

+
## References -{% bibliography --file rpc %} \ No newline at end of file +{% bibliography --file rpc --cited %} diff --git a/resources/img/rpc_chapter_1_ycog_10_steps.png b/resources/img/rpc_chapter_1_ycog_10_steps.png new file mode 100644 index 0000000..86c7432 Binary files /dev/null and b/resources/img/rpc_chapter_1_ycog_10_steps.png differ -- cgit v1.2.3 From 4016d5b399e39ae77afa36e1a25c4980fa1e4c9a Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 9 Dec 2016 11:59:39 -0500 Subject: First Draft-Muzammil --- _bibliography/rpc.bib | 26 ++++++++++++++++++++++++++ chapter/1/rpc.md | 30 ++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/_bibliography/rpc.bib b/_bibliography/rpc.bib index db77c12..266c603 100644 --- a/_bibliography/rpc.bib +++ b/_bibliography/rpc.bib @@ -333,3 +333,29 @@ author={Schantz, Richard}, year={1975} } + +@misc{grpcbetter, + title={GRPC Authentication}, + author={Google}, + url = {https://www.quora.com/Is-GRPC-better-than-Thrift}, + publisher = {Quora} +} + +@misc{multiplexingthrift, + title={Added service multiplexing support}, + author={Yu, Lixin}, + url = {https://github.com/eleme/thriftpy/pull/88/commits/0877531f9246ca993c1d9af5d29cd009ee6ec7d4}, + publisher = {Github} +} + +@techreport{rfc5531, + title={RFC 5531: RPC: Remote Procedure Call Protocol Specification Version 2}, + author={Thurlow, R}, + year={2009} +} + +@techreport{rfc1831, + title={RFC 1831: RPC: Remote Procedure Call Protocol Specification Version 2}, + author={Srinivasan, R}, + year={1995} +} diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index 05a6452..6806580 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -109,11 +109,10 @@ Thrift is an RPC system created by Facebook and now part of the Apache Foundatio #### Finagle Finagle was generated by Twitter and is an RPC system written in Scala and can run on a JVM. It is based on three object types: Service objects, Filter objects and Future objects {% cite finagle --file rpc %}. The Future objects act by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. -#### Open Network Computing RPC -* Pros and Cons +#### Open Network Computing RPC(ONC RPC) +ONC was originally introduced as SunRPC {%cite sunrpc --file rpc %} for the Sun NFS. It supported NFS read, write, truncate, unlink, etc operations. However, it was later revised as ONC in 1995 {%cite rfc1831 --file rpc %} and then in 2009 {%cite rfc5531 --file rpc %}. The IDL used in ONC is External Data Representation (XDR), a serialization mechanism specific to networks communication and therefore, ONC is limited to applications like Network File Systems. #### Mobile Assistance Using Infrastructure(MAUI) - The MAUI project {% cite maui --file rpc %}, developed by Microsoft is a computation offloading system for mobile systems. It's an automated system that offloads a mobile code to a dedicated infrastructure in order to increase the battery life of the mobile, minimize the load on the programmer and perform complex computations offsite. MAUI uses RPC as the communication protocol between the mobile and the infrastructure. #### gRPC @@ -139,24 +138,35 @@ Thrift was actually released a few years ago, while the first stable release for gRPC {% cite gRPCLanguages --file rpc %} and Thrift, both, support most of the popular languages, including Java, C/C++, and Python. Thrift supports other languages, like Ruby, Erlang, Perl, Javascript, Node.js and OCaml while gRPC currently supports Node.js and Go. -Thrift provides exception-handling as a message while the programmer has to handle exceptions in gRPC. +The gRPC core is written in C(with the exception of Java and Go) and wrappers are written in other languages to communicate with the core, while the Thrift core is written in C++. + +gRPC also provides easier bi-drectional streaming communicaiton between the caller and callee. The client generally initiates the communication {% cite gRPCLanguages --file rpc %} and once the connection is established the client and the server can perform reads and writes independently of each other. However, bi-directional streaming in Thrift might be a little difficult to handle, since it focuses explicitly on a client-server model. To enable bi-directionaly, async streaming, one may have to run two seperate systems {%cite grpcbetter --file rpc%}. + +Thrift provides exception-handling as a message while the programmer has to handle exceptions in gRPC. In Thrift, exceptions can be returned built into the message, while in gRPC, the programmer explicitly defines this behaviour. This Thrift exception-handling makes it easier to write client-side applications. Although custom authentication mechanisms can be implemented in both these system, gRPC come with a Google-backed authentication using SSL/TLS and Google Tokens {% cite grpcauth --file rpc %}. -The major differences between gRPC and Thrift can be summed in this table. +Moreover, gRPC-based network communication is done using HTTP/2. HTTP/2 makes it feasible for communicating parties to multiplex network connections using the same port. This is more efficient(in terms of memory usage) as compared to HTTP/1.1. Since, gRPC communication is done HTTP/2, it means that gRPC can easily multiplex different services. As for Thrift, multiplexing services is possible, however, due to lack of support from underlying transport protocol, it is performed using a `TMulitplexingProcessor` class {% cite multiplexingthrift --file rpc %}. + +However, both gRPC and Thrift allow async RPC calls. This means that a client can send a request to the server and continue with its execution and the response from the server is processed it arrives. + + +The major comparison between gRPC and Thrift can be summed in this table. | Comparison | Thrift | gRPC | | ----- | ----- | ----- | | License | Apache2 | BSD | +| Sync/Async RPC | Both | Both | | Supported Languages | C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml | C/C++, Python, Go, Java, Ruby, PHP, C#, Node.js, Objective-C | +| Core Language | C++| C | | Exceptions | Allows being built in the message | Implemented by the programmer | | Authentication | Custom | Custom + Google Tokens | -| Bi-Directionality | Not straightforward | Core Implementation via HTTP/2 | -| Multiplexing | Some functionality via multiplexed server | Core Implementation via HTTP/2 | +| Bi-Directionality | Not straightforward | Straightforward | +| Multiplexing | Possible via | Possible via HTTP/2 | Although, it's difficult to specifically choose one over the other, however, with increasing popularity of gRPC, and the fact that it's still in early stages of development, the general trend{%cite trendrpcthrift --file rpc %} over the past year has started to shift in favor of gRPC and it's giving Thrift a run for its money. -**Note:** This study is performed in December 2016 so the results are expected to change with time. +**Note:** This study is performed in December 2016 so the results are expected to change with time. ## Applications @@ -198,7 +208,7 @@ RPC can be defined as the "glue" that holds all the microservices together{% cit An example of a microservices ecosystem that uses futures/promises is Finagle at Twitter. -## Security in RPC: +## Security in RPC The initial RPC implementation {% cite implementingrpc --file rpc %} was developed for an isolated LAN network and didn't focus much on security. There're various attack surfaces in that model, from the malicious registry, to a malicious server, to a client targeting for Denial-of-Service to Man-in-the-Middle attack between client and server. @@ -208,7 +218,7 @@ For example, the Oracle Network File System uses a *Secure RPC*{% cite oraclenfs Although, it's possible to come up with a *Threat Model* that would make an RPC implementation insecure to use, however, one has to understand that using any distributed system increases the attack surface anyways and claiming one *paradigm* to be more secure than another would be a biased statement, since *paradigms* are generally an idea and it depends on different system designers to use these *paradigms* to build their systems and take care of features specific to real systems, like security and load-balancing. There's always a possibility of rerouting a request to a malicious server(if the registry gets hacked), or there's no trust between the *caller* and *callee*. However, we maintain that RPC *paradigm* is not secure or insecure(for that matter), and that the most secure systems are the ones that are in an isolated environment, disconnected from the public internet with a self-destruct mechanism{% cite self --file rpc %} in place, in an impenetrable bunker, and guarded by the Knights Templar(*they don't exist! Well, maybe Fort Meade comes close*). -## Discussion: +## Discussion RPC *paradigm* shines the most in *request-response* mechanisms. Futures and Promises also appear to a new breed of RPC. This leads one to question, as to whether every *request-response* system is a modified implementation to of the RPC *paradigm*, or does it actually bring anything new to the table? These modern communication protocols, like HTTP and REST, might just be a different flavor of RPC. In HTTP, a client *requests* a web page(or some other content), the server then *responds* with the required content. The dynamics of this communication might be slightly different from your traditional RPC, however, an HTTP Stateless server adheres to most of the concepts behind RPC *paradigm*. Similarly, consider sending a request to your favorite Google API. Say, you want to translate your latitude/longitude to an address using their Reverse Geocoding API, or maybe want to find out a good restaurant in your vicinity using their Places API, you'll send a *request* to their server to perform a *procedure* that would take a few input arguments, like the coordinates, and return the result. Even though these APIs follow a RESTful design, it appears to be an extension to the RPC *paradigm*. -- cgit v1.2.3 From e040c6af0f6e58a11fc638ff66c2dc93b960a361 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 9 Dec 2016 12:14:59 -0500 Subject: Muzammil-minor --- chapter/1/rpc.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index 6806580..99f6d4e 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -4,7 +4,7 @@ title: "RPC is Not Dead: Rise, Fall and the Rise of Remote Procedure Calls" by: "Muzammil Abdul Rehman and Paul Grosu" --- -## Introduction +## Introduction: *Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build modular, scalable, distributed, language-agnostic ecosystem of applications. This RPC *paradigm* has been part of the driving force in creating truly revolutionizing distributed systems and giving rise to various communication schemes and protocols between diverse systems. @@ -168,7 +168,7 @@ Although, it's difficult to specifically choose one over the other, however, wit **Note:** This study is performed in December 2016 so the results are expected to change with time. -## Applications +## Applications: Since its inception, various papers have been published in applying RPC paradigm to different domains, as well as using RPC implementations to create new systems. Here are some of applications and systems that incorporated RPC. @@ -208,7 +208,7 @@ RPC can be defined as the "glue" that holds all the microservices together{% cit An example of a microservices ecosystem that uses futures/promises is Finagle at Twitter. -## Security in RPC +## Security in RPC: The initial RPC implementation {% cite implementingrpc --file rpc %} was developed for an isolated LAN network and didn't focus much on security. There're various attack surfaces in that model, from the malicious registry, to a malicious server, to a client targeting for Denial-of-Service to Man-in-the-Middle attack between client and server. @@ -218,7 +218,7 @@ For example, the Oracle Network File System uses a *Secure RPC*{% cite oraclenfs Although, it's possible to come up with a *Threat Model* that would make an RPC implementation insecure to use, however, one has to understand that using any distributed system increases the attack surface anyways and claiming one *paradigm* to be more secure than another would be a biased statement, since *paradigms* are generally an idea and it depends on different system designers to use these *paradigms* to build their systems and take care of features specific to real systems, like security and load-balancing. There's always a possibility of rerouting a request to a malicious server(if the registry gets hacked), or there's no trust between the *caller* and *callee*. However, we maintain that RPC *paradigm* is not secure or insecure(for that matter), and that the most secure systems are the ones that are in an isolated environment, disconnected from the public internet with a self-destruct mechanism{% cite self --file rpc %} in place, in an impenetrable bunker, and guarded by the Knights Templar(*they don't exist! Well, maybe Fort Meade comes close*). -## Discussion +## Discussion: RPC *paradigm* shines the most in *request-response* mechanisms. Futures and Promises also appear to a new breed of RPC. This leads one to question, as to whether every *request-response* system is a modified implementation to of the RPC *paradigm*, or does it actually bring anything new to the table? These modern communication protocols, like HTTP and REST, might just be a different flavor of RPC. In HTTP, a client *requests* a web page(or some other content), the server then *responds* with the required content. The dynamics of this communication might be slightly different from your traditional RPC, however, an HTTP Stateless server adheres to most of the concepts behind RPC *paradigm*. Similarly, consider sending a request to your favorite Google API. Say, you want to translate your latitude/longitude to an address using their Reverse Geocoding API, or maybe want to find out a good restaurant in your vicinity using their Places API, you'll send a *request* to their server to perform a *procedure* that would take a few input arguments, like the coordinates, and return the result. Even though these APIs follow a RESTful design, it appears to be an extension to the RPC *paradigm*. -- cgit v1.2.3 From 232cb8c6e6c0dc94190bdd88f8733621fc606ff6 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 9 Dec 2016 12:33:32 -0500 Subject: Muzammil-minor again --- chapter/1/rpc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index 99f6d4e..7688455 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -110,7 +110,7 @@ Thrift is an RPC system created by Facebook and now part of the Apache Foundatio Finagle was generated by Twitter and is an RPC system written in Scala and can run on a JVM. It is based on three object types: Service objects, Filter objects and Future objects {% cite finagle --file rpc %}. The Future objects act by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. #### Open Network Computing RPC(ONC RPC) -ONC was originally introduced as SunRPC {%cite sunrpc --file rpc %} for the Sun NFS. It supported NFS read, write, truncate, unlink, etc operations. However, it was later revised as ONC in 1995 {%cite rfc1831 --file rpc %} and then in 2009 {%cite rfc5531 --file rpc %}. The IDL used in ONC is External Data Representation (XDR), a serialization mechanism specific to networks communication and therefore, ONC is limited to applications like Network File Systems. +ONC was originally introduced as SunRPC {%cite sunrpc --file rpc %} for the Sun NFS. The Sun NFS system had a stateless server, with client-side caching, unique file-handlers, and supported NFS read, write, truncate, unlink, etc operations. However, SunRPC was later revised as ONC in 1995 {%cite rfc1831 --file rpc %} and then in 2009 {%cite rfc5531 --file rpc %}. The IDL used in ONC(and SunRPC) is External Data Representation (XDR), a serialization mechanism specific to networks communication and therefore, ONC is limited to applications like Network File Systems. #### Mobile Assistance Using Infrastructure(MAUI) The MAUI project {% cite maui --file rpc %}, developed by Microsoft is a computation offloading system for mobile systems. It's an automated system that offloads a mobile code to a dedicated infrastructure in order to increase the battery life of the mobile, minimize the load on the programmer and perform complex computations offsite. MAUI uses RPC as the communication protocol between the mobile and the infrastructure. -- cgit v1.2.3 From d29720b469b4d72434200ab15873c03c225dbd8b Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 17:58:10 -0500 Subject: Update futures.md --- chapter/2/futures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 5c56e92..1ddbc02 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -1,11 +1,11 @@ --- layout: page title: "Futures" -by: "Joe Schmoe and Mary Jane" +by: "Kisalaya Prasad and Avanti Patil" --- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file futures %} ## References -{% bibliography --file futures %} \ No newline at end of file +{% bibliography --file futures %} -- cgit v1.2.3 From f9a6cc50e91d4adc1817715fb265874eb7cb253a Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 9 Dec 2016 19:42:03 -0500 Subject: Dynamo --- chapter/7/langs-consistency.md | 61 +++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 6eddfc5..3de0848 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -1,32 +1,67 @@ --- layout: page -title: "Languages Built For Consistency" +title: "Formal, Yet Relaxed: Models for Consistency" by: "James Larisch" --- -# Languages Built For Consistency +# Formal, Yet Relaxed: Models for Consistency ## What's the problem? - As processors become expensive and the limits of Moore's Law are pushed, programmers lately find themselves in situations where they need to connect multiple computers together using a network cable. Perhaps it's not even due to cost or performance constraints; perhaps your company has servers in New York and San Fransisco, and there is some global state that requires synchronization across the country. Problems requiring solutions of this nature can be described as "distributed systems" problems. Your data / processing power / entry points are distributed for some reason. In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination is required. + As processors become expensive and the limits of Moore's Law are pushed, programmers find themselves in situations where they need to connect multiple computers together using a network cable. Perhaps it's not even due to cost or performance constraints; perhaps your company has servers in New York and San Fransisco, and there is some global state that requires synchronization across the country. Problems requiring solutions of this nature can be described as "distributed systems" problems. Your data / processing power / entry points are distributed for some reason. In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination is required. - As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network simply is not reliable - and if we wait for it to be reliable, we sacrifice things like timeliness. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the properties of our systems. As stated, we can't perfectly emulate a single computer, but once we accept that fact... there are plenty of things we *can* do! + As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network is simply not as reliable as, say, memory - and waiting for responses can result in a lack of timeliness for the application's client. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the properties of our systems. As stated, we can't perfectly emulate a single computer using multiple machines, but once we accept that fact and learn to work with it... there are plenty of things we *can* do! ## The Shopping Cart Let's bring all these theorem talk back to reality. Let's say you're working at a new e-commerce startup, and you'd like to revolutionize the electronic shopping cart. You'd like to give the customer the ability to do the following: - * Log in to the site and add a candle to the cart while traveling Beijing. - * Take a HyperLoop train (3 hours) from Beijing to Los Angeles. - * Log back into the site, remove the candle from their cart, and add a skateboard to their cart. - * Take another HyperLoop train from Los Angeles to Paris (5 hours). - * Log back into the site, add another skateboard, and checkout. + 1. Log in to the site and add a candle to the cart while traveling in Beijing. + 1. Take a HyperLoop (3 hours) from Beijing to Los Angeles. + 1. Log back in, remove the candle from the cart, and add a skateboard. + 1. Take another HyperLoop train from Los Angeles to Paris (5 hours). + 1. Log back into the site, add another skateboard, and checkout. Let's assume you have a server in every single country, and customers connect to the geographically closest server. - If you only had 1 user of your website, this wouldn't be too hard. You could constantly send out messages to all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay i-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. + How can we ensure that the client sees the same cart at every point in her trip? - But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which server she asks. + If you only had one user of your website, this wouldn't be too hard. You could manually, constantly modify and check on all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay in-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. - If you're confident that the servers' state will eventually converge, you could present the user with an error message until the states have converged. That way, you know the user is looking at consistent state. [I may be overlapping too much with Aviral's section here. will wait until I see his draft before continuing. + But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). - Mention Amazon's Dynamo + shopping cart. + It's possible to implement "consensus" protocols that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? + + This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be *available*. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. + + Turns out there's a company out there called Amazon.com - and they've been having a similar problem. Amazon sells things on their website too, and users can add and remove things from their cart. Amazon has lots of servers spread out across the world. They also have quite a few customers. They need to ensure their customers' carts are robust: if/when servers fail or lose communication with one another, a "best-effort" should be made to display the customer's cart. Amazon acknowledges that failure, latency, or HyperLoop-traveling users can cause inconsistent cart data, depending on which server you ask. How does Amazon resolve these issues? + +## Dynamo + Amazon built DynamoDB, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. + + Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts...aaaaaand... push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. + + How should Amazon's shopping cart procede with resolution? It may be fed two cart states like so: + + ``` + James's Cart V1 | James's Cart V2 + ----------------------------------- + Red Candle | Red Candle + Blue Skateboard | Green Umbrella + ``` + + Amazon doesn't want to accidently *remove* anything from your cart, so it errs on the side of inclusion. If given this particular conflict, you would see: + + ``` + James's Cart + ------------ + Red Candle + Blue Skateboard + Green Umbrella + ``` + + It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. + + + + + Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. [This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.] ### Example -- cgit v1.2.3 From 44217735c8052e4ab60253892df6ffa8b5197bf2 Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Fri, 9 Dec 2016 20:47:21 -0500 Subject: Added a chapter to introduce strong eventual consistency and join semilattices and CRDTs --- chapter/6/being-consistent.md | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 chapter/6/being-consistent.md diff --git a/chapter/6/being-consistent.md b/chapter/6/being-consistent.md new file mode 100644 index 0000000..233d987 --- /dev/null +++ b/chapter/6/being-consistent.md @@ -0,0 +1,82 @@ +--- +layout: page +title: "Being Consistent" +by: "Aviral Goel" +--- + +## Replication and Consistency +Availability and Consistency are the defining characteristics of any distributed system. As dictated by the CAP theorem, accommodating network partitions requires a trade off between the two properties. Modern day large scale internet based distributed systems have to be highly available. To manage huge volumes of data (big data) and to reduce access latency for geographically diverse user base, their data centers also have to be geographically spread out. Network partitions which would otherwise happen with a low probability on a local network become certain events in such systems. To ensure availability in the event of partitions, these systems have to replicate data objects. This begs the question, how to ensure consistency of these replicas? It turns out there are different notions of consistency which the system can adhere to. + +* **Strong Consistency** implies linearizability of updates, i.e., all updates applied to a replicated data type are serialized in a global total order. This means that any update will have to be simultaneously applied to all other replicas. Its obvious that this notion of consistency is too restrictive. A single unavailable node will violate this condition. Forcing all updates to happen synchronously will impact system availability negatively. This notion clearly does not fit the requirements of highly available fault tolerant systems. + +* **Eventual Consistency** is a weaker model of consistency that does not guarantee immediate consistency of all replicas. Any local update is immediately executed on the replica. The replica then sends its state asynchronously to other replicas. As long as all replicas share their states with each other, the system eventually achieves stability. Each replica finally contains the same value. During the execution, all updates happen asynchronously at all replicas in a non-deterministic order. So replicas can be inconsistent between updates. If updates arrive concurrently at a replica, a consensus protocol can be employed to ensure that both updates taken together do not violate an invariant. If they do, a rollback has to be performed and the new state is communicated to all the other replicas. + +Most large scale distributed systems try to be **Eventually Consistent** to ensure high availability and partition-tolerance. But conflict resolution is hard. There is little guidance on correct approaches to consensus and its easy to come up with an error prone ad-hoc approach. What if we side-step conflict resolution and rollback completely? Is there a way to design data structures which do not require any consensus protocols to merge concurrent updates? + +## A Distributed Setting + +### TODO need to write pseudocode. Will finish this part with the detailed explanation of CRDTs in the next chapter. +Consider a replicated counter. Each node can increment the value of its local copy. The figure below shows three nodes which increment their local copies at arbitrary time points and each replica sends its value asynchronously to the other two replicas. Whenever it recieves the value of its replica, it adds it to its current value. If two values are received concurrently, both will be added together to its current value. So merging replicas in this example becomes trivial. + +Let's take a look at another interesting generalization of this. Integer Vector + + +We can make an interesting observation from the previous examples: + +__*All distributed data structures don't need conflict resolution*__ + +This raises the following question: + +__*How can we design a distributed structure such that we don't need conflict resolution?*__ + +The answer to this question lies in an algebraic structure called the **join semilattice**. + +## Join Semilattice +A join-semilattice or upper semilattice is a *partial order* `≤` with a *least upper bound* (LUB) `⊔` for all pairs. +`m = x ⊔ y` is a Least Upper Bound of `{` `x` `,` `y` `}` under `≤` iff `∀m′, x ≤ m′ ∧ y ≤ m′ ⇒ x ≤ m ∧ y ≤ m ∧ m ≤ m′`. + +`⊔` is: + +**Associative** + +`(x ⊔ y) ⊔ z = x ⊔ (y ⊔ z)` + +**Commutative** + +`x ⊔ y = y ⊔ x` + +**Idempotent** + +`x ⊔ x = x` + +The examples we saw earlier were of structures that could be modeled as join semilattices. The merge operation for the increment only counter is the summation function and for the integer vector it is the per-index maximum of the vectors being merged. +So, if we can model the state of the data structure as a partially ordered set and design the merge operation to always compute the "larger" of the two states, its replicas will never need consensus. They will always converge as execution proceeds. Such data structures are called CRDTs (Conflict-free Replicated Data Type). But what about consistency of these replicas? + +## Strong Eventual Consistency (SEC) +We discussed a notion of consistency, *Eventual Consistency*, in which replicas eventually become consistent if there are no more updates to be merged. But the update operation is left unspecified. Its possible for an update to render the replica in a state that causes it to conflict with a later update. In this case the replica may have to roll back and use consensus to ensure that all replicas do the same to ensure consistency. This is complicated and wasteful. But if replicas are modeled as CRDTs, the updates never conflict. Regardless of the order in which the updates are applied, all replicas will eventually have equivalent state. Note that no conflict arbitration is necessary. This kind of Eventual Consistency is a stronger notion of consistency than the one that requires conflict arbitration and hence is called *Strong Eventual Consistency*. + +### Strong Eventual Consistency and CAP Theorem + +Let's study SEC data objects from the perspective of CAP theorem. + +#### 1. Consistency and Network Partition +Each distributed replica will communicate asynchronously with other reachable replicas. These replicas will eventually converge to the same value. There is no consistency guarantee on the value of replicas not reachable due to network conditions and hence this condition is strictly weaker than strong consistency. But as soon as those replicas can be reached, they will also converge in a self-stabilizing manner. + +#### 2. Availability and Network Partition +Each distributed replica will always be available for local reads and writes regardless of network partitions. In fact, if there are n replicas, a single replica will function even if the remaining n - 1 replicas crash simultaneously. This **provides an extreme form of availability**. + +SEC facilitates maximum consistency and availability in the event of network partitions by relaxing the requirement of global consistency. Note that this is achieved by virtue of modeling the data objects as join semilattices. + +#### Strong Eventual Consistency and Linearizability +In a distributed setting, a replica has to handle concurrent updates. In addition to its sequential behavior, a CRDT also has to ensure that its concurrent behavior also ensures strong eventual consistency. This makes it possible for CRDTs to exhibit behavior that is simply not possible for sequentially consistent objects. +Consider a set CRDT used in a distributed setting. One of the replicas pi executes the sequence `add(a); remove(b)`. Another replica pj executes the sequence `add(b); remove(a)`. Now both send their states asynchronously to another replica pk which has to merge them concurrently. Same element exists in one of the sets and does not exist in the other set. There are multiple choices that the CRDT designer can make. Let's assume that the implementation always prefers inclusion over exclusion. So in this case, pk will include both `a` and `b`. +Now consider a sequential execution of the two sequences on set data structure. The order of execution will be either `add(a); remove(b); add(b); remove(a)` or `add(b); remove(a); add(a); remove(b)`. In both cases one of the elements is excluded. This is different from the state of the CRDT set implementation. +Thus, strong eventually consistent data structures can be sequentially inconsistent. +Similarly, if there are `n` sequentially consistent replicas, then they would need consensus to ensure a single order of execution of operations across all replicas. But if `n - 1` replicas crash, then consensus cannot happen. This makes the idea of sequential consistency incomparable to that of strong eventual consistency. + +## What Next? +This chapter introduced Strong Eventual Consistency and the formalism behind CRDTs, join semilattices, which enables CRDTs to exhibit strong eventual consistency. The discussion however does not answer an important question: + +__*Can all standard data structures be designed as CRDTs?*__ + +The next chapter sheds more light on the design of CRDTs and attempts to answer this question. -- cgit v1.2.3 From 367377b63bcd32848d0d44d7a3dd85fe429f8143 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 9 Dec 2016 21:03:21 -0500 Subject: Examples --- chapter/7/langs-consistency.md | 91 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 3de0848..4584fd2 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -26,7 +26,7 @@ by: "James Larisch" But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). - It's possible to implement "consensus" protocols that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? + It's possible to implement "consensus" protocols such as Paxos and 3-Phase-Commit that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be *available*. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. @@ -58,10 +58,21 @@ by: "James Larisch" It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. +### Good & Bad + What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has an insanely high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. + Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed general-purpose data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. + Let's try this transfiguration on the shopping cart. Let's strip it down: how does Amazon handle resolution, really? It treats shopping cart versions as sets of items. In order to perform resolution, Amazon unions the two sets. - Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. [This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.] + ``` + { Red Candle, Blue Skateboard } U { Red Candle, Green Umbrella } == { Red Candle, Blue Skateboard, Green Umbrella } + ``` + + Cool. Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. + + + (Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.) ### Example @@ -90,9 +101,11 @@ by: "James Larisch" } run() { - var clientAddition = Interface.receiveInput(); // contrived - this.addItem(clientAddition); - var receivedState = mySocket.nonBlockingRead(); // contrived + var clientAddition = Interface.nonBlockingReceiveInput(); // invented + if (clientAddition !== undefined) { + this.addItem(clientAddition); + } + var receivedState = mySocket.nonBlockingRead(); // invented if (receivedState !== undefined) { this.receiveState(receivedState); } @@ -104,26 +117,84 @@ by: "James Larisch" // theoretical usage - var socket = new UDPSocket(); // contrived + var socket = new UDPSocket(); // invented var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets cart.run(); + cart.items // the cart's items ``` Here is an (almost) fully functional shopping cart program. You can imagine this code running across multiple nodes scattered over the world. The meat of the program lies in the `run()` method. Let's walk through that: 1. Program receives an addition to the cart from the user. - 2. Program adds that item to the current local state. + 2. Program adds that item to the current local state if it exists. 3. Program checks its UDP socket for any messages. 4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. 5. Synchronize our current state by sending our state to every peer that we know about. 6. Sleep for 10 seconds. 7. Repeat! - Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. + W - This is the *Strong Eventual Consistency* concept that Aviral introduced in Section 6. It's *eventual* because given a long enough timeline the clients' states will sync up: they are constantly trying to synchronize. [mention you can't remove things trivially, this is actually a CRDT, union is a monotonic operation] + Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. This should remind you of Dynamo's gossiping: nodes are constantly attempting to converge. + + Both systems are eventually consistent - the difference here is our Javascript shopping cart displays *strong* eventual consistency. It's strong because it requires no specialized resolution. When a node transmits its state to another node, there's absolutely no question about how to integrate that state into the current one. There's no conflict. ### The Intern - Unfortunately Jerry, the intern, has found your code. He'd like to make a few changes. He messes it up somehow. I'm not entirely sure how yet. + Unfortunately Jerry, the intern, has found your code. He'd like to add `remove` functionality to the cart. So he makes the following changes: + + ```javascript + class Cart { + constructor(peers, socket) { + this.mySocket = socket; + this.peers = peers; + this.items = new Set(); + } + + addItem(item) { + this.items.add(item); + } + + synchronize() { + peers.forEach(function(peer) { + peer.send(items); + }); + } + + receiveState(items) { + // JERRY WAS HERE + this.items = this.items.intersection(items); + // END JERRY WAS HERE + } + + run() { + var clientAddition = Interface.nonBlockingReceiveInput(); // invented + if (clientAddition !== undefined) { + this.addItem(clientAddition); + } + // JERRY WAS HERE + var clientDeletion = Interface.nonBlockingReceiveInput(): + if (clientDeletion !== undefined) { + this.items.delete(clientDeletion); + } + // END JERRY WAS HERE + var receivedState = mySocket.nonBlockingRead(); // invented + if (receivedState !== undefined) { + this.receiveState(receivedState); + } + synchronize(); + sleep(10); + run(); + } + } + + // theoretical usage + + var socket = new UDPSocket(); // invented + var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets + cart.run(); + cart.items // the cart's items + ``` + + Uh-oh. Can you spot the problem? Let's break it down. In the original code, the current node's cart items were *unioned* with the communicating node's cart. Since there was no deletion, ### Guarantees The original Javascript we wrote down exhibits the property from Section 6 known as *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. -- cgit v1.2.3 From 734128b127c0322699feb036305e4896421783c2 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:12:57 -0500 Subject: Update futures.md --- chapter/2/futures.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 1ddbc02..02c2d5f 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -4,7 +4,20 @@ title: "Futures" by: "Kisalaya Prasad and Avanti Patil" --- -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. {% cite Uniqueness --file futures %} +#Introduction + +As human beings we have an ability to multitask ie. we can walk, talk and eat at the same time except when you sneeze. Sneeze is like a blocking activity from the normal course of action, because it forces you to stop what you’re doing for a brief moment and then you resume where you left off. Activities like multitasking are called multithreading in computer lingo. In contrast to this behaviour, computer processors are single threaded. So when we say that a computer system has multi-threaded environment, it is actually just an illusion created by processor where processor’s time is shared between multiple processes. Sometimes processor gets blocked when some tasks are hindered from normal execution due to blocking calls. Such blocking calls can range from IO operations like read/write to disk or sending/receiving packets to/from network. Blocking calls can take disproportionate amount of time compared to the processor’s task execution i.e. iterating over a list. + + +The processor can either handle blocking calls in two ways: +- **Synchronous method**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. +- **Asynchronous method**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. + +In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. + + + +{% cite Uniqueness --file futures %} ## References -- cgit v1.2.3 From 016620da68e304dfa1032c4d90204d94d9a2de69 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:16:02 -0500 Subject: Update futures.md --- chapter/2/futures.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 02c2d5f..1584075 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -16,8 +16,10 @@ The processor can either handle blocking calls in two ways: In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. +
+ timeline +
-{% cite Uniqueness --file futures %} ## References -- cgit v1.2.3 From e2a8069e2e8cb013483920a7ab5222528bd885ea Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:16:56 -0500 Subject: Add files via upload --- chapter/2/1.png | Bin 0 -> 14176 bytes chapter/2/10.png | Bin 0 -> 9834 bytes chapter/2/11.png | Bin 0 -> 12134 bytes chapter/2/12.png | Bin 0 -> 17071 bytes chapter/2/13.png | Bin 0 -> 21547 bytes chapter/2/14.png | Bin 0 -> 11405 bytes chapter/2/15.png | Bin 0 -> 15262 bytes chapter/2/2.png | Bin 0 -> 6152 bytes chapter/2/3.png | Bin 0 -> 13719 bytes chapter/2/4.png | Bin 0 -> 25404 bytes chapter/2/5.png | Bin 0 -> 20821 bytes chapter/2/6.png | Bin 0 -> 19123 bytes chapter/2/7.png | Bin 0 -> 30068 bytes chapter/2/8.png | Bin 0 -> 13899 bytes chapter/2/9.png | Bin 0 -> 6463 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 chapter/2/1.png create mode 100644 chapter/2/10.png create mode 100644 chapter/2/11.png create mode 100644 chapter/2/12.png create mode 100644 chapter/2/13.png create mode 100644 chapter/2/14.png create mode 100644 chapter/2/15.png create mode 100644 chapter/2/2.png create mode 100644 chapter/2/3.png create mode 100644 chapter/2/4.png create mode 100644 chapter/2/5.png create mode 100644 chapter/2/6.png create mode 100644 chapter/2/7.png create mode 100644 chapter/2/8.png create mode 100644 chapter/2/9.png diff --git a/chapter/2/1.png b/chapter/2/1.png new file mode 100644 index 0000000..1d98f19 Binary files /dev/null and b/chapter/2/1.png differ diff --git a/chapter/2/10.png b/chapter/2/10.png new file mode 100644 index 0000000..f54711d Binary files /dev/null and b/chapter/2/10.png differ diff --git a/chapter/2/11.png b/chapter/2/11.png new file mode 100644 index 0000000..7673d90 Binary files /dev/null and b/chapter/2/11.png differ diff --git a/chapter/2/12.png b/chapter/2/12.png new file mode 100644 index 0000000..7b2e13f Binary files /dev/null and b/chapter/2/12.png differ diff --git a/chapter/2/13.png b/chapter/2/13.png new file mode 100644 index 0000000..a2b8457 Binary files /dev/null and b/chapter/2/13.png differ diff --git a/chapter/2/14.png b/chapter/2/14.png new file mode 100644 index 0000000..5027666 Binary files /dev/null and b/chapter/2/14.png differ diff --git a/chapter/2/15.png b/chapter/2/15.png new file mode 100644 index 0000000..4f2c188 Binary files /dev/null and b/chapter/2/15.png differ diff --git a/chapter/2/2.png b/chapter/2/2.png new file mode 100644 index 0000000..a75c08b Binary files /dev/null and b/chapter/2/2.png differ diff --git a/chapter/2/3.png b/chapter/2/3.png new file mode 100644 index 0000000..9cc66b5 Binary files /dev/null and b/chapter/2/3.png differ diff --git a/chapter/2/4.png b/chapter/2/4.png new file mode 100644 index 0000000..8cfec98 Binary files /dev/null and b/chapter/2/4.png differ diff --git a/chapter/2/5.png b/chapter/2/5.png new file mode 100644 index 0000000..b86de04 Binary files /dev/null and b/chapter/2/5.png differ diff --git a/chapter/2/6.png b/chapter/2/6.png new file mode 100644 index 0000000..aaafdbd Binary files /dev/null and b/chapter/2/6.png differ diff --git a/chapter/2/7.png b/chapter/2/7.png new file mode 100644 index 0000000..7183fb6 Binary files /dev/null and b/chapter/2/7.png differ diff --git a/chapter/2/8.png b/chapter/2/8.png new file mode 100644 index 0000000..d6d2e0e Binary files /dev/null and b/chapter/2/8.png differ diff --git a/chapter/2/9.png b/chapter/2/9.png new file mode 100644 index 0000000..1b67a45 Binary files /dev/null and b/chapter/2/9.png differ -- cgit v1.2.3 From aaea26ca23ddb0492e9d74623a2c62d844286e13 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:27:44 -0500 Subject: Update futures.md
timeline
--- chapter/2/futures.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 1584075..2d7c51c 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -20,6 +20,185 @@ In the world of asynchronous communications many terminologies were defined to h timeline +#Motivation + + +A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. + + +The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. + + +Thunks can be thought of as a primitive notion of a Future or Promise. According to its inventor P. Z. Ingerman, thunks are "A piece of coding which provides an address". They were designed as a way of binding actual parameters to their formal definitions in Algol-60 procedure calls. If a procedure is called with an expression in the place of a formal parameter, the compiler generates a thunk which computes the expression and leaves the address of the result in some standard location. + + +The first mention of Futures was by Baker and Hewitt in a paper on Incremental Garbage Collection of Processes. They coined the term - call-by-futures to describe a calling convention in which each formal parameter to a method is bound to a process which evaluates the expression in the parameter in parallel with other parameters. Before this paper, Algol 68 also presented a way to make this kind of concurrent parameter evaluation possible, using the collateral clauses and parallel clauses for parameter binding. + + +In their paper, Baker and Hewitt introduced a notion of Futures as a 3-tuple representing an expression E consisting of (1) A process which evaluates E, (2) A memory location where the result of E needs to be stored, (3) A list of processes which are waiting on E. But, the major focus of their work was not on role of futures and the role they play in Asynchronous distributed computing, and focused on garbage collecting the processes which evaluate expressions not needed by the function. + + +The Multilisp language, presented by Halestead in 1985 built upon this call-by-future with a Future annotation. Binding a variable to a future expression creates a process which evaluates that expression and binds x to a token which represents its (eventual) result. This design of futures influenced the paper of design of Promises in Argus by Liskov and Shrira in 1988. Building upon the initial design of Future in Multilisp, they extended the original idea by introducing strongly typed Promises and integration with call streams.This made it easier to handle exception propagation from callee to the caller and also to handle the typical problems in a multi-computer system like network failures. This paper also talked about stream composition, a notion which is similar to promise pipelining today. + + +E is an object-oriented programming language for secure distributed computing, created by Mark S. Miller, Dan Bornstein, and others at Electric Communities in 1997. One of the major contribution of E was the first non-blocking implementation of Promises. It traces its routes to Joule which was a dataflow programming language. The notion of promise pipelining in E is inherited from Joule. + + +Among the modern languages, Python was perhaps the first to come up with something on the lines of E’s promises with the Twisted library. Coming out in 2002, it had a concept of Deferred objects, which were used to receive the result of an operation not yet completed. They were just like normal objects and could be passed along, but they didn’t have a value. They supported a callback which would get called once the result of the operation was complete. + + +Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API, it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. + + +#Different Definitions + + +Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. The definitions have changed a little over the years but the idea remained the same. + + +In some languages however, there is a subtle difference between what is a Future and a Promise. +“A ‘Future’ is a read-only reference to a yet-to-be-computed value”. +“A ‘Promise’ is a pretty much the same except that you can write to it as well.” + + +In other words, you can read from both Futures and Promises, but you can only write to Promises. You can get the Future associated with a Promise by calling the future method on it, but conversion in the other direction is not possible. Another way to look at it would be, if you Promise something, you are responsible for keeping it, but if someone else makes a Promise to you, you expect them to honor it in Future. + + +More technically, in Scala, “SIP-14 – Futures and Promises” defines them as follows: +A future is as a placeholder object for a result that does not yet exist. +A promise is a writable, single-assignment container, which completes a future. Promises can complete the future with a result to indicate success, or with an exception to indicate failure. + + +C# also makes the distinction between futures and promises. In C#, futures are implemented as Task and in fact in earlier versions of the Task Parallel Library futures were implemented with a class Future which later became Task. The result of the future is available in the readonly property Task.Result which returns T + + +In Javascript world, Jquery introduces a notion of Deferred objects which are used to represent a unit of work which is not yet finished. Deferred object contains a promise object which represent the result of that unit of work. Promises are values returned by a function, while the deferred object can be canceled by its caller. + + +In Java 8, the Future interface has methods to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation when it is complete. CompletableFutures can be thought of as Promises as their value can be set. But it also implements the Future interface and therefore it can be used as a Future too. Promises can be thought of as a future with a public set method which the caller (or anybody else) can use to set the value of the future. + +# Semantics of Execution + +Over the years promises and futures have been implemented in different programming languages and created a buzz in parallel computing world. We will take a look at some of the programming languages who designed frameworks to enhance performance of applications using Promises and futures. + +## Fork-Join + +Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheap. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. + + +In Java executor is an object which executes the Runnable tasks. Executors provides a way of abstracting out how the details of how a task will actually run. These details, like selecting a thread to run the task, how the task is scheduled are managed by the object implementing the Executor interface. Threads are an example of a Runnable in java. Executors can be used instead of creating a thread explicitly. + + +Similar to Executor, there is an ExecutionContext as part of scala.concurrent. The basic intent behind it is same as an Executor : it is responsible for executing computations. How it does it can is opaque to the caller. It can create a new thread, use a pool of threads or run it on the same thread as the caller, although the last option is generally not recommended. Scala.concurrent package comes with an implementation of ExecutionContext by default, which is a global static thread pool. + + +ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. + + +Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. + + +In Scala, futures are created using an ExecutionContext. This gives the users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Futures in scala are placeholders for a yet unknown value. A promise then can be thought of as a way to provide that value. A promise p completes the future returned by p.future. + + +Scala futures api expects an ExecutionContext to be passed along. This parameter is implicit, and usually ExecutionContext.global. An example : + + +
+ timeline +
+ +In this example, the global execution context is used to asynchronously run the created future. Taking another example, + +
+ timeline +
+ +It is generally a good idea to use callbacks with Futures, as the value may not be available when you want to use it. + +So, how does it all work together ? + +As we mentioned, Futures require an ExecutionContext, which is an implicit parameter to virtually all of the futures API. This ExecutionContext is used to execute the future. Scala is flexible enough to let users implement their own Execution Contexts, but let’s talk about the default ExecutionContext, which is a ForkJoinPool. + + +ForkJoinPool is ideal for many small computations that spawn off and then come back together. Scala’s ForkJoinPool requires the tasks submitted to it to be a ForkJoinTask. The tasks submitted to the global ExecutionContext is quietly wrapped inside a ForkJoinTask and then executed. ForkJoinPool also supports a possibly blocking task, using ManagedBlock method which creates a spare thread if required to ensure that there is sufficient parallelism if the current thread is blocked. To summarize, ForkJoinPool is an really good general purpose ExecutionContext, which works really well in most of the scenarios. + + + +## Event Loops + +Modern systems typically rely on many other systems to provide the functionality they do. There’s a file system underneath, a database system, and other web services to rely on for the information. Interaction with these components typically involves a period where we’re doing nothing but waiting for the response back. This is single largest waste of computing resources. + + +Javascript is a single threaded asynchronous runtime. Now, conventionally async programming is generally associated with multi-threading, but we’re not allowed to create new threads in Javascript. Instead, asynchronicity in Javascript is achieved using an event-loop mechanism. + + +Javascript has historically been used to interact with the DOM and user interactions in the browser, and thus an event-driven programming model was a natural fit for the language. This has scaled up surprisingly well in high throughput scenarios in NodeJS. + + +The general idea behind event-driven programming model is that the logic flow control is determined by the order in which events are processed. This is underpinned by a mechanism which is constantly listening for events and fires a callback when it is detected. This is the Javascript’s event loop in a nutshell. + + +A typical Javascript engine has a few basic components. They are : +- **Heap** +Used to allocate memory for objects +- **Stack** +Function call frames go into a stack from where they’re picked up from top to be executed. +- **Queue** + A message queue holds the messages to be processed. + + +Each message has a callback function which is fired when the message is processed. These messages can be generated by user actions like button clicks or scrolling, or by actions like HTTP requests, request to a database to fetch records or reading/writing to a file. + + +Separating when a message is queued from when it is executed means the single thread doesn’t have to wait for an action to complete before moving on to another. We attach a callback to the action we want to do, and when the time comes, the callback is run with the result of our action. Callbacks work good in isolation, but they force us into a continuation passing style of execution, what is otherwise known as Callback hell. + +
+ timeline +
+ +**Programs must be written for people to read, and only incidentally for machines to execute.** - *Harold Abelson and Gerald Jay Sussman* + +Promises are an abstraction which make working with async operations in javascript much more fun. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. + +The ES2015 spec specifies that “promises must not fire their resolution/rejection function on the same turn of the event loop that they are created on.” This is an important property because it ensures deterministic order of execution. Also, once a promise is fulfilled or failed, the promise’s value MUST not be changed. This ensures that a promise cannot be resolved more than once. + +Let’s take an example to understand the promise resolution workflow as it happens inside the Javascript Engine. + +Suppose we execute a function, here g() which in turn, calls function f(). Function f returns a promise, which, after counting down for 1000 ms, resolves the promise with a single value, true. Once f gets resolved, a value true or false is alerted based on the value of the promise. + + +
+ timeline +
+ +Now, javascript’s runtime is single threaded. This statement is true, and not true. The thread which executes the user code is single threaded. It executes what is on top of the stack, runs it to completion, and then moves onto what is next on the stack. But, there are also a number of helper threads which handle things like network or timer/settimeout type events. This timing thread handles the counter for setTimeout. + +
+ timeline +
+ +Once the timer expires, the timer thread puts a message on the message queue. The queued up messages are then handled by the event loop. The event loop as described above, is simply an infinite loop which checks if a message is ready to be processed, picks it up and puts it on the stack for it’s callback to be executed. + +
+ timeline +
+ +Here, since the future is resolved with a value of true, we are alerted with a value true when the callback is picked up for execution. + +
+ timeline +
+ +Some finer details : +We’ve ignored the heap here, but all the functions, variables and callbacks are stored on heap. +As we’ve seen here, even though Javascript is said to be single threaded, there are number of helper threads to help main thread do things like timeout, UI, network operations, file operations etc. +Run-to-completion helps us reason about the code in a nice way. Whenever a function starts, it needs to finish before yielding the main thread. The data it accesses cannot be modified by someone else. This also means every function needs to finish in a reasonable amount of time, otherwise the program seems hung. This makes Javascript well suited for I/O tasks which are queued up and then picked up when finished, but not for data processing intensive tasks which generally take long time to finish. +We haven’t talked about error handling, but it gets handled the same exact way, with the error callback being called with the error object the promise is rejected with. + + +Event loops have proven to be surprisingly performant. When network servers are designed around multithreading, as soon as you end up with a few hundred concurrent connections, the CPU spends so much of its time task switching that you start to lose overall performance. Switching from one thread to another has overhead which can add up significantly at scale. Apache used to choke even as low as a few hundred concurrent users when using a thread per connection while Node can scale up to a 100,000 concurrent connections based on event loops and asynchronous IO. + ## References -- cgit v1.2.3 From fbbddcd432af1391fedacb92441097aa90f78681 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:28:38 -0500 Subject: Update futures.md --- chapter/2/futures.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 2d7c51c..e7affd9 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -200,6 +200,31 @@ We haven’t talked about error handling, but it gets handled the same exact way Event loops have proven to be surprisingly performant. When network servers are designed around multithreading, as soon as you end up with a few hundred concurrent connections, the CPU spends so much of its time task switching that you start to lose overall performance. Switching from one thread to another has overhead which can add up significantly at scale. Apache used to choke even as low as a few hundred concurrent users when using a thread per connection while Node can scale up to a 100,000 concurrent connections based on event loops and asynchronous IO. +##Thread Model + + +Oz programming language introduced an idea of dataflow concurrency model. In Oz, whenever the program comes across an unbound variable, it waits for it to be resolved. This dataflow property of variables helps us write threads in Oz that communicate through streams in a producer-consumer pattern. The major benefit of dataflow based concurrency model is that it’s deterministic - same operation called with same parameters always produces the same result. It makes it a lot easier to reason about concurrent programs, if the code is side-effect free. + + +Alice ML is a dialect of Standard ML with support for lazy evaluation, concurrent, distributed, and constraint programming. The early aim of Alice project was to reconstruct the functionalities of Oz programming language on top of a typed programming language. Building on the Standard ML dialect, Alice also provides concurrency features as part of the language through the use of a future type. Futures in Alice represent an undetermined result of a concurrent operation. Promises in Alice ML are explicit handles for futures. + + +Any expression in Alice can be evaluated in it's own thread using spawn keyword. Spawn always returns a future which acts as a placeholder for the result of the operation. Futures in Alice ML can be thought of as functional threads, in a sense that threads in Alice always have a result. A thread is said to be touching a future if it performs an operation that requires the value future is a placeholder for. All threads touching a future are blocked until the future is resolved. If a thread raises an exception, the future is failed and this exception is re-raised in the threads touching it. Futures can also be passed along as values. This helps us achieve the dataflow model of concurrency in Alice. + + +Alice also allows for lazy evaluation of expressions. Expressions preceded with the lazy keyword are evaluated to a lazy future. The lazy future is evaluated when it is needed. If the computation associated with a concurrent or lazy future ends with an exception, it results in a failed future. Requesting a failed future does not block, it simply raises the exception that was the cause of the failure. + +#Implicit vs. Explicit Promises + + +We define Implicit promises as ones where we don’t have to manually trigger the computation vs Explicit promises where we have to trigger the resolution of future manually, either by calling a start function or by requiring the value. This distinction can be understood in terms of what triggers the calculation : With Implicit promises, the creation of a promise also triggers the computation, while with Explicit futures, one needs to triggers the resolution of a promise. This trigger can in turn be explicit, like calling a start method, or implicit, like lazy evaluation where the first use of a promise’s value triggers its evaluation. + + +The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. Also, lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. + + +Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. + ## References {% bibliography --file futures %} -- cgit v1.2.3 From 7c22b7077038d0947f2159118c3ee3d18b321fe7 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:32:15 -0500 Subject: Update futures.md --- chapter/2/futures.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index e7affd9..45d2d0b 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -222,9 +222,88 @@ We define Implicit promises as ones where we don’t have to manually trigger th The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. Also, lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. - Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. +# Promise Pipelining +One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. + +
+ timeline +
+ +Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. + +The history of promise pipelining can be traced back to the call-streams in Argus and channels in Joule. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated futures into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. + + +Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable over Promises, and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. + + +In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. + +# Handling Errors + +In a synchronous programming model, the most logical way of handling errors is a try...catch block. + +
+ timeline +
+ + +Unfortunately, the same thing doesn’t directly translate to asynchronous code. + +
+ timeline +
+ +In javascript world, some patterns emerged, most noticeably the error-first callback style, also adopted by Node. Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises come to the rescue. + + +Although most of the earlier papers did not talk about error handling, the Promises paper by Liskov and Shrira did acknowledge the possibility of failure in a distributed environment. They talked about propagation of exceptions from the called procedure to the caller and also about call streams, and how broken streams could be handled. E language also talked about broken promises and setting a promise to the exception of broken references. + +In modern languages, Promises generally come with two callbacks. One to handle the success case and other to handle the failure. + +
+ timeline +
+ +In Javascript, Promises also have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. + + +
+ timeline +
+ +The same behavior can be written using catch block. + +
+ timeline +
+ + +#Futures and Promises in Action + + +##Twitter Finagle + + +Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes it easy to build robust clients and servers in Java, Scala, or any JVM-hosted language. It uses idea of Futures to encapsulate concurrent tasks and are analogous to threads, but even more lightweight. + + +##Correctables +Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adrian Seredinschi at OSDI ‘16, in a paper titled Incremental Consistency Guarantees for Replicated Objects. As the title suggests, Correctables aim to solve the problems with consistency in replicated objects. They provide incremental consistency guarantees by capturing successive changes to the value of a replicated object. Applications can opt to receive a fast but possibly inconsistent result if eventual consistency is acceptable, or to wait for a strongly consistent result. Correctables API draws inspiration from, and builds on the API of Promises. Promises have a two state model to represent an asynchronous task, it starts in blocked state and proceeds to a ready state when the value is available. This cannot represent the incremental nature of correctables. Instead, Correctables have a updating state when it starts. From there on, it remains in updating state during intermediate updates, and when the final result is available, it transitions to final state. If an error occurs in between, it moves into an error state. Each state change triggers a callback. + +
+ timeline +
+ +##Folly Futures +Folly is a library by Facebook for asynchronous C++ inspired by the implementation of Futures by Twitter for Scala. It builds upon the Futures in the C++11 Standard. Like Scala’s futures, they also allow for implementing a custom executor which provides different ways of running a Future (thread pool, event loop etc). + + +##NodeJS Fiber +Fibers provide coroutine support for v8 and node. Applications can use Fibers to allow users to write code without using a ton of callbacks, without sacrificing the performance benefits of asynchronous IO. Think of fibers as light-weight threads for nodejs where the scheduling is in the hands of the programmer. The node-fibers library doesn’t recommend using raw API and code together without any abstractions, and provides a Futures implementation which is ‘fiber-aware’. + ## References {% bibliography --file futures %} -- cgit v1.2.3 From 485dcc5031aab9668acb93a0f37728f5e0bbd183 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Fri, 9 Dec 2016 21:37:19 -0500 Subject: Delete temp.md --- chapter/2/temp.md | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 chapter/2/temp.md diff --git a/chapter/2/temp.md b/chapter/2/temp.md deleted file mode 100644 index 0506ded..0000000 --- a/chapter/2/temp.md +++ /dev/null @@ -1,24 +0,0 @@ -# What are promises ? - -- Future, promise, delay, or deferred. -- Definition -- States of promises - -# Historical Background - -- Algol thunk -- Incremental garbage collection of Processes - 1977 -- 1995 Joule channels -- 1997 Mark Miller - E - -# Current state of things - -- Lot of work done in Javascript -- Scala -- Finagle -- Java8 -- ? - -# Future Work - -- ? -- cgit v1.2.3 From 58eb648a4e803c5e015d765ce5914899f1f374b7 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 9 Dec 2016 23:46:53 -0500 Subject: Lacking programming language specifics --- chapter/7/langs-consistency.md | 322 +++++++++++++++++++++++------------------ 1 file changed, 184 insertions(+), 138 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 4584fd2..a77110f 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -18,206 +18,252 @@ by: "James Larisch" 1. Take another HyperLoop train from Los Angeles to Paris (5 hours). 1. Log back into the site, add another skateboard, and checkout. - Let's assume you have a server in every single country, and customers connect to the geographically closest server. +Let's assume you have a server in every single country, and customers connect to the geographically closest server. - How can we ensure that the client sees the same cart at every point in her trip? +How can we ensure that the client sees the same cart at every point in her trip? - If you only had one user of your website, this wouldn't be too hard. You could manually, constantly modify and check on all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay in-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. +If you only had one user of your website, this wouldn't be too hard. You could manually, constantly modify and check on all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay in-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. - But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). +But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). - It's possible to implement "consensus" protocols such as Paxos and 3-Phase-Commit that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? +It's possible to implement "consensus" protocols such as Paxos and 3-Phase-Commit that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? - This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be *available*. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. +This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be *available*. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. - Turns out there's a company out there called Amazon.com - and they've been having a similar problem. Amazon sells things on their website too, and users can add and remove things from their cart. Amazon has lots of servers spread out across the world. They also have quite a few customers. They need to ensure their customers' carts are robust: if/when servers fail or lose communication with one another, a "best-effort" should be made to display the customer's cart. Amazon acknowledges that failure, latency, or HyperLoop-traveling users can cause inconsistent cart data, depending on which server you ask. How does Amazon resolve these issues? +Turns out there's a company out there called Amazon.com - and they've been having a similar problem. Amazon sells things on their website too, and users can add and remove things from their cart. Amazon has lots of servers spread out across the world. They also have quite a few customers. They need to ensure their customers' carts are robust: if/when servers fail or lose communication with one another, a "best-effort" should be made to display the customer's cart. Amazon acknowledges that failure, latency, or HyperLoop-traveling users can cause inconsistent cart data, depending on which server you ask. How does Amazon resolve these issues? ## Dynamo - Amazon built DynamoDB, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. +Amazon built DynamoDB, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. - Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts...aaaaaand... push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. +Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts...aaaaaand... push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. - How should Amazon's shopping cart procede with resolution? It may be fed two cart states like so: +How should Amazon's shopping cart procede with resolution? It may be fed two cart states like so: - ``` - James's Cart V1 | James's Cart V2 - ----------------------------------- - Red Candle | Red Candle - Blue Skateboard | Green Umbrella - ``` +``` +James's Cart V1 | James's Cart V2 +----------------------------------- +Red Candle | Red Candle +Blue Skateboard | Green Umbrella +``` - Amazon doesn't want to accidently *remove* anything from your cart, so it errs on the side of inclusion. If given this particular conflict, you would see: +Amazon doesn't want to accidently *remove* anything from your cart, so it errs on the side of inclusion. If given this particular conflict, you would see: - ``` - James's Cart - ------------ - Red Candle - Blue Skateboard - Green Umbrella - ``` +``` +James's Cart +------------ +Red Candle +Blue Skateboard +Green Umbrella +``` - It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. +It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. ### Good & Bad - What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has an insanely high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. +What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has an insanely high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. - Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed general-purpose data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. +Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed general-purpose data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. - Let's try this transfiguration on the shopping cart. Let's strip it down: how does Amazon handle resolution, really? It treats shopping cart versions as sets of items. In order to perform resolution, Amazon unions the two sets. +Let's try this transfiguration on the shopping cart. Let's strip it down: how does Amazon handle resolution, really? It treats shopping cart versions as sets of items. In order to perform resolution, Amazon unions the two sets. - ``` - { Red Candle, Blue Skateboard } U { Red Candle, Green Umbrella } == { Red Candle, Blue Skateboard, Green Umbrella } - ``` +``` +{ Red Candle, Blue Skateboard } U { Red Candle, Green Umbrella } == { Red Candle, Blue Skateboard, Green Umbrella } +``` - Cool. Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. +Cool. Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. - (Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.) +(Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.) ### Example - Let's take a look at the following Javascript. For simplicity's sake, let's pretend users can only add things to their shopping cart. +Let's take a look at the following Javascript. For simplicity's sake, let's pretend users can only add things to their shopping cart. - ```javascript - class Cart { - constructor(peers, socket) { - this.mySocket = socket; - this.peers = peers; - this.items = new Set(); - } +```javascript +class Cart { + constructor(peers, socket) { + this.mySocket = socket; + this.peers = peers; + this.items = new Set(); + } - addItem(item) { - this.items.add(item); - } + addItem(item) { + this.items.add(item); + } - synchronize() { - peers.forEach(function(peer) { - peer.send(items); - }); - } + synchronize() { + peers.forEach(function(peer) { + peer.send(items); + }); + } - receiveState(items) { - this.items = this.items.union(items); - } + receiveState(items) { + this.items = this.items.union(items); + } - run() { - var clientAddition = Interface.nonBlockingReceiveInput(); // invented - if (clientAddition !== undefined) { - this.addItem(clientAddition); - } - var receivedState = mySocket.nonBlockingRead(); // invented - if (receivedState !== undefined) { - this.receiveState(receivedState); - } - synchronize(); - sleep(10); - run(); + run() { + var clientAddition = Interface.nonBlockingReceiveInput(); // invented + if (clientAddition !== undefined) { + this.addItem(clientAddition); + } + var receivedState = mySocket.nonBlockingRead(); // invented + if (receivedState !== undefined) { + this.receiveState(receivedState); } + synchronize(); + sleep(10); + run(); } +} - // theoretical usage +// theoretical usage - var socket = new UDPSocket(); // invented - var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets - cart.run(); - cart.items // the cart's items - ``` +var socket = new UDPSocket(); // invented +var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets +cart.run(); +cart.items // the cart's items +``` - Here is an (almost) fully functional shopping cart program. You can imagine this code running across multiple nodes scattered over the world. The meat of the program lies in the `run()` method. Let's walk through that: - 1. Program receives an addition to the cart from the user. - 2. Program adds that item to the current local state if it exists. - 3. Program checks its UDP socket for any messages. - 4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. - 5. Synchronize our current state by sending our state to every peer that we know about. - 6. Sleep for 10 seconds. - 7. Repeat! +Here is an (almost) fully functional shopping cart program. You can imagine this code running across multiple nodes scattered over the world. The meat of the program lies in the `run()` method. Let's walk through that: +1. Program receives an addition to the cart from the user. +2. Program adds that item to the current local state if it exists. +3. Program checks its UDP socket for any messages. +4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. +5. Synchronize our current state by sending our state to every peer that we know about. +6. Sleep for 10 seconds. +7. Repeat! - W +Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. This should remind you of Dynamo's gossiping: nodes are constantly attempting to converge. - Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. This should remind you of Dynamo's gossiping: nodes are constantly attempting to converge. - - Both systems are eventually consistent - the difference here is our Javascript shopping cart displays *strong* eventual consistency. It's strong because it requires no specialized resolution. When a node transmits its state to another node, there's absolutely no question about how to integrate that state into the current one. There's no conflict. +Both systems are eventually consistent - the difference here is our Javascript shopping cart displays *strong* eventual consistency. It's strong because it requires no specialized resolution. When a node transmits its state to another node, there's absolutely no question about how to integrate that state into the current one. There's no conflict. ### The Intern - Unfortunately Jerry, the intern, has found your code. He'd like to add `remove` functionality to the cart. So he makes the following changes: - - ```javascript - class Cart { - constructor(peers, socket) { - this.mySocket = socket; - this.peers = peers; - this.items = new Set(); - } +Unfortunately Jerry, the intern, has found your code. He'd like to add `remove` functionality to the cart. So he makes the following changes: + +```javascript +class Cart { + constructor(peers, socket) { + this.mySocket = socket; + this.peers = peers; + this.items = new Set(); + } - addItem(item) { - this.items.add(item); - } + addItem(item) { + this.items.add(item); + } - synchronize() { - peers.forEach(function(peer) { - peer.send(items); - }); - } + synchronize() { + peers.forEach(function(peer) { + peer.send(items); + }); + } - receiveState(items) { - // JERRY WAS HERE - this.items = this.items.intersection(items); - // END JERRY WAS HERE - } + receiveState(items) { + // JERRY WAS HERE + this.items = this.items.intersection(items); + // END JERRY WAS HERE + } - run() { - var clientAddition = Interface.nonBlockingReceiveInput(); // invented - if (clientAddition !== undefined) { - this.addItem(clientAddition); - } - // JERRY WAS HERE - var clientDeletion = Interface.nonBlockingReceiveInput(): - if (clientDeletion !== undefined) { - this.items.delete(clientDeletion); - } - // END JERRY WAS HERE - var receivedState = mySocket.nonBlockingRead(); // invented - if (receivedState !== undefined) { - this.receiveState(receivedState); - } - synchronize(); - sleep(10); - run(); + run() { + var clientAddition = Interface.nonBlockingReceiveInput(); // invented + if (clientAddition !== undefined) { + this.addItem(clientAddition); } + // JERRY WAS HERE + var clientDeletion = Interface.nonBlockingReceiveInput(): + if (clientDeletion !== undefined) { + this.items.delete(clientDeletion); + } + // END JERRY WAS HERE + var receivedState = mySocket.nonBlockingRead(); // invented + if (receivedState !== undefined) { + this.receiveState(receivedState); + } + synchronize(); + sleep(10); + run(); } +} + +// theoretical usage + +var socket = new UDPSocket(); // invented +var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets +cart.run(); +cart.items // the cart's items +``` + +Uh-oh. Can you spot the problem? Let's break it down. In the original code, the current node's cart items were *unioned* with the communicating node's cart. Since there was no deletion, carts could only ever expand. Here was Jerry's plan: + +``` +> I want to delete things. If you delete something from node 1, and intersect it's state from node 2, the item will be deleted from node 2 as well. + +Node 1: { A, B } +Node 2: { A, B } + +delete(Node2, A) + +Node 1: { A, B } +Node 2: { B } - // theoretical usage +Node1 = Node1.intersect(Node2) +Node1: { B } +``` - var socket = new UDPSocket(); // invented - var cart = new Cart(peerSockets, socket); // peerSockets is an array of UDP sockets - cart.run(); - cart.items // the cart's items - ``` +The reasoning is sound. However, there's a huge issue here. We've flipped the `union` operation on its head! Now, carts can *never* expand! They can only either stay the same size or shrink. So although Jerry's contrived example works, it's impossible to ever reach the beginning states of Node 1 and Node 2 unless those two nodes receive *the same writes*. Let's take it from the top: - Uh-oh. Can you spot the problem? Let's break it down. In the original code, the current node's cart items were *unioned* with the communicating node's cart. Since there was no deletion, +``` +Node 1: { } +Node 2: { } + +add(Node1, A) +add(Node2, B) + +Node 1: { A } +Node 2: { B } + +Node1_temp = Node1.intersect(Node2) +Node2_temp = Node2.intersect(Node1) +Node1 = Node1_temp +Node2 = Node2_temp + +Node 1: { } +Node 2: { } +``` + +This is pretty nasty. Jerry has come along and with a few lines of code he's obliterated our nice strong eventually consistent code. Surely there's a better way. ### Guarantees - The original Javascript we wrote down exhibits the property from Section 6 known as *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. +The original Javascript we wrote down exhibits the property from Section 6 known as *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. - But where should these guarantees live? In the above Javascript example, the guarantees aren't guarantees at all, really. There's no restriction on what the programmer is allowed to do - the programmer has simply constructed a program that mirrors guarantees that she has modeled in her brain. In order to maintain properties such as *monotonicity*, she must constantly check the model in her brain against the code. We haven't really helped the programmer out that much - she has a lot of thinking to do. +But where should these guarantees live? In the above Javascript example, the guarantees aren't guarantees at all, really. There's no restriction on what the programmer is allowed to do - the programmer has simply constructed a program that mirrors guarantees that she has modeled in her brain. In order to maintain properties such as *monotonicity*, she must constantly check the model in her brain against the code. We haven't really helped the programmer out that much - she has a lot of thinking to do. - At the disk hardward level, there are certain mechanisms in place to ensure that data does not become corrupted when multiple things attempt to write bits to the same physical location. This is considered a type of IO-consistency. It doesn't help much with our shopping cart, but it's certainly necessary. These important guarantees facilitate the higher level abstractions by ensuring low-level safety. It would be unreasonable to expect our disks to enforce monotonicity, for example, since this would restrict usage of disks to monotonic programs only (more on this later!). But on the other hand, as we've seen, pushing the consistency to the application/programmer level is also unreasonable. Our tools should work for us. +Databases such as PostgreSQL have issues like this as well, though they handle them quite differently, masters may need to ensure that write have occurred on every slave before the database becomes available for reading. A database system like this has pushed consistency concerns to the IO-level, completely out of the users control. They are enforced on system reads and system writes. This approach gives programmers no flexibility: as demonstrated with our shopping cart example, there's no need for these type of restrictions; we can tolerate inconsistency in order to maintain availability. - Why not push the consistency guarantees in between? Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. +Why not push the consistency guarantees in between the IO-level and the application-level? Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. - Wouldn't it be great if tools like this existed? +Wouldn't it be great if tools like this existed? ### Bloom - The dudes/dudettes at Berkeley seem to think so too. +[ Introduce Bloom ] #### Restriction & Danger - [Bloom restricts you, it's different, and it's dangerous] +[Bloom restricts you, it's different, and it's dangerous] ### Lasp - [Library not language, embeddable, not dangerous] - Instead of trying to do it all (and accepting danger), it tries to be embeddable (and truly restrictive.) +[ Introduce Lasp ] +Instead of trying to do it all (and accepting danger), it tries to be embeddable (and truly restrictive.) + +### Utilization + +Lasp is an Erlang library, and for good reason. Remember the initial discussion and reasoning for models such as Bloom and Lasp: we have a specific type of application that doesn't require tight consistency constraints. The constraints that do exist have been formalized, and we can be quite sure that by using a DSL like Lasp, we'll be safe from interns like Jerry. But Lasp can't do everything. More generally, eventual consistency doesn't solve every problem. + +PostgreSQL enforce very specific and restrictive IO-level consistency, and this was too much for our needs. But it's certainly not too much for *all* needs. There certainly are applications (take banking, for example) in which consistency is extremely important. You certainly are not allowed to double spend your money depending on how fast you can travel to a different server, so eventual consistency is not enough! All servers must coordinate. + +There's a key principle here, however: distributed programming models that attempt to accomdate everything end up doing nothing well; models that accept compromises and formalize certain properties end up being extremely useful for a subset of domains. +Most programming languages are "general-use". This works for single machine programming. As the world moves toward distributed programming, programmers must adopt models / languages / libraries that are built for their domain. It forces serious thought on the part of the programmer: what *exactly* am I trying to achieve, and what am I willing to sacrifice? +We've known for quite a while that when we're talking about multiple machines, we can't have it all. Our tools must now reflect this mantra. Our sanity and the safety of our programs depends on it. ## References -- cgit v1.2.3 From 1b7f186b0ce9dd0014e021331f70dc22bb5798ff Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 9 Dec 2016 23:53:49 -0500 Subject: formatting --- chapter/7/langs-consistency.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index a77110f..b8f013f 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -124,13 +124,13 @@ cart.items // the cart's items ``` Here is an (almost) fully functional shopping cart program. You can imagine this code running across multiple nodes scattered over the world. The meat of the program lies in the `run()` method. Let's walk through that: -1. Program receives an addition to the cart from the user. -2. Program adds that item to the current local state if it exists. -3. Program checks its UDP socket for any messages. -4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. -5. Synchronize our current state by sending our state to every peer that we know about. -6. Sleep for 10 seconds. -7. Repeat! + 1. Program receives an addition to the cart from the user. + 2. Program adds that item to the current local state if it exists. + 3. Program checks its UDP socket for any messages. + 4. If it received one, it's means another instance of this program has sent us its state. What is state in this case? Simply a set of cart items. Let's handle this set of items by unioning it with our current set. + 5. Synchronize our current state by sending our state to every peer that we know about. + 6. Sleep for 10 seconds. + 7. Repeat! Hopefully it's clear that if a client adds an item to her cart in Beijing and then 10 seconds later checks her cart in Paris, she should see the same thing. Well, not exactly - remember, the network is unreliable, and Beijing's `synchronize` messages might have been dropped. But no worries! Beijing is `synchronizing` again in another 10 seconds. This should remind you of Dynamo's gossiping: nodes are constantly attempting to converge. -- cgit v1.2.3 From 4800d06cb7fd90b100ff590250986b7f2de0324c Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Mon, 12 Dec 2016 00:18:04 -0500 Subject: Added a lot of stuff about partitions and handling them --- ...dic-to-basic-how-the-database-ph-has-changed.md | 69 ++++++++++++---------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md index 99b12d0..540a2a9 100644 --- a/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md +++ b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md @@ -24,7 +24,7 @@ Single node databases can simply rely upon locking to ensure *ACID*ity. Each tra This model works well on a single node. But it exposes a serious limitation when too many concurrent transactions are performed. A single node database server will only be able to process so many concurrent read operations. The situation worsens when many concurrent write operations are performed. To guarantee *ACID*ity, the write operations will be performed in sequence. The last write request will have to wait for an arbitrary amount of time, a totally unacceptable situation for many real time systems. This requires the application developer to decide on a **Scaling** strategy. -## 2. Transaction Volume +### Transaction Volume To increase the volume of transactions against a database, two scaling strategies can be considered @@ -40,23 +40,21 @@ Horizontal Scaling through functional partitioning enables high degree of scalab 2PC is a blocking protocol and is usually employed for updates which can take from a few milliseconds up to a few minutes to commit. This means that while a transaction is being processed, other transactions will be blocked. So the application that initiated the transaction will be blocked. Another option is to handle the consistency across databases at the application level. This only complicates the situation for the application developer who is likely to implement a similar strategy if *ACID*ity is to be maintained. -# The part below is in bits and pieces. A lot of details need to be filled in. -## 3. A Distributed Concoction - -**I am a cute diagram for the paragraph below.** - -In the network above, all messages between the node set G1 and G2 are lost due to a network issue. The system as a whole detects this situation. There are two options - +## The **ACID*ic side effect +Traditional distributed databases provide strong consistency guarantees. While processing a transaction, they block other client requests. Imagine a large scale internet based shopping service with consistency enforced across functional partitions. This means that any tra -* The system allows any application to read and write to data objects on these nodes as they are **available**. The application writes to a data object. This write operation completes in one of the nodes of G1. Due to **network partition**, this change is not propagated to replicas of the data object in G2. Subsequently the application tries to read the value of that data object and the read operation executes in one of the nodes of G2. The read operation returns the older value of the data object, thus making the application state not **consistent**. +## 3. A Distributed Concoction -## 4. The volatile network +![Partitioned Network](resources/partitioned-network.jpg) -Network Partition is a contentious subject among distributed database architects. While some maintain that network partitions are rare, other point to their. 9 +In the network above, all messages between the node set M and N are lost due to a network issue. The system as a whole detects this situation. There are two options - +1. **Availability first** - The system allows any application to read and write to data objects on these nodes independently even though they are not able to communicate. The application writes to a data object on node M. Due to **network partition**, this change is not propagated to replicas of the data object in N. Subsequently, the application tries to read the value of that data object and the read operation executes in one of the nodes of N. The read operation returns the older value of the data object, thus making the application state not **consistent**. -## 4. The spicy ingredients +2. **Consistency first** - The system does not allow any application to write to data objects as it cannot ensure **consistency** of replica states. This means that the system is perceived to be **unavailable** by the applications. +If there are no partitions, clearly both consistency and availability can be guaranteed by the system. This simple observation shows a tension between three issues concerning distributed systems - @@ -66,7 +64,6 @@ This simple observation shows a tension between three issues concerning distribu **Partitioning** is the loss of messages between the nodes of a distributed system. - This observation led Eric Brewer to conjecture in an invited talk at PODC 2000 -
It is impossible for a web service to provide the following three guarantees: @@ -74,39 +71,49 @@ Consistency Availability Partition Tolerance
-It is clear that the prime culprit here is network partition. If there are no network partitions, any distributed service will be both highly available and provide strong consistency of shared data objects. Unfortunately, network partitions cannot be remedied in a distributed system. - - - - - -## 3. Strong Consistency - - - - +This is called the CAP theorem. It is clear that the prime culprit here is network partition. If there are no network partitions, any distributed service will be both highly available and provide strong consistency of shared data objects. Unfortunately, network partitions cannot be remedied in a distributed system. +## The **BASE**ic distributed state +When viewed through the lens of CAP theorem and its consequences on distributed application design, we realize that we cannot commit to perfect availability and strong consistency. But surely we can explore the middle ground. We can guarantee availability most of the time with sometimes inconsistent view of the data. The consistency is eventually achieved when the communication between the nodes resumes. This leads to the following properties of the current distributed applications, referred to by the acronym BASE. +**Basically Available** services are those which are partially available when partitions happen. Thus, they appear to work most of the time. Partial failures result in the system being unavailable only for a section of the users. +**Soft State** services provide no strong consistency guarantees. They are not write consistent. Since replicas may not be mutually consistent, applications have to accept stale data. +**Eventually Consistent** services try to make application state consistent whenever possible. +## Partitions and latency +Any large scale distributed system has to deal with latency issue. In fact, network partitions and latency are fundamentally related. Once a request is made and no response is received within some duration, the sender node has to assume that a partition has happened. The sender node can take one of the following steps: -We observed how in the event of a network partition, we could not have both availability and consistency at the same time. Let's study their pairwise interaction - +1) Cancel the operation as a whole. In doing so, the system is choosing consistency over availability. +2) Proceed with the rest of the operation. This can lead to inconsistency but makes the system highly available. +3) Retry the operation until it succeeds. This means that the system is trying to ensure consistency and reducing availability. +Essentially, a partition is an upper bound on the time spent waiting for a response. Whenever this upper bound is exceeded, the system chooses C over A or A over C. Also, the partition may be perceived only by two nodes of a system as opposed to all of them. This means that partitions are a local occurrence. -For many applications *ACID*ic datastores impose a more severe consistency guarantee than is actually needed and this reduces their availability. By relaxing the constraints on data consistency one can achieve higher scalability and availability. +## Handling Partitions +Once a partition has happened, it has to be handled explicitly. The designer has to decide which operations will be functional during partitions. The partitioned nodes will continue their attempts at communication. When the nodes are able to establish communication, the system has to take steps to recover from the partitions. -### 2. The **BASE**ic distributed state +### Partition mode functionality +When at least one side of the system has entered into partition mode, the system has to decide which functionality to support. Deciding this depends on the invariants that the system must maintain. Depending on the nature of problem, the designer may choose to compromise on certain invariants by allowing partitioned system to provide functionality which might violate them. This means the designer is choosing availability over consistency. Certain invariants may have to be maintained and operations that will violate them will either have to be modified or prohibited. This means the designer is choosing consistency over availability. +Deciding which operations to prohibit, modify or delay also depends on other factors such as the node. If the data is stored on the same node, then operations on that data can typically proceed on that node but not on other node. +In any event, the bottomline is that if the designer wishes for the system to be available, certain operations have to be allowed. The node has to maintain a history of these operations so that it can be merged with the rest of the system when it is able to reconnect. +Since the operations can happen simultaneously on multiple disconnected nodes, all sides will maintain this history. One way to maintain this information is through version vectors. +Another interesting problem is to communicate the progress of these operations to the user. Until the system gets out of partition mode, the operations cannot be committed completely. Till then, the user interface has to faithfully represent their incomplete or in-progress status to the user. -When viewed through the lens of CAP theorem and its consequences on distributed applications we realize that we cannot commit to perfect availability and strong consistency. But surely we can explore the middle ground. We can guarantee availability most of the time with sometimes inconsistent view of the data. The consistency is eventually achieved when the communication between the nodes resumes. This leads to the following properties of the current distributed applications, referred to by the acronym BASE. +### Partition Recovery +When the partitioned nodes are able to communicate, they have to exchange information to maintain consistency. Both sides continued in their independent direction but now the delayed operations on either side have to be performed and violated invariants have to be fixed. Given the state and history of both sides, the system has to accomplish the following tasks. -**Basically Available** services are those which are partially available when partitions happen. Thus, they appear to work most of the time. -**Soft State** services provide no strong consistency guarantees. They are not write consistent. Since replicas may not be mutually consistent, applications have to accept stale data. -**Eventually Consistent** services try to make application state consistent whenever possible. +#### Consistency +During recovery, the system has to reconcile the inconsistency in state of both nodes. This is relatively straightforward to accomplish. One approach is to start from the state at the time of partition and apply operations of both sides in an appropriate manner, ensuring that the invariants are maintained. Depending on operations allowed during the partition phase, this process may or may not be possible. The general problem of conflict resolution is not solvable but a restricted set of operations may ensure that the system can always always merge conflicts. For example, Google Docs limits operations to style and text editing. But source-code control systems such as Concurrent Versioning System (CVS) may encounter conflict which require manual resolution. Research has been done on techniques for automatic state convergence. Using commutative operations allows the system to sort the operations in a consistent global order and execute them. Though all operations can't be commutative, for example - addition with bounds checking is not commutative. Mark Shapiro and his colleagues at INRIA have developed *commutative replicated data types (CRDTs)* that provably converge as operations are performed. By implementing state through CRDTs, we can ensure Availability and automatic state convergence after partitions. +#### Compensation +During partition, its possible for both sides to perform a series of actions which are externalized, i.e. their effects are visible outside the system. To compensate for these actions, the partitioned nodes have to maintain a history. +For example, consider a system in which both sides have executed the same order during a partition. During the recovery phase, the system has to detect this and distinguish it from two intentional orders. Once detected, the duplicate order has to be rolled back. If the order has been committed successfully then the problem has been externalized. The user will see twice the amount deducted from his account for a single purchase. Now, the system has to credit the appropriate amount to the user's account and possibly send an email explaining the entire debacle. All this depends on the system maintaining the history during partition. If the history is not present, then duplicate orders cannot be detected and the user will have to catch the mistake and ask for compensation. +It would have been great if the duplicate order was not issued by the system in the first place. But the requirement to maintain system availability trumps consistency. Mistakes in such cases cannot always be corrected internally. But by admitting them and compensating for them, the system arguably exhibits equivalent behavior. ### What's the right pH for my distributed solution? -Whether an application chooses to an *ACID*ic or *BASE*ic service depends on the domain. An application developer has to consider the consistency-availability tradeoff on a case by case basis. *ACID*ic databases provide a very simple and strong consistency model making application development easy for domains where data inconsistency cannot be tolerated. *BASE*ic databases provide a very loose consistency model, placing more burden on the application developer to understand the limitations of the database and work around that, retaining sane application behavior. +Whether an application chooses to be an *ACID*ic or *BASE*ic service depends on the domain. An application developer has to consider the consistency-availability tradeoff on a case by case basis. *ACID*ic databases provide a very simple and strong consistency model making application development easy for domains where data inconsistency cannot be tolerated. *BASE*ic systems provide a very loose consistency model, placing more burden on the application developer to understand the invariants and manage them carefully during partitions by appropriately limiting or modifying the operations. ## References -- cgit v1.2.3 From fb6430b0f1575b2168bed24534535bfc9241132b Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Mon, 12 Dec 2016 00:20:44 -0500 Subject: added image for network partition --- chapter/6/resources/partitioned-network.jpg | Bin 0 -> 23772 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 chapter/6/resources/partitioned-network.jpg diff --git a/chapter/6/resources/partitioned-network.jpg b/chapter/6/resources/partitioned-network.jpg new file mode 100644 index 0000000..2c91607 Binary files /dev/null and b/chapter/6/resources/partitioned-network.jpg differ -- cgit v1.2.3 From 7a779a5b66e67d89cc1b2a95c4fb878e91c96ab3 Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Mon, 12 Dec 2016 15:50:22 -0500 Subject: Changed figure, updated content for CAP, 2 out of 3 covered in detail --- ...dic-to-basic-how-the-database-ph-has-changed.md | 84 +++++++++++++-------- chapter/6/resources/partitioned-network.jpg | Bin 23772 -> 24303 bytes 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md index 540a2a9..ffc94c0 100644 --- a/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md +++ b/chapter/6/acidic-to-basic-how-the-database-ph-has-changed.md @@ -10,7 +10,7 @@ Relational Database Management Systems are the most ubiquitous database systems * **Atomicity** guarantees that any transaction will either complete or leave the database unchanged. If any operation of the transaction fails, the entire transaction fails. Thus, a transaction is perceived as an atomic operation on the database. This property is guaranteed even during power failures, system crashes and other erroneous situations. -* **Consistency** guarantees that any transaction will always result in a valid database state, i.e., the transaction preserves all database rules, such as unique keys, etc. +* **Consistency** guarantees that any transaction will always result in a valid database state, i.e., the transaction preserves all database rules, such as unique keys. * **Isolation** guarantees that concurrent transactions do not interfere with each other. No transaction views the effects of other transactions prematurely. In other words, they execute on the database as if they were invoked serially (though a read and write can still be executed in parallel). @@ -20,17 +20,17 @@ Relational Database Management Systems are the most ubiquitous database systems Because of the strong guarantees this model simplifies the life of the developer and has been traditionally the go to approach in application development. It is instructive to examine how these properties are enforced. -Single node databases can simply rely upon locking to ensure *ACID*ity. Each transaction marks the data it operates upon, thus enabling the database to block other concurrent transactions from modifying the same data. The lock has to be acquired both while reading and writing data. The locking mechanism enforces a strict linearizable consistency. An alternative, *multiversioning* allows a read and write operation to execute in parallel. Each transaction which reads data from the database is provided the earlier unmodified version of the data that is being modified by a write operation. This means that read operations don't have to acquire locks on the database. This enables read operations to execute without blocking write operations and write operations to execute without blocking read operations. +Single node databases can simply rely upon locking to ensure *ACID*ity. Each transaction marks the data it operates upon, thus enabling the database to block other concurrent transactions from modifying the same data. The lock has to be acquired both while reading and writing data. The locking mechanism enforces a strict linearizable consistency, i.e., all transactions are performed in a particular sequence and invariants are always maintained by them. An alternative, *multiversioning* allows a read and write operation to execute in parallel. Each transaction which reads data from the database is provided the earlier unmodified version of the data that is being modified by a write operation. This means that read operations don't have to acquire locks on the database. This enables read operations to execute without blocking write operations and write operations to execute without blocking read operations. This model works well on a single node. But it exposes a serious limitation when too many concurrent transactions are performed. A single node database server will only be able to process so many concurrent read operations. The situation worsens when many concurrent write operations are performed. To guarantee *ACID*ity, the write operations will be performed in sequence. The last write request will have to wait for an arbitrary amount of time, a totally unacceptable situation for many real time systems. This requires the application developer to decide on a **Scaling** strategy. -### Transaction Volume +### 1.2. Scaling transaction volume To increase the volume of transactions against a database, two scaling strategies can be considered -**Vertical Scaling** is the easiest approach to scale a relational database. The database is simply moved to a larger computer which provides more transactional capacity. Unfortunately, its far too easy to outgrow the capacity of the largest system available and it is costly to purchase a bigger system each time that happens. Since its not commodity hardware, vendor lock-in will add to further costs. +* **Vertical Scaling** is the easiest approach to scale a relational database. The database is simply moved to a larger computer which provides more transactional capacity. Unfortunately, its far too easy to outgrow the capacity of the largest system available and it is costly to purchase a bigger system each time that happens. Since its specialized hardware, vendor lock-in will add to further costs. -**Horizontal Scaling** is a more viable option and can be implemented in two ways. Data can be segregated into functional groups spread across databases. This is called *Functional Scaling*. Data within a functional group can be further split across multiple databases, enabling functional areas to be scaled independently of one another for even more transactional capacity. This is called *sharding*. +* **Horizontal Scaling** is a more viable option and can be implemented in two ways. Data can be segregated into functional groups spread across databases. This is called *Functional Scaling*. Data within a functional group can be further split across multiple databases, enabling functional areas to be scaled independently of one another for even more transactional capacity. This is called *sharding*. Horizontal Scaling through functional partitioning enables high degree of scalability. However, the functionally separate tables employ constraints such as foreign keys. For these constraints to be enforced by the database itself, all tables have to reside on a single database server. This limits horizontal scaling. To work around this limitation the tables in a functional group have to be stored on different database servers. But now, a single database server can no longer enforce constraints between the tables. In order to ensure *ACID*ity of distributed transactions, distributed databases employ a two-phase commit (2PC) protocol. @@ -38,13 +38,22 @@ Horizontal Scaling through functional partitioning enables high degree of scalab * In the second phase, the coordinator asks each database to commit the data. -2PC is a blocking protocol and is usually employed for updates which can take from a few milliseconds up to a few minutes to commit. This means that while a transaction is being processed, other transactions will be blocked. So the application that initiated the transaction will be blocked. Another option is to handle the consistency across databases at the application level. This only complicates the situation for the application developer who is likely to implement a similar strategy if *ACID*ity is to be maintained. +2PC is a blocking protocol and updates can take from a few milliseconds up to a few minutes to commit. This means that while a transaction is being processed, other transactions will be blocked. So the application that initiated the transaction will be blocked. Another option is to handle the consistency across databases at the application level. This only complicates the situation for the application developer who is likely to implement a similar strategy if *ACID*ity is to be maintained. +## 2. The Distributed Concoction -## The **ACID*ic side effect -Traditional distributed databases provide strong consistency guarantees. While processing a transaction, they block other client requests. Imagine a large scale internet based shopping service with consistency enforced across functional partitions. This means that any tra +A distributed application is expected to have the following three desirable properties: -## 3. A Distributed Concoction +1. **Consistency** - This is the guarantee of total ordering of all operations on a data object such that each operation appears indivisible. This means that any read operation must return the most recently written value. This provides a very convenient invariant to the client application. This definition of consistency is the same as the **Atomic**ity guarantee provided by relational database transactions. + +2. **Availability** - Every request to a distributed system must result in a response. However, this is too vague a definition. Whether a node failed in the process of responding or it ran a really long computation to generate a response or whether the request or the response got lost due to network issues is generally impossible to determine by the client and willHence, for all practical purposes, availability can be defined as the service responding to a request in a timely fashion, the amount of delay an application can bear depends on the application domain. + +3. **Partition Tolerance** - Partitioning is the loss of messages between the nodes of a distributed system. During a network partition, the system can lose arbitrary number of messages between nodes. A partition tolerant system will always respond correctly unless a total network failure happens. + +Consistency requirement implies that every request will be treated atomically by the system even if the nodes lose messages due to network partitions. +Availability requirement implies that every request should receive a response even if a partition causes messages to be lost arbitrarily. + +## 3. The CAP Theorem ![Partitioned Network](resources/partitioned-network.jpg) @@ -54,34 +63,43 @@ In the network above, all messages between the node set M and N are lost due to 2. **Consistency first** - The system does not allow any application to write to data objects as it cannot ensure **consistency** of replica states. This means that the system is perceived to be **unavailable** by the applications. -If there are no partitions, clearly both consistency and availability can be guaranteed by the system. +If there are no partitions, clearly both consistency and availability can be guaranteed by the system. This observation led Eric Brewer to conjecture in an invited talk at PODC 2000- + +
It is impossible for a web service to provide the following three guarantees: +Consistency +Availability +Partition Tolerance
+ +This is called the CAP theorem. -This simple observation shows a tension between three issues concerning distributed systems - +It is clear that the prime culprit here is network partition. If there are no network partitions, any distributed service will be both highly available and provide strong consistency of shared data objects. Unfortunately, network partitions cannot be remedied in a distributed system. -**Consistency** is the guarantee of total ordering of all operations on a data object such that each operation appears indivisible. This means that any read operation must return the most recently written value. This provides a very convenient invariant to the client application that uses the distributed data store. This definition of consistency is the same as the **Atomic**ity guarantee provided by relational database transactions. +## 4. Two of Three - Exploring the CAP Theorem -**Availability** is the guarantee that every request to a distributed system must result in a response. However, this is too vague a definition. Whether a node failed in the process of responding or it ran a really long computation to generate a response or whether the request or the response got lost due to network issues is generally impossible to determine by the client and willHence, for all practical purposes, availability can be defined as the service responding to a request in a timely fashion, the amount of delay an application can bear depends on the application domain. +The CAP theorem dictates that the three desirable properties, consistency, availability and partition tolerance cannot be offered simultaneously. Let's study if its possible to achieve two of these three properties. -**Partitioning** is the loss of messages between the nodes of a distributed system. +### Consistency and Availability +If there are no network partitions, then there is no loss of messages and all requests receive a response within the stipulated time. It is clearly possible to achieve both consistency and availability. Distributed systems over intranet are an example of such systems. -This observation led Eric Brewer to conjecture in an invited talk at PODC 2000 - +### Consistency and Partition Tolerance +Without availability, both of these properties can be achieved easily. A centralized system can provide these guarantees. The state of the application is maintained on a single designated node. All updates from the client are forwarded by the nodes to this designated node. It updates the state and sends the response. When a failure happens, then the system does not respond and is perceived as unavailable by the client. Distributed locking algorithms in databases also provide these guarantees. -
It is impossible for a web service to provide the following three guarantees: -Consistency -Availability -Partition Tolerance
+### Availability and Partition Tolerance +Without atomic consistency, it is very easy to achieve availability even in the face of partitions. Even if nodes fail to communicate with each other, they can individually handle query and update requests issued by the client. The same data object will have different states on different nodes as the nodes progress independently. This weak consistency model is exhibited by web caches. + +Its clear that two of these three properties are easy to achieve in any distributed system. Since large scale distributed systems have to take partitions into account, will they have to sacrifice availability for consistency or consistency for availability? Clearly giving up either consistency or availability is too big a sacrifice. + +## 5. The **BASE**ic distributed state -This is called the CAP theorem. It is clear that the prime culprit here is network partition. If there are no network partitions, any distributed service will be both highly available and provide strong consistency of shared data objects. Unfortunately, network partitions cannot be remedied in a distributed system. +When viewed through the lens of CAP theorem and its consequences on distributed application design, we realize that we cannot commit to perfect availability and strong consistency. But surely we can explore the middle ground. We can guarantee availability most of the time with occasional inconsistent view of the data. The consistency is eventually achieved when the communication between the nodes resumes. This leads to the following properties of the current distributed applications, referred to by the acronym BASE. -## The **BASE**ic distributed state +* **Basically Available** services are those which are partially available when partitions happen. Thus, they appear to work most of the time. Partial failures result in the system being unavailable only for a section of the users. -When viewed through the lens of CAP theorem and its consequences on distributed application design, we realize that we cannot commit to perfect availability and strong consistency. But surely we can explore the middle ground. We can guarantee availability most of the time with sometimes inconsistent view of the data. The consistency is eventually achieved when the communication between the nodes resumes. This leads to the following properties of the current distributed applications, referred to by the acronym BASE. +* **Soft State** services provide no strong consistency guarantees. They are not write consistent. Since replicas may not be mutually consistent, applications have to accept stale data. -**Basically Available** services are those which are partially available when partitions happen. Thus, they appear to work most of the time. Partial failures result in the system being unavailable only for a section of the users. -**Soft State** services provide no strong consistency guarantees. They are not write consistent. Since replicas may not be mutually consistent, applications have to accept stale data. -**Eventually Consistent** services try to make application state consistent whenever possible. +* **Eventually Consistent** services try to make application state consistent whenever possible. -## Partitions and latency +## 6. Partitions and latency Any large scale distributed system has to deal with latency issue. In fact, network partitions and latency are fundamentally related. Once a request is made and no response is received within some duration, the sender node has to assume that a partition has happened. The sender node can take one of the following steps: 1) Cancel the operation as a whole. In doing so, the system is choosing consistency over availability. @@ -90,32 +108,32 @@ Any large scale distributed system has to deal with latency issue. In fact, netw Essentially, a partition is an upper bound on the time spent waiting for a response. Whenever this upper bound is exceeded, the system chooses C over A or A over C. Also, the partition may be perceived only by two nodes of a system as opposed to all of them. This means that partitions are a local occurrence. -## Handling Partitions +## 7. Handling Partitions Once a partition has happened, it has to be handled explicitly. The designer has to decide which operations will be functional during partitions. The partitioned nodes will continue their attempts at communication. When the nodes are able to establish communication, the system has to take steps to recover from the partitions. -### Partition mode functionality +### 7.1. Partition mode functionality When at least one side of the system has entered into partition mode, the system has to decide which functionality to support. Deciding this depends on the invariants that the system must maintain. Depending on the nature of problem, the designer may choose to compromise on certain invariants by allowing partitioned system to provide functionality which might violate them. This means the designer is choosing availability over consistency. Certain invariants may have to be maintained and operations that will violate them will either have to be modified or prohibited. This means the designer is choosing consistency over availability. Deciding which operations to prohibit, modify or delay also depends on other factors such as the node. If the data is stored on the same node, then operations on that data can typically proceed on that node but not on other node. In any event, the bottomline is that if the designer wishes for the system to be available, certain operations have to be allowed. The node has to maintain a history of these operations so that it can be merged with the rest of the system when it is able to reconnect. Since the operations can happen simultaneously on multiple disconnected nodes, all sides will maintain this history. One way to maintain this information is through version vectors. Another interesting problem is to communicate the progress of these operations to the user. Until the system gets out of partition mode, the operations cannot be committed completely. Till then, the user interface has to faithfully represent their incomplete or in-progress status to the user. -### Partition Recovery +### 7.2. Partition Recovery When the partitioned nodes are able to communicate, they have to exchange information to maintain consistency. Both sides continued in their independent direction but now the delayed operations on either side have to be performed and violated invariants have to be fixed. Given the state and history of both sides, the system has to accomplish the following tasks. -#### Consistency +#### 7.2.1. Consistency During recovery, the system has to reconcile the inconsistency in state of both nodes. This is relatively straightforward to accomplish. One approach is to start from the state at the time of partition and apply operations of both sides in an appropriate manner, ensuring that the invariants are maintained. Depending on operations allowed during the partition phase, this process may or may not be possible. The general problem of conflict resolution is not solvable but a restricted set of operations may ensure that the system can always always merge conflicts. For example, Google Docs limits operations to style and text editing. But source-code control systems such as Concurrent Versioning System (CVS) may encounter conflict which require manual resolution. Research has been done on techniques for automatic state convergence. Using commutative operations allows the system to sort the operations in a consistent global order and execute them. Though all operations can't be commutative, for example - addition with bounds checking is not commutative. Mark Shapiro and his colleagues at INRIA have developed *commutative replicated data types (CRDTs)* that provably converge as operations are performed. By implementing state through CRDTs, we can ensure Availability and automatic state convergence after partitions. -#### Compensation +#### 7.2.2. Compensation During partition, its possible for both sides to perform a series of actions which are externalized, i.e. their effects are visible outside the system. To compensate for these actions, the partitioned nodes have to maintain a history. For example, consider a system in which both sides have executed the same order during a partition. During the recovery phase, the system has to detect this and distinguish it from two intentional orders. Once detected, the duplicate order has to be rolled back. If the order has been committed successfully then the problem has been externalized. The user will see twice the amount deducted from his account for a single purchase. Now, the system has to credit the appropriate amount to the user's account and possibly send an email explaining the entire debacle. All this depends on the system maintaining the history during partition. If the history is not present, then duplicate orders cannot be detected and the user will have to catch the mistake and ask for compensation. It would have been great if the duplicate order was not issued by the system in the first place. But the requirement to maintain system availability trumps consistency. Mistakes in such cases cannot always be corrected internally. But by admitting them and compensating for them, the system arguably exhibits equivalent behavior. -### What's the right pH for my distributed solution? +### 8. What's the right pH for my distributed solution? Whether an application chooses to be an *ACID*ic or *BASE*ic service depends on the domain. An application developer has to consider the consistency-availability tradeoff on a case by case basis. *ACID*ic databases provide a very simple and strong consistency model making application development easy for domains where data inconsistency cannot be tolerated. *BASE*ic systems provide a very loose consistency model, placing more burden on the application developer to understand the invariants and manage them carefully during partitions by appropriately limiting or modifying the operations. -## References +## 9. References https://neo4j.com/blog/acid-vs-base-consistency-models-explained/ https://en.wikipedia.org/wiki/Eventual_consistency/ diff --git a/chapter/6/resources/partitioned-network.jpg b/chapter/6/resources/partitioned-network.jpg index 2c91607..513fc13 100644 Binary files a/chapter/6/resources/partitioned-network.jpg and b/chapter/6/resources/partitioned-network.jpg differ -- cgit v1.2.3 From 147af3f9983cf4b485c1323870830f606268711e Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Tue, 13 Dec 2016 02:33:36 -0500 Subject: Update futures.md --- chapter/2/futures.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 45d2d0b..4e6a3ee 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -10,10 +10,10 @@ As human beings we have an ability to multitask ie. we can walk, talk and eat at The processor can either handle blocking calls in two ways: -- **Synchronous method**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. -- **Asynchronous method**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. +- **Synchronously**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. Also, there is a possiblity of deadlocks here, which can be tricky to recover from. +- **Asynchronously**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. -In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. +In the world of asynchronous communications many programming models were introduced to help programmers wrangle with dependencies between processes optimally. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages.
@@ -53,7 +53,7 @@ Promises and javascript have an interesting history. In 2007 inspired by Python #Different Definitions -Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. The definitions have changed a little over the years but the idea remained the same. +Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. In some languages however, there is a subtle difference between what is a Future and a Promise. -- cgit v1.2.3 From 33c1a42832803f24c70fa7966e5194e8884cca14 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Wed, 14 Dec 2016 11:58:53 -0500 Subject: Update futures.md --- chapter/2/futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 4e6a3ee..d3e2d7e 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -222,7 +222,7 @@ We define Implicit promises as ones where we don’t have to manually trigger th The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. Also, lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. -Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. +Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of futures in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. -- cgit v1.2.3 From 29e7482cd1d768a805e4ce89213b2ac7b0e9a297 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 02:00:56 -0500 Subject: General wording improvements from Heather --- chapter/3/message-passing.md | 45 +++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index ee489ff..6e898ba 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -10,48 +10,53 @@ Message passing programming models have essentially been discussed since the beg In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, especially those focused on the actor model of programming and organizing units of computation. -In this chapter I describe four different actor models: classic actors, process-based actors, communicating event-loops, and active objects. I attempt to highlight historic and modern languages that exemplify these models, as well as the philosophies and tradeoffs that programmers need to be aware of to understand these models. +In this chapter I describe the four primary variants of the actor model: classic actors, process-based actors, communicating event-loops, and active objects. I attempt to highlight historic and modern languages that exemplify these models, as well as the philosophies and tradeoffs that programmers need to be aware of to understand and best make use of these models. -Actor programming models are continuing to develop and become more robust, as some of the recently published papers and systems in the field show. There are a few robust industrial-strength actor systems that are being used to power massive scalable distributed systems. There are a couple of different approaches to building actor frameworks that are detailed later in the chapter. +Despite the actor model's originating as far back as the 1970s, it is still being developed and being incorporated into the programming languages of today, as many recently published papers and systems in the field demonstrate. There are a few robust industrial-strength actor systems that are being used to power massive scalable distributed systems. There are a couple of different approaches to building actor frameworks that are detailed later in the chapter. -I think an important framing for the models and sources presented is “Why message passing, and specifically why the actor model?” There are a number of distributed programming models, so why was this one so important when it was initially proposed? What are the advantages of it for the programmer? Why has it facilitated advanced languages, systems, and libraries that are widely used today? The broad advantages of the actor model are around isolation of state, scalability, and simplifying the programmer's ability to reason about their system. +An important framing for the actor models presented is in the question "Why message passing, and specifically why the actor model?" Given the vast number of distributed programming models out there, one might ask, why this one was so important when it was initially proposed? Why has it facilitated advanced languages, systems, and libraries that are widely used today? As we'll see throughout this chapter, some of the broadest advantages of the actor model include isolation of state managed by the given actor, scalability, and simplifying the programmer's ability to reason about their system. # Original proposal of the actor model The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel computation in communication in a way that could be safely distributed concurrently across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. -They define actors as independent units of computation with isolated state. These units can send messages to one another, and have a mailbox which contains messages they have received. These messages are of the form: +Actors are defined as independent units of computation with isolated state. These units have two core characteristics: + +* they can send messages to one another, and, +* they have a mailbox which contains messages that they have received. + +Messages are of the form: ``` (request: reply-to: ) ``` -Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a type of continuation, where the continuation is the message to another actor. These messages are one-way and make no claims about whether a message will ever be received in response. This model is limited compared to many of the others, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. +Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a type of continuation, where the continuation is the message to another actor. These messages are one-way and, there are no guarantees that a message will ever be received in response. This originally-proposed variant of the actor model is limited compared to many of the others, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. -One interesting thing to note is that this original paper talks about actors in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept reminds me of something like a Lisp machine, but built to specially utilize the actor model of computation for artificial intelligence. +Interestingly, the original paper introducing the actor model does so in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept is reminiscent of something like a Lisp machine, though specially built to utilize the actor model of computation for artificial intelligence. # Classic actor model -The classic actor model was formalized as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor expands on the original proposal of actors, keeping the ideas of asynchronous communication through messages between isolated units of computation and state. The classic actor is formalized as the following primitive operations: +The classic actor model was formalized as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor expands on the original proposal of actors, keeping the ideas of asynchronous communication through messages between isolated units of computation and state. The classic actor contains the following primitive operations: * `create`: create an actor from a behavior description and a set of parameters, including other existing actors * `send`: send a message to another actor * `become`: have an actor replace their behavior with a new one -As originally described, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are uniquely addressable, and have their own independent mailboxes or message queues. State changes using the classic actor model are specified and aggregated using the `become` operation. Each time an actor processes a communication it computes a behavior in response to the next type of communication it expects to process. A `become` operation's argument is another named behavior with some state to pass to that named behavior. +As in the original actor model, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are uniquely addressable, and have their own independent mailboxes or message queues. State changes using the classic actor model are specified and aggregated using the `become` operation. Each time an actor processes a message it computes a behavior in response to the next type of message it expects to process. A `become` operation's argument is a named continuation, `b`, representing behavior that the actor should be updated with, along with some state that should be passed to `b`. For purely functional actors the new behavior would be identical to the original. For more complex actors however, this enables the aggregation of state changes at a higher level of granularity than something like a variable assignment. This enables flexibility in the behavior of an actor over time in response to the actions of other actors in the system. Additionally, this isolation changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. -If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. Kay's ideas sound closer to what we consider the actor model today, and less like what we consider object-oriented programming. The focus is on designing the messaging and communications that dictate how objects interact. +If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. Kay's ideas sound closer to what we consider the actor model today, and less like what we consider object-oriented programming. That is, Kay's focus in this description is on designing the messaging and communications that dictate how objects interact. TODO: transition ## Concurrent Object-Oriented Programming (1990) -This is the seminal paper for the classic actor model, as it offers classic actors as a natural solution to solving problems at the intersection of two trends in computing: increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. +One could say that the renaissance of actor models in mainstream program began with seminal paper, _Concurrent Object-Oriented Programming_ (citation), as it offers classic actors as a natural solution to solving problems at the intersection of two trends in computing; increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. -This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to actually identify some of the programmer-centric advantages of actors. One of the core languages used for examples in the paper is Rosette, but the paper largely focuses on the potential and benefits of the model. Agha claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. "It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs." This flexibility turns out to be a highly discussed advantage which continues to be touted in modern actor systems. +This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to identify some of the advantages from the perspective of programmers of programming with actors. One of the core languages used for examples in the paper is Rosette, but the paper largely focuses on the potential and benefits of the model. Agha claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. "It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs." This flexibility turns out to be a highly discussed advantage which continues to be touted in modern actor systems. ## Rosette @@ -62,7 +67,7 @@ The motivation behind Rosette was to provide strategies for dealing with problem * _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. * _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. -The Rosette language has a number of object-oriented features, many of which we take for granted in modern object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. I think the more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. In Rosette, the concurrency is an inherent property of the program structure and resource allocation. This is different from a language like Java, where all of the concurrency is very explicit. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers and architectures have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the specific machine makes it difficult to re-map a program to another one. I think this idea of using actors as a more flexible and natural abstraction over concurrency and distribution of resources is an important one which is seen in some form within many actor systems. +The Rosette language has a number of object-oriented features, many of which we take for granted in modern object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. The more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. In Rosette, the concurrency is an inherent property of the program structure and resource allocation. This is different from a language like Java, where all of the concurrency is very explicit. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers and architectures have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the specific machine makes it difficult to re-map a program to another one. This idea of using actors as a more flexible and natural abstraction over concurrency and distribution of resources is an important one which is seen in some form within many actor systems. Actors in Rosette are organized into three types of classes which describe different aspects of the actors within the system: @@ -76,7 +81,7 @@ These classes represent a concrete object-oriented abstraction to organize actor Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. -Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching patter to handle the message. The message will simply be discarded an an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. +Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded an an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. The Akka runtime also provides performance advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. @@ -120,7 +125,7 @@ The realization of this model depends on efficiently multiplexing actors to thre ## Cloud Haskell -Cloud Haskell is an extension or domain specific language of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. This alone isn't so different from Erlang, which operates on immutable data in the context of isolated memory. +Cloud Haskell is an extension of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. This alone isn't so different from Erlang, which operates on immutable data in the context of isolated memory. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. In Erlang, all you have is dynamic pattern matching based on values patterns, and the hope that the wrong types of message don't get passed around your system. Cloud Haskell processes can also use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Haskell's monadic types make it possible for programmers to use a programming style, where they can ensure that pure and effective code are not mixed. This makes reasoning about where side-effects happen in your system easier. Cloud Haskell has shared memory within an actor process, which is useful for certain applications. This might sound like it could cause problems, but shared-memory structures are forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. This means that as long as a function and its environment are serializable, they can be spun off as a remote computation and seamlessly continued elsewhere. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. Cloud Haskell is currently supported and also has developed the Cloud Haskell Platform, which aims to provide common functionality needed to build and manage a production actor system using Cloud Haskell. @@ -221,27 +226,29 @@ These attributes give us a good basis for analyzing whether an actor system can ## Actors as a framework -One trend that seems common among the actor systems we see in production is extensive environments and tooling. I would argue that Akka, Erlang, and Orleans are the primary actor systems that see real production use, and I think the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. +One trend that seems common among the actor systems we see in production is extensive environments and tooling. Akka, Erlang, and Orleans are the primary actor systems that see real production use, and the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. + +Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. -Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. Cloud Haskell also provides something analogous to Erlang's OTP called the Cloud Haskell Platform. +Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. Cloud Haskell also provides something analogous to Erlang's OTP called the Cloud Haskell Platform. Orleans is different from these as it is built from the ground up with a more declarative style and runtime. This does a lot of the work of distributing and scaling actors for you, but it is still definitely a framework which handles a lot of the common problems of distribution so that programmers can focus on building the logic of their system. ## Module vs. managed runtime approaches -Based on my research there have been two prevalent approaches to frameworks which are actually used to build production actor systems in industry. These are high-level philosophies about the meta-organization of an actor system. They are the design philosophies that aren't even directly considered when just looking at the base actor programming and execution models. I think the easiest way to describe these is are as the "module approach" and the "managed runtime approach". A high-level analogy to describe these is that the module approach is similar to manually managing memory, while the managed runtime approach is similar to garbage collection. In the module approach, you care about the lifecycle and physical allocation of actors within your system, while in the managed runtime approach you care more about the reconciliation behavior and flow of persistent state between automatic instantiations of your actors. +Based on my research there have been two prevalent approaches to frameworks which are actually used to build production actor systems in industry. These are high-level philosophies about the meta-organization of an actor system. They are the design philosophies that aren't even directly considered when just looking at the base actor programming and execution models. The easiest way to describe these is are as the "module approach" and the "managed runtime approach". A high-level analogy to describe these is that the module approach is similar to manually managing memory, while the managed runtime approach is similar to garbage collection. In the module approach, you care about the lifecycle and physical allocation of actors within your system, while in the managed runtime approach you care more about the reconciliation behavior and flow of persistent state between automatic instantiations of your actors. Both Akka and Erlang take a module approach to building their actor systems. This means that when you build a system using these languages/frameworks, you are using smaller composable components as pieces of the larger system you want to build. You are explicitly dealing with the lifecycles and instantiations of actors within your system, where to distribute them across physical machines, and how to balance actors to scale. Some of these problems might be handled by libraries, but at some level you are specifying how all of the organization of your actors is happening. The JVM or Erlang VM isn't doing it for you. Orleans goes in another direction, which I call the managed runtime approach. Instead of providing small components which let you build your own abstractions, they provide a runtime in the cloud that attempts to abstract away a lot of the details of managing actors. It does this to such an extent that you no longer even directly manage actor lifecycles, where they live on machines, or how they are replicated and scaled. Instead you program with actors in a more declarative style. You never explicitly instantiate actors, instead you assume that the runtime will figure it out for you in response to requests to your system. You program in strategies to deal with problems like domain-specific reconciliation of data across instances, but you generally leave it to the runtime to scale and distribute the actor instances within your system. -I don't have an opinion on which of these is right. Both approaches have been successful in industry. Erlang has the famous use case of a telephone exchange and a successful history since then. Akka has an entire page detailing its usage in giant companies. Orleans has been used as a backend to massive Microsoft-scale games and applications with millions of users. It seems like the module approach is more popular, but there's only really one example of the managed runtime approach out there. There's no equivalent to Orleans on the JVM or Erlang VM, so realistically it doesn't have as much exposure in the distributed programming community. +Both approaches have been successful in industry. Erlang has the famous use case of a telephone exchange and a successful history since then. Akka has an entire page detailing its usage in giant companies. Orleans has been used as a backend to massive Microsoft-scale games and applications with millions of users. It seems like the module approach is more popular, but there's only really one example of the managed runtime approach out there. There's no equivalent to Orleans on the JVM or Erlang VM, so realistically it doesn't have as much exposure in the distributed programming community. ## Comparison to Communicating Sequential Processes (CSP) TODO: where should this live in the chapter? -You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. I think a lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. I think this view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. +You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. A lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. # References -- cgit v1.2.3 From c9aaf8c95d2f4aeeb7fe6e1eda873b4f082c27f2 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Thu, 15 Dec 2016 12:13:38 -0500 Subject: Update futures.md --- chapter/2/futures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index d3e2d7e..61df8b6 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -83,7 +83,7 @@ Over the years promises and futures have been implemented in different programmi ## Fork-Join -Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheap. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. +Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheaper than what it is between processes. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. In Java executor is an object which executes the Runnable tasks. Executors provides a way of abstracting out how the details of how a task will actually run. These details, like selecting a thread to run the task, how the task is scheduled are managed by the object implementing the Executor interface. Threads are an example of a Runnable in java. Executors can be used instead of creating a thread explicitly. @@ -95,7 +95,7 @@ Similar to Executor, there is an ExecutionContext as part of scala.concurrent. T ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. -Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. +Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. In Scala, futures are created using an ExecutionContext. This gives the users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Futures in scala are placeholders for a yet unknown value. A promise then can be thought of as a way to provide that value. A promise p completes the future returned by p.future. -- cgit v1.2.3 From 4a56dd765cc86ab891c3f76d9693c0bad40e86e6 Mon Sep 17 00:00:00 2001 From: kisalaya89 Date: Thu, 15 Dec 2016 13:02:48 -0500 Subject: Update futures.md --- chapter/2/futures.md | 164 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 61 deletions(-) diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 61df8b6..6e019c7 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -6,14 +6,14 @@ by: "Kisalaya Prasad and Avanti Patil" #Introduction -As human beings we have an ability to multitask ie. we can walk, talk and eat at the same time except when you sneeze. Sneeze is like a blocking activity from the normal course of action, because it forces you to stop what you’re doing for a brief moment and then you resume where you left off. Activities like multitasking are called multithreading in computer lingo. In contrast to this behaviour, computer processors are single threaded. So when we say that a computer system has multi-threaded environment, it is actually just an illusion created by processor where processor’s time is shared between multiple processes. Sometimes processor gets blocked when some tasks are hindered from normal execution due to blocking calls. Such blocking calls can range from IO operations like read/write to disk or sending/receiving packets to/from network. Blocking calls can take disproportionate amount of time compared to the processor’s task execution i.e. iterating over a list. +As human beings we have an ability to multitask ie. we can walk, talk and eat at the same time except when you sneeze. Sneeze is like a blocking activity from the normal course of action, because it forces you to stop what you’re doing for a brief moment and then you resume where you left off. Activities like multitasking are called multithreading in computer lingo. In contrast to this behaviour, computer processors are single threaded. So when we say that a computer system has multi-threaded environment, it is actually just an illusion created by processor where processor’s time is shared between multiple processes. Sometimes processor gets blocked when some tasks are hindered from normal execution due to blocking calls. Such blocking calls can range from IO operations like read/write to disk or sending/receiving packets to/from network. Blocking calls can take disproportionate amount of time compared to the processor’s task execution i.e. iterating over a list. The processor can either handle blocking calls in two ways: -- **Synchronously**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. Also, there is a possiblity of deadlocks here, which can be tricky to recover from. -- **Asynchronously**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. +- **Synchronous method**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. +- **Asynchronous method**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. -In the world of asynchronous communications many programming models were introduced to help programmers wrangle with dependencies between processes optimally. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. +In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages.
@@ -23,22 +23,22 @@ In the world of asynchronous communications many programming models were introdu #Motivation -A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. +A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. -The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. +The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. -Thunks can be thought of as a primitive notion of a Future or Promise. According to its inventor P. Z. Ingerman, thunks are "A piece of coding which provides an address". They were designed as a way of binding actual parameters to their formal definitions in Algol-60 procedure calls. If a procedure is called with an expression in the place of a formal parameter, the compiler generates a thunk which computes the expression and leaves the address of the result in some standard location. +Thunks can be thought of as a primitive notion of a Future or Promise. According to its inventor P. Z. Ingerman, thunks are "A piece of coding which provides an address". They were designed as a way of binding actual parameters to their formal definitions in Algol-60 procedure calls. If a procedure is called with an expression in the place of a formal parameter, the compiler generates a thunk which computes the expression and leaves the address of the result in some standard location. -The first mention of Futures was by Baker and Hewitt in a paper on Incremental Garbage Collection of Processes. They coined the term - call-by-futures to describe a calling convention in which each formal parameter to a method is bound to a process which evaluates the expression in the parameter in parallel with other parameters. Before this paper, Algol 68 also presented a way to make this kind of concurrent parameter evaluation possible, using the collateral clauses and parallel clauses for parameter binding. +The first mention of Futures was by Baker and Hewitt in a paper on Incremental Garbage Collection of Processes. They coined the term - call-by-futures to describe a calling convention in which each formal parameter to a method is bound to a process which evaluates the expression in the parameter in parallel with other parameters. Before this paper, Algol 68 also presented a way to make this kind of concurrent parameter evaluation possible, using the collateral clauses and parallel clauses for parameter binding. In their paper, Baker and Hewitt introduced a notion of Futures as a 3-tuple representing an expression E consisting of (1) A process which evaluates E, (2) A memory location where the result of E needs to be stored, (3) A list of processes which are waiting on E. But, the major focus of their work was not on role of futures and the role they play in Asynchronous distributed computing, and focused on garbage collecting the processes which evaluate expressions not needed by the function. -The Multilisp language, presented by Halestead in 1985 built upon this call-by-future with a Future annotation. Binding a variable to a future expression creates a process which evaluates that expression and binds x to a token which represents its (eventual) result. This design of futures influenced the paper of design of Promises in Argus by Liskov and Shrira in 1988. Building upon the initial design of Future in Multilisp, they extended the original idea by introducing strongly typed Promises and integration with call streams.This made it easier to handle exception propagation from callee to the caller and also to handle the typical problems in a multi-computer system like network failures. This paper also talked about stream composition, a notion which is similar to promise pipelining today. +The Multilisp language, presented by Halestead in 1985 built upon this call-by-future with a Future annotation. Binding a variable to a future expression creates a process which evaluates that expression and binds x to a token which represents its (eventual) result. This design of futures influenced the paper of design of Promises in Argus by Liskov and Shrira in 1988. Building upon the initial design of Future in Multilisp, they extended the original idea by introducing strongly typed Promises and integration with call streams.This made it easier to handle exception propagation from callee to the caller and also to handle the typical problems in a multi-computer system like network failures. This paper also talked about stream composition, a notion which is similar to promise pipelining today. E is an object-oriented programming language for secure distributed computing, created by Mark S. Miller, Dan Bornstein, and others at Electric Communities in 1997. One of the major contribution of E was the first non-blocking implementation of Promises. It traces its routes to Joule which was a dataflow programming language. The notion of promise pipelining in E is inherited from Joule. @@ -47,35 +47,35 @@ E is an object-oriented programming language for secure distributed computing, c Among the modern languages, Python was perhaps the first to come up with something on the lines of E’s promises with the Twisted library. Coming out in 2002, it had a concept of Deferred objects, which were used to receive the result of an operation not yet completed. They were just like normal objects and could be passed along, but they didn’t have a value. They supported a callback which would get called once the result of the operation was complete. -Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API, it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. +Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API, it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. #Different Definitions -Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. +Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. The definitions have changed a little over the years but the idea remained the same. -In some languages however, there is a subtle difference between what is a Future and a Promise. -“A ‘Future’ is a read-only reference to a yet-to-be-computed value”. -“A ‘Promise’ is a pretty much the same except that you can write to it as well.” +In some languages however, there is a subtle difference between what is a Future and a Promise. +“A ‘Future’ is a read-only reference to a yet-to-be-computed value”. +“A ‘Promise’ is a pretty much the same except that you can write to it as well.” In other words, you can read from both Futures and Promises, but you can only write to Promises. You can get the Future associated with a Promise by calling the future method on it, but conversion in the other direction is not possible. Another way to look at it would be, if you Promise something, you are responsible for keeping it, but if someone else makes a Promise to you, you expect them to honor it in Future. -More technically, in Scala, “SIP-14 – Futures and Promises” defines them as follows: -A future is as a placeholder object for a result that does not yet exist. +More technically, in Scala, “SIP-14 – Futures and Promises” defines them as follows: +A future is as a placeholder object for a result that does not yet exist. A promise is a writable, single-assignment container, which completes a future. Promises can complete the future with a result to indicate success, or with an exception to indicate failure. C# also makes the distinction between futures and promises. In C#, futures are implemented as Task and in fact in earlier versions of the Task Parallel Library futures were implemented with a class Future which later became Task. The result of the future is available in the readonly property Task.Result which returns T -In Javascript world, Jquery introduces a notion of Deferred objects which are used to represent a unit of work which is not yet finished. Deferred object contains a promise object which represent the result of that unit of work. Promises are values returned by a function, while the deferred object can be canceled by its caller. +In Javascript world, Jquery introduces a notion of Deferred objects which are used to represent a unit of work which is not yet finished. Deferred object contains a promise object which represent the result of that unit of work. Promises are values returned by a function, while the deferred object can be canceled by its caller. -In Java 8, the Future interface has methods to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation when it is complete. CompletableFutures can be thought of as Promises as their value can be set. But it also implements the Future interface and therefore it can be used as a Future too. Promises can be thought of as a future with a public set method which the caller (or anybody else) can use to set the value of the future. +In Java 8, the Future interface has methods to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation when it is complete. CompletableFutures can be thought of as Promises as their value can be set. But it also implements the Future interface and therefore it can be used as a Future too. Promises can be thought of as a future with a public set method which the caller (or anybody else) can use to set the value of the future. # Semantics of Execution @@ -83,19 +83,19 @@ Over the years promises and futures have been implemented in different programmi ## Fork-Join -Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheaper than what it is between processes. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. +Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheap. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. -In Java executor is an object which executes the Runnable tasks. Executors provides a way of abstracting out how the details of how a task will actually run. These details, like selecting a thread to run the task, how the task is scheduled are managed by the object implementing the Executor interface. Threads are an example of a Runnable in java. Executors can be used instead of creating a thread explicitly. +In Java executor is an object which executes the Runnable tasks. Executors provides a way of abstracting out how the details of how a task will actually run. These details, like selecting a thread to run the task, how the task is scheduled are managed by the object implementing the Executor interface. Threads are an example of a Runnable in java. Executors can be used instead of creating a thread explicitly. -Similar to Executor, there is an ExecutionContext as part of scala.concurrent. The basic intent behind it is same as an Executor : it is responsible for executing computations. How it does it can is opaque to the caller. It can create a new thread, use a pool of threads or run it on the same thread as the caller, although the last option is generally not recommended. Scala.concurrent package comes with an implementation of ExecutionContext by default, which is a global static thread pool. +Similar to Executor, there is an ExecutionContext as part of scala.concurrent. The basic intent behind it is same as an Executor : it is responsible for executing computations. How it does it can is opaque to the caller. It can create a new thread, use a pool of threads or run it on the same thread as the caller, although the last option is generally not recommended. Scala.concurrent package comes with an implementation of ExecutionContext by default, which is a global static thread pool. -ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. +ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. -Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. +Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. In Scala, futures are created using an ExecutionContext. This gives the users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Futures in scala are placeholders for a yet unknown value. A promise then can be thought of as a way to provide that value. A promise p completes the future returned by p.future. @@ -104,15 +104,27 @@ In Scala, futures are created using an ExecutionContext. This gives the users fl Scala futures api expects an ExecutionContext to be passed along. This parameter is implicit, and usually ExecutionContext.global. An example : -
- timeline -
+```scala +implicit val ec = ExecutionContext.global +val f : Future[String] = Future { “hello world” } +``` In this example, the global execution context is used to asynchronously run the created future. Taking another example, -
- timeline -
+ +```scala +implicit val ec = ExecutionContext.global + +val f = Future { + Http("http://api.fixed.io/latest?base=USD").asString +} + +f.onComplete { + case success(response) => println(response.body) + case Failure(t) => println(t) +} +``` + It is generally a good idea to use callbacks with Futures, as the value may not be available when you want to use it. @@ -121,16 +133,15 @@ So, how does it all work together ? As we mentioned, Futures require an ExecutionContext, which is an implicit parameter to virtually all of the futures API. This ExecutionContext is used to execute the future. Scala is flexible enough to let users implement their own Execution Contexts, but let’s talk about the default ExecutionContext, which is a ForkJoinPool. -ForkJoinPool is ideal for many small computations that spawn off and then come back together. Scala’s ForkJoinPool requires the tasks submitted to it to be a ForkJoinTask. The tasks submitted to the global ExecutionContext is quietly wrapped inside a ForkJoinTask and then executed. ForkJoinPool also supports a possibly blocking task, using ManagedBlock method which creates a spare thread if required to ensure that there is sufficient parallelism if the current thread is blocked. To summarize, ForkJoinPool is an really good general purpose ExecutionContext, which works really well in most of the scenarios. - +ForkJoinPool is ideal for many small computations that spawn off and then come back together. Scala’s ForkJoinPool requires the tasks submitted to it to be a ForkJoinTask. The tasks submitted to the global ExecutionContext is quietly wrapped inside a ForkJoinTask and then executed. ForkJoinPool also supports a possibly blocking task, using ManagedBlock method which creates a spare thread if required to ensure that there is sufficient parallelism if the current thread is blocked. To summarize, ForkJoinPool is an really good general purpose ExecutionContext, which works really well in most of the scenarios. ## Event Loops -Modern systems typically rely on many other systems to provide the functionality they do. There’s a file system underneath, a database system, and other web services to rely on for the information. Interaction with these components typically involves a period where we’re doing nothing but waiting for the response back. This is single largest waste of computing resources. +Modern systems typically rely on many other systems to provide the functionality they do. There’s a file system underneath, a database system, and other web services to rely on for the information. Interaction with these components typically involves a period where we’re doing nothing but waiting for the response back. This is single largest waste of computing resources. -Javascript is a single threaded asynchronous runtime. Now, conventionally async programming is generally associated with multi-threading, but we’re not allowed to create new threads in Javascript. Instead, asynchronicity in Javascript is achieved using an event-loop mechanism. +Javascript is a single threaded asynchronous runtime. Now, conventionally async programming is generally associated with multi-threading, but we’re not allowed to create new threads in Javascript. Instead, asynchronicity in Javascript is achieved using an event-loop mechanism. Javascript has historically been used to interact with the DOM and user interactions in the browser, and thus an event-driven programming model was a natural fit for the language. This has scaled up surprisingly well in high throughput scenarios in NodeJS. @@ -143,12 +154,12 @@ A typical Javascript engine has a few basic components. They are : - **Heap** Used to allocate memory for objects - **Stack** -Function call frames go into a stack from where they’re picked up from top to be executed. +Function call frames go into a stack from where they’re picked up from top to be executed. - **Queue** - A message queue holds the messages to be processed. + A message queue holds the messages to be processed. -Each message has a callback function which is fired when the message is processed. These messages can be generated by user actions like button clicks or scrolling, or by actions like HTTP requests, request to a database to fetch records or reading/writing to a file. +Each message has a callback function which is fired when the message is processed. These messages can be generated by user actions like button clicks or scrolling, or by actions like HTTP requests, request to a database to fetch records or reading/writing to a file. Separating when a message is queued from when it is executed means the single thread doesn’t have to wait for an action to complete before moving on to another. We attach a callback to the action we want to do, and when the time comes, the callback is run with the result of our action. Callbacks work good in isolation, but they force us into a continuation passing style of execution, what is otherwise known as Callback hell. @@ -159,13 +170,13 @@ Separating when a message is queued from when it is executed means the single th **Programs must be written for people to read, and only incidentally for machines to execute.** - *Harold Abelson and Gerald Jay Sussman* -Promises are an abstraction which make working with async operations in javascript much more fun. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. +Promises are an abstraction which make working with async operations in javascript much more fun. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. -The ES2015 spec specifies that “promises must not fire their resolution/rejection function on the same turn of the event loop that they are created on.” This is an important property because it ensures deterministic order of execution. Also, once a promise is fulfilled or failed, the promise’s value MUST not be changed. This ensures that a promise cannot be resolved more than once. +The ES2015 spec specifies that “promises must not fire their resolution/rejection function on the same turn of the event loop that they are created on.” This is an important property because it ensures deterministic order of execution. Also, once a promise is fulfilled or failed, the promise’s value MUST not be changed. This ensures that a promise cannot be resolved more than once. Let’s take an example to understand the promise resolution workflow as it happens inside the Javascript Engine. -Suppose we execute a function, here g() which in turn, calls function f(). Function f returns a promise, which, after counting down for 1000 ms, resolves the promise with a single value, true. Once f gets resolved, a value true or false is alerted based on the value of the promise. +Suppose we execute a function, here g() which in turn, calls function f(). Function f returns a promise, which, after counting down for 1000 ms, resolves the promise with a single value, true. Once f gets resolved, a value true or false is alerted based on the value of the promise.
@@ -184,7 +195,7 @@ Once the timer expires, the timer thread puts a message on the message queue. Th timeline
-Here, since the future is resolved with a value of true, we are alerted with a value true when the callback is picked up for execution. +Here, since the future is resolved with a value of true, we are alerted with a value true when the callback is picked up for execution.
timeline @@ -192,18 +203,18 @@ Here, since the future is resolved with a value of true, we are alerted with a v Some finer details : We’ve ignored the heap here, but all the functions, variables and callbacks are stored on heap. -As we’ve seen here, even though Javascript is said to be single threaded, there are number of helper threads to help main thread do things like timeout, UI, network operations, file operations etc. -Run-to-completion helps us reason about the code in a nice way. Whenever a function starts, it needs to finish before yielding the main thread. The data it accesses cannot be modified by someone else. This also means every function needs to finish in a reasonable amount of time, otherwise the program seems hung. This makes Javascript well suited for I/O tasks which are queued up and then picked up when finished, but not for data processing intensive tasks which generally take long time to finish. +As we’ve seen here, even though Javascript is said to be single threaded, there are number of helper threads to help main thread do things like timeout, UI, network operations, file operations etc. +Run-to-completion helps us reason about the code in a nice way. Whenever a function starts, it needs to finish before yielding the main thread. The data it accesses cannot be modified by someone else. This also means every function needs to finish in a reasonable amount of time, otherwise the program seems hung. This makes Javascript well suited for I/O tasks which are queued up and then picked up when finished, but not for data processing intensive tasks which generally take long time to finish. We haven’t talked about error handling, but it gets handled the same exact way, with the error callback being called with the error object the promise is rejected with. -Event loops have proven to be surprisingly performant. When network servers are designed around multithreading, as soon as you end up with a few hundred concurrent connections, the CPU spends so much of its time task switching that you start to lose overall performance. Switching from one thread to another has overhead which can add up significantly at scale. Apache used to choke even as low as a few hundred concurrent users when using a thread per connection while Node can scale up to a 100,000 concurrent connections based on event loops and asynchronous IO. +Event loops have proven to be surprisingly performant. When network servers are designed around multithreading, as soon as you end up with a few hundred concurrent connections, the CPU spends so much of its time task switching that you start to lose overall performance. Switching from one thread to another has overhead which can add up significantly at scale. Apache used to choke even as low as a few hundred concurrent users when using a thread per connection while Node can scale up to a 100,000 concurrent connections based on event loops and asynchronous IO. ##Thread Model -Oz programming language introduced an idea of dataflow concurrency model. In Oz, whenever the program comes across an unbound variable, it waits for it to be resolved. This dataflow property of variables helps us write threads in Oz that communicate through streams in a producer-consumer pattern. The major benefit of dataflow based concurrency model is that it’s deterministic - same operation called with same parameters always produces the same result. It makes it a lot easier to reason about concurrent programs, if the code is side-effect free. +Oz programming language introduced an idea of dataflow concurrency model. In Oz, whenever the program comes across an unbound variable, it waits for it to be resolved. This dataflow property of variables helps us write threads in Oz that communicate through streams in a producer-consumer pattern. The major benefit of dataflow based concurrency model is that it’s deterministic - same operation called with same parameters always produces the same result. It makes it a lot easier to reason about concurrent programs, if the code is side-effect free. Alice ML is a dialect of Standard ML with support for lazy evaluation, concurrent, distributed, and constraint programming. The early aim of Alice project was to reconstruct the functionalities of Oz programming language on top of a typed programming language. Building on the Standard ML dialect, Alice also provides concurrency features as part of the language through the use of a future type. Futures in Alice represent an undetermined result of a concurrent operation. Promises in Alice ML are explicit handles for futures. @@ -217,12 +228,12 @@ Alice also allows for lazy evaluation of expressions. Expressions preceded with #Implicit vs. Explicit Promises -We define Implicit promises as ones where we don’t have to manually trigger the computation vs Explicit promises where we have to trigger the resolution of future manually, either by calling a start function or by requiring the value. This distinction can be understood in terms of what triggers the calculation : With Implicit promises, the creation of a promise also triggers the computation, while with Explicit futures, one needs to triggers the resolution of a promise. This trigger can in turn be explicit, like calling a start method, or implicit, like lazy evaluation where the first use of a promise’s value triggers its evaluation. +We define Implicit promises as ones where we don’t have to manually trigger the computation vs Explicit promises where we have to trigger the resolution of future manually, either by calling a start function or by requiring the value. This distinction can be understood in terms of what triggers the calculation : With Implicit promises, the creation of a promise also triggers the computation, while with Explicit futures, one needs to triggers the resolution of a promise. This trigger can in turn be explicit, like calling a start method, or implicit, like lazy evaluation where the first use of a promise’s value triggers its evaluation. The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. Also, lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. -Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of futures in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. +Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. @@ -231,30 +242,49 @@ One of the criticism of traditional RPC systems would be that they’re blocking timeline
-Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. +Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. The history of promise pipelining can be traced back to the call-streams in Argus and channels in Joule. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated futures into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. -Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable over Promises, and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. +Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable over Promises, and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. # Handling Errors -In a synchronous programming model, the most logical way of handling errors is a try...catch block. +In a synchronous programming model, the most logical way of handling errors is a try...catch block. -
- timeline -
+```javascript +try{ + do something1; + do something2; + do something3; + ... +} catch ( exception ){ + HandleException; +} -Unfortunately, the same thing doesn’t directly translate to asynchronous code. +``` + +Unfortunately, the same thing doesn’t directly translate to asynchronous code. + + +```javascript + +foo = doSomethingAsync(); + +try{ + foo(); + // This doesn’t work as the error might not have been thrown yet +} catch ( exception ){ + handleException; +} -
- timeline -
+ +``` In javascript world, some patterns emerged, most noticeably the error-first callback style, also adopted by Node. Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises come to the rescue. @@ -267,7 +297,7 @@ In modern languages, Promises generally come with two callbacks. One to handle timeline
-In Javascript, Promises also have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. +In Javascript, Promises also have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler.
@@ -276,10 +306,22 @@ In Javascript, Promises also have a catch method, which help deal with errors in The same behavior can be written using catch block. -
- timeline -
+```scala + +work("") +.then(work) +.then(error) +.then(work) +.catch(handleError) +.then(check); + +function check(data) { + console.log(data == "1123"); + return Promise.resolve(); +} + +``` #Futures and Promises in Action @@ -291,7 +333,7 @@ Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes i ##Correctables -Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adrian Seredinschi at OSDI ‘16, in a paper titled Incremental Consistency Guarantees for Replicated Objects. As the title suggests, Correctables aim to solve the problems with consistency in replicated objects. They provide incremental consistency guarantees by capturing successive changes to the value of a replicated object. Applications can opt to receive a fast but possibly inconsistent result if eventual consistency is acceptable, or to wait for a strongly consistent result. Correctables API draws inspiration from, and builds on the API of Promises. Promises have a two state model to represent an asynchronous task, it starts in blocked state and proceeds to a ready state when the value is available. This cannot represent the incremental nature of correctables. Instead, Correctables have a updating state when it starts. From there on, it remains in updating state during intermediate updates, and when the final result is available, it transitions to final state. If an error occurs in between, it moves into an error state. Each state change triggers a callback. +Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adrian Seredinschi at OSDI ‘16, in a paper titled Incremental Consistency Guarantees for Replicated Objects. As the title suggests, Correctables aim to solve the problems with consistency in replicated objects. They provide incremental consistency guarantees by capturing successive changes to the value of a replicated object. Applications can opt to receive a fast but possibly inconsistent result if eventual consistency is acceptable, or to wait for a strongly consistent result. Correctables API draws inspiration from, and builds on the API of Promises. Promises have a two state model to represent an asynchronous task, it starts in blocked state and proceeds to a ready state when the value is available. This cannot represent the incremental nature of correctables. Instead, Correctables have a updating state when it starts. From there on, it remains in updating state during intermediate updates, and when the final result is available, it transitions to final state. If an error occurs in between, it moves into an error state. Each state change triggers a callback.
timeline -- cgit v1.2.3 From 372c6fac3d1264a84160ae957d136dadae80d3d1 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Thu, 15 Dec 2016 13:40:50 -0500 Subject: reduced image boundry --- chapter/2/15.png | Bin 15262 -> 25242 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chapter/2/15.png b/chapter/2/15.png index 4f2c188..f61e288 100644 Binary files a/chapter/2/15.png and b/chapter/2/15.png differ -- cgit v1.2.3 From d93e39c28c0112f23ba2e39632421596e547dce5 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Thu, 15 Dec 2016 13:42:13 -0500 Subject: removed images --- chapter/2/10.png | Bin 9834 -> 0 bytes chapter/2/11.png | Bin 12134 -> 0 bytes chapter/2/12.png | Bin 17071 -> 0 bytes chapter/2/14.png | Bin 11405 -> 0 bytes chapter/2/2.png | Bin 6152 -> 0 bytes chapter/2/3.png | Bin 13719 -> 0 bytes chapter/2/futures.md | 63 +++++++++++++++++++++++++++++++++------------------ 7 files changed, 41 insertions(+), 22 deletions(-) delete mode 100644 chapter/2/10.png delete mode 100644 chapter/2/11.png delete mode 100644 chapter/2/12.png delete mode 100644 chapter/2/14.png delete mode 100644 chapter/2/2.png delete mode 100644 chapter/2/3.png diff --git a/chapter/2/10.png b/chapter/2/10.png deleted file mode 100644 index f54711d..0000000 Binary files a/chapter/2/10.png and /dev/null differ diff --git a/chapter/2/11.png b/chapter/2/11.png deleted file mode 100644 index 7673d90..0000000 Binary files a/chapter/2/11.png and /dev/null differ diff --git a/chapter/2/12.png b/chapter/2/12.png deleted file mode 100644 index 7b2e13f..0000000 Binary files a/chapter/2/12.png and /dev/null differ diff --git a/chapter/2/14.png b/chapter/2/14.png deleted file mode 100644 index 5027666..0000000 Binary files a/chapter/2/14.png and /dev/null differ diff --git a/chapter/2/2.png b/chapter/2/2.png deleted file mode 100644 index a75c08b..0000000 Binary files a/chapter/2/2.png and /dev/null differ diff --git a/chapter/2/3.png b/chapter/2/3.png deleted file mode 100644 index 9cc66b5..0000000 Binary files a/chapter/2/3.png and /dev/null differ diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 6e019c7..c264dab 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -4,7 +4,7 @@ title: "Futures" by: "Kisalaya Prasad and Avanti Patil" --- -#Introduction +# Introduction As human beings we have an ability to multitask ie. we can walk, talk and eat at the same time except when you sneeze. Sneeze is like a blocking activity from the normal course of action, because it forces you to stop what you’re doing for a brief moment and then you resume where you left off. Activities like multitasking are called multithreading in computer lingo. In contrast to this behaviour, computer processors are single threaded. So when we say that a computer system has multi-threaded environment, it is actually just an illusion created by processor where processor’s time is shared between multiple processes. Sometimes processor gets blocked when some tasks are hindered from normal execution due to blocking calls. Such blocking calls can range from IO operations like read/write to disk or sending/receiving packets to/from network. Blocking calls can take disproportionate amount of time compared to the processor’s task execution i.e. iterating over a list. @@ -16,11 +16,11 @@ The processor can either handle blocking calls in two ways: In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. -
+
timeline
-#Motivation +# Motivation A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. @@ -81,7 +81,7 @@ In Java 8, the Future interface has methods to check if the computation is co Over the years promises and futures have been implemented in different programming languages and created a buzz in parallel computing world. We will take a look at some of the programming languages who designed frameworks to enhance performance of applications using Promises and futures. -## Fork-Join +## Thread Pools Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheap. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. @@ -164,7 +164,7 @@ Each message has a callback function which is fired when the message is processe Separating when a message is queued from when it is executed means the single thread doesn’t have to wait for an action to complete before moving on to another. We attach a callback to the action we want to do, and when the time comes, the callback is run with the result of our action. Callbacks work good in isolation, but they force us into a continuation passing style of execution, what is otherwise known as Callback hell. -
+
timeline
@@ -179,25 +179,25 @@ Let’s take an example to understand the promise resolution workflow as it happ Suppose we execute a function, here g() which in turn, calls function f(). Function f returns a promise, which, after counting down for 1000 ms, resolves the promise with a single value, true. Once f gets resolved, a value true or false is alerted based on the value of the promise. -
+
timeline
Now, javascript’s runtime is single threaded. This statement is true, and not true. The thread which executes the user code is single threaded. It executes what is on top of the stack, runs it to completion, and then moves onto what is next on the stack. But, there are also a number of helper threads which handle things like network or timer/settimeout type events. This timing thread handles the counter for setTimeout. -
+
timeline
Once the timer expires, the timer thread puts a message on the message queue. The queued up messages are then handled by the event loop. The event loop as described above, is simply an infinite loop which checks if a message is ready to be processed, picks it up and puts it on the stack for it’s callback to be executed. -
+
timeline
Here, since the future is resolved with a value of true, we are alerted with a value true when the callback is picked up for execution. -
+
timeline
@@ -211,7 +211,7 @@ We haven’t talked about error handling, but it gets handled the same exact way Event loops have proven to be surprisingly performant. When network servers are designed around multithreading, as soon as you end up with a few hundred concurrent connections, the CPU spends so much of its time task switching that you start to lose overall performance. Switching from one thread to another has overhead which can add up significantly at scale. Apache used to choke even as low as a few hundred concurrent users when using a thread per connection while Node can scale up to a 100,000 concurrent connections based on event loops and asynchronous IO. -##Thread Model +## Thread Model Oz programming language introduced an idea of dataflow concurrency model. In Oz, whenever the program comes across an unbound variable, it waits for it to be resolved. This dataflow property of variables helps us write threads in Oz that communicate through streams in a producer-consumer pattern. The major benefit of dataflow based concurrency model is that it’s deterministic - same operation called with same parameters always produces the same result. It makes it a lot easier to reason about concurrent programs, if the code is side-effect free. @@ -225,7 +225,7 @@ Any expression in Alice can be evaluated in it's own thread using spawn keyword. Alice also allows for lazy evaluation of expressions. Expressions preceded with the lazy keyword are evaluated to a lazy future. The lazy future is evaluated when it is needed. If the computation associated with a concurrent or lazy future ends with an exception, it results in a failed future. Requesting a failed future does not block, it simply raises the exception that was the cause of the failure. -#Implicit vs. Explicit Promises +# Implicit vs. Explicit Promises We define Implicit promises as ones where we don’t have to manually trigger the computation vs Explicit promises where we have to trigger the resolution of future manually, either by calling a start function or by requiring the value. This distinction can be understood in terms of what triggers the calculation : With Implicit promises, the creation of a promise also triggers the computation, while with Explicit futures, one needs to triggers the resolution of a promise. This trigger can in turn be explicit, like calling a start method, or implicit, like lazy evaluation where the first use of a promise’s value triggers its evaluation. @@ -238,7 +238,7 @@ Implicit futures were introduced originally by Friedman and Wise in a paper in 1 # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. -
+
timeline
@@ -264,8 +264,8 @@ try{ do something3; ... } catch ( exception ){ - HandleException; -} + HandleException; +} ``` @@ -281,7 +281,7 @@ try{ // This doesn’t work as the error might not have been thrown yet } catch ( exception ){ handleException; -} +} ``` @@ -293,14 +293,33 @@ Although most of the earlier papers did not talk about error handling, the Promi In modern languages, Promises generally come with two callbacks. One to handle the success case and other to handle the failure. -
- timeline -
-In Javascript, Promises also have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. +#### In Scala +```scala + +f onComplete { + case Success(data) => handleSuccess(data) + case Failure(e) => handleFailure(e) +} +``` + +#### In Javascript +```javascript + +promise.then(function (data) { + // success callback + console.log(data); +}, function (error) { + // failure callback + console.error(error); +}); +``` -
+In Javascript, Promises have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. + + +
timeline
@@ -315,7 +334,7 @@ work("") .then(work) .catch(handleError) .then(check); - + function check(data) { console.log(data == "1123"); return Promise.resolve(); @@ -335,7 +354,7 @@ Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes i ##Correctables Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adrian Seredinschi at OSDI ‘16, in a paper titled Incremental Consistency Guarantees for Replicated Objects. As the title suggests, Correctables aim to solve the problems with consistency in replicated objects. They provide incremental consistency guarantees by capturing successive changes to the value of a replicated object. Applications can opt to receive a fast but possibly inconsistent result if eventual consistency is acceptable, or to wait for a strongly consistent result. Correctables API draws inspiration from, and builds on the API of Promises. Promises have a two state model to represent an asynchronous task, it starts in blocked state and proceeds to a ready state when the value is available. This cannot represent the incremental nature of correctables. Instead, Correctables have a updating state when it starts. From there on, it remains in updating state during intermediate updates, and when the final result is available, it transitions to final state. If an error occurs in between, it moves into an error state. Each state change triggers a callback. -
+
timeline
-- cgit v1.2.3 From 8b9054bf97086ebd951b25bda63c4f401f8fda57 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Thu, 15 Dec 2016 13:49:05 -0500 Subject: sharper image --- chapter/2/15.png | Bin 25242 -> 48459 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chapter/2/15.png b/chapter/2/15.png index f61e288..15a2a81 100644 Binary files a/chapter/2/15.png and b/chapter/2/15.png differ -- cgit v1.2.3 From 060ea6ba37d8d360d1f1ab9c86bfed4f92433f5b Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 15:52:29 -0500 Subject: Add a bunch of citations and some rewording --- _bibliography/message-passing.bib | 17 +++++++++++++ chapter/3/message-passing.md | 53 ++++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index acb92a4..ed4e623 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -251,3 +251,20 @@ xxxurl = {http://doi.acm.org/10.1145/2034675.2034690}, year = 2011 } + +@book{Agha:1986:AMC:7929, + author = {Agha, Gul}, + title = {Actors: A Model of Concurrent Computation in Distributed Systems}, + year = {1986}, + isbn = {0-262-01092-5}, + publisher = {MIT Press}, + address = {Cambridge, MA, USA}, +} + +@book{Peierls:2005:JCP:1076522, + author = {Peierls, Tim and Goetz, Brian and Bloch, Joshua and Bowbeer, Joseph and Lea, Doug and Holmes, David}, + title = {Java Concurrency in Practice}, + year = {2005}, + isbn = {0321349601}, + publisher = {Addison-Wesley Professional}, +} diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 6e898ba..e6e7e4b 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -8,7 +8,7 @@ by: "Nathaniel Dempkowski" Message passing programming models have essentially been discussed since the beginning of distributed computing and as a result message passing can be taken to mean a lot of things. If you look up a broad definition on Wikipedia, it includes things like RPC, CSP, and MPI. In practice when people talk about message passing today they mostly mean the actor model. -In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in newer papers. It is enlightening to see which aspects of the models have stuck around, and many of the newer papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, especially those focused on the actor model of programming and organizing units of computation. +In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in more recent papers. It is enlightening to see which aspects of the models have stuck around, and many of the more recent papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, especially those focused on the actor model of programming and organizing units of computation. In this chapter I describe the four primary variants of the actor model: classic actors, process-based actors, communicating event-loops, and active objects. I attempt to highlight historic and modern languages that exemplify these models, as well as the philosophies and tradeoffs that programmers need to be aware of to understand and best make use of these models. @@ -18,12 +18,12 @@ An important framing for the actor models presented is in the question "Why mess # Original proposal of the actor model -The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel computation in communication in a way that could be safely distributed concurrently across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. +The actor model was originally proposed in _A Universal Modular ACTOR Formalism for Artificial Intelligence_ {% cite Hewitt:1973:UMA:1624775.1624804 --file message-passing %} in 1973 as a method of computation for artificial intelligence research. The original goal of the model was to model parallel computation in communication in a way that could be safely distributed concurrently across workstations. The paper makes few presumptions about implementation details, instead defining the high-level message passing communication model. Gul Agha developed the model further, by focusing on using actors as a basis for concurrent object-oriented programming. This work is collected in _Actors: A Model of Concurrent Computation in Distributed Systems_. {% cite Agha:1986:AMC:7929 --file message-passing %} Actors are defined as independent units of computation with isolated state. These units have two core characteristics: -* they can send messages to one another, and, -* they have a mailbox which contains messages that they have received. +* they can send messages asynchronously to one another, and, +* they have a mailbox which contains messages that they have received, allowing messages to be received at any time and then queued for processing. Messages are of the form: @@ -32,13 +32,13 @@ Messages are of the form: reply-to: ) ``` -Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a type of continuation, where the continuation is the message to another actor. These messages are one-way and, there are no guarantees that a message will ever be received in response. This originally-proposed variant of the actor model is limited compared to many of the others, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. +Actors attempt to process messages from their mailboxes by matching their `request` field sequentially against patterns or rules which can be specific values or logical statements. When a pattern is matched, computation occurs and the result of that computation is implicitly returned to the reference in the message's `reply-to` field. This is a type of continuation, where the continuation is the message to another actor. These messages are one-way and, there are no guarantees that a message will ever be received in response. The actor model is so general because it places few restrictions on systems. Asynchrony and the absence of message delivery guarantees enable modeling real distributed systems using the actor model. For example, if message delivery was guaranteed, then the model would be much less general, and only able to model systems which include complex message-delivery protocols. This originally-proposed variant of the actor model is limited compared to many of the others, but the early ideas of taking advantage of distribution of processing power to enable greater parallel computation are there. Interestingly, the original paper introducing the actor model does so in the context of hardware. They mention actors as almost another machine architecture. This paper describes the concepts of an "actor machine" and a "hardware actor" as the context for the actor model, which is totally different from the way we think about modern actors as abstracting away a lot of the hardware details we don't want to deal with. This concept is reminiscent of something like a Lisp machine, though specially built to utilize the actor model of computation for artificial intelligence. # Classic actor model -The classic actor model was formalized as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. The classic actor expands on the original proposal of actors, keeping the ideas of asynchronous communication through messages between isolated units of computation and state. The classic actor contains the following primitive operations: +The classic actor model was formalized as a unit of computation in Agha's _Concurrent Object-Oriented Programming_. {% cite Agha:1990:COP:83880.84528 --file message-passing %} The classic actor expands on the original proposal of actors, keeping the ideas of asynchronous communication through messages between isolated units of computation and state. The classic actor contains the following primitive operations: * `create`: create an actor from a behavior description and a set of parameters, including other existing actors * `send`: send a message to another actor @@ -50,24 +50,43 @@ For purely functional actors the new behavior would be identical to the original If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. Kay's ideas sound closer to what we consider the actor model today, and less like what we consider object-oriented programming. That is, Kay's focus in this description is on designing the messaging and communications that dictate how objects interact. +
+

The big idea is "messaging" -- that is what the kernal [sic] of Smalltalk/Squeak is all about (and it's something that was never quite completed in our Xerox PARC phase). The Japanese have a small word -- ma -- for "that which is in between" -- perhaps the nearest English equivalent is "interstitial". The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.

+
Alan Kay
+
+ TODO: transition ## Concurrent Object-Oriented Programming (1990) -One could say that the renaissance of actor models in mainstream program began with seminal paper, _Concurrent Object-Oriented Programming_ (citation), as it offers classic actors as a natural solution to solving problems at the intersection of two trends in computing; increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. +One could say that the renaissance of actor models in mainstream program began with Gul Agha's work. His seminal book _Actors: A Model of Concurrent Computation in Distributed Systems_ {% cite Agha:1986:AMC:7929 --file message-passing %} and later paper, _Concurrent Object-Oriented Programming_ {% cite Agha:1990:COP:83880.84528 --file message-passing %}, offer classic actors as a natural solution to solving problems at the intersection of two trends in computing; increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. + +This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to identify some of the advantages from the perspective of programmers of programming with actors. One of the core languages used for examples in the paper is Rosette {% cite Tomlinson:1988:ROC:67387.67410 --file message-passing %}, but the paper largely focuses on the potential and benefits of the model. Agha claims the benefits of using objects stem from a separation of concerns. + +
+

By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects.

+
Gul Agha {% cite Agha:1990:COP:83880.84528 --file message-passing %}
+
-This paper looks at a lot of systems and languages that are implementing solutions in this space, and starts to identify some of the advantages from the perspective of programmers of programming with actors. One of the core languages used for examples in the paper is Rosette, but the paper largely focuses on the potential and benefits of the model. Agha claims the benefits of using objects stem from a separation of concerns. "By separating the specification of what is done (the abstraction) from how it is done (the implementation), the concept of objects provides modularity necessary for programming in the large. It turns out that concurrency is a natural consequence of the concept of objects." Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs, as Agha states. "It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs." This flexibility turns out to be a highly discussed advantage which continues to be touted in modern actor systems. +Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs. + +
+It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs. +
Gul Agha {% cite Agha:1990:COP:83880.84528 --file message-passing %}
+
+ +This flexibility turns out to be a highly discussed advantage which continues to be touted in modern actor systems. ## Rosette -Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, and the code examples given in the paper are written in Rosette. Agha is even an author on the Rosette paper, so its clear that Rosette is foundational to the classic actor model. It seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. +Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette {% cite Tomlinson:1988:ROC:67387.67410 --file message-passing %} is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, {% cite Agha:1990:COP:83880.84528 --file message-passing %} and the code examples given in the paper are written in Rosette. Agha is even an author on the Rosette paper, so its clear that Rosette is foundational to the classic actor model. It seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means to control how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined, but rather depends on some heuristic to return results. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: * _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. * _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. -The Rosette language has a number of object-oriented features, many of which we take for granted in modern object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. The more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. In Rosette, the concurrency is an inherent property of the program structure and resource allocation. This is different from a language like Java, where all of the concurrency is very explicit. The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers and architectures have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the specific machine makes it difficult to re-map a program to another one. This idea of using actors as a more flexible and natural abstraction over concurrency and distribution of resources is an important one which is seen in some form within many actor systems. +The Rosette language has a number of object-oriented features, many of which we take for granted in modern object-oriented programming languages. It implements dynamic creation and modification of objects for extensible and reconfigurable systems, supports inheritance, and has objects which can be organized into classes. The more interesting characteristic is that the concurrency in Rosette is inherent and declarative rather than explicit as with many modern object-oriented languages. In Rosette, the concurrency is an inherent property of the program structure and resource allocation. This is different from a language like Java, where all of the concurrency is very explicit. The Java concurrency model is best covered in _Java Concurrency in Practice_, though Java 8 introduces a few new concurrency techniques that the book does not discuss. {% cite Peierls:2005:JCP:1076522 --file message-passing %} The motivation behind this declarative concurrency comes from the heterogeneous nature of distributed concurrent computers. Different computers and architectures have varying concurrency characteristics, and the authors argue that forcing the programmer to tailor their concurrency to the specific machine makes it difficult to re-map a program to another one. This idea of using actors as a more flexible and natural abstraction over concurrency and distribution of resources is an important one which is seen in some form within many actor systems. Actors in Rosette are organized into three types of classes which describe different aspects of the actors within the system: @@ -79,9 +98,9 @@ These classes represent a concrete object-oriented abstraction to organize actor ## Akka -Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. +Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing} -Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded an an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. +Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded and an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. The Akka runtime also provides performance advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. @@ -110,7 +129,7 @@ Erlang actors run as lightweight isolated processes. They do not have visibility Erlang implements a blocking `receive` operation as a means of processing messages from a processes' mailbox. They use value matching on message tuples as a means of describing the types of messages a given actor can accept. -Erlang also seeks to build failure into the programming model, as one of the core assumptions of a distributed system is that things are going to fail. Erlang provides the ability for processes to monitor one another through two primitives: +Erlang also seeks to build failure into the programming model, as one of the core assumptions of a distributed system is that machines and network connections are going to fail. Erlang provides the ability for processes to monitor one another through two primitives: * `monitor`: one-way unobtrusive notification of process failure/shutdown * `link`: two-way notification of process failure/shutdown allowing for coordinated termination @@ -146,11 +165,11 @@ The difference in semantics between the two types of references means that only The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. -TODO: write more here, maybe something around promise pipelining and partial failure? implications of different types of communication? maybe mention some of the points that inspire some aspects of modern actors? +TODO: Mention partial failure and implications of different types of communication ## AmbientTalk/2 -AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between and movement of passive objects. +AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects {% cite Yonezawa:1986:OCP:960112.28722 --file message-passing %}, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between and movement of passive objects. Actors in AmbientTalk/2 are representations of event loops. The message queue is the event queue, messages are events, asynchronous message sends are event notifications, and object methods are the event handlers. The event loop serially processes messages from the queue to avoid race conditions. Local objects within an actor are owned by that actor, which is the only entity allowed to directly execute methods on them. Like E, objects within an actor can communicate using synchronous or asynchronous methods of communication. Again similar to E, objects that are referenced outside of an actor can only be communicated to asynchronously by sending messages. Objects can additionally declare themselves serializable, which means they can be copied and sent to other actors for use as local objects. When this happens, there is no maintained relationship between the original object and its copy. @@ -176,7 +195,7 @@ The active object model as initially described in the ABCL/1 language defines ob ## ABCL/1 Language -The ABCL/1 language implements the active object model described above, representing a system as a collection of objects, and the interactions between those objects as concurrent messages being passed around. One interesting aspect of ABCL/1 is the idea of explicitly different modes of message passing. Other actor models generally have a notion of priority around the values, types, or patterns of messages they process, usually defined by the ordering of their receive operation, but ABCL/1 implements two different modes of message passing with different semantics. They have standard queued messages in the `ordinary` mode, but more interestingly they have `express` priority messages. When an object receives an express message it halts any other processing of ordinary messages it is performing, and processes the `express` message immediately. This enables an actor to accept high-priority messages while in `active` mode, and also enables monitoring and interrupting actors. +The ABCL/1 language implements the active object model described above, representing a system as a collection of objects, and the interactions between those objects as concurrent messages being passed around. {% cite Yonezawa:1986:OCP:960112.28722 --file message-passing %} One interesting aspect of ABCL/1 is the idea of explicitly different modes of message passing. Other actor models generally have a notion of priority around the values, types, or patterns of messages they process, usually defined by the ordering of their receive operation, but ABCL/1 implements two different modes of message passing with different semantics. They have standard queued messages in the `ordinary` mode, but more interestingly they have `express` priority messages. When an object receives an express message it halts any other processing of ordinary messages it is performing, and processes the `express` message immediately. This enables an actor to accept high-priority messages while in `active` mode, and also enables monitoring and interrupting actors. The language also offers different models of synchronization around message-passing between actors. Three different message-passing models are given that enable different use cases: @@ -246,8 +265,6 @@ Both approaches have been successful in industry. Erlang has the famous use case ## Comparison to Communicating Sequential Processes (CSP) -TODO: where should this live in the chapter? - You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. A lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. # References -- cgit v1.2.3 From e58ca2aa1bbf801dbbbce3d6c51465fa0cecd8c6 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 15:56:02 -0500 Subject: Fix missing % --- chapter/3/message-passing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index e6e7e4b..a11f977 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -98,7 +98,7 @@ These classes represent a concrete object-oriented abstraction to organize actor ## Akka -Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing} +Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing %} Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded and an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. -- cgit v1.2.3 From 074a631ef668c6ffd08adb629862a6a2b5c2d3f2 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 16:28:19 -0500 Subject: Add example of pure vs. stateful classic actor --- chapter/3/message-passing.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index a11f977..6fbb069 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -46,7 +46,30 @@ The classic actor model was formalized as a unit of computation in Agha's _Concu As in the original actor model, classic actors communicate by asynchronous message passing. They are a primitive independent unit of computation which can be used to build higher-level abstractions for concurrent programming. Actors are uniquely addressable, and have their own independent mailboxes or message queues. State changes using the classic actor model are specified and aggregated using the `become` operation. Each time an actor processes a message it computes a behavior in response to the next type of message it expects to process. A `become` operation's argument is a named continuation, `b`, representing behavior that the actor should be updated with, along with some state that should be passed to `b`. -For purely functional actors the new behavior would be identical to the original. For more complex actors however, this enables the aggregation of state changes at a higher level of granularity than something like a variable assignment. This enables flexibility in the behavior of an actor over time in response to the actions of other actors in the system. Additionally, this isolation changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. +This continuation model is flexible. You could create a purely functional actor where the new behavior would be identical to the original and no state would be passed. An example of this is the `AddOne` actor below, which processes a message according to a single fixed behavior. + +``` +(define AddOne + [add-one [n] + (return (+ n 1))]) +``` + +The model also enables the creation of stateful actors which change behavior and pass along an object representing some state. This state can be the result of many operations, which enables the aggregation of state changes at a higher level of granularity than something like variable assignment. An example of this is a `BankAccount` actor given in _Concurrent Object-Oriented Programming_. {% cite Agha:1990:COP:83880.84528 --file message-passing %} + +``` +(define BankAccount + (mutable [balance] + [withdraw-from [amount] + (become BankAccount (- balance amount)) + (return 'withdrew amount)] + [deposit-to [amount] + (become BankAccount (+ balance amount)) + (return 'deposited amount)] + [balance-query + (return 'balance-is balance)])) +``` + +Stateful continuations enable flexibility in the behavior of an actor over time in response to the actions of other actors in the system. Limiting state and behavior changes to `become` operations changes the level at which one analyzes a system, freeing the programmer from worrying about interference during state changes. In the example above, the programmer only has to worry about changes to the account's balance during `become` statements in response to a sequential queue of well-defined message types. If you squint a little, this actor definition sounds similar to Alan Kay’s original definition of Object Oriented programming. This definition describes a system where objects have a behavior, their own memory, and communicate by sending and receiving messages that may contain other objects or simply trigger actions. Kay's ideas sound closer to what we consider the actor model today, and less like what we consider object-oriented programming. That is, Kay's focus in this description is on designing the messaging and communications that dictate how objects interact. -- cgit v1.2.3 From 9454c5b53bab5f0d8a4f5755af4e4829e9d200b7 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Thu, 15 Dec 2016 17:29:49 -0500 Subject: moved images to new folder and added code --- chapter/2/1.png | Bin 14176 -> 0 bytes chapter/2/13.png | Bin 21547 -> 0 bytes chapter/2/15.png | Bin 48459 -> 0 bytes chapter/2/4.png | Bin 25404 -> 0 bytes chapter/2/5.png | Bin 20821 -> 0 bytes chapter/2/6.png | Bin 19123 -> 0 bytes chapter/2/7.png | Bin 30068 -> 0 bytes chapter/2/8.png | Bin 13899 -> 0 bytes chapter/2/9.png | Bin 6463 -> 0 bytes chapter/2/futures.md | 112 ++++++++++++++++++++++++++++++++++++++++----------- 10 files changed, 88 insertions(+), 24 deletions(-) delete mode 100644 chapter/2/1.png delete mode 100644 chapter/2/13.png delete mode 100644 chapter/2/15.png delete mode 100644 chapter/2/4.png delete mode 100644 chapter/2/5.png delete mode 100644 chapter/2/6.png delete mode 100644 chapter/2/7.png delete mode 100644 chapter/2/8.png delete mode 100644 chapter/2/9.png diff --git a/chapter/2/1.png b/chapter/2/1.png deleted file mode 100644 index 1d98f19..0000000 Binary files a/chapter/2/1.png and /dev/null differ diff --git a/chapter/2/13.png b/chapter/2/13.png deleted file mode 100644 index a2b8457..0000000 Binary files a/chapter/2/13.png and /dev/null differ diff --git a/chapter/2/15.png b/chapter/2/15.png deleted file mode 100644 index 15a2a81..0000000 Binary files a/chapter/2/15.png and /dev/null differ diff --git a/chapter/2/4.png b/chapter/2/4.png deleted file mode 100644 index 8cfec98..0000000 Binary files a/chapter/2/4.png and /dev/null differ diff --git a/chapter/2/5.png b/chapter/2/5.png deleted file mode 100644 index b86de04..0000000 Binary files a/chapter/2/5.png and /dev/null differ diff --git a/chapter/2/6.png b/chapter/2/6.png deleted file mode 100644 index aaafdbd..0000000 Binary files a/chapter/2/6.png and /dev/null differ diff --git a/chapter/2/7.png b/chapter/2/7.png deleted file mode 100644 index 7183fb6..0000000 Binary files a/chapter/2/7.png and /dev/null differ diff --git a/chapter/2/8.png b/chapter/2/8.png deleted file mode 100644 index d6d2e0e..0000000 Binary files a/chapter/2/8.png and /dev/null differ diff --git a/chapter/2/9.png b/chapter/2/9.png deleted file mode 100644 index 1b67a45..0000000 Binary files a/chapter/2/9.png and /dev/null differ diff --git a/chapter/2/futures.md b/chapter/2/futures.md index c264dab..5ab4c3e 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -17,7 +17,7 @@ In the world of asynchronous communications many terminologies were defined to h
- timeline + timeline
# Motivation @@ -50,11 +50,10 @@ Among the modern languages, Python was perhaps the first to come up with somethi Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API, it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. -#Different Definitions +# Different Definitions -Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. The definitions have changed a little over the years but the idea remained the same. - +Future, promise, Delay or Deferred generally refer to same synchronisation mechanism where an object acts as a proxy for a yet unknown result. When the result is discovered, promises hold some code which then gets executed. In some languages however, there is a subtle difference between what is a Future and a Promise. “A ‘Future’ is a read-only reference to a yet-to-be-computed value”. @@ -79,7 +78,7 @@ In Java 8, the Future interface has methods to check if the computation is co # Semantics of Execution -Over the years promises and futures have been implemented in different programming languages and created a buzz in parallel computing world. We will take a look at some of the programming languages who designed frameworks to enhance performance of applications using Promises and futures. +Over the years promises and futures have been implemented in different programming languages. Different languages chose to implement futures/promises in a different way. In this section, we try to introduce some different ways in which futures and promises actually get executed and resolved underneath their APIs. ## Thread Pools @@ -164,11 +163,52 @@ Each message has a callback function which is fired when the message is processe Separating when a message is queued from when it is executed means the single thread doesn’t have to wait for an action to complete before moving on to another. We attach a callback to the action we want to do, and when the time comes, the callback is run with the result of our action. Callbacks work good in isolation, but they force us into a continuation passing style of execution, what is otherwise known as Callback hell. -
- timeline -
-**Programs must be written for people to read, and only incidentally for machines to execute.** - *Harold Abelson and Gerald Jay Sussman* +```javascript + +getData = function(param, callback){ + $.get('http://example.com/get/'+param, + function(responseText){ + callback(responseText); + }); +} + +getData(0, function(a){ + getData(a, function(b){ + getData(b, function(c){ + getData(c, function(d){ + getData(d, function(e){ + + }); + }); + }); + }); +}); + +``` + +

VS

+ +```javascript + +getData = function(param, callback){ + return new Promise(function(resolve, reject) { + $.get('http://example.com/get/'+param, + function(responseText){ + resolve(responseText); + }); + }); +} + +getData(0).then(getData) + .then(getData). + then(getData). + then(getData); + + +``` + +> **Programs must be written for people to read, and only incidentally for machines to execute.** - *Harold Abelson and Gerald Jay Sussman* Promises are an abstraction which make working with async operations in javascript much more fun. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. @@ -180,25 +220,25 @@ Suppose we execute a function, here g() which in turn, calls function f(). Funct
- timeline + timeline
Now, javascript’s runtime is single threaded. This statement is true, and not true. The thread which executes the user code is single threaded. It executes what is on top of the stack, runs it to completion, and then moves onto what is next on the stack. But, there are also a number of helper threads which handle things like network or timer/settimeout type events. This timing thread handles the counter for setTimeout.
- timeline + timeline
Once the timer expires, the timer thread puts a message on the message queue. The queued up messages are then handled by the event loop. The event loop as described above, is simply an infinite loop which checks if a message is ready to be processed, picks it up and puts it on the stack for it’s callback to be executed.
- timeline + timeline
Here, since the future is resolved with a value of true, we are alerted with a value true when the callback is picked up for execution.
- timeline + timeline
Some finer details : @@ -239,7 +279,7 @@ Implicit futures were introduced originally by Friedman and Wise in a paper in 1 One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises.
- timeline + timeline
Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. @@ -319,14 +359,38 @@ promise.then(function (data) { In Javascript, Promises have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. -
- timeline -
+```javascript +function work(data) { + return Promise.resolve(data+"1"); +} + +function error(data) { + return Promise.reject(data+"2"); +} + +function handleError(error) { + return error +"3"; +} + + +work("") +.then(work) +.then(error) +.then(work) // this will be skipped +.then(work, handleError) +.then(check); + +function check(data) { + console.log(data == "1123"); + return Promise.resolve(); +} + +``` The same behavior can be written using catch block. -```scala +```javascript work("") .then(work) @@ -342,27 +406,27 @@ function check(data) { ``` -#Futures and Promises in Action +# Futures and Promises in Action -##Twitter Finagle +## Twitter Finagle Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes it easy to build robust clients and servers in Java, Scala, or any JVM-hosted language. It uses idea of Futures to encapsulate concurrent tasks and are analogous to threads, but even more lightweight. -##Correctables +## Correctables Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adrian Seredinschi at OSDI ‘16, in a paper titled Incremental Consistency Guarantees for Replicated Objects. As the title suggests, Correctables aim to solve the problems with consistency in replicated objects. They provide incremental consistency guarantees by capturing successive changes to the value of a replicated object. Applications can opt to receive a fast but possibly inconsistent result if eventual consistency is acceptable, or to wait for a strongly consistent result. Correctables API draws inspiration from, and builds on the API of Promises. Promises have a two state model to represent an asynchronous task, it starts in blocked state and proceeds to a ready state when the value is available. This cannot represent the incremental nature of correctables. Instead, Correctables have a updating state when it starts. From there on, it remains in updating state during intermediate updates, and when the final result is available, it transitions to final state. If an error occurs in between, it moves into an error state. Each state change triggers a callback.
- timeline + timeline
-##Folly Futures +## Folly Futures Folly is a library by Facebook for asynchronous C++ inspired by the implementation of Futures by Twitter for Scala. It builds upon the Futures in the C++11 Standard. Like Scala’s futures, they also allow for implementing a custom executor which provides different ways of running a Future (thread pool, event loop etc). -##NodeJS Fiber +## NodeJS Fiber Fibers provide coroutine support for v8 and node. Applications can use Fibers to allow users to write code without using a ton of callbacks, without sacrificing the performance benefits of asynchronous IO. Think of fibers as light-weight threads for nodejs where the scheduling is in the hands of the programmer. The node-fibers library doesn’t recommend using raw API and code together without any abstractions, and provides a Futures implementation which is ‘fiber-aware’. ## References -- cgit v1.2.3 From c5231852c5b1d8d0d712ea552305c3fa0ee1a927 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 17:43:54 -0500 Subject: Further clarification in some sections, add E diagrams to explain vats and modes of message-processing better --- chapter/3/E_account_spreadsheet_vats.png | Bin 0 -> 183811 bytes chapter/3/E_vat.png | Bin 0 -> 53914 bytes chapter/3/message-passing.md | 47 ++++++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 chapter/3/E_account_spreadsheet_vats.png create mode 100644 chapter/3/E_vat.png diff --git a/chapter/3/E_account_spreadsheet_vats.png b/chapter/3/E_account_spreadsheet_vats.png new file mode 100644 index 0000000..8ce9624 Binary files /dev/null and b/chapter/3/E_account_spreadsheet_vats.png differ diff --git a/chapter/3/E_vat.png b/chapter/3/E_vat.png new file mode 100644 index 0000000..131b0de Binary files /dev/null and b/chapter/3/E_vat.png differ diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 6fbb069..3824cd1 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -104,7 +104,7 @@ This flexibility turns out to be a highly discussed advantage which continues to Rosette was both a language for concurrent object-oriented programming of actors, as well as a runtime system for managing the usage of and access to resources by those actors. Rosette {% cite Tomlinson:1988:ROC:67387.67410 --file message-passing %} is mentioned throughout Agha's _Concurrent Object-Oriented Programming_, {% cite Agha:1990:COP:83880.84528 --file message-passing %} and the code examples given in the paper are written in Rosette. Agha is even an author on the Rosette paper, so its clear that Rosette is foundational to the classic actor model. It seems to be a language which almost defines what the classic actor model looks like in the context of concurrent object-oriented programming. -The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means to control how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. This supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined, but rather depends on some heuristic to return results. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: +The motivation behind Rosette was to provide strategies for dealing with problems like search, where the programmer needs a means to control how resources are allocated to sub-computations to optimize performance in the face of combinatorial explosion. For example in a search problem, you might first compute an initial set of results that you want to further refine. It would be too computationally expensive to exhaustively refine every result, so you want to choose the best ones based on some metric and only proceed with those. Rosette supports the use of concurrency in solving computationally intensive problems whose structure is not statically defined, but rather depends on some heuristic to return results. Rosette has an architecture which uses actors in two distinct ways. They describe two different layers with different responsibilities: * _Interface layer_: This implements mechanisms for monitoring and control of resources. The system resources and hardware are viewed as actors. * _System environment_: This is comprised of actors who actually describe the behavior of concurrent applications and implement resource management policies based on the interface layer. @@ -123,7 +123,7 @@ These classes represent a concrete object-oriented abstraction to organize actor Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing %} -Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded and an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. +Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded and an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. Akka actors have a limited `ActorRef` interface which only provides methods to send or forward messages to its actor, additionally checks are done to ensure that no direct reference to an instance of an `Actor` subclass is accessible after an actor is created. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. The Akka runtime also provides performance advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. @@ -159,11 +159,13 @@ Erlang also seeks to build failure into the programming model, as one of the cor These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. Erlang's philosophies used to build a reliable fault-tolerant telephone exchange seem to be broadly applicable to the fault-tolerance problems of distributed systems. +It is worth mentioning that Erlang achieves all of this through the Erlang Virtual Machine (BEAM), which runs as a single OS process and OS thread per core. These single OS processes then manage many lightweight Erlang processes. The Erlang VM implements all of the concurrency, monitoring, and garbage collection for Erlang processes within this VM, which almost acts like an operating system itself. This is unlike any other language or actor system described here. + ## Scala Actors Scala Actors is an example of taking and enhancing the Erlang model while bringing it to a new platform. Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors claim that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” -The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. In addition to the more natural abstraction, the Erlang model was further enhanced with Scala's type system and advanced pattern-matching capabilities. +The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. The Erlang model was further enhanced with Scala's pattern-matching capabilities which enable more advanced pattern-matching on messages compared to Erlang's tuple value matching. Scala Actors are of the type `Any => Unit`, which means that they are essentially untyped. They can receive literally any type and match on it with potential side effects. This behavior could be problematic and systems like Cloud Haskell and Akka aim to improve on it. ## Cloud Haskell @@ -179,13 +181,50 @@ The communicating event-loop model was introduced in the E language, and is one The E language implements a model which is closer to imperative object-oriented programming. Within a single actor-like node of computation called a "vat" many objects are contained. This vat contains not just objects, but a mailbox for all of the objects inside, as well as a call stack for methods on those objects. There is a shared message queue and event-loop that acts as one abstraction barrier for computation across actors. The actual references to objects within a vat are used for addressing communication and computation across actors and operate at a different level of abstraction. -When handing out references at a different level of granularity than actor-global, how do you ensure the benefits of isolation that the actor model provides? After all, by referencing objects inside of an actor from many places it sounds like we're just reinventing shared-memory problems. The answer is that E's reference-states define many of the isolation guarantees around computation that we expect from actors. Two different ways to reference objects are defined: +This immediately raises other concerns. When handing out references at a different level of granularity than actor-global, how do you ensure the benefits of isolation that the actor model provides? After all, by referencing objects inside of an actor from many places it sounds like we're just reinventing shared-memory problems. This is answered by two different modes of execution: immediate and eventual calls. + +
+ An E vat +
{% cite Miller:2005:CSP:1986262.1986274 --file message-passing %}
+
+ +This diagram shows an E vat, which consists of a heap of objects and a thread of control for executing methods on those objects. The stack and queue represent messages in the two different modes of execution that are used when operating on objects in E. The stack is used for immediate execution, while the queue is used for eventual execution. Immediate calls are processed first, and new immediate calls are added to the top of the stack. Eventual calls are then processed from the queue afterwards. These different modes of message passing are highlighted in communication across vats below. + +
+ Communication between E vats +
{% cite Miller:2005:CSP:1986262.1986274 --file message-passing %}
+
+ +From this diagram we can see that local calls among objects within a vat are handled on the immediate stack. Then when a call needs to be made across vats, it is handled on the eventual queue, and delivered to the appropriate object within the vat at some point in the future. + +E's reference-states define many of the isolation guarantees around computation that we expect from actors. Two different ways to reference objects are defined: * _Near reference_: This is a reference only possible between two objects in the same vat. These expose both synchronous immediate-calls and asynchronous eventual-sends. * _Eventual reference_: This is a reference which crosses vat boundaries, and only exposes asynchronous eventual-sends, not synchronous immediate-calls. The difference in semantics between the two types of references means that only objects within the same vat are granted synchronous access to one another. The most an eventual reference can do is asynchronously send and queue a message for processing at some unspecified point in the future. This means that within the execution of a vat, a degree of temporal isolation can be defined between the objects and communications within the vat, and the communications to and from other vats. +TODO: explain this code example in the context of the above diagram or come up with a new one? + +``` +def makeStatusHolder(var myStatus) { + def myListeners := [].diverge() + def statusHolder { + to addListener(newListener) { + myListeners.push(newListener) + } + to getStatus() { return myStatus } + to setStatus(newStatus) { + myStatus := newStatus + for listener in myListeners { + listener.statusChanged(newStatus) + } + } + } + return statusHolder +} +``` + The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. TODO: Mention partial failure and implications of different types of communication -- cgit v1.2.3 From bb776e36fc584c7a1a90dfa33887a13e705f7780 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 18:58:43 -0500 Subject: Expand more on production use of actors and actors as frameworks --- _bibliography/message-passing.bib | 25 ++++++++++++++++++++++++- chapter/3/message-passing.md | 28 +++++++++++++++++++++------- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index ed4e623..b708a8a 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -267,4 +267,27 @@ year = {2005}, isbn = {0321349601}, publisher = {Addison-Wesley Professional}, -} +} + +@misc{OrleansHalo4Talk, + title = {Building the Halo 4 Services with Orleans}, + author = {Caitie McCaffrey}, + year = {2015}, + note = {QCon}, + url = {https://www.infoq.com/presentations/halo-4-orleans}, +} + +@misc{ErlangWhatsAppTalk, + title= {Scaling to Millions of Simultaneous Connections}, + author = {Rick Reed}, + year = {2012}, + note= {Erlang Factory SF Bay Area}, + url = {https://vimeo.com/44312354}, +} + +@misc{PayPalAkka, + title = {squbs: A New, Reactive Way for PayPal to Build Applications}, + author = {Akara Sucharitakul }, + year = {2016}, + url = {https://www.paypal-engineering.com/2016/05/11/squbs-a-new-reactive-way-for-paypal-to-build-applications/}, +} diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 3824cd1..a63cc75 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -12,7 +12,7 @@ In the field of message passing programming models, it is not only important to In this chapter I describe the four primary variants of the actor model: classic actors, process-based actors, communicating event-loops, and active objects. I attempt to highlight historic and modern languages that exemplify these models, as well as the philosophies and tradeoffs that programmers need to be aware of to understand and best make use of these models. -Despite the actor model's originating as far back as the 1970s, it is still being developed and being incorporated into the programming languages of today, as many recently published papers and systems in the field demonstrate. There are a few robust industrial-strength actor systems that are being used to power massive scalable distributed systems. There are a couple of different approaches to building actor frameworks that are detailed later in the chapter. +Despite the actor model's originating as far back as the 1970s, it is still being developed and being incorporated into the programming languages of today, as many recently published papers and systems in the field demonstrate. There are a few robust industrial-strength actor systems that are being used to power massive scalable distributed systems; for example Akka has been used to serve PayPal's billions of transactions, {% cite PayPalAkka --file message-passing %} Erlang has been used to send messages for WhatsApp's hundreds of millions of users, {% cite ErlangWhatsAppTalk --file message-passing %} and Orleans has been used to serve Halo 4's millions of players. {% cite OrleansHalo4Talk --file message-passing %} There are a couple of different approaches to building industrial actor frameworks around monitoring, handling fault-tolerance, and managing actor lifecycles which are detailed later in the chapter. An important framing for the actor models presented is in the question "Why message passing, and specifically why the actor model?" Given the vast number of distributed programming models out there, one might ask, why this one was so important when it was initially proposed? Why has it facilitated advanced languages, systems, and libraries that are widely used today? As we'll see throughout this chapter, some of the broadest advantages of the actor model include isolation of state managed by the given actor, scalability, and simplifying the programmer's ability to reason about their system. @@ -78,8 +78,6 @@ If you squint a little, this actor definition sounds similar to Alan Kay’s ori
Alan Kay
-TODO: transition - ## Concurrent Object-Oriented Programming (1990) One could say that the renaissance of actor models in mainstream program began with Gul Agha's work. His seminal book _Actors: A Model of Concurrent Computation in Distributed Systems_ {% cite Agha:1986:AMC:7929 --file message-passing %} and later paper, _Concurrent Object-Oriented Programming_ {% cite Agha:1990:COP:83880.84528 --file message-passing %}, offer classic actors as a natural solution to solving problems at the intersection of two trends in computing; increased distributed computing resources and the rising popularity of object-oriented programming. The paper defines common patterns of parallelism: pipeline concurrency, divide and conquer, and cooperative problem solving. It then focuses on how the actor model can be used to solve these problems in an object-oriented style, and some of the challenges that arise with distributed actors and objects, as well as strategies and tradeoffs for communication and reasoning about behaviors. @@ -307,13 +305,29 @@ These attributes give us a good basis for analyzing whether an actor system can ## Actors as a framework -One trend that seems common among the actor systems we see in production is extensive environments and tooling. Akka, Erlang, and Orleans are the primary actor systems that see real production use, and the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. +One trend that seems common among the actor systems we see in production is extensive environments and tooling. Akka, Erlang, and Orleans are the primary actor systems that see real production use, and the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. They offer support for managing and monitoring the deployment of actors as well as patterns or modules to handle problems like fault-tolerance and load balancing which every distributed actor system has to address. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. + +Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. For example Akka includes modules to deal with the following common issues (and more): + +* Fault Tolerance via supervision hierarchies +* Routing to balance load across actors +* Persistence to save and recover actor state across failures and restarts +* A testing framework specifically for actors +* Cluster management to group and distribute actors across physical machines + +Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. + +Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. Some examples of OTP libraries are: -Akka and Erlang provide modules that you can piece together to build various pieces of functionality into your system. Akka provides a huge number of modules and extensions to configure and monitor a distributed system built using actors. They provide a number of utilities to meet common use-case and deployment scenarios, and these are thoroughly listed and documented. Additionally they provide support for Akka Extensions, which are a mechanism for adding your own features to Akka. These are powerful enough that some core features of Akka like Typed Actors or Serialization are implemented as Akka Extensions. +* A real-time distributed database +* An interface to relational databases +* A monitoring framework for machine resource usage +* Support for interfacing with other communication protocols like SSH +* A test framework -Erlang provides the Open Telecom Platform (OTP), which is a framework comprised of a set of modules and standards designed to help build applications. OTP takes the generic patterns and components of Erlang, and provides them as libraries that enable code reuse and best practices when developing new systems. Cloud Haskell also provides something analogous to Erlang's OTP called the Cloud Haskell Platform. +Cloud Haskell also provides something analogous to Erlang's OTP called the Cloud Haskell Platform. -Orleans is different from these as it is built from the ground up with a more declarative style and runtime. This does a lot of the work of distributing and scaling actors for you, but it is still definitely a framework which handles a lot of the common problems of distribution so that programmers can focus on building the logic of their system. +Orleans is different from these as it is built from the ground up with a more declarative style and runtime. This does a lot of the work of distributing and scaling actors for you, but it is still definitely a framework which handles a lot of the common problems of distribution so that programmers can focus on building the logic of their system. Orleans takes care of the distribution of actors across machines, as well as creating new actor instances to handle increased load. Additionally, Orleans also deals with reconciliation of consistency issues across actor instantiations, as well as persistence of actor data to durable storage. These are common issues that the other industrial actor frameworks also address in some capacity using modules and extensions. ## Module vs. managed runtime approaches -- cgit v1.2.3 From 8f24113ed25eea2629f4f0836caabe22938dd99f Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Thu, 15 Dec 2016 20:47:40 -0500 Subject: Revise CSP section and intro --- chapter/3/message-passing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index a63cc75..51a73c1 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -6,7 +6,7 @@ by: "Nathaniel Dempkowski" # Introduction -Message passing programming models have essentially been discussed since the beginning of distributed computing and as a result message passing can be taken to mean a lot of things. If you look up a broad definition on Wikipedia, it includes things like RPC, CSP, and MPI. In practice when people talk about message passing today they mostly mean the actor model. +Message passing programming models have essentially been discussed since the beginning of distributed computing and as a result message passing can be taken to mean a lot of things. If you look up a broad definition on Wikipedia, it includes things like Remote Procedure Calls (RPC), and Message Passing Interface (MPI). Additionally, there are popular process-calculi like the pi-calculus and Communicating Sequential Processes (CSP) which have inspired practical message passing systems. For example, Go's channels are based on the idea of first-class communication channels from the pi-calculus and Clojure's `core.async` library is based on CSP. However, when people talk about message passing today they mostly mean the actor model. It is a ubiquitous and general message passing programming model that has been developing since the 1970's and is used today to build massive scalable systems. In the field of message passing programming models, it is not only important to consider recent state of the art research, but additionally the historic initial papers on message passing and the actor model that are the roots of the programming models described in more recent papers. It is enlightening to see which aspects of the models have stuck around, and many of the more recent papers reference and address deficiencies present in older papers. There have been plenty of programing languages designed around message passing, especially those focused on the actor model of programming and organizing units of computation. @@ -341,7 +341,7 @@ Both approaches have been successful in industry. Erlang has the famous use case ## Comparison to Communicating Sequential Processes (CSP) -You might argue that I've ignored some other concurrency primitives that could be considered message-passing or actors at some level. After all, from a high level a Goroutine with channels feels a bit like an actor. As does an RPC system which can buffer sequential calls. A lot of discussions of actors are looking at them form a not-so-useful level of abstraction. A lot of the discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This view is zoomed out too far, and misses many of the subtleties that differentiate these programming models. Many of these differences stem from the flexibility and scalability of actors. Trying to use CSP-like channels to build a scalable system like you would an actor system would arguably be a tightly-coupled nightmare. The advantages of actors are around the looser coupling, variable topology, and focus on isolation of state and behavior. CSP has a place in building systems, and has proven to be a popular concurrency primitive, but lumping actors in with CSP misses the point of both. Actors are operating at a fundamentally different level of abstraction from CSP. +One popular model of message-passing concurrency that has been getting attention is CSP. The basic idea behind CSP is that concurrent communication between processes is done by passing messages through channels. Arguably the most popular modern implementation of this is Go's channels. A lot of the surface-level discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This zoomed-out view might conflate CSP-style channels and actors, but it misses a lot of subtleties as CSP really can't be considered an actor framework. The core difference is that CSP implements some form of synchronous messaging between processes, while the actor model entirely decouples messaging between a sender and a receiver. Actors are much more independent, meaning its easier to run them in a distributed environment without changing their semantics. Additionally, receiver failures don't affect senders in the actor model. Actors are a more loosely-coupled abstraction across a distributed environment, while CSP embraces tight-coupling as a means of synchronization across processes. To conflate the two misses the point of both, as actors are operating at a fundamentally different level of abstraction from CSP. # References -- cgit v1.2.3 From 94626547d5c756dc0f19f4d31f65ba5eb9df992f Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Thu, 15 Dec 2016 22:36:37 -0500 Subject: moved images and fixed comments --- chapter/2/futures.md | 25 +++++++++++++------------ chapter/2/images/1.png | Bin 0 -> 14176 bytes chapter/2/images/15.png | Bin 0 -> 48459 bytes chapter/2/images/5.png | Bin 0 -> 20821 bytes chapter/2/images/6.png | Bin 0 -> 19123 bytes chapter/2/images/7.png | Bin 0 -> 30068 bytes chapter/2/images/8.png | Bin 0 -> 13899 bytes chapter/2/images/9.png | Bin 0 -> 6463 bytes 8 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 chapter/2/images/1.png create mode 100644 chapter/2/images/15.png create mode 100644 chapter/2/images/5.png create mode 100644 chapter/2/images/6.png create mode 100644 chapter/2/images/7.png create mode 100644 chapter/2/images/8.png create mode 100644 chapter/2/images/9.png diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 5ab4c3e..612ed8e 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -25,7 +25,6 @@ In the world of asynchronous communications many terminologies were defined to h A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. - The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. @@ -47,7 +46,7 @@ E is an object-oriented programming language for secure distributed computing, c Among the modern languages, Python was perhaps the first to come up with something on the lines of E’s promises with the Twisted library. Coming out in 2002, it had a concept of Deferred objects, which were used to receive the result of an operation not yet completed. They were just like normal objects and could be passed along, but they didn’t have a value. They supported a callback which would get called once the result of the operation was complete. -Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API, it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. +Promises and javascript have an interesting history. In 2007 inspired by Python’s twisted, dojo came up with it’s own implementation of of dojo.Deferred. This inspired Kris Zyp to then come up with the CommonJS Promises/A spec in 2009. Ryan Dahl introduced the world to NodeJS in the same year. In it’s early versions, Node used promises for the non-blocking API. When NodeJS moved away from promises to its now familiar error-first callback API (the first argument for the callback should be an error object), it left a void for a promises API. Q.js was an implementation of Promises/A spec by Kris Kowal around this time. FuturesJS library by AJ ONeal was another library which aimed to solve flow-control problems without using Promises in the strictest of senses. In 2011, JQuery v1.5 first introduced Promises to its wider and ever-growing audience. The API for JQuery was subtly different than the Promises/A spec. With the rise of HTML5 and different APIs, there came a problem of different and messy interfaces which added to the already infamous callback hell. A+ promises aimed to solve this problem. From this point on, leading from widespread adoption of A+ spec, promises was finally made a part of ECMAScript® 2015 Language Specification. Still, a lack of backward compatibility and additional features provided means that libraries like BlueBird and Q.js still have a place in the javascript ecosystem. # Different Definitions @@ -60,21 +59,23 @@ In some languages however, there is a subtle difference between what is a Future “A ‘Promise’ is a pretty much the same except that you can write to it as well.” -In other words, you can read from both Futures and Promises, but you can only write to Promises. You can get the Future associated with a Promise by calling the future method on it, but conversion in the other direction is not possible. Another way to look at it would be, if you Promise something, you are responsible for keeping it, but if someone else makes a Promise to you, you expect them to honor it in Future. +In other words, a future is a read-only window to a value written into a promise. You can get the Future associated with a Promise by calling the future method on it, but conversion in the other direction is not possible. Another way to look at it would be, if you Promise something, you are responsible for keeping it, but if someone else makes a Promise to you, you expect them to honor it in Future. More technically, in Scala, “SIP-14 – Futures and Promises” defines them as follows: A future is as a placeholder object for a result that does not yet exist. A promise is a writable, single-assignment container, which completes a future. Promises can complete the future with a result to indicate success, or with an exception to indicate failure. +An important difference between Scala and Java (6) futures is that Scala futures were asynchronous in nature. Java's future, at least till Java 6, were blocking. Java 7 introduced the Futures as the asynchronous construct which are more familiar in the distributed computing world. + -C# also makes the distinction between futures and promises. In C#, futures are implemented as Task and in fact in earlier versions of the Task Parallel Library futures were implemented with a class Future which later became Task. The result of the future is available in the readonly property Task.Result which returns T +In Java 8, the Future interface has methods to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation when it is complete. CompletableFutures can be thought of as Promises as their value can be set. But it also implements the Future interface and therefore it can be used as a Future too. Promises can be thought of as a future with a public set method which the caller (or anybody else) can use to set the value of the future. In Javascript world, Jquery introduces a notion of Deferred objects which are used to represent a unit of work which is not yet finished. Deferred object contains a promise object which represent the result of that unit of work. Promises are values returned by a function, while the deferred object can be canceled by its caller. -In Java 8, the Future interface has methods to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation when it is complete. CompletableFutures can be thought of as Promises as their value can be set. But it also implements the Future interface and therefore it can be used as a Future too. Promises can be thought of as a future with a public set method which the caller (or anybody else) can use to set the value of the future. +C# also makes the distinction between futures and promises. In C#, futures are implemented as Task and in fact in earlier versions of the Task Parallel Library futures were implemented with a class Future which later became Task. The result of the future is available in the readonly property Task.Result which returns T. Tasks are asynchronous in C#. # Semantics of Execution @@ -82,7 +83,7 @@ Over the years promises and futures have been implemented in different programmi ## Thread Pools -Doing things in parallel is usually an effective way of doing things in modern systems. The systems are getting more and more capable of running more than one things at once, and the latency associated with doing things in a distributed environment is not going away anytime soon. Inside the JVM, threads are a basic unit of concurrency. Threads are independent, heap-sharing execution contexts. Threads are generally considered to be lightweight when compared to a process, and can share both code and data. The cost of context switching between threads is cheap. But, even if we claim that threads are lightweight, the cost of creation and destruction of threads in a long running threads can add up to something significant. A practical way is address this problem is to manage a pool of worker threads. +Thread pools are a group of ready, idle threads which can be given work. They help with the overhead of worker creation, which can add up in a long running process. The actual implementation may vary everywhere, but what differentiates thread pools is the number of threads it uses. It can either be fixed, or dynamic. Advantage of having a fixed thread pool is that it degrades gracefully : the amount of load a system can handle is fixed, and using fixed thread pool, we can effectively limit the amount of load it is put under. Granularity of a thread pool is the number of threads it instantiates. In Java executor is an object which executes the Runnable tasks. Executors provides a way of abstracting out how the details of how a task will actually run. These details, like selecting a thread to run the task, how the task is scheduled are managed by the object implementing the Executor interface. Threads are an example of a Runnable in java. Executors can be used instead of creating a thread explicitly. @@ -94,11 +95,9 @@ Similar to Executor, there is an ExecutionContext as part of scala.concurrent. T ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. -Futures are generally a good way to reason about asynchronous code. A good way to call a webservice, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. - - -In Scala, futures are created using an ExecutionContext. This gives the users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Futures in scala are placeholders for a yet unknown value. A promise then can be thought of as a way to provide that value. A promise p completes the future returned by p.future. +Futures are generally a good way to reason about asynchronous code. A good way to call a web service, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. +Using ExecutionContext gives users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Scala futures api expects an ExecutionContext to be passed along. This parameter is implicit, and usually ExecutionContext.global. An example : @@ -210,7 +209,8 @@ getData(0).then(getData) > **Programs must be written for people to read, and only incidentally for machines to execute.** - *Harold Abelson and Gerald Jay Sussman* -Promises are an abstraction which make working with async operations in javascript much more fun. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. + +Promises are an abstraction which make working with async operations in javascript much more fun. Callbacks lead to inversion of control, which is difficult to reason about at scale. Moving on from a continuation passing style, where you specify what needs to be done once the action is done, the callee simply returns a Promise object. This inverts the chain of responsibility, as now the caller is responsible for handling the result of the promise when it is settled. The ES2015 spec specifies that “promises must not fire their resolution/rejection function on the same turn of the event loop that they are created on.” This is an important property because it ensures deterministic order of execution. Also, once a promise is fulfilled or failed, the promise’s value MUST not be changed. This ensures that a promise cannot be resolved more than once. @@ -275,6 +275,7 @@ The idea for explicit futures were introduced in the Baker and Hewitt paper. The Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. + # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. @@ -427,7 +428,7 @@ Folly is a library by Facebook for asynchronous C++ inspired by the implementati ## NodeJS Fiber -Fibers provide coroutine support for v8 and node. Applications can use Fibers to allow users to write code without using a ton of callbacks, without sacrificing the performance benefits of asynchronous IO. Think of fibers as light-weight threads for nodejs where the scheduling is in the hands of the programmer. The node-fibers library doesn’t recommend using raw API and code together without any abstractions, and provides a Futures implementation which is ‘fiber-aware’. +Fibers provide coroutine support for v8 and node. Applications can use Fibers to allow users to write code without using a ton of callbacks, without sacrificing the performance benefits of asynchronous IO. Think of fibers as light-weight threads for NodeJs where the scheduling is in the hands of the programmer. The node-fibers library doesn’t recommend using raw API and code together without any abstractions, and provides a Futures implementation which is ‘fiber-aware’. ## References diff --git a/chapter/2/images/1.png b/chapter/2/images/1.png new file mode 100644 index 0000000..1d98f19 Binary files /dev/null and b/chapter/2/images/1.png differ diff --git a/chapter/2/images/15.png b/chapter/2/images/15.png new file mode 100644 index 0000000..15a2a81 Binary files /dev/null and b/chapter/2/images/15.png differ diff --git a/chapter/2/images/5.png b/chapter/2/images/5.png new file mode 100644 index 0000000..b86de04 Binary files /dev/null and b/chapter/2/images/5.png differ diff --git a/chapter/2/images/6.png b/chapter/2/images/6.png new file mode 100644 index 0000000..aaafdbd Binary files /dev/null and b/chapter/2/images/6.png differ diff --git a/chapter/2/images/7.png b/chapter/2/images/7.png new file mode 100644 index 0000000..7183fb6 Binary files /dev/null and b/chapter/2/images/7.png differ diff --git a/chapter/2/images/8.png b/chapter/2/images/8.png new file mode 100644 index 0000000..d6d2e0e Binary files /dev/null and b/chapter/2/images/8.png differ diff --git a/chapter/2/images/9.png b/chapter/2/images/9.png new file mode 100644 index 0000000..1b67a45 Binary files /dev/null and b/chapter/2/images/9.png differ -- cgit v1.2.3 From c97cb3b91165525b1cb0c3273ad1edc59f9f2bd9 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Thu, 15 Dec 2016 23:08:03 -0500 Subject: Small fixes --- chapter/7/langs-consistency.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index b8f013f..78162bc 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -6,7 +6,7 @@ by: "James Larisch" # Formal, Yet Relaxed: Models for Consistency ## What's the problem? - As processors become expensive and the limits of Moore's Law are pushed, programmers find themselves in situations where they need to connect multiple computers together using a network cable. Perhaps it's not even due to cost or performance constraints; perhaps your company has servers in New York and San Fransisco, and there is some global state that requires synchronization across the country. Problems requiring solutions of this nature can be described as "distributed systems" problems. Your data / processing power / entry points are distributed for some reason. In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination is required. + In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination between computers is required. As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network is simply not as reliable as, say, memory - and waiting for responses can result in a lack of timeliness for the application's client. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the properties of our systems. As stated, we can't perfectly emulate a single computer using multiple machines, but once we accept that fact and learn to work with it... there are plenty of things we *can* do! @@ -35,7 +35,7 @@ Turns out there's a company out there called Amazon.com - and they've been havin ## Dynamo Amazon built DynamoDB, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. -Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts...aaaaaand... push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. +Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts and push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. How should Amazon's shopping cart procede with resolution? It may be fed two cart states like so: @@ -59,7 +59,7 @@ Green Umbrella It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. ### Good & Bad -What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has an insanely high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. +What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed general-purpose data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. -- cgit v1.2.3 From fb20edb958f8e6bdf7d793dd27f19eba739c1327 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 00:54:13 -0500 Subject: addind bib --- _bibliography/futures.bib | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index 416b697..cf17cac 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -23,4 +23,23 @@ number = {6}, year = {2004}, pages = {727-739}, -} \ No newline at end of file +} + +@article{Halstead:1985:MLC:4472.4478, + author = {Halstead,Jr., Robert H.}, + title = {MULTILISP: A Language for Concurrent Symbolic Computation}, + journal = {ACM Trans. Program. Lang. Syst.}, + issue_date = {Oct. 1985}, + volume = {7}, + number = {4}, + month = oct, + year = {1985}, + issn = {0164-0925}, + pages = {501--538}, + numpages = {38}, + url = {http://doi.acm.org/10.1145/4472.4478}, + doi = {10.1145/4472.4478}, + acmid = {4478}, + publisher = {ACM}, + address = {New York, NY, USA}, +} -- cgit v1.2.3 From e080d050eb7e0f5373a90488338ea4011ab402c0 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Fri, 16 Dec 2016 01:06:51 -0500 Subject: Add code examples for E, Erlang, Orleans --- chapter/3/message-passing.md | 82 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 51a73c1..200286c 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -135,6 +135,21 @@ Process-based actors are defined as a computation which runs from start to compl These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. `receive` statements have some notion of defining acceptable messages, usually based on patterns, conditionals or types. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. +An example of these core concepts of a process with a defined lifecycle and use of the `receive` statement to match messages is a simple counter process written in Erlang. {% cite Armstrong:2010:ERL:1810891.1810910 --file message-passing %} + +``` +counter(N) -> + receive + tick -> + counter(N+1); + {From, read} -> + From ! {self(), N}, + counter(N) + end. +``` + +This demonstrates the use of `receive` to match on two different values of messages `tick`, which increments the counter, and `{From, read}` where `From` is a process identifier and `read` is a literal. In response to another process sending the message `tick` by doing something like `CounterId ! tick.` the process calls itself with an incremented value which demonstrates a similarity to the `become` statement, but using recursion and an argument value instead of a named behavior continuation and some state. If the counter receives a message of the form `{, read}` it will then send that process a message with the counter's processId and value, and call itself recursively with the same value. + ## Erlang Erlang's implementation of process-based actors gets to the core of what it means to be a process-based actor. Erlang was the origin of the process-based actor model. The Ericsson company originally developed this model to program large highly-reliable fault-tolerant telecommunications switching systems. Erlang's development started in 1985, but its model of programming is still used today. The motivations of the Erlang model were around four key properties that were needed to program fault-tolerant operations: @@ -157,6 +172,23 @@ Erlang also seeks to build failure into the programming model, as one of the cor These primitives can be used to construct complex hierarchies of supervision that can be used to handle failure in isolation, rather than failures impacting your entire system. Supervision hierarchies are notably almost the only scheme for fault-tolerance that exists in the world of actors. Almost every actor system that is used to build distributed systems takes a similar approach, and it seems to work. Erlang's philosophies used to build a reliable fault-tolerant telephone exchange seem to be broadly applicable to the fault-tolerance problems of distributed systems. +An example of a process `monitor` written in Erlang is given below. {% cite Armstrong:2010:ERL:1810891.1810910 --file message-passing %} + +``` +on_exit(Pid, F) -> + spawn(fun() -> monitor(Pid, F) end). + +monitor(Pid, F) -> + process_flag(trap_exit, true), + link(Pid), + receive + {‘EXIT’, Pid, Why} -> + F(Why) +end. +``` + +This defines two processes: `on_exit` which simply spawns a `monitor` process to call a given function when a given process id exits, and `monitor` which uses `link` to receive a message when the given process id exists, and to call a function with the reason it exited. You could imagine chaining many of these `monitor` and `link` operations together to build processes to monitor one another for failure and perform recovery operations depending on the failure behavior. + It is worth mentioning that Erlang achieves all of this through the Erlang Virtual Machine (BEAM), which runs as a single OS process and OS thread per core. These single OS processes then manage many lightweight Erlang processes. The Erlang VM implements all of the concurrency, monitoring, and garbage collection for Erlang processes within this VM, which almost acts like an operating system itself. This is unlike any other language or actor system described here. ## Scala Actors @@ -202,20 +234,23 @@ E's reference-states define many of the isolation guarantees around computation The difference in semantics between the two types of references means that only objects within the same vat are granted synchronous access to one another. The most an eventual reference can do is asynchronously send and queue a message for processing at some unspecified point in the future. This means that within the execution of a vat, a degree of temporal isolation can be defined between the objects and communications within the vat, and the communications to and from other vats. -TODO: explain this code example in the context of the above diagram or come up with a new one? +This code example ties into the previous diagrams, and demonstrates the two different types reference semantics. {% cite Miller:2005:CSP:1986262.1986274 --file message-passing %} ``` def makeStatusHolder(var myStatus) { def myListeners := [].diverge() + def statusHolder { to addListener(newListener) { myListeners.push(newListener) } + to getStatus() { return myStatus } + to setStatus(newStatus) { myStatus := newStatus for listener in myListeners { - listener.statusChanged(newStatus) + listener <- statusChanged(newStatus) } } } @@ -223,6 +258,8 @@ def makeStatusHolder(var myStatus) { } ``` +This creates an object `statusHolder` with methods defined by `to` statements. A method invocation from another vat-local object like `statusHolder.setStatus(123)` causes a message to be synchronously delivered to this object. Other objects can register as event listeners by calling either `statusHolder.addListener()` or `statusHolder <- addListener()` to either synchronously or eventually register as listeners. They will be notified eventually when the value of the `statusHolder` changes. This is done via `<-` which is the eventual-send operator. + The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. TODO: Mention partial failure and implications of different types of communication @@ -273,7 +310,46 @@ Orleans takes the concept of actors whose lifecycle is dependent on messaging or Orleans uses a different notion of identity than other actor systems. In other systems an "actor" might refer to a behavior and instances of that actor might refer to identities that the actor represents like individual users. In Orleans, an actor represents that persistent identity, and the actual instantiations are in fact reconcilable copies of that identity. -The programmer essentially assumes that a single entity is handling requests to an actor, but the Orleans runtime actually allows for multiple instantiations for scalability. These instantiations are invoked in response to an RPC-like call from the programmer which immediately returns an asynchronous promise. Multiple instances of an actor can be running and modifying the state of that actor at the same time. The immediate question here is how does that actually work? It doesn't intuitively seem like transparently accessing and changing multiple isolated copies of the same state should produce anything but problems when its time to do something with that state. +The programmer essentially assumes that a single entity is handling requests to an actor, but the Orleans runtime actually allows for multiple instantiations for scalability. These instantiations are invoked in response to an RPC-like call from the programmer which immediately returns an asynchronous promise. + +In Orleans, declaring an actor just looks like making any other class which implements a specific interface. A simple example here is a `PlayerGrain` which can join games. All methods of an Orleans actor (grain) interface must return a `Task`, as they are all asynchronous. + +``` +public interface IPlayerGrain : IGrainWithGuidKey +{ + Task GetCurrentGame(); + Task JoinGame(IGameGrain game); +} + +public class PlayerGrain : Grain, IPlayerGrain +{ + private IGameGrain currentGame + + public Task GetCurrentGame() + { + return Task.FromResult(currentGame); + } + + public Task JoinGame(IGameGrain game) + { + currentGame = game; + Console.WriteLine("Player {0} joined game {1}", this.GetPrimaryKey(), game.GetPrimaryKey()); + return TaskDone.Done; + } +} +``` + +Invoking a method on an actor is done like any other asynchronous call, using the `await` keyword in C#. This can be done from either a client or inside another actor (grain). In both cases the call looks almost exactly the same, the only different being clients use `GrainClient.GrainFactory` while actors can use `GrainFactory` directly. + +``` +IPlayerGrain player = GrainClient.GrainFactory.GetGrain(playerId); +Task joinGameTask = player.JoinGame(currentGame); +await joinGameTask; +``` + +Here a game client gets a reference to a specific player, and has that player join the current game. This code looks like any other asynchronous C# code a developer would be used to writing, but this is really an actor system where the runtime has abstracted away many of the details. The runtime handles all of the actor lifecycle in response to the requests clients and other actors within the system make, as well as persistence of state to long-term storage. + +Multiple instances of an actor can be running and modifying the state of that actor at the same time. The immediate question here is how does that actually work? It doesn't intuitively seem like transparently accessing and changing multiple isolated copies of the same state should produce anything but problems when its time to do something with that state. Orleans solves this problem by providing mechanisms to reconcile conflicting changes. If multiple instances of an actor modify persistent state, they need to be reconciled into a consistent state in some meaningful way. The default here is a last-write-wins strategy, but Orleans also exposes the ability to create fine-grained reconciliation policies, as well as a number of common reconcilable data structures. If an application requires a certain reconciliation algorithm, the developer can implement it using Orleans. These reconciliation mechanisms are built upon Orleans' concept of transactions. -- cgit v1.2.3 From 3dc8ca64299e1cfc53b194174d15f8449246b985 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 01:40:39 -0500 Subject: added bib --- _bibliography/futures.bib | 333 +++++++++++++++++++++++++++++++++++++++++++++- chapter/2/futures.md | 16 +++ 2 files changed, 348 insertions(+), 1 deletion(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index cf17cac..b3d0d19 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -25,7 +25,7 @@ pages = {727-739}, } -@article{Halstead:1985:MLC:4472.4478, +@article{1, author = {Halstead,Jr., Robert H.}, title = {MULTILISP: A Language for Concurrent Symbolic Computation}, journal = {ACM Trans. Program. Lang. Syst.}, @@ -43,3 +43,334 @@ publisher = {ACM}, address = {New York, NY, USA}, } + +@inproceedings{2, + author = {Liskov, B. and Shrira, L.}, + title = {Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems}, + booktitle = {Proceedings of the ACM SIGPLAN 1988 Conference on Programming Language Design and Implementation}, + series = {PLDI '88}, + year = {1988}, + isbn = {0-89791-269-1}, + location = {Atlanta, Georgia, USA}, + pages = {260--267}, + numpages = {8}, + url = {http://doi.acm.org/10.1145/53990.54016}, + doi = {10.1145/53990.54016}, + acmid = {54016}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@article{3, + author = {Liskov, B. and Shrira, L.}, + title = {Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems}, + journal = {SIGPLAN Not.}, + issue_date = {July 1988}, + volume = {23}, + number = {7}, + month = jun, + year = {1988}, + issn = {0362-1340}, + pages = {260--267}, + numpages = {8}, + url = {http://doi.acm.org/10.1145/960116.54016}, + doi = {10.1145/960116.54016}, + acmid = {54016}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@article{4, + author = {Eriksen, Marius}, + title = {Your Server As a Function}, + journal = {SIGOPS Oper. Syst. Rev.}, + issue_date = {January 2014}, + volume = {48}, + number = {1}, + month = may, + year = {2014}, + issn = {0163-5980}, + pages = {51--57}, + numpages = {7}, + url = {http://doi.acm.org/10.1145/2626401.2626413}, + doi = {10.1145/2626401.2626413}, + acmid = {2626413}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@misc{5, + title={What are the differences between JavaScript Promise and a Java Future?}, + author={Google}, + url = {https://www.quora.com/What-are-the-differences-between-JavaScript-Promise-and-a-Java-Future}, + publisher = {Quora} +} + +@misc{6, + title={The JavaScript Event Loop: Explained}, + author={Erin Swenson-Healey}, + url = {http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/} +} + +@misc{7, + title={The JavaScript Event Loop: Explained}, + author={Erin Swenson-Healey}, + url = {http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/} +} + +@misc{8, + title={Effective Scala}, + author={Marius Eriksen}, + url = {http://twitter.github.io/effectivescala} +} + +@misc{8, + title={Concurrency model and Event Loop}, + author={MDN}, + url = {https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop} +} + +@misc{9, + title={Task vs Future vs Promise}, + author={Ned Stoyanov}, + url = {http://www.nedstoyanov.com/promises-and-futures/} +} + +@misc{10, + title={Futures and Promises - List of Implementations}, + url = {http://www.liquisearch.com/futures_and_promises/list_of_implementations} +} + +@misc{11, + title={Promises and Deferreds}, + author={Trevor Burnham}, + url = {https://www.safaribooksonline.com/library/view/async-javascript/9781941222782/f_0028.html} +} + +@misc{12, + title={Futures and Promises}, + url = {http://www.seastar-project.org/futures-promises/} +} + +@misc{13, + title={Why do Promise libraries use event loops?}, + url = {http://stackoverflow.com/questions/23447876/why-do-promise-libraries-use-event-loops} +} + +@misc{14, + title={Why do Promise libraries use event loops?}, + url = {http://stackoverflow.com/questions/23447876/why-do-promise-libraries-use-event-loops} +} + + +@misc{15, + title={Tasks, microtasks, queues and schedules}, + author={Jake Archibald}, + url = {https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/} +} + +@misc{16, + title={You're Missing the Point of Promises}, + author={Domenic Denicola}, + url = {https://blog.domenic.me/youre-missing-the-point-of-promises/} +} + +@misc{17, + title={Dataflow Concurrency}, + url = {http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html} +} + +@misc{18, + title={Promise & Deferred objects in JavaScript Pt.1: Theory and Semantics}, + author={Chris Webb}, + url = {http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics} +} + +@misc{19, + title={Promise & Deferred objects in JavaScript Pt.1: Theory and Semantics}, + author={Chris Webb}, + url = {http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics} +} + +@misc{20, + title={CompletableFuture}, + author={Doug Lea}, + url = {http://cs.oswego.edu/pipermail/concurrency-interest/2012-December/010423.html} +} + +@misc{21, + title={CompletableFuture}, + author={Doug Lea}, + url = {http://cs.oswego.edu/pipermail/concurrency-interest/2012-December/010423.html} +} + +@misc{22, + title={Difference between Future and Promise}, + url = {http://stackoverflow.com/questions/14541975/difference-between-future-and-promise} +} + +@misc{23, + title={Welcome to Thunk.org!}, + url = {https://thunk.org/} +} + +@misc{24, + title={CompletableFuture}, + author={Doug Lea}, + url = {http://cs.oswego.edu/pipermail/concurrency-interest/2012-December/010423.html} +} + +@misc{25, + title={JVM Internals}, + author={james d bloom}, + url = {http://blog.jamesdbloom.com/JVMInternals.html} +} + +@misc{26, + title={ExecutionContext}, + url = {http://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext.html} +} + +@misc{27, + title={ForkJoinPool: the Other ExecutorService}, + author={Jessica Kerr}, + url = {http://blog.jessitron.com/2014/02/forkjoinpool-other-executorservice.html} +} + +@misc{28, + title={Scala: the global ExecutionContext makes your life easier}, + author={Jessica Kerr}, + url = {http://blog.jessitron.com/2014/02/scala-global-executioncontext-makes.html} +} + +@misc{29, + title={Learn implicits: Scala Futures}, + author={Jorge Montero}, + url = {http://engineering.monsanto.com/2015/06/15/implicits-futures/} +} + +@misc{30, + title={Simple concurrency with Scala Futures (Futures tutorial)}, + author={Alvin Alexander}, + url = {http://alvinalexander.com/scala/concurrency-with-scala-futures-tutorials-examples} +} + +@misc{31, + title={JavaScript Promises and Error Handling}, + author={OdeToCode}, + url = {http://odetocode.com/blogs/scott/archive/2015/10/01/javascript-promises-and-error-handling.aspx} +} + +@inproceedings{32, + author = {Lea, Doug}, + title = {A Java Fork/Join Framework}, + booktitle = {Proceedings of the ACM 2000 Conference on Java Grande}, + series = {JAVA '00}, + year = {2000}, + isbn = {1-58113-288-3}, + location = {San Francisco, California, USA}, + pages = {36--43}, + numpages = {8}, + url = {http://doi.acm.org/10.1145/337449.337465}, + doi = {10.1145/337449.337465}, + acmid = {337465}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@inproceedings{33, + author = {de Boer, Frank S. and Clarke, Dave and Johnsen, Einar Broch}, + title = {A Complete Guide to the Future}, + booktitle = {Proceedings of the 16th European Symposium on Programming}, + series = {ESOP'07}, + year = {2007}, + isbn = {978-3-540-71314-2}, + location = {Braga, Portugal}, + pages = {316--330}, + numpages = {15}, + url = {http://dl.acm.org/citation.cfm?id=1762174.1762205}, + acmid = {1762205}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + +@inproceedings{34, + author = {de Boer, Frank S. and Clarke, Dave and Johnsen, Einar Broch}, + title = {A Complete Guide to the Future}, + booktitle = {Proceedings of the 16th European Symposium on Programming}, + series = {ESOP'07}, + year = {2007}, + isbn = {978-3-540-71314-2}, + location = {Braga, Portugal}, + pages = {316--330}, + numpages = {15}, + url = {http://dl.acm.org/citation.cfm?id=1762174.1762205}, + acmid = {1762205}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + +@article{35, + author = {Friedman, D. P. and Wise, D. S.}, + title = {Aspects of Applicative Programming for Parallel Processing}, + journal = {IEEE Trans. Comput.}, + issue_date = {April 1978}, + volume = {27}, + number = {4}, + month = apr, + year = {1978}, + issn = {0018-9340}, + pages = {289--296}, + numpages = {8}, + url = {http://dx.doi.org/10.1109/TC.1978.1675100}, + doi = {10.1109/TC.1978.1675100}, + acmid = {1310419}, + publisher = {IEEE Computer Society}, + address = {Washington, DC, USA}, + keywords = {Compiling, Lisp, functional combinations, multiprocessing, recursion, suspensions, suspensions, Compiling, functional combinations, Lisp, multiprocessing, recursion}, +} + +@techreport{36, + author = {Baker,Jr., Henry G. and Hewitt, Carl}, + title = {The Incremental Garbage Collection of Processes}, + year = {1977}, + source = {http://www.ncstrl.org:8900/ncstrl/servlet/search?formname=detail\&id=oai%3Ancstrlh%3Amitai%3AMIT-AILab%2F%2FAIM-454}, + publisher = {Massachusetts Institute of Technology}, + address = {Cambridge, MA, USA}, +} + +@inproceedings{37, + author = {Miller, Mark S. and Tribble, E. Dean and Shapiro, Jonathan}, + title = {Concurrency Among Strangers: Programming in E As Plan Coordination}, + booktitle = {Proceedings of the 1st International Conference on Trustworthy Global Computing}, + series = {TGC'05}, + year = {2005}, + isbn = {3-540-30007-4, 978-3-540-30007-6}, + location = {Edinburgh, UK}, + pages = {195--229}, + numpages = {35}, + url = {http://dl.acm.org/citation.cfm?id=1986262.1986274}, + acmid = {1986274}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + +@INPROCEEDINGS{38, + author = {Didier Le Botlan and Guido Tack and Andreas Rossberg and Andreas Rossberg and Didier Le and Botlan Guido Tack and Thorsten Brunklaus and Thorsten Brunklaus and Gert Smolka and Gert Smolka}, + title = {Alice through the looking glass}, + booktitle = {In Trends in Functional Programming}, + year = {2006}, + pages = {79--96}, + publisher = {Intellect Books} +} + +@misc{39, + title={Futures and Promises in Scala 2.10}, + author={Heather Miller}, + url = {https://speakerdeck.com/heathermiller/futures-and-promises-in-scala-2-dot-10} +} + +@misc{40, + title={Futures and promises}, + url = {https://en.wikipedia.org/wiki/Futures_and_promises} +} diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 612ed8e..4e17472 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -275,6 +275,22 @@ The idea for explicit futures were introduced in the Baker and Hewitt paper. The Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. +In Scala, although the futures are implicit, Promises can be used to have an explicit-like behavior. This is useful in a scenario where we need to stack up some computations and then resolve the Promise. + +An Example : + +```scala + +val p = Promise[Foo]() + +p.future.map( ... ).filter( ... ) foreach println + +p.complete(new Foo) + +``` + +Here, we create a Promise, and complete it later. In between we stack up a set of computations which get executed once the promise is completed. + # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. -- cgit v1.2.3 From db54d6db890d4c8e99e138095af8cd8e20755acc Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 13:34:04 -0500 Subject: fixed motivation adding more details --- _bibliography/futures.bib | 5 +++++ chapter/2/futures.md | 28 ++++++++++++---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index b3d0d19..08552e7 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -374,3 +374,8 @@ title={Futures and promises}, url = {https://en.wikipedia.org/wiki/Futures_and_promises} } + +@misc{41, + title={Lazy Futures or Promises?}, + url = {https://groups.google.com/forum/#!topic/scala-language/dP2SyUCF724} +} diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 4e17472..5f8bd74 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -11,9 +11,9 @@ As human beings we have an ability to multitask ie. we can walk, talk and eat at The processor can either handle blocking calls in two ways: - **Synchronous method**: As a part of running task in synchronous method, processor continues to wait for the blocking call to complete the task and return the result. After this processor will resume processing next task. Problem with this kind of method is CPU time not utilized in an ideal manner. -- **Asynchronous method**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. Now when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. +- **Asynchronous method**: When you add asynchrony, you can utilize the time of CPU to work on some other task using one of the preemptive time sharing algorithm. This is not blocking the processor at any time and when the asynchronous call returns the result, processor can again switch back to the previous process using preemption and resume the process from the point where it’d left off. -In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, we will explain programming model associated with it and discuss evolution of this programming construct, finally we will end this discussion with how this construct helps us today in different general purpose programming languages. +In the world of asynchronous communications many terminologies were defined to help programmers reach the ideal level of resource utilization. As a part of this article we will talk about motivation behind rise of Promises and Futures, how the current notion we have of futures and promises have evolved over time, try to explain various execution models associated with it and finally we will end this discussion with how this construct helps us today in different general purpose programming languages.
@@ -22,13 +22,9 @@ In the world of asynchronous communications many terminologies were defined to h # Motivation +The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. As we will see in further sections, this idea of having a *"placeholder for a value that might not be available"* has changed meanings over time. -A “Promise” object represents a value that may not be available yet. A Promise is an object that represents a task with two possible outcomes, success or failure and holds callbacks that fire when one outcome or the other has occurred. - -The rise of promises and futures as a topic of relevance can be traced parallel to the rise of asynchronous or distributed systems. This seems natural, since futures represent a value available in Future which fits in very naturally with the latency which is inherent to these heterogeneous systems. The recent adoption of NodeJS and server side Javascript has only made promises more relevant. But, the idea of having a placeholder for a result came in significantly before than the current notion of futures and promises. - - -Thunks can be thought of as a primitive notion of a Future or Promise. According to its inventor P. Z. Ingerman, thunks are "A piece of coding which provides an address". They were designed as a way of binding actual parameters to their formal definitions in Algol-60 procedure calls. If a procedure is called with an expression in the place of a formal parameter, the compiler generates a thunk which computes the expression and leaves the address of the result in some standard location. +Thunks can be thought of as a primitive notion of a Future or Promise. According to its inventor P. Z. Ingerman, thunks are "A piece of coding which provides an address". {% cite 23 --file futures %} They were designed as a way of binding actual parameters to their formal definitions in Algol-60 procedure calls. If a procedure is called with an expression in the place of a formal parameter, the compiler generates a thunk which computes the expression and leaves the address of the result in some standard location. The first mention of Futures was by Baker and Hewitt in a paper on Incremental Garbage Collection of Processes. They coined the term - call-by-futures to describe a calling convention in which each formal parameter to a method is bound to a process which evaluates the expression in the parameter in parallel with other parameters. Before this paper, Algol 68 also presented a way to make this kind of concurrent parameter evaluation possible, using the collateral clauses and parallel clauses for parameter binding. @@ -37,10 +33,12 @@ The first mention of Futures was by Baker and Hewitt in a paper on Incremental G In their paper, Baker and Hewitt introduced a notion of Futures as a 3-tuple representing an expression E consisting of (1) A process which evaluates E, (2) A memory location where the result of E needs to be stored, (3) A list of processes which are waiting on E. But, the major focus of their work was not on role of futures and the role they play in Asynchronous distributed computing, and focused on garbage collecting the processes which evaluate expressions not needed by the function. -The Multilisp language, presented by Halestead in 1985 built upon this call-by-future with a Future annotation. Binding a variable to a future expression creates a process which evaluates that expression and binds x to a token which represents its (eventual) result. This design of futures influenced the paper of design of Promises in Argus by Liskov and Shrira in 1988. Building upon the initial design of Future in Multilisp, they extended the original idea by introducing strongly typed Promises and integration with call streams.This made it easier to handle exception propagation from callee to the caller and also to handle the typical problems in a multi-computer system like network failures. This paper also talked about stream composition, a notion which is similar to promise pipelining today. +The Multilisp language, presented by Halestead in 1985 built upon this call-by-future with a Future annotation. Binding a variable to a future expression creates a process which evaluates that expression and binds x to a token which represents its (eventual) result. It allowed an operation to move past the actual computation without waiting for it to complete. If the value is never used, the current computation will not pause. MultiLisp also had a lazy future construct, called Delay, which only gets evaluated when the value is first required. + + This design of futures influenced the paper of design of Promises in Argus by Liskov and Shrira in 1988. Both futures in MultiLisp and Promises in Argus provisioned for the result of a call to be picked up later. Building upon the initial design of Future in MultiLisp, they extended the original idea by introducing strongly typed Promises and integration with call streams. Call streams are a language-independent communication mechanism connecting a sender and a receiver in a distributed programming environment. It is used to make calls from sender to receiver like normal RPC. In addition, sender could also make stream-calls where it chooses to not wait for the reply and can make further calls. Stream calls seem like a good use-case for a placeholder to access the result of a call in the future : Promises. Call streams also had provisions for handling network failures. This made it easier to handle exception propagation from callee to the caller and also to handle the typical problems in a multi-computer system. This paper also talked about stream composition. The call-streams could be arranged in pipelines where output of one stream could be used as input on next stream. This notion is not much different to what is known as promise pipelining today, which will be introduced in more details later. -E is an object-oriented programming language for secure distributed computing, created by Mark S. Miller, Dan Bornstein, and others at Electric Communities in 1997. One of the major contribution of E was the first non-blocking implementation of Promises. It traces its routes to Joule which was a dataflow programming language. The notion of promise pipelining in E is inherited from Joule. +E is an object-oriented programming language for secure distributed computing, created by Mark S. Miller, Dan Bornstein, and others at Electric Communities in 1997. One of the major contribution of E was the first non-blocking implementation of Promises. It traces its routes to Joule which was a dataflow programming language. E had an eventually operator, * <- * . This created what is called an eventual send in E : the program doesn't wait for the operation to complete and moves to next sequential statement. Eventual-sends queue a pending delivery and complete immediately, returning a promise. A pending delivery includes a resolver for the promise. Further messages can also be eventually send to a promise before it is resolved. These messages are queued up and forwarded once the promise is resolved. The notion of promise pipelining in E is also inherited from Joule. Among the modern languages, Python was perhaps the first to come up with something on the lines of E’s promises with the Twisted library. Coming out in 2002, it had a concept of Deferred objects, which were used to receive the result of an operation not yet completed. They were just like normal objects and could be passed along, but they didn’t have a value. They supported a callback which would get called once the result of the operation was complete. @@ -61,9 +59,8 @@ In some languages however, there is a subtle difference between what is a Future In other words, a future is a read-only window to a value written into a promise. You can get the Future associated with a Promise by calling the future method on it, but conversion in the other direction is not possible. Another way to look at it would be, if you Promise something, you are responsible for keeping it, but if someone else makes a Promise to you, you expect them to honor it in Future. - More technically, in Scala, “SIP-14 – Futures and Promises” defines them as follows: -A future is as a placeholder object for a result that does not yet exist. +A future is a placeholder object for a result that does not yet exist. A promise is a writable, single-assignment container, which completes a future. Promises can complete the future with a result to indicate success, or with an exception to indicate failure. An important difference between Scala and Java (6) futures is that Scala futures were asynchronous in nature. Java's future, at least till Java 6, were blocking. Java 7 introduced the Futures as the asynchronous construct which are more familiar in the distributed computing world. @@ -92,12 +89,10 @@ In Java executor is an object which executes the Runnable tasks. Executors provi Similar to Executor, there is an ExecutionContext as part of scala.concurrent. The basic intent behind it is same as an Executor : it is responsible for executing computations. How it does it can is opaque to the caller. It can create a new thread, use a pool of threads or run it on the same thread as the caller, although the last option is generally not recommended. Scala.concurrent package comes with an implementation of ExecutionContext by default, which is a global static thread pool. -ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would typically come with a bad system design. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. - +ExecutionContext.global is an execution context backed by a ForkJoinPool. ForkJoin is a thread pool implementation designed to take advantage of a multiprocessor environment. What makes fork join unique is that it implements a type of work-stealing algorithm : idle threads pick up work from still busy threads. ForkJoinPool manages a small number of threads, usually limited to the number of processor cores available. It is possible to increase the number of threads, if all of the available threads are busy and wrapped inside a blocking call, although such situation would be highly undesirable for most of the systems. ForkJoin framework work to avoid pool-induced deadlock and minimize the amount of time spent switching between the threads. -Futures are generally a good way to reason about asynchronous code. A good way to call a web service, add a block of code to do something when you get back the response, and move on without waiting for the response. They’re also a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking. in Scala, futures (and promises) are based on ExecutionContext. -Using ExecutionContext gives users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. +In Scala, Futures are generally a good framework to reason about concurrency as they can be executed in parallel, waited on, are composable, immutable once written and most importantly, are non blocking (although it is possible to have blocking futures, like Java 6). In Scala, futures (and promises) are based on ExecutionContext. Using ExecutionContext gives users flexibility to implement their own ExecutionContext if they need a specific behavior, like blocking futures. The default ForkJoin pool works well in most of the scenarios. Scala futures api expects an ExecutionContext to be passed along. This parameter is implicit, and usually ExecutionContext.global. An example : @@ -309,6 +304,7 @@ Modern promise specifications, like one in Javascript comes with methods which h In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. + # Handling Errors In a synchronous programming model, the most logical way of handling errors is a try...catch block. -- cgit v1.2.3 From 16384f769fbe273b1f5937f9d823d2d045e82e73 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Fri, 16 Dec 2016 15:41:08 -0500 Subject: Section about failure handling and monitoring --- chapter/3/message-passing.md | 35 +++++++++++++++++++++++++++++++---- chapter/3/sentinel_nodes.png | Bin 0 -> 157837 bytes chapter/3/supervision_tree.png | Bin 0 -> 143187 bytes 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 chapter/3/sentinel_nodes.png create mode 100644 chapter/3/supervision_tree.png diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 200286c..cb97bf3 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -119,13 +119,13 @@ These classes represent a concrete object-oriented abstraction to organize actor ## Akka -Akka is an actively developed project built out of the work on [Scala Actors](#scala-actors) in Scala to provide the actor model of programming as a framework to Java and Scala. It is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing %} +Akka is an effort to bring an industrial-strength actor model to the JVM runtime, which was not explicitly designed to support actors. Akka was developed out of initial efforts of [Scala Actors](#scala-actors) to bring the actor model to the JVM. There are a few notable changes from Scala Actors that make Akka worth mentioning, especially as it is being actively developed while Scala Actors is not. Some important changes are detailed in _On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_. {% cite Haller:2012:IAM:2414639.2414641 --file message-passing %} Akka provides a programming interface with both Java and Scala bindings for actors which looks similar to Scala Actors, but has different semantics in how it processes messages. Akka's `receive` operation defines a global message handler which doesn't block on the receipt of no matching messages, and is instead only triggered when a matching message can be processed. It also will not leave a message in an actor's mailbox if there is no matching pattern to handle the message. The message will simply be discarded and an event will be published to the system. Akka's interface also provides stronger encapsulation to avoid exposing direct references to actors. Akka actors have a limited `ActorRef` interface which only provides methods to send or forward messages to its actor, additionally checks are done to ensure that no direct reference to an instance of an `Actor` subclass is accessible after an actor is created. To some degree this fixes problems in Scala Actors where public methods could be called on actors, breaking many of the guarantees programmers expect from message-passing. This system is not perfect, but in most cases it limits the programmer to simply sending messages to an actor using a limited interface. The Akka runtime also provides performance advantages over Scala Actors. The runtime uses a single continuation closure for many or all messages an actor processes, and provides methods to change this global continuation. This can be implemented more efficiently on the JVM, as opposed to Scala Actors' continuation model which uses control-flow exceptions which cause additional overhead. Additionally, nonblocking message insert and task schedule operations are used for extra performance. -Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. More detail about this is given when describing the production usage of actors. +Akka is the production-ready result of the classic actor model lineage. It is actively developed and actually used to build scalable systems. The production usage of Akka is detailed later in this chapter. Akka has been successful enough that it has been ported to other languages/runtimes. There is an [Akka.NET](http://getakka.net/) project which brings the Akka programming model to .NET and Mono using C# and F#. Akka has even been ported to JavaScript as [Akka.js](https://github.com/unicredit/akka.js/), built on top of [Scala.js](http://www.scala-js.org/). # Process-based actors @@ -133,7 +133,7 @@ The process-based actor model is essentially an actor modeled as a process that Process-based actors are defined as a computation which runs from start to completion, rather than the classic actor model, which defines an actor almost as a state machine of behaviors and the logic to transition between those. Similar state-machine like behavior transitions are possible through recursion with process-based actors, but programming them feels fundamentally different than using the previously described `become` statement. -These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. `receive` statements have some notion of defining acceptable messages, usually based on patterns, conditionals or types. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. +These actors use a `receive` primitive to specify messages that an actor can receive during a given state/point in time. `receive` statements have some notion of defining acceptable messages, usually based on patterns, conditionals or types. If a message is matched, corresponding code is evaluated, but otherwise the actor simply blocks until it gets a message that it knows how to handle. The semantics of this `receive` are different than the receive previously described in the section about Akka. Akka's `receive` is explicitly only triggered when an actor gets a message it knows how to handle. Depending on the language implementation `receive` might specify an explicit message type or perform some pattern matching on message values. An example of these core concepts of a process with a defined lifecycle and use of the `receive` statement to match messages is a simple counter process written in Erlang. {% cite Armstrong:2010:ERL:1810891.1810910 --file message-passing %} @@ -195,7 +195,7 @@ It is worth mentioning that Erlang achieves all of this through the Erlang Virtu Scala Actors is an example of taking and enhancing the Erlang model while bringing it to a new platform. Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors claim that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” -The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. The Erlang model was further enhanced with Scala's pattern-matching capabilities which enable more advanced pattern-matching on messages compared to Erlang's tuple value matching. Scala Actors are of the type `Any => Unit`, which means that they are essentially untyped. They can receive literally any type and match on it with potential side effects. This behavior could be problematic and systems like Cloud Haskell and Akka aim to improve on it. +The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. The Erlang model was further enhanced with Scala's pattern-matching capabilities which enable more advanced pattern-matching on messages compared to Erlang's tuple value matching. Scala Actors are of the type `Any => Unit`, which means that they are essentially untyped. They can receive literally any type and match on it with potential side effects. This behavior could be problematic and systems like Cloud Haskell and Akka aim to improve on it. Akka especially directly draws on the work of Scala Actors, and has now become the standard actor framework for Scala programmers. ## Cloud Haskell @@ -379,6 +379,33 @@ _On the Integration of the Actor Model into Mainstream Technologies_ by Philipp These attributes give us a good basis for analyzing whether an actor system can be successful in production. These are attributes that are necessary, but not sufficient for an actor system to be useful in production. +## Failure handling + +One of the most important concepts and reasons people use actor systems in production is their support for failure handling and recovery. The root of this support is the previously mentioned ability for actors to supervise one another, and to have supervisors notified of failures. _Designing Reactive Systems: The Role of Actors in Distributed Architecture_ {% cite ReactiveSystems --file message-passing %} details four well-known recovery steps that a supervising actor may take when they are notified of a problem with one of their workers. + +* Ignore the error and let the worker resume processing +* Restart the worker and reset their state +* Stop the worker entirely +* Escalate the problem to the supervisor's supervising actor + +Based on this scheme, all actors within a system will have a supervisor, which amounts to a large tree of supervision. At the top of the tree is the actor system itself, which may have a default recovery scheme like simply restarting the actor. An interesting note is that this frees up individual actors from handling their failures. The philosophy around failure shifts to "actors will fail" and that we need other explicit actors and methods for handling failure outside of the business logic of the individual actor. + +
+ An actor supervision hierarchy tree +
An actor supervision hierarchy. {% cite ReactiveSystems --file message-passing %}
+
+ +Another approach that naturally falls out of supervision heirarchies, is that they can be distributed across machines (nodes) within a cluster of actors for fault tolerance. + +
+ Actor supervision across cluster nodes. +
Actor supervision across cluster nodes. {% cite ReactiveSystems --file message-passing %}
+
+ +Critical actors can be monitored across nodes, which means that failures can be detected across nodes within a cluster. This allows for other actors within the cluster to easily react to the entire state of the system, not just the state of their local machine. This is important for a number of problems that arise in distributed systems like load-balancing and data/request partitioning. This also allows naturally allows for some form of recovery from the other machines within a cluster, such as spinning up another node automatically or restarting the failed machine/node. + +Flexibility around failure handling is a key advantage of using actors in production systems. Supervision means that worker actors can focus on business logic, and failure-handling actors can focus on managing and recovering those actors. Actors can also be cluster-aware and have a view into the state of the entire distributed system. + ## Actors as a framework One trend that seems common among the actor systems we see in production is extensive environments and tooling. Akka, Erlang, and Orleans are the primary actor systems that see real production use, and the reason for this is that they essentially act as frameworks where many of the common problems of actors are taken care of for you. They offer support for managing and monitoring the deployment of actors as well as patterns or modules to handle problems like fault-tolerance and load balancing which every distributed actor system has to address. This allows the programmer to focus on the problems within their domain, rather than the common problems of monitoring, deployment, and composition. diff --git a/chapter/3/sentinel_nodes.png b/chapter/3/sentinel_nodes.png new file mode 100644 index 0000000..21e8bd1 Binary files /dev/null and b/chapter/3/sentinel_nodes.png differ diff --git a/chapter/3/supervision_tree.png b/chapter/3/supervision_tree.png new file mode 100644 index 0000000..95bc84b Binary files /dev/null and b/chapter/3/supervision_tree.png differ -- cgit v1.2.3 From cd3dc1c8c5777a239d417caf00d53f6d2348dca9 Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Fri, 16 Dec 2016 15:44:47 -0500 Subject: Remove TODOs --- chapter/3/message-passing.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index cb97bf3..434f83a 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -260,9 +260,7 @@ def makeStatusHolder(var myStatus) { This creates an object `statusHolder` with methods defined by `to` statements. A method invocation from another vat-local object like `statusHolder.setStatus(123)` causes a message to be synchronously delivered to this object. Other objects can register as event listeners by calling either `statusHolder.addListener()` or `statusHolder <- addListener()` to either synchronously or eventually register as listeners. They will be notified eventually when the value of the `statusHolder` changes. This is done via `<-` which is the eventual-send operator. -The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. - -TODO: Mention partial failure and implications of different types of communication +The motivation for this referencing model comes from wanting to work at a finer-grained level of references than a traditional actor exposes. The simplest example is that you want to ensure that another actor in your system can read a value, but can't write to it. How do you do that within another actor model? You might imagine creating a read-only variant of an actor which doesn't expose a write message type, or proxies only `read` messages to another actor which supports both `read` and `write` operations. In E because you are handing out object references, you would simply only pass around references to a `read` method, and you don't have to worry about other actors in your system being able to write values. These finer-grained references make reasoning about state guarantees easier because you are no longer exposing references to an entire actor, but instead the granular capabilities of the actor. Finer-grained references also enable partial failures and recoveries within an actor. Individual objects within an actor can fail and be restarted without affecting the health of the entire actor. This is in a way similar to the supervision hierarchies seen in Erlang, and even means that messages to a failed object could be queued for processing while that object is recovering. This is something that could not happen with the same granularity in another actor system, but feels like a natural outcome of object-level references in E. ## AmbientTalk/2 @@ -448,6 +446,4 @@ One popular model of message-passing concurrency that has been getting attention # References -TODO: Add non-journal references - {% bibliography --file message-passing %} -- cgit v1.2.3 From b194e06bb58c6a840fd080466bb62b14cf64e201 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 16 Dec 2016 15:46:17 -0500 Subject: Bloom, first pass --- chapter/7/langs-consistency.md | 295 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 292 insertions(+), 3 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 78162bc..cd0a7e5 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -244,10 +244,299 @@ Why not push the consistency guarantees in between the IO-level and the applicat Wouldn't it be great if tools like this existed? ### Bloom -[ Introduce Bloom ] +Before talking about such tools, I'd like you to forget almost everything you know about programming for a second (unless of course you've never programmed in a Von Neumann-based language in which you sequentially update pieces of memory; which, by the way, you have). -#### Restriction & Danger -[Bloom restricts you, it's different, and it's dangerous] +Imagine the following scenario: you are "programming" a node in a cluster of computers. All of the other computers work as expected. When you receive a message (all messages will include an integer), your task is to save the message, increment the integer, and resend the message back to its originator. You must also send messages you've received from `stdin`. Unfortunately, the programming environment isn't like anything you've encountered before. +You have access to five buffers: +* Messages you have received in the last 5 seconds +* Inputs you've received from `stdin` in the last 5 seconds +* An outgoing messages buffer: flushed & sent every 5 seconds +* A bucket of saved messages: *never* flushed + +However, you only have access to these buffers *every 5 seconds*. If messages are formatted as such: `(SOURCE, INTEGER, T)`, your buffers might look like when `t = 0`. (`t` is the number of seconds elapsed) + +``` + +RECV-BUFFER: [(A, 1, 0), (B, 2, 0)] +RSTDIN-INPUTS: [(A, 5, 0), (C, 10, 0)] +SEND-BUFFER: [] +SAVED: [(D, -1, 0), (E, -100, 0)] +``` + +If you don't write any code to manipulate these buffers, when `t = 5`, your buffers might look like: + +``` + +RECV-BUFFER: [(C, 10, 5)] +STDIN-INPUTS: [(X, 1, 5)] +SEND-BUFFER: [] +SAVED: [(D, -1, 0), (E, -100, 0)] +``` + +You can see that from `t = 0` to `t = 5`, you received one message from `C` and someone typed a message to `X` via `stdin`. + +Remember our goals? +* Save received messages from the network +* Send out messages received from `stdin` +* For all received network messages, increment the integer and resend it back to the originator + +In Javascript, perhaps you code up something like this: + +```javascript +onFiveSecondInterval(function() { + recvBuffer.forEach(function(msg) { + savedBuffer.push(msg); // save message + let newMsg = msg.clone() + newMsg.integer++; // increment recv'd message + sendBuffer.push(newMsg); // send it out + }); + + stdinInputBuffer.forEach(function(msg) { + sendBuffer.push(msg); // send stdin message + }); +}); +``` + +or Ruby: + +```ruby +on_five_second_interval do + recv_buffer.each do |msg| + saved_buffer << msg + new_msg = msg.clone + new_msg.integer += 1 + send_buffer << new_msg + end + + stdin_input_buffer.each do |msg| + send_buffer << msg + end +end +``` + +We have expressed this model using an event-driven programming style: the main event is `t % 5 = 0`: when the buffers populate & flush. + +Notice we perform a few "copies". We read something from one buffer and place it into another one, perhaps after applying some modification. Perhaps we place a message from a given buffer into two buffers (`recv_buffer` to `saved_buffer` & `send_buffer`). + +This situation screams for a more functional approach: +```ruby +on_five_second_interval do + saved_buffer += recv_buffer # add everything in recv_buffer to saved_buffer + + send_buffer += recv_buffer.map do |msg| # map over the recv_buffer, increment integers, add to send_buffer + new_msg = msg.clone + new_msg.integer += 1 + new_msg # this block returns new_msg + end + + send_buffer += stdin_input_buffer # add stdin messages to the send buffer +end +``` + +After this block/callback is called, the system automatically flushes & routes messages as described above. + +Bloom, a research language developed at UC Berkeley, has a similar programming model to the one described above. Execution is broken up into a series of "timesteps". In the above example, one "timestemp" would be the execution of one `on_five_second_interval` function. Bloom, like the theoretical system above, automatically flushes and populates the buffers before and after each timestep. In the above example, 5 seconds was an arbitrary amount of time. In Bloom, timesteps (rounds of evaluation) are logical tools - they may happen every second, 10 seconds, etc. Logically, it shouldn't affect how your program executes. In reality, Bud's timesteps correspond to evaluation iterations. Your code is evaluated, executed, and the process repeats. + +So what does a Bloom program look like? Bloom's prototypal implementation is called Bud and is implemented in Ruby. There are two main parts to a Bloom program: +1. User defined buffers: rather than the four buffers I gave you above, Bloom users can define their own buffers. There are different types of buffers depending on the behavior you desire: + * `channel`: Above, `recv_buffer` and `send_buffer` would be considered channels. They facilitate sending network messages to and from other nodes. Like the messages above, messages sent into these channels contain a "location-specifier", which tells Bloom where the message should be sent. If you wanted to send a message to `A`, you could push the message `(@A, 10)` into your send buffer (in Ruby, `["@A", 10]`). The `@` denotes the location-specifier. + * `table`: Above, `saved_buffer` would be considered a table. The contents of tables persist across timesteps. +2. Code to be executed at each timestep. A Bloom (Bud) program can be seen as the inside of the block passed to `on_five_second_interval`. In fact, it looks very similar, as we'll see. + +For the purposes of this chapter, let's assume `stdin_input_buffer` is a special kind of channel in which are sent in via `stdin`. Let's also assume this channel exists in all Bloom programs. + +Let's take a look at an example Bud program. + +First, let's declare our state. + +```ruby +module Incrementer + def state + channel :network_channel ['@dst', 'src', 'integer'] + table :saved_buffer ['dst', 'src', 'integer'] + # implied channel :stdin_input_buffer ['@dst', 'src', 'integer'] + end +end +``` + +The first line of `state` means: declare a channel called `network_channel` in which messages are 3-tuples. The first field of the message is called `dst`, the second `src`, and the third is called `integer`. `@` is our location-specifier, so if a program wants to send a message to a node at a given identifier, they will place it in the first `dst` field. + +The second line means: declare a table (persists) called `saved_buffer` in which messages follow the same format as `network_channel`. There's no location specifier since this collection is not network-connected. + +You can think of the Ruby array after the channel name as the "schema" of that collection. + +Notice how we only have one network channel for both receiving and sending. Before, we had two buffers, one for sending and one for receiving. When we place items *into* `network_channel`, Bud will automatically send messages to the appropriate `@dst`. + +Next, let's write our code. This code will be executed at every timestamp. In fact, you can think of a Bud program as the code inside of a timestamp callback. Let's model the raw Ruby code we saw above. + +```ruby +module Incrementer + def state + channel :network_channel ['@dst', 'src', 'integer'] + table :saved_buffer ['dst', 'src', 'integer'] + # implied channel :stdin_input_buffer ['@dst', 'src', 'integer'] + end + + declare + def increment_messages + network_channel <~ network_channel.map { |x| [x.src, x.dst, x.integer + 1] } + end + + declare + def save_messages + saved_buffer <= network_channel + end + + declare + def send_messages + network_channel <~ stdin_input_buffer + end +end +``` + +Don't panic. Remember - the output of this program is identical to our Ruby callback program from earlier. Let's walk through it step by step. +```ruby +declare +def increment_messages + network_channel <~ network_channel.map { |x| [x.src, x.dst, x.integer] } +end +``` +Here, we take messages we've received from the network channel and send them back into the network channel. The `<~` operator says "copy all of the elements in the right-hand-side and eventually send them off onto the network in the channel on the left-hand-side". So, we map over the contents of network channel *in the current timestep*: switching the `src` and `dst` fields, and incrementing the integer. This mapped collection is passed back into the network channel. Bud will ensure those messages sent off at some point. + +``` +declare +def save_messages + saved_buffer <= network_channel +end +``` +In `save_messages`, we use the `<=` operator. `<=` says "copy all of the elements in the right-hand-side and add them to the table on the left-hand-side." It's important to note that this movement occurs *within the current timestep*. This means if `saved_buffer` is referenced elsewhere in the code, it will include the contents of `network_channel`. If we had used the `<+` operator instead, the contents of `network_channel` would show up in `saved_buffer` in the *next* timestep. The latter is useful if you'd like to operate on the current contents of `saved_buffer` in the current timestep but want to specify how `saved_buffer` should be updated for the next timestep. + +Remember, all of this code is executed in each timestep - the separation of code into separate methods is merely for readability. + +``` +declare +def send_messages + network_channel <~ stdin_input_buffer +end +``` + +`send_messages` operates very much like `increment_messages`, except it reads the contents of `stdin_input_buffer` and places them into the network channel to be sent off at an indeterminite time. + +#### Details + +Examine Bloom's "style". Compare it to your (probably) standard way of programming. Compare it to the Javascript & Ruby examples within this strange "timestep" model. Bloom has a more "declarative" style: what does this mean? Look at our Javascript: + +```javascript +onFiveSecondInterval(function() { + recvBuffer.forEach(function(msg) { + savedBuffer.push(msg); // save message + let newMsg = msg.clone() + newMsg.integer++; // increment recv'd message + sendBuffer.push(newMsg); // send it out + }); + + stdinInputBuffer.forEach(function(msg) { + sendBuffer.push(msg); // send stdin message + }); +}); +``` + +"Every five seconds, loop over the received messages. For each one, do this, then that, then that." We are telling the computer each step we'd like it to perform. In Bud, however, we describe the state of tables and channels at either the current or next timestep using operators and other tables and channels. We describe what we'd like our collections to include and look like, rather than what to do. You declare what you'd like the state of the world to be at the current instant and at following instants. + +#### Isn't this chapter about consistency? + +It's time to implement our shopping cart in Bloom. We are going to introduce one more collection: a `periodic`. For example, `periodic :timer 10` instantiates a new periodic collection. This collection is "not empty" every 10 seconds. Alone, it's not all that useful. However, when `join`'d with another table, it can be used to perform actions every `x` seconds. + +```ruby +module ShoppingCart + include MulticastProtocol + + def state + table :cart ['item'] + channel :recv_channel ['@src', 'dst', 'item'] + # implied channel :stdin_input_buffer ['item'] + periodic :timer 10 + end + + declare + def add_items + cart <= stdin_input_buffer + end + + declare + def send_items + send_mcast <= join([cart, timer]).map { |item, timer| item } + end + + declare + def receive_items + cart <+ recv_channel.map { |x| x.item } + end +end +``` + +`send_mcast` is a special type of channel we receive from the `MulticastProtocol` mixin. It sends all items in the right-hand-side to every known peer. +* `add_items`: receive items from stdin, add them to the cart +* `send_items`: join our cart with the 10-second timer. Since the timer only "appears" every 10 seconds, this `join` will produce a result every 10 seconds. When it does, send all cart items to all peers via `send_mcast`. +* `receive_items`: when we receive a message from a peer, add the item to our cart. + +Functionally, this code is equivalent to our working Javascript shopping cart implementation. A few important things to note: +* In our Javascript example, we broadcasted our entire cart to all peers. When a peer received a message, they unioned their current cart with the received one. Here, each node broadcasts each element in the cart. When a node receives an item, it adds it to the current cart. Since tables are represented as sets, repeated or unordered additions do not matter. You can think of `{A, B, C}.add(D)` as equivalent to `{A, B, C}.union({D})`. +* You cannot add items twice. Since tables are represented as sets and we simply add items to our set, an item can only ever exist once. This was true of our Javascript example as well. +* You still cannot remove items! + +Bloom has leveraged the montononic, add-only set and constructed a declarative programming model based around these sets. When you treat everything as sets (not unlike SQL) and you introduce the notion of "timestemps", you can express programs as descriptions of state rather than an order of operations. Besides being a rather unique model, Bloom presents an accessible and (perhaps...) safe model for programming eventually consistent programs. + +#### Sets only? +Bloom's programming model is built around the set. As Aviral discussed in the previous chapter, however, sets are not the only monotonic data structures. Other CRDTs are incredibly useful for programming eventually consistent distributed programs. + +Recall that a *bounded join semilattice* (CRDT) can be represented as a 3-tuple: `(S, U, ⊥)`. `S` is the set of all elements within the semilattice. `U` is the `least-upper bound` operation. `⊥` is the "least" element within the set. For example, for add-only sets, `S = the set of all sets`, `U = union` and `⊥ = {}`. Elements of these semilattices, when `U` is applied, can only "stay the same or get larger". Sets can only stay the same size or get larger - they can never rollback. For some element `e` in `S`, `e U ⊥` must equal `e`. +For a semilattice we'll call `integerMax`, `S = the set of all integers`, `U = max(x, y)`, and `⊥ = -Infinity`. + +These semilattices (and many more!) can be used to program other types of distributed, eventually consistent programs. Although sets are powerful, there might be more expressive ways to describe your program. It's not difficult to imagine using `integerMax` to keep a global counter across multiple machines. + +Unfortunately, Bloom does not provide support for other CRDTs. In fact, you cannot define your own datatypes at all. You are bound by the collections described. + +BloomL, an addendum to the Bloom language, provides support for these types of data structures. Specifically, BloomL does two things: +* Adds a number of built-in lattices such as `lmax` (`integerMax`), `lmin`, etc. +* Adds an "interface" for lattices: the user can define lattices that "implement" this interface. + +This interface, if in an OO language like Java, would look something like: + +```java +interface Lattice { + static Lattice leastElement(); + Lattice merge(Lattice a, Lattice b); +} +``` + +[I am purposely leaving out morphisms & monotones for the sake of simplicity.] + +This provides the user with much more freedom in terms of the types of Bloom programs she can write. + +#### Review + +Bloom aims to provide a new model for writing distributed programs. And since bloom only allows for monotonic data structures with monotonicity-preserving operations, we're safe from Jerry the intern, right? + +Wrong. Unfortunately, I left out an operator from Bloom's set of collection operators. `<-` removes all elements in the right-hand-size from the table in the left-hand-side. As we've seen from Jerry's work on our original Javascript shopping cart implementation, naively attempting to remove elements from a distributed set is not a safe operation. Rollbacks can potentially destroy the properties we worked so hard to achieve. So what gives? Why would the Bloom developers add this operation? + +Despite putting so much emphasis on consistency via logical monotonicity, the Bloom programmers recognize that your program might need *some* coordination. + +In our example, we don't require coordination. We accept the fact that a user may ask a given node for the current state of her shopping cart and may not receive the most up-to-date response. There's no need for coordination, because we've used our domain knowledge to accept a compromise. + +For our shopping cart examples: when a client asks a given node what's in her cart, that node will respond with the information it's received so far. We know this information won't be *incorrect*, but this data could be *stale*. That client might be missing information. + +The Bloom team calls these points in your program *points of order*. They are points in your program where coordination may be required. In fact, the Bloom developers provide analysis tools for identifying points of order within your program. There's no reason why you couldn't implement a non-monotonic shopping cart in which all nodes must synchronize before giving a response to the user. The Bloom analysis tool would tell you where the points of order lie in your program and you would need to add coordination. + +So what does Bloom really give us? First off, it demonstrates an unusual and possibly more expressive way to program distributed systems. Consistency-wise, it uses sets under the hood for its collections. As long as you shy away from `<-` operator, you can be confident that your collections will only monotonically grow. Since the order of packets is not guaranteed, structuring these eventually consistent applications is reasonably easy within Bloom. BloomL also gives us the power to define our own monotonic data structures by "implementing" the lattice interface. + +However, Bloom makes it easy to program non-monotonic distributed programs as well. Applications may require coordination and the `<-` operator in particular can cause serious harm to our desired formal properties. Luckily, Bloom attempts to let the programmer know exactly when coordination may be required within their programs. Whenever an operation may return a stale or non-up-to-date value, Bloom's analysis tools let the programmer know. + +Another thing to consider: BloomL's user-defined lattices are just that - user-defined. It's up to the programmer to ensure that the data structures that implement the lattice interface are actually valid lattice structures. If not, Bloom can't help you. + +Currently Bloom exists as a Ruby prototype: Bud. Hypothetically speaking, there's nothing stopping the programmer from writing normal, sequentially evaluated Ruby code within Bud. This can also cause harm to our formal properties. + +All in all, Bloom provides programmers with a new model for writing distributed programs. If the user desires monotonic data structures and operations, it's relatively easy to use and reason about. Rather than blindly destroying the properties of your system, you will know exactly when you introduce a possible point of order into your program. It's up to you to decide whether or not you need to introduce coordination. ### Lasp [ Introduce Lasp ] -- cgit v1.2.3 From 9ce0c4ac2cdd2f26d677911efbca509031fdde29 Mon Sep 17 00:00:00 2001 From: msabhi Date: Fri, 16 Dec 2016 15:53:42 -0500 Subject: Update big-data.md --- chapter/8/big-data.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapter/8/big-data.md b/chapter/8/big-data.md index f3055b0..194dd5a 100644 --- a/chapter/8/big-data.md +++ b/chapter/8/big-data.md @@ -457,12 +457,12 @@ Spark achieves fault tolerant, high throughput data streaming workloads in real- *Apache Mesos* -Apache Mesos{%cite hindman2011mesos --file big-data%} is an open source cluster/resource manager developed at the University of California, Berkley and used by companies such as Twitter, Airbnb, Netflix etc. for handling workloads in a distributed environment through dynamic resource sharing and isolation. It aids in the deployment and management of applications in large-scale clustered environments. Mesos abstracts node allocation by combining the existing resources of the machines/nodes in a cluster into a single pool and enabling fault-tolerant elastic distributed systems. Variety of workloads can utilize the nodes from this single pool voiding the need of allocating specific machines for different workloads. Mesos is highly scalable, achieves fault tolerance through Apache Zookeeper and is a efficient CPU and memory-aware resource scheduler. +Apache Mesos{%cite hindman2011mesos --file big-data%} is an open source cluster/resource manager developed at the University of California, Berkley and used by companies such as Twitter, Airbnb, Netflix etc. for handling workloads in a distributed environment through dynamic resource sharing and isolation. It aids in the deployment and management of applications in large-scale clustered environments. Mesos abstracts node allocation by combining the existing resources of the machines/nodes in a cluster into a single pool and enabling fault-tolerant elastic distributed systems. Variety of workloads can utilize the nodes from this single pool voiding the need of allocating specific machines for different workloads. Mesos is highly scalable, achieves fault tolerance through Apache Zookeeper {%cite hunt2010zookeeper --file big-data%} and is a efficient CPU and memory-aware resource scheduler. *Alluxio/Tachyon* -Alluxio/Tachyon{% cite Tachyon --file big-data%} is an open source memory-centric distributed storage system that provides high throughput writes and reads enabling reliable data sharing at memory-speed across cluster jobs. Tachyon can integrate with different computation frameworks, such as Apache Spark and Apache MapReduce. In the big data ecosystem, Tachyon fits between computation frameworks or jobs like spark or mapreducce and various kinds of storage systems, such as Amazon S3, OpenStack Swift, GlusterFS, HDFS, or Ceph. It caches the frequently read datasets in memory, thereby avoiding going to disk to load every dataset. In Spark RDDs can automatically be stored inside Tachyon to make Spark more resilient and avoid GC overheads. +Alluxio/Tachyon{% cite li2014tachyon --file big-data%} is an open source memory-centric distributed storage system that provides high throughput writes and reads enabling reliable data sharing at memory-speed across cluster jobs. Tachyon can integrate with different computation frameworks, such as Apache Spark and Apache MapReduce. In the big data ecosystem, Tachyon fits between computation frameworks or jobs like spark or mapreducce and various kinds of storage systems, such as Amazon S3, OpenStack Swift, GlusterFS, HDFS, or Ceph. It caches the frequently read datasets in memory, thereby avoiding going to disk to load every dataset. In Spark RDDs can automatically be stored inside Tachyon to make Spark more resilient and avoid GC overheads. -- cgit v1.2.3 From 7ecfbfe698bc2f62cbabe3966ca81b8da480c753 Mon Sep 17 00:00:00 2001 From: msabhi Date: Fri, 16 Dec 2016 15:59:07 -0500 Subject: Fixed mesos comment --- chapter/8/big-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/8/big-data.md b/chapter/8/big-data.md index 194dd5a..6c0781d 100644 --- a/chapter/8/big-data.md +++ b/chapter/8/big-data.md @@ -457,7 +457,7 @@ Spark achieves fault tolerant, high throughput data streaming workloads in real- *Apache Mesos* -Apache Mesos{%cite hindman2011mesos --file big-data%} is an open source cluster/resource manager developed at the University of California, Berkley and used by companies such as Twitter, Airbnb, Netflix etc. for handling workloads in a distributed environment through dynamic resource sharing and isolation. It aids in the deployment and management of applications in large-scale clustered environments. Mesos abstracts node allocation by combining the existing resources of the machines/nodes in a cluster into a single pool and enabling fault-tolerant elastic distributed systems. Variety of workloads can utilize the nodes from this single pool voiding the need of allocating specific machines for different workloads. Mesos is highly scalable, achieves fault tolerance through Apache Zookeeper {%cite hunt2010zookeeper --file big-data%} and is a efficient CPU and memory-aware resource scheduler. +Apache Mesos{%cite hindman2011mesos --file big-data%} is an open source heterogenous cluster/resource manager developed at the University of California, Berkley and used by companies such as Twitter, Airbnb, Netflix etc. for handling workloads in a distributed environment through dynamic resource sharing and isolation. It aids in the deployment and management of applications in large-scale clustered environments. Mesos abstracts node allocation by combining the existing resources of the machines/nodes in a cluster into a single pool and enabling fault-tolerant elastic distributed systems. Variety of workloads can utilize the nodes from this single pool voiding the need of allocating specific machines for different workloads. Mesos is highly scalable, achieves fault tolerance through Apache Zookeeper {%cite hunt2010zookeeper --file big-data%} and is a efficient CPU and memory-aware resource scheduler. *Alluxio/Tachyon* -- cgit v1.2.3 From d47d895eeb3df40cf31979c46a7d87c94a653777 Mon Sep 17 00:00:00 2001 From: Nat Dempkowski Date: Fri, 16 Dec 2016 16:02:46 -0500 Subject: Formatting fixes/cleanup --- _bibliography/message-passing.bib | 7 +++++++ chapter/3/message-passing.md | 28 +++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/_bibliography/message-passing.bib b/_bibliography/message-passing.bib index b708a8a..7bb6962 100644 --- a/_bibliography/message-passing.bib +++ b/_bibliography/message-passing.bib @@ -291,3 +291,10 @@ year = {2016}, url = {https://www.paypal-engineering.com/2016/05/11/squbs-a-new-reactive-way-for-paypal-to-build-applications/}, } + +@misc{KayQuote, + title = {prototypes vs classes was: Re: Sun's HotSpot}, + author = {Alan Kay}, + year = {1998}, + url = {http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html}, +} diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index 434f83a..ecde506 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -75,7 +75,7 @@ If you squint a little, this actor definition sounds similar to Alan Kay’s ori

The big idea is "messaging" -- that is what the kernal [sic] of Smalltalk/Squeak is all about (and it's something that was never quite completed in our Xerox PARC phase). The Japanese have a small word -- ma -- for "that which is in between" -- perhaps the nearest English equivalent is "interstitial". The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.

-
Alan Kay
+
Alan Kay {% cite KayQuote --file message-passing %}
## Concurrent Object-Oriented Programming (1990) @@ -92,7 +92,7 @@ This paper looks at a lot of systems and languages that are implementing solutio Splitting concerns into multiple pieces allows for the programmer to have an easier time reasoning about the behavior of the program. It also allows the programmer to use more flexible abstractions in their programs.
-It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs. +

It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.

Gul Agha {% cite Agha:1990:COP:83880.84528 --file message-passing %}
@@ -359,15 +359,29 @@ All of this is a longwinded way of saying that Orleans' programmer-centric contr The actor programming model offers benefits to programmers of distributed systems by allowing for easier programmer reasoning about behavior, providing a lightweight concurrency primitive that naturally scales across many machines, and enabling looser coupling among components of a system allowing for change without service disruption. Actors enable a programmer to easier reason about their behavior because they are at a fundamental level isolated from other actors. When programming an actor, the programmer only has to worry about the behavior of that actor and the messages it can send and receive. This alleviates the need for the programmer to reason about an entire system. Instead the programmer has a fixed set of concerns, meaning they can ensure behavioral correctness in isolation, rather than having to worry about an interaction they hadn’t anticipated occurring. Actors provide a single means of communication (message-passing), meaning that a lot of concerns a programmer has around concurrent modification of data are alleviated. Data is restricted to the data within a single actor and the messages it has been passed, rather than all of the accessible data in the whole system. -Actors are lightweight, meaning that the programmer usually does not have to worry about how many actors they are creating. This is a contrast to other fundamental units of concurrency like threads or processes, which a programmer has to be acutely aware of, as they incur high costs of creation, and quickly run into machine resource and performance limitations. Haller (2009) says that without a lightweight process abstraction, burden is increased on the programmer to write their code in an obscured style (Philipp Haller, 2009). Unlike threads and processes, actors can also easily be told to run on other machines as they are functionally isolated. This cannot traditionally be done with threads or processes, as they are unable to be passed over the network to run elsewhere. Messages can be passed over the network, so an actor does not have to care where it is running as long as it can send and receive messages. They are more scalable because of this property, and it means that actors can naturally be distributed across a number of machines to meet the load or availability demands of the system. +Actors are lightweight, meaning that the programmer usually does not have to worry about how many actors they are creating. This is a contrast to other fundamental units of concurrency like threads or processes, which a programmer has to be acutely aware of, as they incur high costs of creation, and quickly run into machine resource and performance limitations. -Finally, because actors are loosely coupled, only depending on a set of input and output messages to and from other actors, their behavior can be modified and upgraded without changing the entire system. For example, a single actor could be upgraded to use a more performant algorithm to do its work, and as long as it can process the same input and output messages, nothing else in the system has to change. This isolation is a contrast to methods of concurrent programming like remote procedure calls, futures, and promises. These models emphasize a tighter coupling between units of computation, where a process may call a method directly on another process and expect a specific result. This means that both the caller and callee (receiver of the call) need to have knowledge of the code being run, so you lose the ability to upgrade one without impacting the other. This becomes a problem in practice, as it means that as the complexity of your distributed system grows, more and more pieces become linked together. Agha (1990) states, “It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.” This is not desirable, as a key characteristic of distributed systems is availability, and the more things are linked together, the more of your system you have to take down or halt to make changes/upgrades. Actors compare favorably to other concurrent programming primitives like threads or remote procedure calls due to their low cost and loosely coupled nature. They are also programmer friendly, and ease the programmer burden of reasoning about a distributed system. +
+

Without a lightweight process abstraction, users are often forced to write parts of concurrent applications in an event-driven style which obscures control flow, and increases the burden on the programmer.

+
Philipp Haller {% cite Haller:2009:SAU:1496391.1496422 --file message-passing %}
+
+ +Unlike threads and processes, actors can also easily be told to run on other machines as they are functionally isolated. This cannot traditionally be done with threads or processes, as they are unable to be passed over the network to run elsewhere. Messages can be passed over the network, so an actor does not have to care where it is running as long as it can send and receive messages. They are more scalable because of this property, and it means that actors can naturally be distributed across a number of machines to meet the load or availability demands of the system. + +Finally, because actors are loosely coupled, only depending on a set of input and output messages to and from other actors, their behavior can be modified and upgraded without changing the entire system. For example, a single actor could be upgraded to use a more performant algorithm to do its work, and as long as it can process the same input and output messages, nothing else in the system has to change. This isolation is a contrast to methods of concurrent programming like remote procedure calls, futures, and promises. These models emphasize a tighter coupling between units of computation, where a process may call a method directly on another process and expect a specific result. This means that both the caller and callee (receiver of the call) need to have knowledge of the code being run, so you lose the ability to upgrade one without impacting the other. This becomes a problem in practice, as it means that as the complexity of your distributed system grows, more and more pieces become linked together. + +
+

It is important to note that the actor languages give special emphasis to developing flexible program structures which simplify reasoning about programs.

+
Gul Agha {% cite Agha:1990:COP:83880.84528 --file message-passing %}
+
+ +This is not desirable, as a key characteristic of distributed systems is availability, and the more things are linked together, the more of your system you have to take down or halt to make changes/upgrades. Actors compare favorably to other concurrent programming primitives like threads or remote procedure calls due to their low cost and loosely coupled nature. They are also programmer friendly, and ease the programmer burden of reasoning about a distributed system. # Modern usage in production It is important when reviewing models of programming distributed systems not to look just to academia, but to see which of these systems are actually used in industry to build things. This can give us insight into which features of actor systems are actually useful, and the trends that exist throughout these systems. -_On the Integration of the Actor Model into Mainstream Technologies_ by Philipp Haller provides some insight into the requirements of an industrial-strength actor implementation on a mainstream platform. These requirements were drawn out of an initial effort with [Scala Actors](#scala-actors) to bring the actor model to mainstream software engineering, as well as lessons learned from the deployment and advancement of production actors in [Akka](#akka). +_On the Integration of the Actor Model in Mainstream Technologies: The Scala Perspective_ {% cite Haller:2012:IAM:2414639.2414641 --file message-passing %} provides some insight into the requirements of an industrial-strength actor implementation on a mainstream platform. These requirements were drawn out of an initial effort with [Scala Actors](#scala-actors) to bring the actor model to mainstream software engineering, as well as lessons learned from the deployment and advancement of production actors in [Akka](#akka). * _Library-based implementation_: It is not obvious which concurrency abstraction wins in real world cases, and different concurrency models might be used to solve different problems, so implementing a concurrency model as a library enables flexibility in usage. * _High-level domain-specific language_: A domain-specific language or something comparable is a requirement to compete with languages that specialize in concurrency, otherwise your abstractions are lacking in idioms and expressiveness. @@ -440,9 +454,9 @@ Orleans goes in another direction, which I call the managed runtime approach. In Both approaches have been successful in industry. Erlang has the famous use case of a telephone exchange and a successful history since then. Akka has an entire page detailing its usage in giant companies. Orleans has been used as a backend to massive Microsoft-scale games and applications with millions of users. It seems like the module approach is more popular, but there's only really one example of the managed runtime approach out there. There's no equivalent to Orleans on the JVM or Erlang VM, so realistically it doesn't have as much exposure in the distributed programming community. -## Comparison to Communicating Sequential Processes (CSP) +## Comparison to Communicating Sequential Processes -One popular model of message-passing concurrency that has been getting attention is CSP. The basic idea behind CSP is that concurrent communication between processes is done by passing messages through channels. Arguably the most popular modern implementation of this is Go's channels. A lot of the surface-level discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This zoomed-out view might conflate CSP-style channels and actors, but it misses a lot of subtleties as CSP really can't be considered an actor framework. The core difference is that CSP implements some form of synchronous messaging between processes, while the actor model entirely decouples messaging between a sender and a receiver. Actors are much more independent, meaning its easier to run them in a distributed environment without changing their semantics. Additionally, receiver failures don't affect senders in the actor model. Actors are a more loosely-coupled abstraction across a distributed environment, while CSP embraces tight-coupling as a means of synchronization across processes. To conflate the two misses the point of both, as actors are operating at a fundamentally different level of abstraction from CSP. +One popular model of message-passing concurrency that has been getting attention is Communicating Sequential Processes (CSP). The basic idea behind CSP is that concurrent communication between processes is done by passing messages through channels. Arguably the most popular modern implementation of this is Go's channels. A lot of the surface-level discussions of actors simply take them as something that is a lightweight concurrency primitive which passes messages. This zoomed-out view might conflate CSP-style channels and actors, but it misses a lot of subtleties as CSP really can't be considered an actor framework. The core difference is that CSP implements some form of synchronous messaging between processes, while the actor model entirely decouples messaging between a sender and a receiver. Actors are much more independent, meaning its easier to run them in a distributed environment without changing their semantics. Additionally, receiver failures don't affect senders in the actor model. Actors are a more loosely-coupled abstraction across a distributed environment, while CSP embraces tight-coupling as a means of synchronization across processes. To conflate the two misses the point of both, as actors are operating at a fundamentally different level of abstraction from CSP. # References -- cgit v1.2.3 From 27d0d572274392a6e14bb2bf4f807f55ef4d08ed Mon Sep 17 00:00:00 2001 From: Nathaniel Dempkowski Date: Fri, 16 Dec 2016 16:09:04 -0500 Subject: More citations --- chapter/3/message-passing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chapter/3/message-passing.md b/chapter/3/message-passing.md index ecde506..a35a75e 100644 --- a/chapter/3/message-passing.md +++ b/chapter/3/message-passing.md @@ -129,7 +129,7 @@ Akka is the production-ready result of the classic actor model lineage. It is ac # Process-based actors -The process-based actor model is essentially an actor modeled as a process that runs from start to completion. This view is broadly similar to the classic actor, but different mechanics exist around managing the lifecycle and behaviors of actors between the models. The first language to explicitly implement this model is Erlang, and they even say in a retrospective that their view of computation is broadly similar to the Agha's classic actor model. +The process-based actor model is essentially an actor modeled as a process that runs from start to completion. This view is broadly similar to the classic actor, but different mechanics exist around managing the lifecycle and behaviors of actors between the models. The first language to explicitly implement this model is Erlang, {% cite Armstrong:2010:ERL:1810891.1810910 --file message-passing %} and they even say in a retrospective that their view of computation is broadly similar to the Agha's classic actor model. Process-based actors are defined as a computation which runs from start to completion, rather than the classic actor model, which defines an actor almost as a state machine of behaviors and the logic to transition between those. Similar state-machine like behavior transitions are possible through recursion with process-based actors, but programming them feels fundamentally different than using the previously described `become` statement. @@ -193,19 +193,19 @@ It is worth mentioning that Erlang achieves all of this through the Erlang Virtu ## Scala Actors -Scala Actors is an example of taking and enhancing the Erlang model while bringing it to a new platform. Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors claim that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” +Scala Actors is an example of taking and enhancing the Erlang model while bringing it to a new platform. Scala Actors brings lightweight Erlang-style message-passing concurrency to the JVM and integrates it with the heavyweight thread/process concurrency models. {% cite Haller:2009:SAU:1496391.1496422 --file message-passing %} This is stated well in the original paper about Scala Actors as "an impedance mismatch between message-passing concurrency and virtual machines such as the JVM." VMs usually map threads to heavyweight processes, but that a lightweight process abstraction reduces programmer burden and leads to more natural abstractions. The authors claim that “The user experience gained so far indicates that the library makes concurrent programming in a JVM-based system much more accessible than previous techniques.” The realization of this model depends on efficiently multiplexing actors to threads. This technique was originally developed in Scala actors, and later was adopted by Akka. This integration allows for Actors to invoke methods that block the underlying thread in a way that doesn't prevent actors from making process. This is important to consider in an event-driven system where handlers are executed on a thread pool, because the underlying event-handlers can't block threads without risking thread pool starvation. The end result here is that Scala Actors enabled a new lightweight concurrency primitive on the JVM, with enhancements over Erlang's model. The Erlang model was further enhanced with Scala's pattern-matching capabilities which enable more advanced pattern-matching on messages compared to Erlang's tuple value matching. Scala Actors are of the type `Any => Unit`, which means that they are essentially untyped. They can receive literally any type and match on it with potential side effects. This behavior could be problematic and systems like Cloud Haskell and Akka aim to improve on it. Akka especially directly draws on the work of Scala Actors, and has now become the standard actor framework for Scala programmers. ## Cloud Haskell -Cloud Haskell is an extension of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. This alone isn't so different from Erlang, which operates on immutable data in the context of isolated memory. +Cloud Haskell is an extension of Haskell which essentially implements an enhanced version of the computational message-passing model of Erlang in Haskell. {% cite epstein2011 --file message-passing %} It enhances Erlang's model with advantages from Haskell's model of functional programming in the form of purity, types, and monads. Cloud Haskell enables the use of pure functions for remote computation, which means that these functions are idempotent and can be restarted or run elsewhere in the case of failure without worrying about side-effects or undo mechanisms. This alone isn't so different from Erlang, which operates on immutable data in the context of isolated memory. One of the largest improvements over Erlang is the introduction of typed channels for sending messages. These provide guarantees to the programmer about the types of messages their actors can handle, which is something Erlang lacks. In Erlang, all you have is dynamic pattern matching based on values patterns, and the hope that the wrong types of message don't get passed around your system. Cloud Haskell processes can also use multiple typed channels to pass messages between actors, rather than Erlang's single untyped channel. Haskell's monadic types make it possible for programmers to use a programming style, where they can ensure that pure and effective code are not mixed. This makes reasoning about where side-effects happen in your system easier. Cloud Haskell has shared memory within an actor process, which is useful for certain applications. This might sound like it could cause problems, but shared-memory structures are forbidden by the type system from being shared across actors. Finally, Cloud Haskell allows for the serialization of function closures, which means that higher-order functions can be distributed across actors. This means that as long as a function and its environment are serializable, they can be spun off as a remote computation and seamlessly continued elsewhere. These improvements over Erlang make Cloud Haskell a notable project in the space of process-based actors. Cloud Haskell is currently supported and also has developed the Cloud Haskell Platform, which aims to provide common functionality needed to build and manage a production actor system using Cloud Haskell. # Communicating event-loops -The communicating event-loop model was introduced in the E language, and is one that aims to change the level of granularity at which communication happens within an actor-based system. The previously described actor systems organize communication at the actor level, while the communicating event model puts communication between actors in the context of actions on objects within those actors. The overall messages still reference higher-level actors, but those messages refer to more granular actions within an actor's state. +The communicating event-loop model was introduced in the E language, {% cite Miller:2005:CSP:1986262.1986274 --file message-passing %} and is one that aims to change the level of granularity at which communication happens within an actor-based system. The previously described actor systems organize communication at the actor level, while the communicating event model puts communication between actors in the context of actions on objects within those actors. The overall messages still reference higher-level actors, but those messages refer to more granular actions within an actor's state. ## E Language @@ -264,7 +264,7 @@ The motivation for this referencing model comes from wanting to work at a finer- ## AmbientTalk/2 -AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. This idea was originally realized in AmbientTalk/1 where actors were modelled as ABCL/1-like active objects {% cite Yonezawa:1986:OCP:960112.28722 --file message-passing %}, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between and movement of passive objects. +AmbientTalk/2 is a modern revival of the communicating event-loops actor model as a distributed programming language with an emphasis on developing mobile peer-to-peer applications. {% cite Cutsem:2007:AOE:1338443.1338745 --file message-passing %} This idea was originally realized in AmbientTalk/1 {% cite Dedecker:2006:APA:2171327.2171349 --file message-passing %} where actors were modelled as ABCL/1-like active objects {% cite Yonezawa:1986:OCP:960112.28722 --file message-passing %}, but AmbientTalk/2 models actors similarly to E's vats. The authors of AmbientTalk/2 felt limited by not allowing passive objects within an actor to be referenced by other actors, so they chose to go with the more fine-grained approach which allows for remote interactions between and movement of passive objects. Actors in AmbientTalk/2 are representations of event loops. The message queue is the event queue, messages are events, asynchronous message sends are event notifications, and object methods are the event handlers. The event loop serially processes messages from the queue to avoid race conditions. Local objects within an actor are owned by that actor, which is the only entity allowed to directly execute methods on them. Like E, objects within an actor can communicate using synchronous or asynchronous methods of communication. Again similar to E, objects that are referenced outside of an actor can only be communicated to asynchronously by sending messages. Objects can additionally declare themselves serializable, which means they can be copied and sent to other actors for use as local objects. When this happens, there is no maintained relationship between the original object and its copy. @@ -304,7 +304,7 @@ The key difference here is around how this different style of actors relates to ## Orleans -Orleans takes the concept of actors whose lifecycle is dependent on messaging or requests and places them in the context of cloud applications. Orleans does this via actors (called "grains") which are isolated units of computation and behavior that can have multiple instantiations (called "activations") for scalability. These actors also have persistence, meaning they have a persistent state that is kept in durable storage so that it can be used to manage things like user data. +Orleans takes the concept of actors whose lifecycle is dependent on messaging or requests and places them in the context of cloud applications. {% cite Bykov:2011:OCC:2038916.2038932 --file message-passing %} Orleans does this via actors (called "grains") which are isolated units of computation and behavior that can have multiple instantiations (called "activations") for scalability. These actors also have persistence, meaning they have a persistent state that is kept in durable storage so that it can be used to manage things like user data. Orleans uses a different notion of identity than other actor systems. In other systems an "actor" might refer to a behavior and instances of that actor might refer to identities that the actor represents like individual users. In Orleans, an actor represents that persistent identity, and the actual instantiations are in fact reconcilable copies of that identity. -- cgit v1.2.3 From 33f3c34e429b7002bc84297ce9ee4d8abc703c85 Mon Sep 17 00:00:00 2001 From: Muzammil Date: Fri, 16 Dec 2016 17:02:36 -0500 Subject: RPC-Muzammil-Final --- _bibliography/rpc.bib | 113 +++++++++++++- chapter/1/rpc.md | 244 ++++++++++++++++++++++++------- resources/img/rpc_chapter_1_asyncrpc.jpg | Bin 0 -> 28192 bytes resources/img/rpc_chapter_1_syncrpc.jpg | Bin 0 -> 27675 bytes 4 files changed, 304 insertions(+), 53 deletions(-) create mode 100644 resources/img/rpc_chapter_1_asyncrpc.jpg create mode 100644 resources/img/rpc_chapter_1_syncrpc.jpg diff --git a/_bibliography/rpc.bib b/_bibliography/rpc.bib index 266c603..638cf1b 100644 --- a/_bibliography/rpc.bib +++ b/_bibliography/rpc.bib @@ -208,7 +208,7 @@ organization={Springer} } -@misc{orcalenfs, +@misc{oraclenfs, title={Overview of Secure RPC}, author={Oracle}, url = {https://docs.oracle.com/cd/E23823_01/html/816-4557/auth-2.html}, @@ -267,7 +267,7 @@ } @misc{Apigee, - title={Is Cap'n Proto Secure?}, + title={gRPC: The Story of Microservices at Square}, author={Surtani, Manick and Ho, Alan}, url = {https://www.youtube.com/watch?v=-2sWDr3Z0Wo}, } @@ -359,3 +359,112 @@ author={Srinivasan, R}, year={1995} } + +@misc{grpcchapter, + title={gRPC}, + booktitle={Programming Models for Distributed Computation}, + author={Grosu, P and Abdul Rehman, M and Anderson, E and Pai, V and Miller, H}, + url = {http://dist-prog-book.com/chapter/1/gRPC.html}, + publisher = {Github} +} + +@misc{stubrpc, + title={stub}, + author={Rouse, M}, + url = {http://whatis.techtarget.com/definition/stub}, + publisher = {WhatIs} +} + + +@misc{grpcpersec, + title={Scalable Realtime Microservices with Kubernetes and gRPC}, + author={Mandel, M}, + url = {https://www.youtube.com/watch?v=xb8u2s7cxzg&t=486s}, + publisher = {YouTube} +} + +@misc{usgdp, + title={2016 United States Budget Estimate}, + author={InsideGov}, + url = {http://federal-budget.insidegov.com/l/119/2016-Estimate}, + publisher = {InsideGov} +} + + +@incollection{notedistributed, + title={A note on distributed computing}, + author={Waldo, Jim and Wyant, Geoff and Wollrath, Ann and Kendall, Sam}, + booktitle={Mobile Object Systems Towards the Programmable Internet}, + pages={49--64}, + year={1997}, + publisher={Springer} +} + +@misc{dewan, + title={Synchronous vs Asynchronous}, + author={Dewan, P}, + url = {http://www.cs.unc.edu/~dewan/242/s07/notes/ipc/node9.html}, + year={2006}, + publisher = {UNC} +} + + +@misc{async, + title={Asynchronous RPC}, + url = {https://msdn.microsoft.com/en-us/library/windows/desktop/aa373550(v=vs.85).aspx}, + year={2006}, + publisher = {Microsoft} +} + +@misc{roi, + title={Remote Object Invocation}, + author={Setia, S}, + url = {https://cs.gmu.edu/~setia/cs707/slides/rmi-imp.pdf}, + publisher = {QMU} +} + + +@misc{Wiener, + title={XML-RPC}, + author={Weiner, D}, + url = {http://xmlrpc.scripting.com/spec.html}, + year={1999}, + publisher = {QMU} +} + + +@misc{soapvsxml, + title={XML-RPC}, + author={Jones, M}, + url = {https://www.quora.com/What-is-the-difference-between-xml-rpc-and-soap}, + year={2014}, + publisher = {Quora} +} + +@misc{thrifttut, + title={Apache THRIFT: A Much Needed Tutorial}, + author={Maheshwar, C}, + url = {http://digital-madness.in/blog/wp-content/uploads/2012/11/BSD_08_2013.8-18.pdf}, + year={2013}, + publisher = {Digital Madness} +} + +@misc{finagletut, + title={Finagle-Quickstart}, + author={Twitter}, + url = {https://twitter.github.io/finagle/guide/Quickstart.html}, + year={2016}, + publisher = {Twitter} +} + + +@misc{norman, + title={Communication Fundamentals}, + author={Norman, S}, + url = {http://slideplayer.com/slide/8555756/}, + year={2015}, + publisher = {SlideShare} +} + + + diff --git a/chapter/1/rpc.md b/chapter/1/rpc.md index 7688455..ccc9739 100644 --- a/chapter/1/rpc.md +++ b/chapter/1/rpc.md @@ -6,40 +6,47 @@ by: "Muzammil Abdul Rehman and Paul Grosu" ## Introduction: -*Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. It was initially built as a tool for outsourcing computation to a server in a distributed system, however, it has evolved over the years to build modular, scalable, distributed, language-agnostic ecosystem of applications. This RPC *paradigm* has been part of the driving force in creating truly revolutionizing distributed systems and giving rise to various communication schemes and protocols between diverse systems. +*Remote Procedure Call* (RPC) is a design *paradigm* that allow two entities to communicate over a communication channel in a general request-response mechanism. The definition of RPC has mutated and evolved significantly over the past three decades, and therefore RPC *paradigm* is a generic, broadly classifying term to refer to all RPC-esque systems that have arisen over the past four decades. The *definition* of RPC has evolved over the decades. It has moved on from a simple *client-server* design to a group of inter-connected *services*. While the initial RPC *implementations* were designed as tools for outsourcing computation to a server in a distributed system, however, RPC has evolved over the years to build language-agnostic ecosystem of applications. This RPC *paradigm* has been part of the driving force in creating truly revolutionizing distributed systems and giving rise to various communication schemes and protocols between diverse systems. -RPC *paradigm* has been implemented in various forms in our every-day systems. From lower level applications like Network File Systems{% cite sunnfs --file rpc %} and Remote Direct Memory Access{% cite rpcoverrdma --file rpc %} to access protocols to developing an ecosystem of microservices, RPC has been used everywhere. Some of the major examples of RPC include SunNFS{% cite sunnfs --file rpc %}, Twitter's Finagle{% cite finagle --file rpc %}, Apache Thrift{% cite thrift --file rpc %}, Java RMI{% cite rmipaper --file rpc %}, SOAP, CORBA{% cite corba --file rpc %}, Google's gRPC{% cite grpc --file rpc %}. +RPC *paradigm* has been used to implement our every-day systems. From lower level applications like Network File Systems{% cite sunnfs --file rpc %} and Remote Direct Memory Access{% cite rpcoverrdma --file rpc %} to access protocols to developing an ecosystem of microservices, RPC has been used everywhere. RPC has a diverse variety of applications -- SunNFS{% cite sunnfs --file rpc %}, Twitter's Finagle{% cite finagle --file rpc %}, Apache Thrift{% cite thrift --file rpc %}, Java RMI{% cite rmipaper --file rpc %}, SOAP, CORBA{% cite corba --file rpc %} and Google's gRPC{% cite grpc --file rpc %} to name a few. -RPC has evolved over the years. Starting off as a synchronous, insecure, request-response system, RPC has evolved into a secure, asynchronous, resilient *paradigm* that has influenced protocols and programming designs, like, HTTP, REST, and just about anything with a request-response system. It has transitioned to an asynchronous bidirectional communication for connecting services and devices across the internet. RPC has influenced various design paradigms and communication protocols. +RPC has evolved over the years. Starting off as a synchronous, insecure, request-response system, RPC has evolved into a secure, asynchronous, resilient *paradigm* that has influenced protocols and programming designs, like, HTTP, REST, and just about anything with a request-response system. It has transitioned to an asynchronous, bidirectional, communication mechanism for connecting services and devices across the internet. While the initial RPC implementations mainly focused on a local, private network with multiple clients communicating with a server and synchronously waiting for the response from the server, modern RPC systems have *endpoints* communicating with each other, asynchronously passing arguments and processing responses, as well having two-way request-response streams(from client to server, and also from server to client). RPC has influenced various design paradigms and communication protocols. ## Remote Procedure Calls: -*Remote Procedure Call paradigm* can be defined, at a high level, as a set of two language-agnostic communication *endpoints* connected over a network with one endpoint sending a request and the other endpoint generating a response based on that request. In the simplest terms, it's a request-response paradigm where the two *endpoints*/hosts have different *address space*. The host that requests a remote procedure can be referred to as *caller* and the host that responds to this can be referred to as *callee*. +The *Remote Procedure Call paradigm* can be defined, at a high level, as a set of two communication *endpoints* connected over a network with one endpoint sending a request and the other endpoint generating a response based on that request. In the simplest terms, it's a request-response paradigm where the two *endpoints*/hosts have different *address space*. The host that requests a remote procedure can be referred to as *caller* and the host that responds to this can be referred to as *callee*. The *endpoints* in the RPC can either be a client and a server, two nodes in a peer-to-peer network, two hosts in a grid computation system, or even two microservices. The RPC communication is not limited to two hosts, rather could have multiple hosts or *endpoints* involved {% cite anycastrpc --file rpc %}. +

+[ Image Source: {% cite rpcimage --file rpc %}] +

RPC in 10 Steps. -

Fig1. - Remote Procedure Call{% cite rpcimage --file rpc %}.

+

Fig1. - Remote Procedure Call.

-The simplest RPC implementation looks like Fig1. In this case, the *client*(or *caller*) and the *server*(or *callee*) are separated by a physical network. The main components of the system are the client routine/program, the client stub, the server routine/program, the server stub, and the network routines. The client program can only interact with the client stub that provides the interface of the remote server to the client. This stub also provides marshalling/pickling/serialization of the input arguments sent to the stub by the client routine. Similarly, the server stub provides a client interface to the server routines. Whenever a client routine has to perform a *remote procedure*, it calls the client stub, which serializes the input argument. This serialized data is sent to the server using OS network routines (TCP/IP). The data is serialized by the server stub, present to the server routines for the given arguments. The return value from the server routines is serialized again and sent over the network back to the client where it's deserialized by the client stub and presented to the client routine. This *remote procedure* is generally hidden from the client routine and it appears as a *local procedure* to the client. RPC services also require a discovery service/host-resolution mechanism to bootstrap the communication between the client and the server. +The simplest RPC implementation looks like Fig1. In this case, the *client*(or *caller*) and the *server*(or *callee*) are separated by a physical network. The main components of the system are the client routine/program, the client stub, the server routine/program, the server stub, and the network routines. A *stub* is a small program that is generally used as a stand-in(or an interface) for a larger program{% cite stubrpc --file rpc %}. A *client stub* exposes the functionality provided by the server routine to the client routine while the server stub provides a client-like program to the server routine{% cite rpcimage --file rpc %}. The client stub takes the input arguments from the client program and returns the result, while the server stub provides input arguments to the server program and gets the results. The client program can only interact with the client stub that provides the interface of the remote server to the client. This stub also provides marshalling/pickling/serialization of the input arguments sent to the stub by the client routine. Similarly, the server stub provides a client interface to the server routines as well as the marshalling services. + +When a client routine performs a *remote procedure*, it calls the client stub, which serializes the input argument. This serialized data is sent to the server using OS network routines (TCP/IP){% cite rpcimage --file rpc %}. The data is serialized by the server stub, present to the server routines for the given arguments. The return value from the server routines is serialized again and sent over the network back to the client where it's deserialized by the client stub and presented to the client routine. This *remote procedure* is generally hidden from the client routine and it appears as a *local procedure* to the client. RPC services also require a discovery service/host-resolution mechanism to bootstrap the communication between the client and the server. One important feature of RPC is different *address space* {% cite implementingrpc --file rpc %} for all the endpoints, however, passing the locations to a global storage(Amazon S3, Microsoft Azure, Google Cloud Store) is not impossible. In RPC, all the hosts have separate *address spaces*. They can't share pointers or references to a memory location in one host. This *address space* isolation means that all the information is passed in the messages between the host communicating as a value (objects or variables) but not by reference. Since RPC is a *remote* procedure call, the values sent to the *remote* host cannot be pointers or references to a *local* memory. However, passing links to a global shared memory location is not impossible but rather dependent on the type of system (see *Applications* section for detail). -Originally, RPC was developed as a synchronous, language-specific marshalling service with a custom network protocol to outsource computation {% cite implementingrpc --file rpc %}. It had registry-system to register all the servers. One of the earliest RPC-based system {% cite implementingrpc --file rpc %} was implemented in the Cedar programming language in early 1980's. The goal of this system was to provide similar programming semantics as local procedure calls. Developed for a LAN network with an inefficient network protocol and a *serialization* scheme to transfer information using the said network protocol, this system aimed at executing a *procedure*(also referred as *method* or a *function*) in a remote *address space*. The single-thread synchronous client and the server were written in an old *Cedar* programming language with a registry system used by the servers to *bind*(or register) their procedures. The clients used this registry system to find a specific server to execute their *remote* procedures. +Originally, RPC was developed as a synchronous request-response mechanism, tied to a specific programming language implementation, with a custom network protocol to outsource computation {% cite implementingrpc --file rpc %}. It had registry system to register all the servers. One of the earliest RPC-based system {% cite implementingrpc --file rpc %} was implemented in the Cedar programming language in early 1980's. The goal of this system was to provide similar programming semantics as local procedure calls. Developed for a LAN network with an inefficient network protocol and a *serialization* scheme to transfer information using the said network protocol, this system aimed at executing a *procedure*(also referred as *method* or a *function*) in a remote *address space*. The single-thread synchronous client and the server were written in an old *Cedar* programming language with a registry system used by the servers to *bind*(or register) their procedures. The clients used this registry system to find a specific server to execute their *remote* procedures. This RPC implementation {% cite implementingrpc --file rpc %} had a very specific use-case. It was built specifically for outsourcing computation between a "Xerox research internetwork", a small, closed, ethernet network with 16-bit addresses{% cite implementingrpc --file rpc %}. -Modern RPC-based systems are language-agnostic, asynchronous, load-balanced systems. Authentication and authorization to these systems have been added as needed along with other security features. Most of these systems have fault-handling built into them as modules. +Modern RPC-based systems are language-agnostic, asynchronous, load-balanced systems. Authentication and authorization to these systems have been added as needed along with other security features. Most of these systems have fault-handling built into them as modules and the systems are generally spread all across the internet. RPC programs have a network (or a communication channel), therefore, they need to handle remote errors and be able to communication information successfully. Error handling generally varies and is categorized as *remote-host* or *network* failure handling. Depending on the type of the system, and the error, the caller (or the callee) return an error and these errors can be handled accordingly. For asynchronous RPC calls, it's possible to specify events to ensure progress. RPC implementations use a *serialization*(also referred to as *marshalling* or *pickling*) scheme on top of an underlying communication protocol (traditionally TCP over IP). These *serialization* schemes allow both the caller *caller* and *callee* to become language agnostic allowing both these systems to be developed in parallel without any language restrictions. Some examples of serialization schemes are JSON, XML, or Protocol Buffers {% cite grpc --file rpc %}. -RPC allows different components of a larger system to be developed independently of one another. The language-agnostic nature combined with a decoupling of some parts of the system allows the two components (caller and callee) to scale separately and add new functionalities. This independent scaling of the system might lead to a mesh of interconnected RPC *services* facilitating one another. +Modern RPC systems allow different components of a larger system to be developed independently of one another. The language-agnostic nature combined with a decoupling of some parts of the system allows the two components (caller and callee) to scale separately and add new functionalities. This independent scaling of the system might lead to a mesh of interconnected RPC *services* facilitating one another. ### Examples of RPC -RPC has become very predominant in modern systems. In the simplest RPC systems, a client connects to a server over a network connection and performs a *procedure*. This procedure could be as simple as `return "Hello World"` in your favorite programming language. However, the complexity of the of this remote procedure has no upper bound. +RPC has become very predominant in modern systems. Google even performs orders of 10^10^ RPC calls per second {% cite grpcpersec --file rpc %}. That's *tens of trillions* of RPC calls *every second*. It's more than the *annual GDP of United States* {%cite usgdp --file rpc%}. + +In the simplest RPC systems, a client connects to a server over a network connection and performs a *procedure*. This procedure could be as simple as `return "Hello World"` in your favorite programming language. However, the complexity of the of this remote procedure has no upper bound. Here's the code of this simple RPC server, written in Python3. ```python @@ -70,69 +77,200 @@ One can even view the *three-way handshake* as an example of RPC paradigm. The ## Evolution of RPC: -RPC paradigm was first proposed in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be globally implemented. It has had a long and arduous journey to its current state. Here are the three main(overlapping) stages that RPC went through. +RPC paradigm was first proposed in 1980’s and still continues as a relevant model of performing distributed computation, which initially was developed for a LAN and now can be implemented on open networks, as web services across the internet. It has had a long and arduous journey to its current state. Here are the three main(overlapping) stages that RPC went through. ### The Rise: All Hail RPC(Early 1970's - Mid 1980's) -RPC started off strong. With RFCs{% cite rfc674 rfc707 --file rpc %} coming out and specifying the design of Remote Procedure Calls, followed by Nelson et. al{% cite implementingrpc --file rpc %} coming up with a first implementation for the Cedar programming language, RPC revolutionized systems in general and gave rise to one of the earliest distributed systems(apart from the internet, of course). +RPC started off strong. With RFC 674{% cite rfc674 --file rpc %} and RFC 707{% cite rfc674 rfc707 --file rpc %} coming out and specifying the design of Remote Procedure Calls, followed by Nelson et. al{% cite implementingrpc --file rpc %} coming up with a first RPC implementation for the Cedar programming language, RPC revolutionized systems in general and gave rise to one of the earliest distributed systems(apart from the internet, of course). With these early achievements, people started using RPC as the defacto design choice. It became a Holy Grail in the systems community for a few years after the first implementation. -### The Fall: RPC is Dead(Late 1970's - Late 1980's) +### The Fall: RPC is Dead(Late 1970's - Late 1990's) + +RPC, despite being an initial success, wasn't without flaws. Within a year of its inception, the limitation of the RPC started to catch up with it. RFC 684 criticized RPC for latency, failures, and the cost. It also focussed on message-passing systems as an alternative to RPC design. Similarly, a few years down the road, in 1988, Tenenbaum et.~al presented similar concerns against RPC {%cite critiqueofrpc --file rpc %}. It talked about problems heterogeneous devices, message passing as an alternative, packet loss, network failure, RPC's synchronous nature, and highlighted that RPC is not a one-size-fits-all model. + +In 1994, *A Note on Distributed Computing* was published. This paper claimed RPC to be "fundamentally flawed" {%cite notedistributed --file rpc %}. It talked about a unified object view and cited four main problems with dividing these objects for distributed computing in RPC: communication latency, address space separation, partial failures and concurrency issues(resulting from accessing same remote object by two concurrent client requests). Although most of these problems(except partial failures) were inherently associated with distributed computing itself but partial failures for RPC systems meant that progress might not always be possible in an RPC system. + +This era wasn't a dead end for RPC, though. Some of the preliminary designs for modern RPC systems were introduced in this era. Perhaps, the earliest system in this era was SunRPC {% cite sunnfs --file rpc %} used for the Sun Network File System(NFS). Soon to follow SunRPC was CORBA{% cite corba --file rpc %} which was followed by Java RMI{% cite rmipaper --file rpc %}. -RPC, despite being an initial success, wasn't without flaws. Within a year of its inception, the limitation of the RPC started to catch up with it. RFC 684 criticized RPC for latency, failures, and the cost. It also focussed on message-passing systems as an alternative to RPC design. Similarly, a few years down the road, in 1988, Tenenbaum et. al presented similar concerns against RPC {%cite critiqueofrpc --file rpc %}. It talked about problems heterogeneous devices, message passing as an alternative, packet loss, network failure, RPC's synchronous nature, and highlighted that RPC is not a one-size-fits-all model. +However, the initial implementations of these systems were riddled with various issues and design flaws. For instance, Java RMI didn't handle network failures and assumed a reliable network with zero-latency{% cite rmipaper --file rpc %}. -### The Rise, Again: Long Live RPC(Early 1990's - Today) +### The Rise, Again: Long Live RPC(Late 1990's - Today) -Despite facing problems in its early days, RPC withstood the test of time. Researchers realized the limitations of RPC and focussed on rectifying and instead of enforcing RPC, they started to use RPC in applications where it was needed. The designer started adding exception-handling, async, network failure handling and heterogenity between different languages/devices to RPC. +Despite facing problems in its early days, RPC withstood the test of time. Researchers realized the limitations of RPC and focussed on rectifying and instead of enforcing RPC, they started to use RPC in applications where it was needed. The designer started adding exception-handling, async, network failure handling and heterogeneity between different languages/devices to RPC. -Perhaps, the earliest system in this era was SunRPC {% cite sunnfs --file rpc %} used for the Sun Network File System(NFS). This SunRPC has gone under various additions and is now referred to as Open Network Computing RPC(ONC RPC). +In this era, SunRPC went through various additions and became came to be known as Open Network Computing RPC(ONC RPC). CORBA and RMI have also undergone various modifications as internet standards were set. -Soon to follow SunRPC was the language-agnostic CORBA{% cite corba --file rpc %} which was followed by Java RMI{% cite rmipaper --file rpc %}. CORBA and RMI have also undergone various modifications as internet standards were set and TCP/IP became the norm. +A new breed of RPC also started in this era, Async(asynchronous) RPC, giving rise to systems that use *futures* and *promises*, like Finagle{% cite finagle --file rpc %} and Cap'n Proto(post-2010). -A new breed of RPC also started in this era(early 2000's), Async RPC, giving rise to systems that use *futures* and *promises*, like Finagle{% cite finagle --file rpc %} and Cap'n Proto(post-2010). -In the post-2000 era, MAUI{% cite maui --file rpc %}, Cap'n Proto{% cite capnproto --file rpc %}, gRPC{% cite grpc --file rpc %}, Thrift{% cite thrift --file rpc %} and Finagle{% cite finagle --file rpc %} have been released, which have significantly boosted the widespread use of RPC. A level overview of some of the most important RPC implementation is as follows. +

+[ Image Source: {% cite norman --file rpc %}] +

+
+ RPC in 10 Steps. +

Fig2. - Synchronous RPC.

+
+ + +

+[ Image Source: {% cite norman --file rpc %}] +

+
+ RPC in 10 Steps. +

Fig3. - Asynchronous RPC.

+
+ + +A traditional, synchronous RPC is a *blocking* operation while an asynchronous RPC is a *non-blocking* operation{%cite dewan --file rpc %}. Fig2. shows a synchronous RPC call while Fig3. shows an asynchronous RPC call. In synchronous RPC, the client sends a request to the server and blocks and waits for the server to perform its computation and return the result. Only after getting the result from the server, the client proceeds onwards. In an asynchronous RPC, the client performs a request to the server and waits only for the acknowledgment of the delivery of input parameters/arguments. After this, the client proceeds onwards and when the server is finished processing, it sends an interrupt to the client. The client receives this message from the server, receives the results, and continues. + +Asynchronous RPC makes it possible to separate the remote call from the return value making it possible to write a single-threaded client to handle multiple RPC calls at the specific intervals it needs to process{%cite async --file rpc%}. It also allows easier handling of slow clients/servers as well as transferring large data easily(due to their incremental nature){%cite async --file rpc%}. + +In the post-2000 era, MAUI{% cite maui --file rpc %}, Cap'n Proto{% cite capnprotosecure --file rpc %}, gRPC{% cite grpc --file rpc %}, Thrift{% cite thrift --file rpc %} and Finagle{% cite finagle --file rpc %} have been released, which have significantly boosted the widespread use of RPC. + +Most of these newer systems came up with their Interface Description Languages(IDLs). These IDLs specified the common protocols and interfacing language that could be used to transfer information clients and servers written in different programming languages, making these RPC implementations language-agnostic. Some of the most common IDLs are JSON, XML, and ProtoBufs. + +A high-level overview of some of the most important RPC implementation is as follows. #### Java Remote Method Invocation -Java RMI (Java Remote Method Invocation){% cite rmibook --file rpc %} is a Java implementation for performing RPC (Remote Procedure Calls) between a client and a server. The client using a stub passes via a socket connection the information over the network to the server. The Remote Object Registry (ROR){% cite rmipaper --file rpc %} on the server contains the references to objects that can be accessed remotely and through which the client will connect to. The client then can request of the invocation of methods on the server for processing the requested call and then responds with the answer. RMI provides some security by being encoded but not encrypted, though that can be augmented by tunneling over a secure connection or other methods. +Java RMI (Java Remote Method Invocation){% cite rmibook --file rpc %} is a Java implementation for performing RPC (Remote Procedure Calls) between a client and a server. The client using a stub passes via a socket connection the information over the network to the server that contains remote objects. The Remote Object Registry (ROR){% cite rmipaper --file rpc %} on the server contains the references to objects that can be accessed remotely and through which the client will connect to. The client then can request the invocation of methods on the server for processing the requested call and then responds with the answer. + +RMI provides some security by being encoded but not encrypted, though that can be augmented by tunneling over a secure connection or other methods. Moreover, RMI is very specific to Java. It cannot be used to take advantage of the language-independence feature that is inherent to most RPC implementations. Perhaps the main problem with RMI is that it doesn't provide *access transparency*. This means that a programmer(not the client program) cannot distinguish between the local objects or the remote objects making it relatively difficult handle partial failures in the network{%cite roi --file rpc %}. #### CORBA -CORBA (Common Object Request Broker Architecture){% cite corba --file rpc %} was created by the Object Management Group {% cite corbasite --file rpc %} to allow for language-agnostic communication among multiple computers. It is an object-oriented model defined via an Interface Definition Language (IDL) and the communication is managed through an Object Request Broker (ORB). Each client and server have an ORB by which they communicate. The benefits of CORBA is that it allows for multi-language implementations that can communicate with each other, but much of the criticism around CORBA relates to poor consistency among implementations. +CORBA (Common Object Request Broker Architecture){% cite corba --file rpc %} was created by the Object Management Group {% cite corbasite --file rpc %} to allow for language-agnostic communication among multiple computers. It is an object-oriented model defined via an Interface Definition Language (IDL) and the communication is managed through an Object Request Broker (ORB). This ORB acts as a broker for objects. CORBA can be viewed as a language-independent RMI system where each client and server have an ORB by which they communicate. The benefits of CORBA is that it allows for multi-language implementations that can communicate with each other, but much of the criticism around CORBA relates to poor consistency among implementations and it's relatively outdated by now. Moreover, CORBA suffers from same access transparency issues as Java RMI. #### XML-RPC and SOAP -SOAP (Simple Object Access Protocol) is a successor of XML-RPC as a web-services protocol for communicating between a client and server. It was initially designed by a group at Microsoft {% cite soaparticle1 --file rpc %}. The SOAP message is an XML-formatted message composed of an envelope inside which a header and a body are provided. The body of the message contains the request and response of the message, which is transmitted over HTTP or SMTP. The benefit of such a protocol is that it provides the flexibility for transmission over multiple transport protocol, though parsing such messages could become a bottleneck. +The XML-RPC specifications {% cite Wiener --file rpc%} performs an HTTP Post request to a server formatted as XML composed of a *header* and *payload* that calls only one method. It was originally released in the late 1990's and unlike RMI, it provides transparency by using HTTP as a transparent mechanism. + +The header has to provide the basic information, like user agent and the size of the payload. The payload has to initiate a `methodCall` structure by specifying the name via `methodName` and associated parameter values. Parameters for the method can be scalar, structures or (recursive) arrays. The types of scalar can be one of `i4`, `int`, `boolean`, `string`, `double`, `dateTime.iso8601` or `base64`. The scalars are used to create more complex structures and arrays. + +Below is an example as provided by the XML-RPC documentation{% cite Wiener --file rpc%}: + +```XML + +POST /RPC2 HTTP/1.0 +User-Agent: Frontier/5.1.2 (WinNT) +Host: betty.userland.com +Content-Type: text/xml +Content-length: 181 + + + + examples.getStateName + + + 41 + + + +``` + +The response to a request will have the `methodResponse` with `params` and values, or a `fault` with the associated `faultCode` in case of an error {% cite Wiener --file rpc %}: + +```XML +HTTP/1.1 200 OK +Connection: close +Content-Length: 158 +Content-Type: text/xml +Date: Fri, 17 Jul 1998 19:55:08 GMT +Server: UserLand Frontier/5.1.2-WinNT + + + + + + South Dakota + + + +``` + +SOAP (Simple Object Access Protocol) is a successor of XML-RPC as a web-services protocol for communicating between a client and server. It was initially designed by a group at Microsoft {% cite soaparticle1 --file rpc %}. The SOAP message is an XML-formatted message composed of an envelope inside which a header and a payload are provided(just like XML-RPC). The payload of the message contains the request and response of the message, which is transmitted over HTTP or SMTP(unlike XML-RPC). + +SOAP can be viewed as the superset of XML-RPC that provides support for more complex authentication schemes{%cite soapvsxml --file rpc %} as well as its support for WSDL(Web Services Description Language), allowing easier discovery and integration with remote web services{%cite soapvsxml --file rpc %}. + +The benefit of SOAP is that it provides the flexibility for transmission over multiple transport protocol. The XML-based messages allow SOAP to become language agnostic, though parsing such messages could become a bottleneck. #### Thrift -Thrift is an RPC system created by Facebook and now part of the Apache Foundation {% cite thrift --file rpc %}. It is a language-agnostic IDL by which one generates the code for the client and server. It provides the opportunity for compressed serialization by customizing the protocol and the transport after the description file has been processed. +Thrift is an *asynchronous* RPC system created by Facebook and now part of the Apache Foundation {% cite thrift --file rpc %}. It is a language-agnostic Interface Description Language(IDL) by which one generates the code for the client and server. It provides the opportunity for compressed serialization by customizing the protocol and the transport after the description file has been processed. + +Perhaps, the biggest advantage of Thrift is that its binary data format has a very low overhead. It has a relatively lower transmission cost(as compared to other alternatives like SOAP){%cite thrifttut --file rpc %} making it very efficient for large amounts of data transfer. #### Finagle -Finagle was generated by Twitter and is an RPC system written in Scala and can run on a JVM. It is based on three object types: Service objects, Filter objects and Future objects {% cite finagle --file rpc %}. The Future objects act by asynchronously being requested for a computation that would return a response at some time in the future. The Service objects are an endpoint that will return a Future upon processing a request. A Filter object transforms requests for further processing in case additional customization is required from a request. +Finagle is a fault-tolerant, protocol-agnostic runtime for doing RPC and high-level API for composing futures(see Async RPC section), with RPC calls generated under the hood. It was created by Twitter and is written in Scala to run on a JVM. It is based on three object types: Service objects, Filter objects and Future objects {% cite finagle --file rpc %}. + +The Future objects act by asynchronously being requested for a computation that would return a response at some time in the future. These Future objects are the main communication mechanism in Finagle. All the inputs and the output are represented as Future objects. + +The Service objects are an endpoint that will return a Future upon processing a request. These Service objects can be viewed as the interfaces used to implement a client or a server. + +A sample Finagle Server that reads a request and returns the version of the request is shown below. This example is taken from Finagle documentation{% cite finagletut --file rpc %} + +```Scala +import com.twitter.finagle.{Http, Service} +import com.twitter.finagle.http +import com.twitter.util.{Await, Future} + +object Server extends App { + val service = new Service[http.Request, http.Response] { + def apply(req: http.Request): Future[http.Response] = + Future.value( + http.Response(req.version, http.Status.Ok) + ) + } + val server = Http.serve(":8080", service) + Await.ready(server) +} +``` + +A Filter object transforms requests for further processing in case additional customization is required from a request. These provide program-independent operations like, timeouts, etc. They take in a Service and provide a new Service object with the applied Filter. Aggregating multiple Filters is alos possible in Finagle. + +A sample timeout Filter that takes in a service and creates a new service with timeouts is shown below. This example is taken from Finagle documentation{% cite finagletut --file rpc %} + +```Scala +import com.twitter.finagle.{Service, SimpleFilter} +import com.twitter.util.{Duration, Future, Timer} + +class TimeoutFilter[Req, Rep](timeout: Duration, timer: Timer) + extends SimpleFilter[Req, Rep] { + + def apply(request: Req, service: Service[Req, Rep]): Future[Rep] = { + val res = service(request) + res.within(timer, timeout) + } +} +``` #### Open Network Computing RPC(ONC RPC) -ONC was originally introduced as SunRPC {%cite sunrpc --file rpc %} for the Sun NFS. The Sun NFS system had a stateless server, with client-side caching, unique file-handlers, and supported NFS read, write, truncate, unlink, etc operations. However, SunRPC was later revised as ONC in 1995 {%cite rfc1831 --file rpc %} and then in 2009 {%cite rfc5531 --file rpc %}. The IDL used in ONC(and SunRPC) is External Data Representation (XDR), a serialization mechanism specific to networks communication and therefore, ONC is limited to applications like Network File Systems. +ONC was originally introduced as SunRPC {%cite sunnfs --file rpc %} for the Sun NFS. The Sun NFS system had a stateless server, with client-side caching, unique file handlers, and supported NFS read, write, truncate, unlink, etc operations. However, SunRPC was later revised as ONC in 1995 {%cite rfc1831 --file rpc %} and then in 2009 {%cite rfc5531 --file rpc %}. The IDL used in ONC(and SunRPC) is External Data Representation (XDR), a serialization mechanism specific to networks communication and therefore, ONC is limited to applications like Network File Systems. #### Mobile Assistance Using Infrastructure(MAUI) The MAUI project {% cite maui --file rpc %}, developed by Microsoft is a computation offloading system for mobile systems. It's an automated system that offloads a mobile code to a dedicated infrastructure in order to increase the battery life of the mobile, minimize the load on the programmer and perform complex computations offsite. MAUI uses RPC as the communication protocol between the mobile and the infrastructure. #### gRPC -gRPC has been built as a collaboration between Google and Square as a public replacement of Stubby, ARCWire, and Sake {% cite Apigee --file rpc %}. The IDL for gRPC is Protocol Buffers(also referred as ProtoBuf). +gRPC is a multiplexed, bi-directional streaming RPC protocol developed Google and Square. The IDL for gRPC is Protocol Buffers(also referred as ProtoBuf) and is meant as a public replacement of Stubby, ARCWire, and Sake {% cite Apigee --file rpc %}. More details on Protocol Buffers, Stubby, ARCWire, and Sake are available in our gRPC chapter{% cite grpcchapter --file rpc %}. + +gRPC provides a platform for scalable, bi-directional streaming using both synchronized and asynchronous communication. + +In a general RPC mechanism, the client initiates a connection to the server and only the client can *request* while the server can only *respond* to the incoming requests. However, in bi-directional gRPC streams, although the initial connection is initiated by the client(call it *endpoint 1*), once the connection is established, both the server(call it *endpoint 2*) and the *endpoint 1* can send *requests* and receive *responses*. This significantly eases the development where both *endpoints* are communicating with each other(like, grid computing). It also saves the hassle of creating two separate connections between the endpoints (one from *endpoint 1* to *endpoint 2* and another from *endpoint 2* to *endpoint 1*) since both streams are independent. -gRPC provides a platform for scalable, bi-directional streaming using both synchronized and asynchronous communication. It multiplexes the requests over a single connection using header compression. This makes it possible for gRPC to be used for mobile clients where battery life and data usage are important. +It multiplexes the requests over a single connection using header compression. This makes it possible for gRPC to be used for mobile clients where battery life and data usage are important. The core library is in C -- except for Java and GO -- and surface APIs are implemented for all the other languages connecting through it{% cite CoreSurfaceAPIs --file rpc %}. Since Protocol Buffers has been utilized by many individuals and companies, gRPC makes it natural to extend their RPC ecosystems via gRPC. Companies like Cisco, Juniper and Netflix {% cite gRPCCompanies --file rpc %} have found it practical to adopt it. A majority of the Google Public APIs, like their places and maps APIs, have been ported to gRPC ProtoBuf {% cite gRPCProtos --file rpc %} as well. +More details about gRPC and bi-directional streaming can be found in our gRPC chapter {% cite grpcchapter --file rpc %} + #### Cap'n Proto -CapnProto{% cite capnproto --file rpc %} is a data interchange RPC system between that bypasses data-encoding step(like JSON or ProtoBuf) to significantly improve the performance. It's developed by the original author of gRPC's ProtoBuf, but since it uses bytes(binary data) for encoding/decoding, it outperforms gRPC's ProtoBuf. It uses futures and promises to combine various remote operations into a single to save the transportation round-trips. +CapnProto{% cite capnprotosecure --file rpc %} is a data interchange RPC system that bypasses data-encoding step(like JSON or ProtoBuf) to significantly improve the performance. It's developed by the original author of gRPC's ProtoBuf, but since it uses bytes(binary data) for encoding/decoding, it outperforms gRPC's ProtoBuf. It uses futures and promises to combine various remote operations into a single operation to save the transportation round-trips. This means if an client calls a function `foo` and then calls another function `bar` on the output of `foo`, Cap'n Proto will aggregate these two operations into a single `bar(foo(x))` where `x` is the input to the function `foo` {% cite capnprotosecure --file rpc %}. This saves multiple roundtrips, especially in object-oriented programs. ### The Heir to the Throne: gRPC or Thrift -Although there are many candidates to be considered as top contenders for RPC throne, most of these are targeted for a specific type of application. ONC is generally specific to the Network File System(though it's being pushed as a standard), Cap'n Proto is relatively new and untested, MAUI is specific to mobile systems, the open-source Finagle is primarily being used at Twitter(not widespread), and the Java RMI simply doesn't even close anyways(sorry to burst your bubble Java fans). +Although there are many candidates to be considered as top contenders for RPC throne, most of these are targeted for a specific type of application. ONC is generally specific to the Network File System(though it's being pushed as a standard), Cap'n Proto is relatively new and untested, MAUI is specific to mobile systems, the open-source Finagle is primarily being used at Twitter(not widespread), and the Java RMI simply doesn't even come close due to its transparency issues(sorry to burst your bubble Java fans). -Probably, the most powerful, and practical systems out there are Apache Thrift and Google's gRPC, primarily because *variants* of these two systems have been developed and used by Facebook and Google, respectively. This might be considered as biased view against other RPC implementations, however, when one considers Big Data and Internet-scale, only these two companies (and these two systems) come close. +Probably, the most powerful, and practical systems out there are Apache Thrift and Google's gRPC, primarily because these two systems cater to a large number of programming languages, have a significant performance benefit over other techniques and are being actively developed. Thrift was actually released a few years ago, while the first stable release for gRPC came out in August 2016. However, despite being 'out there', Thrift is currently less popular than gRPC {%cite trendrpcthrift --file rpc %}. @@ -140,13 +278,13 @@ gRPC {% cite gRPCLanguages --file rpc %} and Thrift, both, support most of the p The gRPC core is written in C(with the exception of Java and Go) and wrappers are written in other languages to communicate with the core, while the Thrift core is written in C++. -gRPC also provides easier bi-drectional streaming communicaiton between the caller and callee. The client generally initiates the communication {% cite gRPCLanguages --file rpc %} and once the connection is established the client and the server can perform reads and writes independently of each other. However, bi-directional streaming in Thrift might be a little difficult to handle, since it focuses explicitly on a client-server model. To enable bi-directionaly, async streaming, one may have to run two seperate systems {%cite grpcbetter --file rpc%}. +gRPC also provides easier bidrectional streaming communication between the caller and callee. The client generally initiates the communication {% cite gRPCLanguages --file rpc %} and once the connection is established the client and the server can perform reads and writes independently of each other. However, bi-directional streaming in Thrift might be a little difficult to handle, since it focuses explicitly on a client-server model. To enable bidirectional, async streaming, one may have to run two separate systems {%cite grpcbetter --file rpc%}. -Thrift provides exception-handling as a message while the programmer has to handle exceptions in gRPC. In Thrift, exceptions can be returned built into the message, while in gRPC, the programmer explicitly defines this behaviour. This Thrift exception-handling makes it easier to write client-side applications. +Thrift provides exception-handling as a message while the programmer has to handle exceptions in gRPC. In Thrift, exceptions can be returned built into the message, while in gRPC, the programmer explicitly defines this behavior. This Thrift exception-handling makes it easier to write client-side applications. Although custom authentication mechanisms can be implemented in both these system, gRPC come with a Google-backed authentication using SSL/TLS and Google Tokens {% cite grpcauth --file rpc %}. -Moreover, gRPC-based network communication is done using HTTP/2. HTTP/2 makes it feasible for communicating parties to multiplex network connections using the same port. This is more efficient(in terms of memory usage) as compared to HTTP/1.1. Since, gRPC communication is done HTTP/2, it means that gRPC can easily multiplex different services. As for Thrift, multiplexing services is possible, however, due to lack of support from underlying transport protocol, it is performed using a `TMulitplexingProcessor` class {% cite multiplexingthrift --file rpc %}. +Moreover, gRPC-based network communication is done using HTTP/2. HTTP/2 makes it feasible for communicating parties to multiplex network connections using the same port. This is more efficient(in terms of memory usage) as compared to HTTP/1.1. Since gRPC communication is done HTTP/2, it means that gRPC can easily multiplex different services. As for Thrift, multiplexing services is possible, however, due to lack of support from underlying transport protocol, it is performed using a `TMulitplexingProcessor` class(in code) {% cite multiplexingthrift --file rpc %}. However, both gRPC and Thrift allow async RPC calls. This means that a client can send a request to the server and continue with its execution and the response from the server is processed it arrives. @@ -162,11 +300,11 @@ The major comparison between gRPC and Thrift can be summed in this table. | Exceptions | Allows being built in the message | Implemented by the programmer | | Authentication | Custom | Custom + Google Tokens | | Bi-Directionality | Not straightforward | Straightforward | -| Multiplexing | Possible via | Possible via HTTP/2 | +| Multiplexing | Possible via `TMulitplexingProcessor` class | Possible via HTTP/2 | -Although, it's difficult to specifically choose one over the other, however, with increasing popularity of gRPC, and the fact that it's still in early stages of development, the general trend{%cite trendrpcthrift --file rpc %} over the past year has started to shift in favor of gRPC and it's giving Thrift a run for its money. +Although, it's difficult to specifically choose one over the other, however, with increasing popularity of gRPC, and the fact that it's still in early stages of development, the general trend{%cite trendrpcthrift --file rpc %} over the past year has started to shift in favor of gRPC and it's giving Thrift a run for its money. Although, it may not be considered as a metric, but the gRPC was searched, on average, three times more as compared to Thrift{%cite trendrpcthrift --file rpc %}. -**Note:** This study is performed in December 2016 so the results are expected to change with time. +**Note:** This comparison is performed in December 2016 so the results are expected to change with time. ## Applications: @@ -174,13 +312,15 @@ Since its inception, various papers have been published in applying RPC paradigm #### Shared State and Persistence Layer -One major limitation (and the advantage) of RPC is considered the separate *address space* of all the machines in the network. This means that *pointers* or *references* to a data object cannot be passed between the caller and the callee. Therefore, Interweave {% cite interweave2 interweave1 interweave3 --file rpc %} is a middleware system that allows scalable sharing of arbitrary data-types and language-independent processes running heterogeneous hardware. Interweave is specifically designed and is compatible with RPC-based systems and allows easier access to the shared resources between different applications. It even allows passing C *pointers* between the caller and the callee. +One major limitation(and the advantage) of RPC is considered the separate *address space* of all the machines in the network. This means that *pointers* or *references* to a data object cannot be passed between the caller and the callee. Therefore, Interweave {% cite interweave2 interweave1 interweave3 --file rpc %} is a *middleware* system that allows scalable sharing of arbitrary datatypes and language-independent processes running on heterogeneous hardware. Interweave is specifically designed and is compatible with RPC-based systems and allows easier access to the shared resources between different applications using memory blocks and locks. + +Although research has been done in order to ensure a global shared state for an RPC-based system, However, these systems tend to take away the sense of independence and modularity between the *caller* and the *callee* by using a shared storage instead of a separate *address space*. #### GridRPC Grid computing is one of the most widely used applications of RPC paradigm. At a high level, it can be seen as a mesh (or a network) of computers connected with each other to for *grid* such each system can leverage resources from any other system in the network. -In the GridRPC paradigm, each computer in the network can act as the *caller* or the *callee* depending on the amount of resources required {% cite grid1 --file rpc %}. It's also possible for the same computer to act as the *caller* as well as the *callee* for *different* computations. +In the GridRPC paradigm, each computer in the network can act as the *caller* or the *callee* depending on the amount of resources required {% cite grid1 --file rpc %}. It's also possible for the same computer to act as the *caller* as well as the *callee* for *different* computations. Some of the most popular implementations that allow one to have GridRPC-compliant middleware are GridSolve{% cite gridsolve1 gridsolve2 --file rpc %} and Ninf-G{% cite ninf --file rpc %}. Ninf is relatively older than GridSolve and was first published in the late 1990's. It's a simple RPC layer that also provides authentication and secure communication between the two parties. GridSolve, on the other hand, is relatively complex and provides a middleware for the communications using a client-agent-server model. @@ -188,49 +328,51 @@ Some of the most popular implementations that allow one to have GridRPC-complian Mobile systems have become very powerful these days. With multi-core processors and gigabytes of RAM, they can undertake relatively complex computations without a hassle. Due to this advancement, they consume a larger amount of energy and hence, their batteries, despite becoming larger, drain quickly with usage. Moreover, mobile data (network bandwidth) is still limited and expensive. Due to these requirements, it's better to offload mobile computations from mobile systems when possible. RPC plays an important role in the communication for this *computation offloading*. Some of these services use Grid RPC technologies to offload this computation. Whereas, other technologies use an RMI(Remote Method Invocation) system for this. -The Ibis Project {% cite ibis --file rpc %} builds an RMI and GMI (Group Method Invocation) model to facilitate outsourcing computation. Cuckoo {% cite cuckoo --file rpc %} uses this Ibis communication middleware to offload computation. +The Ibis Project {% cite ibis --file rpc %} builds an RMI(similar to JavaRMI) and GMI (Group Method Invocation) model to facilitate outsourcing computation. Cuckoo {% cite cuckoo --file rpc %} uses this Ibis communication middleware to offload computation from applications(built using Cuckoo) running on Android smartphones to remote Cuckoo servers. The Microsoft's MAUI Project {% cite maui --file rpc %} uses RPC communication and allows partitioning of .NET applications and "fine-grained code offload to maximize energy savings with minimal burden on the programmer". MAUI decides the methods to offload to the external MAUI server at runtime. #### Async RPC, Futures and Promises -Remote Procedure Calls can be asynchronous. Not only that but these async RPCs play in integral role in the *futures* and *promises*. *Future* and *promises* are programming constructs that where a *future* is seen as variable/data/return type/error while a *promise* is seen as a *future* that doesn't have a value, yet. We follow Finagle's {% cite finagle --file rpc %} definition of *futures* and *promises*, where the *promise* of a *future*(an empty *future*) is considered as a *request* while the async fulfillment of this *promise* by a *future* is seen as the *response*. This construct is primarily used for concurrent programming. +Remote Procedure Calls can be asynchronous. Not only that but these async RPCs play in integral role in the *futures* and *promises*. *Future* and *promises* are programming constructs that where a *future* is seen as variable/data/return type/error while a *promise* is seen as a *future* that doesn't have a value, yet. We follow Finagle's {% cite finagle --file rpc %} definition of *futures* and *promises*, where the *promise* of a *future*(an empty *future*) is considered as a *request* while the async fulfillment of this *promise* by a *future* is seen as the *response*. This construct is primarily used for asynchronous programming. -Perhaps the most renowned systems using this type of RPC model are Twitter's Finagle{% cite finagle --file rpc %} and Cap'n Proto{% cite capnproto --file rpc %}. +Perhaps the most renowned systems using this type of RPC model are Twitter's Finagle{% cite finagle --file rpc %} and Cap'n Proto{% cite capnprotosecure --file rpc %}. #### RPC in Microservices Ecosystem: -RPC implementations have moved from a one-server model to multiple servers and on to dynamically-created, load-balanced microservices. RPC started as a separate implementations of REST, Streaming RPC, MAUI, gRPC, Cap'n Proto, and has now made it possible for integration of all these implementations as a single abstraction as a user *endpoint* service. The endpoints are the building blocks of *microservices*. These *microservices* interact with each other and applications and combine to give the feel of one large monolithic service. +RPC implementations have moved from a one-server model to multiple servers and on to dynamically-created, load-balanced microservices. RPC started as separate implementations of REST, Streaming RPC, MAUI, gRPC, Cap'n Proto, and has now made it possible for integration of all these implementations as a single abstraction as a user *endpoint*. The endpoints are the building blocks of *microservices*. A *microservice* is usually *service* with a very simple, well-defined purpose, written in almost any language that interacts with other microservices to give the feel of one large monolithic *service*. These microservices are language-agnostic. One *microservice* for airline tickets written in C/C\++, might be communicating with a number of other microservices for individual airlines written in different languages(Python, C\++, Java, Node.js) using a language-agnostic, asynchronous, RPC framework like gRPC{%cite grpc --file rpc %} or Thrift{%cite thrift --file rpc %}. The use of RPC has allowed us to create new microservices on-the-fly. The microservices can not only created and bootstrapped at runtime but also have inherent features like load-balancing and failure-recovery. This bootstrapping might occur on the same machine, adding to a Docker container {% cite docker --file rpc %}, or across a network (using any combination of DNS, NATs or other mechanisms). -RPC can be defined as the "glue" that holds all the microservices together{% cite microservices1rpc --file rpc %}. This means that RPC is one of the primary communication mechanism between different microservices running on different systems. A microservice requests another microservice to perform an operation/query. The other microservice, upon receiving such request, performs an operation and returns a response. This operation could vary from a simple computation to invoking another microservice creating a series of RPC events to creating new microservices on the fly to dynamically load balance the microservices system. +RPC can be defined as the "glue" that holds all the microservices together{% cite microservices1rpc --file rpc %}. This means that RPC is one of the primary communication mechanism between different microservices running on different systems. A microservice requests another microservice to perform an operation/query. The other microservice, upon receiving such request, performs an operation and returns a response. This operation could vary from a simple computation to invoking another microservice creating a series of RPC events to creating new microservices on the fly to dynamically load balance the microservices system. These microservices are language-agnostic. One *microservice* could be written in C/C++, another one could be in different languages(Python, C++, Java, Node.js) and they all might be communicating with each other using a language-agnostic, asynchronous, performant RPC framework like gRPC{%cite grpc --file rpc %} or Thrift{%cite thrift --file rpc %}. -An example of a microservices ecosystem that uses futures/promises is Finagle at Twitter. +An example of a microservices ecosystem that uses futures/promises is Finagle{%cite finagle --file rpc %} at Twitter. ## Security in RPC: -The initial RPC implementation {% cite implementingrpc --file rpc %} was developed for an isolated LAN network and didn't focus much on security. There're various attack surfaces in that model, from the malicious registry, to a malicious server, to a client targeting for Denial-of-Service to Man-in-the-Middle attack between client and server. +The initial RPC implementation {% cite implementingrpc --file rpc %} was developed for an isolated LAN network and didn't focus much on security. There're various attack surfaces in that model, from the malicious registry to a malicious server, to a client targeting for Denial-of-Service to Man-in-the-Middle attack between client and server. -As time progressed and internet evolved, new standards came along, and RPC implementations became much more secure. Security, in RPC, is generally added as a *module* or a *package*. These modules have libraries for authentication and authorization of the communication services (caller and callee). These modules are not always bug-free and it's possible to gain unauthorized access to the system. Efforts are being made to rectify these situations by the security in general, using code inspection and bug bounty programs to catch these bugs before-hand. However, with time new bugs arise and this cycle continues. It's a vicious cycle between attackers and security experts, both of whom tries to outdo their opponent. +As time progressed and internet evolved, new standards came along, and RPC implementations became much more secure. Security, in RPC, is generally added as a *module* or a *package*. These modules have libraries for authentication and authorization of the communication services (caller and callee). These modules are not always bug-free and it's possible to gain unauthorized access to the system. Efforts are being made to rectify these situations by the security in general, using code inspection and bug bounty programs to catch these bugs beforehand. However, with time new bugs arise and this cycle continues. It's a vicious cycle between attackers and security experts, both of whom tries to outdo their opponent. For example, the Oracle Network File System uses a *Secure RPC*{% cite oraclenfs --file rpc %} to perform authentication in the NFS. This *Secure RPC* uses Diffie-Hellman authentication mechanism with DES encryption to allow only authorized users to access the NFS. Similarly, Cap'n Proto {% cite capnprotosecure --file rpc %} claims that it is resilient to memory leaks, segfaults, and malicious inputs and can be used between mutually untrusting parties. However, in Cap'n Proto "the RPC layer is not robust against resource exhaustion attacks, possibly allowing denials of service", nor has it undergone any formal verification {% cite capnprotosecure --file rpc %}. -Although, it's possible to come up with a *Threat Model* that would make an RPC implementation insecure to use, however, one has to understand that using any distributed system increases the attack surface anyways and claiming one *paradigm* to be more secure than another would be a biased statement, since *paradigms* are generally an idea and it depends on different system designers to use these *paradigms* to build their systems and take care of features specific to real systems, like security and load-balancing. There's always a possibility of rerouting a request to a malicious server(if the registry gets hacked), or there's no trust between the *caller* and *callee*. However, we maintain that RPC *paradigm* is not secure or insecure(for that matter), and that the most secure systems are the ones that are in an isolated environment, disconnected from the public internet with a self-destruct mechanism{% cite self --file rpc %} in place, in an impenetrable bunker, and guarded by the Knights Templar(*they don't exist! Well, maybe Fort Meade comes close*). +Although, it's possible to come up with a *Threat Model* that would make an RPC implementation insecure to use, however, one has to understand that using any distributed system increases the attack surface anyways and claiming one *paradigm* to be more secure than another would be a biased statement, since *paradigms* are generally an idea and it depends on different system designers to use these *paradigms* to build their systems and take care of features specific to real systems, like security and load-balancing. There's always a possibility of rerouting a request to a malicious server(if the registry gets hacked), or there's no trust between the *caller* and *callee*. However, we maintain that RPC *paradigm* is not secure or insecure(for that matter), and that the most secure systems are the ones that are in an isolated environment, disconnected from the public internet with a self-destruct mechanism{% cite selfdest --file rpc %} in place, in an impenetrable bunker, and guarded by the Knights Templar(*they don't exist! Well, maybe Fort Meade comes close*). ## Discussion: RPC *paradigm* shines the most in *request-response* mechanisms. Futures and Promises also appear to a new breed of RPC. This leads one to question, as to whether every *request-response* system is a modified implementation to of the RPC *paradigm*, or does it actually bring anything new to the table? These modern communication protocols, like HTTP and REST, might just be a different flavor of RPC. In HTTP, a client *requests* a web page(or some other content), the server then *responds* with the required content. The dynamics of this communication might be slightly different from your traditional RPC, however, an HTTP Stateless server adheres to most of the concepts behind RPC *paradigm*. Similarly, consider sending a request to your favorite Google API. Say, you want to translate your latitude/longitude to an address using their Reverse Geocoding API, or maybe want to find out a good restaurant in your vicinity using their Places API, you'll send a *request* to their server to perform a *procedure* that would take a few input arguments, like the coordinates, and return the result. Even though these APIs follow a RESTful design, it appears to be an extension to the RPC *paradigm*. -RPC paradigm has evolved over time. It has evolved to the extent that, currently, it's become very difficult differentiate RPC from non-RPC. For the past decades, researchers and industry leaders have tried to come up with *their* definition of RPC. The proponents of RPC paradigm view every *request-response* communication as an implementation the RPC paradigm while those against RPC try to explicitly come up with the bounds of RPC. RPC supporters consider it as the Holy Grail of distributed systems. They view it as the foundation of modern distributed communication. From Apache Thrift and ONC to HTTP and REST, they advocate it all as RPC while REST developers have strong opinions against RPC. +RPC paradigm has evolved over time. It has evolved to the extent that, currently, it's become very difficult differentiate RPC from non-RPC. With each passing year, the restrictions and limitations of RPC evolve. Current RPC implementations even have the support for the server to *request* information from the client to *respond* to these requests and vice versa (bidirectionality). This *bidirectional* nature of RPCs have transitioned RPC from simple *client-server* model to a set of *endpoints* communicating with each other. + +For the past four decades, researchers and industry leaders have tried to come up with *their* definition of RPC. The proponents of RPC paradigm view every *request-response* communication as an implementation the RPC paradigm while those against RPC try to explicitly enumerate the limitations of RPC. These limitations, however, seem to slowly vanish as new RPC models are introduced with time. RPC supporters consider it as the Holy Grail of distributed systems. They view it as the foundation of modern distributed communication. From Apache Thrift and ONC to HTTP and REST, they advocate it all as RPC while REST developers have strong opinions against RPC. Moreover, with modern global storage mechanisms, the need for RPC systems to have a separate *address space* seems to be slowly dissolving and disappearing into thin air. So, the question remains what *is* RPC and what * is not* RPC? This is an open-ended question. There is no unanimous agreement about what RPC should look like, except that it has communication between two *endpoints*. What we think of RPC is: -*"In the world of distributed systems, where every individual component of a system, be it a hard disk, a multi-core processor, or a microservice, is an extension of the RPC, it's difficult to come with a concrete definition of the RPC paradigm. Therefore, anything loosely associated with a request-response mechanism can be considered as RPC".* +*In the world of distributed systems, where every individual component of a system, be it a hard disk, a multi-core processor, or a microservice, is an extension of the RPC, it's difficult to come with a concrete definition of the RPC paradigm. Therefore, anything loosely associated with a request-response mechanism can be considered as RPC.*

-**RPC is not dead, long live RPC!** +**RPC is not dead, long live RPC!**

diff --git a/resources/img/rpc_chapter_1_asyncrpc.jpg b/resources/img/rpc_chapter_1_asyncrpc.jpg new file mode 100644 index 0000000..800c57b Binary files /dev/null and b/resources/img/rpc_chapter_1_asyncrpc.jpg differ diff --git a/resources/img/rpc_chapter_1_syncrpc.jpg b/resources/img/rpc_chapter_1_syncrpc.jpg new file mode 100644 index 0000000..b497fbb Binary files /dev/null and b/resources/img/rpc_chapter_1_syncrpc.jpg differ -- cgit v1.2.3 From b8d19388111b4dacecf4694a8db36f0721f26eb0 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Fri, 16 Dec 2016 17:06:03 -0500 Subject: Begin lasp, add citations --- _bibliography/langs-consistency.bib | 62 ++++++++++++++++++++++++------------- chapter/7/langs-consistency.md | 22 ++++++++----- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/_bibliography/langs-consistency.bib b/_bibliography/langs-consistency.bib index 416b697..2b1165f 100644 --- a/_bibliography/langs-consistency.bib +++ b/_bibliography/langs-consistency.bib @@ -1,26 +1,44 @@ -@inproceedings{Uniqueness, - author = {Philipp Haller and - Martin Odersky}, - title = {Capabilities for Uniqueness and Borrowing}, - booktitle = {ECOOP 2010, Maribor, Slovenia, June 21-25, 2010.}, - pages = {354--378}, - year = {2010}, +@article{BloomL, + author = {Neil Conway, William Marczak, Peter Alvaro, Joseph M. Hellerstein, David Maier}, + title = {Logic and Lattices for Distributed Programming}, + journal = {UC Berkeley Technical Report No. UCB/EECS-2012-167}, + volume = {167}, + year = {2012}, + url = {http://db.cs.berkeley.edu/papers/UCB-lattice-tr.pdf} } -@inproceedings{Elsman2005, - author = {Martin Elsman}, - title = {Type-specialized serialization with sharing}, - booktitle = {Trends in Functional Programming}, - year = {2005}, - pages = {47-62}, +@inproceedings{Bloom, + author = {Peter Alvaro, Neil Conway, Joseph M. Hellerstein, William R. Marczak}, + title = {Consistency Analysis in Bloom: a CALM and Collected Approach}, + booktitle = {Conference on Innovative Data Systems Research}, + series = {CIDR}, + year = {2011}, + url = {http://db.cs.berkeley.edu/papers/cidr11-bloom.pdf} } -@article{Kennedy2004, - author = {Andrew Kennedy}, - title = {Pickler combinators}, - journal = {J. Funct. Program.}, - volume = {14}, - number = {6}, - year = {2004}, - pages = {727-739}, -} \ No newline at end of file +@inproceedings{Lasp, + author = {Christopher Meiklejohn, Peter Van Roy}, + title = {Lasp: A Language for Distributed, Coordination-Free Programming}, + booktitle = {Proceedings of the 17th International Symposium on Principles and Practice of Declarative Programming} + series = {PPDP}, + year = {2015}, + url = {https://pdfs.semanticscholar.org/c0ad/b28526a29f0765669167d3201e2b3605895e.pdf} +} + +@inproceedings{Dynamo, + author = {Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter Vosshall, Werner Vogels}, + title = {Dynamo: Amazon’s Highly Available Key-value Store}, + booktitle = {ACM Symposium on Operating Systems Principles}, + series = {SOSP}, + year = {2007}, + url = {https://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf}, +} + +@inproceedings{ConsistencyWithoutBorders, + author = {Peter Alvaro, Peter Bailis, Neil Conway, Joseph M. Hellerstein}, + title = {Consistency Without Borders}, + booktitle = {ACM Symposium on Cloud Computing}, + series = {SOCC}, + year = {2013}, + url = {http://www.bailis.org/papers/consistency-socc2013.pdf}, +} diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index cd0a7e5..0b10c56 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -33,7 +33,7 @@ This is an important moment. By thinking about our specific problem, we've reali Turns out there's a company out there called Amazon.com - and they've been having a similar problem. Amazon sells things on their website too, and users can add and remove things from their cart. Amazon has lots of servers spread out across the world. They also have quite a few customers. They need to ensure their customers' carts are robust: if/when servers fail or lose communication with one another, a "best-effort" should be made to display the customer's cart. Amazon acknowledges that failure, latency, or HyperLoop-traveling users can cause inconsistent cart data, depending on which server you ask. How does Amazon resolve these issues? ## Dynamo -Amazon built DynamoDB, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. +Amazon built DynamoDB {% cite Dynamo --file langs-consistency %}, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts and push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. @@ -239,7 +239,7 @@ But where should these guarantees live? In the above Javascript example, the gua Databases such as PostgreSQL have issues like this as well, though they handle them quite differently, masters may need to ensure that write have occurred on every slave before the database becomes available for reading. A database system like this has pushed consistency concerns to the IO-level, completely out of the users control. They are enforced on system reads and system writes. This approach gives programmers no flexibility: as demonstrated with our shopping cart example, there's no need for these type of restrictions; we can tolerate inconsistency in order to maintain availability. -Why not push the consistency guarantees in between the IO-level and the application-level? Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. +Why not push the consistency guarantees in between the IO-level and the application-level? {% cite ConsistencyWithoutBorders --file langs-consistency %} { Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. Wouldn't it be great if tools like this existed? @@ -335,7 +335,7 @@ end After this block/callback is called, the system automatically flushes & routes messages as described above. -Bloom, a research language developed at UC Berkeley, has a similar programming model to the one described above. Execution is broken up into a series of "timesteps". In the above example, one "timestemp" would be the execution of one `on_five_second_interval` function. Bloom, like the theoretical system above, automatically flushes and populates the buffers before and after each timestep. In the above example, 5 seconds was an arbitrary amount of time. In Bloom, timesteps (rounds of evaluation) are logical tools - they may happen every second, 10 seconds, etc. Logically, it shouldn't affect how your program executes. In reality, Bud's timesteps correspond to evaluation iterations. Your code is evaluated, executed, and the process repeats. +Bloom {% cite Bloom --file langs-consistency %}, a research language developed at UC Berkeley, has a similar programming model to the one described above. Execution is broken up into a series of "timesteps". In the above example, one "timestemp" would be the execution of one `on_five_second_interval` function. Bloom, like the theoretical system above, automatically flushes and populates the buffers before and after each timestep. In the above example, 5 seconds was an arbitrary amount of time. In Bloom, timesteps (rounds of evaluation) are logical tools - they may happen every second, 10 seconds, etc. Logically, it shouldn't affect how your program executes. In reality, Bud's timesteps correspond to evaluation iterations. Your code is evaluated, executed, and the process repeats. So what does a Bloom program look like? Bloom's prototypal implementation is called Bud and is implemented in Ruby. There are two main parts to a Bloom program: 1. User defined buffers: rather than the four buffers I gave you above, Bloom users can define their own buffers. There are different types of buffers depending on the behavior you desire: @@ -497,7 +497,7 @@ These semilattices (and many more!) can be used to program other types of distri Unfortunately, Bloom does not provide support for other CRDTs. In fact, you cannot define your own datatypes at all. You are bound by the collections described. -BloomL, an addendum to the Bloom language, provides support for these types of data structures. Specifically, BloomL does two things: +BloomL{% cite BloomL --file langs-consistency %}, an addendum to the Bloom language, provides support for these types of data structures. Specifically, BloomL does two things: * Adds a number of built-in lattices such as `lmax` (`integerMax`), `lmin`, etc. * Adds an "interface" for lattices: the user can define lattices that "implement" this interface. @@ -539,8 +539,16 @@ Currently Bloom exists as a Ruby prototype: Bud. Hypothetically speaking, there' All in all, Bloom provides programmers with a new model for writing distributed programs. If the user desires monotonic data structures and operations, it's relatively easy to use and reason about. Rather than blindly destroying the properties of your system, you will know exactly when you introduce a possible point of order into your program. It's up to you to decide whether or not you need to introduce coordination. ### Lasp -[ Introduce Lasp ] -Instead of trying to do it all (and accepting danger), it tries to be embeddable (and truly restrictive.) +Lasp {% cite Lasp --file langs-consistency %}is an Erlang library which aims to facilitate this type of "disorderly" programming. + +Lasp provides access to myriad of CRDTs. It does not allows user-defined CRDTs (lattices), but the programmer can have confidence that the CRDTs obey the lattice formal requirements. + +A Simple Lasp Program is defined as either a: +* Single CRDT instance +* A "Lasp process" with *m* inputs, all Simple Lasp Programs, and one output CRDT instance + +For those of you unfamiliar with Erlang: a *process* can be thought of as an independent piece of code executing asynchronously. Processes can receive messages and send messages to other processes. Process can also subscribe (I think) to other processes' messages. + ### Utilization @@ -548,7 +556,7 @@ Lasp is an Erlang library, and for good reason. Remember the initial discussion PostgreSQL enforce very specific and restrictive IO-level consistency, and this was too much for our needs. But it's certainly not too much for *all* needs. There certainly are applications (take banking, for example) in which consistency is extremely important. You certainly are not allowed to double spend your money depending on how fast you can travel to a different server, so eventual consistency is not enough! All servers must coordinate. -There's a key principle here, however: distributed programming models that attempt to accomdate everything end up doing nothing well; models that accept compromises and formalize certain properties end up being extremely useful for a subset of domains. +There's a key principle here, however: distributed programming models that attempt to accomodate everything end up doing nothing well; models that accept compromises and formalize certain properties end up being extremely useful for a subset of domains. Most programming languages are "general-use". This works for single machine programming. As the world moves toward distributed programming, programmers must adopt models / languages / libraries that are built for their domain. It forces serious thought on the part of the programmer: what *exactly* am I trying to achieve, and what am I willing to sacrifice? -- cgit v1.2.3 From f0e4ab32d559e198cd439fc8b3fc80159f191019 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 19:59:01 -0500 Subject: added more details --- _bibliography/futures.bib | 16 +++++++-- chapter/2/futures.md | 91 +++++++++++++++++++++++++++++++++++++++++++---- chapter/2/images/p-1.svg | 4 +++ chapter/2/images/p-2.svg | 4 +++ 4 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 chapter/2/images/p-1.svg create mode 100644 chapter/2/images/p-2.svg diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index 08552e7..b8634c0 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -113,9 +113,8 @@ } @misc{7, - title={The JavaScript Event Loop: Explained}, - author={Erin Swenson-Healey}, - url = {http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/} + title={jQuery.Deferred()}, + url = {https://api.jquery.com/jquery.deferred/} } @misc{8, @@ -379,3 +378,14 @@ title={Lazy Futures or Promises?}, url = {https://groups.google.com/forum/#!topic/scala-language/dP2SyUCF724} } + +@misc{42, + title={Try}, + url = {http://www.scala-lang.org/api/2.9.3/scala/util/Try.html} +} + + +@misc{42, + title={Finagle: A Protocol-Agnostic RPC System}, + url = {https://blog.twitter.com/2011/finagle-a-protocol-agnostic-rpc-system} +} diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 5f8bd74..842da29 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -290,13 +290,21 @@ Here, we create a Promise, and complete it later. In between we stack up a set o # Promise Pipelining One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. + + +
+ timeline +
+
- timeline + timeline
Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. -The history of promise pipelining can be traced back to the call-streams in Argus and channels in Joule. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated futures into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. +The history of promise pipelining can be traced back to the call-streams in Argus. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated futures into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. + +Channels in Joule were a similar idea, providing a channel which connects an acceptor and a distributor. Joule was a direct ancestor to E language. Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable over Promises, and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. @@ -305,6 +313,8 @@ Modern promise specifications, like one in Javascript comes with methods which h In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. + + # Handling Errors In a synchronous programming model, the most logical way of handling errors is a try...catch block. @@ -339,8 +349,7 @@ try{ ``` -In javascript world, some patterns emerged, most noticeably the error-first callback style, also adopted by Node. Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises come to the rescue. - +In javascript world, some patterns emerged, most noticeably the error-first callback style ( which we've seen before, also adopted by Node). Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises come to the rescue. Although most of the earlier papers did not talk about error handling, the Promises paper by Liskov and Shrira did acknowledge the possibility of failure in a distributed environment. They talked about propagation of exceptions from the called procedure to the caller and also about call streams, and how broken streams could be handled. E language also talked about broken promises and setting a promise to the exception of broken references. @@ -356,6 +365,47 @@ f onComplete { } ``` +In Scala, the Try type represents a computation that may either result in an exception, or return a successfully computed value. For example, Try[Int] represents a computation which can either result in Int if it's successful, or return a Throwable if something is wrong. + +```scala + +val a: Int = 100 +val b: Int = 10 +def divide: Try[Int] = Try(a/b) + +divide match { + case Success(v) => + println(v) + case Failure(e) => + println(e) +} + +``` + +** This prints 10 , while ** + +```scala + +val a: Int = 100 +val b: Int = 0 +def divide: Try[Int] = Try(a/b) + +divide match { + case Success(v) => + println(v) + case Failure(e) => + println(e) +} + +``` + +** This prints java.lang.ArithmeticException: / by zero ** + +Try type can be pipelined, allowing for catching exceptions and recovering from them along the way. + + + + #### In Javascript ```javascript @@ -425,7 +475,34 @@ function check(data) { ## Twitter Finagle -Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes it easy to build robust clients and servers in Java, Scala, or any JVM-hosted language. It uses idea of Futures to encapsulate concurrent tasks and are analogous to threads, but even more lightweight. +Finagle is a protocol-agnostic, asynchronous RPC system for the JVM that makes it easy to build robust clients and servers in Java, Scala, or any JVM-hosted language. It uses Futures to encapsulate concurrent tasks. Finagle +introduces two other abstractions built on top of Futures to reason about distributed software : + +- ** Services ** are asynchronous functions which represent system boundaries. + +- ** Filters ** are application-independent blocks of logic like handling timeouts and authentication. + +In Finagle, operations describe what needs to be done, while the actual execution is left to be handled by the runtime. The runtime comes with a robust implementation of connection pooling, failure detection and recovery and load balancers. + +Example of a Service: + + +```scala + +val service = new Service[HttpRequest, HttpResponse] { + def apply(request: HttpRequest) = + Future(new DefaultHttpResponse(HTTP_1_1, OK)) +} + +``` +A timeout filter can be implemented as : + +```scala + +def timeoutFilter(d: Duration) = + { (req, service) => service(req).within(d) } + +``` ## Correctables @@ -435,6 +512,7 @@ Correctables were introduced by Rachid Guerraoui, Matej Pavlovic, and Dragos-Adr timeline
+ ## Folly Futures Folly is a library by Facebook for asynchronous C++ inspired by the implementation of Futures by Twitter for Scala. It builds upon the Futures in the C++11 Standard. Like Scala’s futures, they also allow for implementing a custom executor which provides different ways of running a Future (thread pool, event loop etc). @@ -442,6 +520,7 @@ Folly is a library by Facebook for asynchronous C++ inspired by the implementati ## NodeJS Fiber Fibers provide coroutine support for v8 and node. Applications can use Fibers to allow users to write code without using a ton of callbacks, without sacrificing the performance benefits of asynchronous IO. Think of fibers as light-weight threads for NodeJs where the scheduling is in the hands of the programmer. The node-fibers library doesn’t recommend using raw API and code together without any abstractions, and provides a Futures implementation which is ‘fiber-aware’. -## References + +# References {% bibliography --file futures %} diff --git a/chapter/2/images/p-1.svg b/chapter/2/images/p-1.svg new file mode 100644 index 0000000..87e180b --- /dev/null +++ b/chapter/2/images/p-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/chapter/2/images/p-2.svg b/chapter/2/images/p-2.svg new file mode 100644 index 0000000..f5c6b05 --- /dev/null +++ b/chapter/2/images/p-2.svg @@ -0,0 +1,4 @@ + + + + -- cgit v1.2.3 From 9583f55e47e787bda753f0b310d0fc48e3cfab06 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 21:04:49 -0500 Subject: added more details --- _bibliography/futures.bib | 12 +++++++++++- chapter/2/futures.md | 28 ++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index b8634c0..4a03a18 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -385,7 +385,17 @@ } -@misc{42, +@misc{43, title={Finagle: A Protocol-Agnostic RPC System}, url = {https://blog.twitter.com/2011/finagle-a-protocol-agnostic-rpc-system} } + +@misc{44, + title={Promise.all()}, + url = {https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all} +} + +@misc{45, + title={Promise Pipelining}, + url = {https://www.revolvy.com/main/index.php?s=Promise%20pipelining} +} diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 842da29..20eb75e 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -302,13 +302,29 @@ One of the criticism of traditional RPC systems would be that they’re blocking Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. -The history of promise pipelining can be traced back to the call-streams in Argus. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated futures into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. +The history of promise pipelining can be traced back to the call-streams in Argus. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated Promises into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. Channels in Joule were a similar idea, providing a channel which connects an acceptor and a distributor. Joule was a direct ancestor to E language. -Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable over Promises, and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. +Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. +```javascript + +var a = Promise.resolve(1); +var b = new Promise(function (resolve, reject) { + setTimeout(resolve, 100, 2); +}); + +Promise.all([p1, p2]).then(values => { + console.log(values); // [1,2] +}); + +Promise.race([p1, p2]).then(function(value) { + console.log(value); // 1 +}); + +``` In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. @@ -375,15 +391,13 @@ def divide: Try[Int] = Try(a/b) divide match { case Success(v) => - println(v) + println(v) // 10 case Failure(e) => println(e) } ``` -** This prints 10 , while ** - ```scala val a: Int = 100 @@ -394,13 +408,11 @@ divide match { case Success(v) => println(v) case Failure(e) => - println(e) + println(e) // java.lang.ArithmeticException: / by zero } ``` -** This prints java.lang.ArithmeticException: / by zero ** - Try type can be pipelined, allowing for catching exceptions and recovering from them along the way. -- cgit v1.2.3 From 92d4bc3799dc7b8ced25742485e232b6f14af3a1 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 22:55:58 -0500 Subject: added more details : final --- _bibliography/futures.bib | 11 +++++ chapter/2/futures.md | 107 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index 4a03a18..eec1c79 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -399,3 +399,14 @@ title={Promise Pipelining}, url = {https://www.revolvy.com/main/index.php?s=Promise%20pipelining} } + +@misc{46, + title={Promises}, + url = {https://christophermeiklejohn.com/pl/2016/03/04/promises.html} +} + +@misc{47, + title={Are JavaScript Promises swallowing your errors?}, + author={James K Nelson}, + url = {http://jamesknelson.com/are-es6-promises-swallowing-your-errors/} +} diff --git a/chapter/2/futures.md b/chapter/2/futures.md index 20eb75e..ff32d19 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -288,23 +288,38 @@ Here, we create a Promise, and complete it later. In between we stack up a set o # Promise Pipelining -One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises. +One of the criticism of traditional RPC systems would be that they’re blocking. Imagine a scenario where you need to call an API ‘a’ and another API ‘b’, then aggregate the results of both the calls and use that result as a parameter to another API ‘c’. Now, the logical way to go about doing this would be to call A and B in parallel, then once both finish, aggregate the result and call C. Unfortunately, in a blocking system, the way to go about is call a, wait for it to finish, call b, wait, then aggregate and call c. This seems like a waste of time, but in absence of asynchronicity, it is impossible. Even with asynchronicity, it gets a little difficult to manage or scale up the system linearly. Fortunately, we have promises.
- timeline + timeline
- timeline + timeline
Futures/Promises can be passed along, waited upon, or chained and joined together. These properties helps make life easier for the programmers working with them. This also reduces the latency associated with distributed computing. Promises enable dataflow concurrency, which is also deterministic, and easier to reason. The history of promise pipelining can be traced back to the call-streams in Argus. In Argus, Call streams are a mechanism for communication between distributed components. The communicating entities, a sender and a receiver are connected by a stream, and sender can make calls to receiver over it. Streams can be thought of as RPC, except that these allow callers to run in parallel with the receiver while processing the call. When making a call in Argus, the caller receives a promise for the result. In the paper on Promises by Liskov and Shrira, they mention that having integrated Promises into call streams, next logical step would be to talk about stream composition. This means arranging streams into pipelines where output of one stream can be used as input of the next stream. They talk about composing streams using fork and coenter. -Channels in Joule were a similar idea, providing a channel which connects an acceptor and a distributor. Joule was a direct ancestor to E language. +Channels in Joule were a similar idea, providing a channel which connects an acceptor and a distributor. Joule was a direct ancestor to E language, and talked about it in more detail. + +``` + +t3 := (x <- a()) <- c(y <- b()) + +t1 := x <- a() +t2 := y <- b() +t3 := t1 <- c(t2) + +``` + +Without pipelining in E, this call will require three round trips. First to send a() to x, then b() to y then finally c to the result t1 with t2 as an argument. But with pipelining, the later messages can be sent with promises as result of earlier messages as argument. This allowed sending all the messages together, thereby saving the costly round trips. This is assuming x and y are on the same remote machine, otherwise we can still evaluate t1 and t2 parallely. + + +Notice that this pipelining mechanism is different from asynchronous message passing, as in asynchronous message passing, even if t1 and t2 get evaluated in parallel, to resolve t3 we still wait for t1 and t2 to be resolved, and send it again in another call to the remote machine. Modern promise specifications, like one in Javascript comes with methods which help working with promise pipelining easier. In javascript, a Promises.all method is provided, which takes in an iterable and returns a new Promise which gets resolved when all the promises in the iterable get resolved. There’s also a race method, which returns a promise which is resolved when the first promise in the iterable gets resolved. @@ -326,14 +341,12 @@ Promise.race([p1, p2]).then(function(value) { ``` -In scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. - - +In Scala, futures have a onSuccess method which acts as a callback to when the future is complete. This callback itself can be used to sequentially chain futures together. But this results in bulkier code. Fortunately, Scala api comes with combinators which allow for easier combination of results from futures. Examples of combinators are map, flatmap, filter, withFilter. # Handling Errors -In a synchronous programming model, the most logical way of handling errors is a try...catch block. +If world would have run without errors we would rejoice in unison, but it is not the case in programming world as well. When you run a program you either receive an expected output or an error. Error can be defined as wrong output or an exception. In a synchronous programming model, the most logical way of handling errors is a try...catch block. ```javascript @@ -365,14 +378,25 @@ try{ ``` -In javascript world, some patterns emerged, most noticeably the error-first callback style ( which we've seen before, also adopted by Node). Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises come to the rescue. -Although most of the earlier papers did not talk about error handling, the Promises paper by Liskov and Shrira did acknowledge the possibility of failure in a distributed environment. They talked about propagation of exceptions from the called procedure to the caller and also about call streams, and how broken streams could be handled. E language also talked about broken promises and setting a promise to the exception of broken references. -In modern languages, Promises generally come with two callbacks. One to handle the success case and other to handle the failure. +Although most of the earlier papers did not talk about error handling, the Promises paper by Liskov and Shrira did acknowledge the possibility of failure in a distributed environment. To put this in Argus's perspective, the 'claim' operation waits until the promise is ready. Then it returns normally if the call terminated normally, and otherwise it signals the appropriate 'exception', e.g., +``` +y: real := pt$claim(x) + except when foo: ... + when unavailable(s: string): . + when failure(s: string): . . + end + +``` +Here x is a promise object of type pt; the form pi$claim illustrates the way Argus identifies an operation of a type by concatenating the type name with the operation name. When there are communication problems, RPCs in Argus terminate either with the 'unavailable' exception or the 'failure' exception. +'Unavailable' - means that the problem is temporary, e.g., communication is impossible right now. +'Failure' - means that the problem is permanent, e.g., the handler’s guardian does not exist. +Thus stream calls (and sends) whose replies are lost because of broken streams will terminate with one of these exceptions. Both exceptions have a string argument that explains the reason for the failure, e.g., future(“handler does not exist”), or unavailable(“cannot communicate”). Since any call can fail, every handler can raise the exceptions failure and unavailable. In this paper they also talked about propagation of exceptions from the called procedure to the caller. In paper about E language they talk about broken promises and setting a promise to the exception of broken references. + +In modern languages like Scala, Promises generally come with two callbacks. One to handle the success case and other to handle the failure. e.g. -#### In Scala ```scala f onComplete { @@ -385,21 +409,6 @@ In Scala, the Try type represents a computation that may either result in an exc ```scala -val a: Int = 100 -val b: Int = 10 -def divide: Try[Int] = Try(a/b) - -divide match { - case Success(v) => - println(v) // 10 - case Failure(e) => - println(e) -} - -``` - -```scala - val a: Int = 100 val b: Int = 0 def divide: Try[Int] = Try(a/b) @@ -415,9 +424,6 @@ divide match { Try type can be pipelined, allowing for catching exceptions and recovering from them along the way. - - - #### In Javascript ```javascript @@ -429,9 +435,46 @@ promise.then(function (data) { console.error(error); }); +``` +Scala futures exception handling: + +When asynchronous computations throw unhandled exceptions, futures associated with those computations fail. Failed futures store an instance of Throwable instead of the result value. Futures provide the onFailure callback method, which accepts a PartialFunction to be applied to a Throwable. TimeoutException, scala.runtime.NonLocalReturnControl[] and ExecutionException exceptions are treated differently + +Scala promises exception handling: + +When failing a promise with an exception, three subtypes of Throwables are handled specially. If the Throwable used to break the promise is a scala.runtime.NonLocalReturnControl, then the promise is completed with the corresponding value. If the Throwable used to break the promise is an instance of Error, InterruptedException, or scala.util.control.ControlThrowable, the Throwable is wrapped as the cause of a new ExecutionException which, in turn, is failing the promise. + + +To handle errors with asynchronous methods and callbacks, the error-first callback style ( which we've seen before, also adopted by Node) is the most common convention. Although this works, but it is not very composable, and eventually takes us back to what is called callback hell. Fortunately, Promises allow asynchronous code to apply structured error handling. Promises .then method takes in two callbacks, a onFulfilled to handle when a promise is resolved successfully and a onRejected to handle if the promise is rejected. + +```javascript + +var p = new Promise(function(resolve, reject){ + resolve(100); +}); + +p.then(function(data){ + console.log(data); // 100 +},function(error){ + console.err(error); +}); + +var q = new Promise(function(resolve, reject){ + reject(new Error( + {'message':'Divide by zero'} + )); +}); + +q.then(function(data){ + console.log(data); +},function(error){ + console.err(error);// {'message':'Divide by zero'} +}); + ``` -In Javascript, Promises have a catch method, which help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. + +Promises also have a catch method, which work the same way as onFailure callback, but also help deal with errors in a composition. Exceptions in promises behave the same way as they do in a synchronous block of code : they jump to the nearest exception handler. ```javascript @@ -464,7 +507,6 @@ function check(data) { The same behavior can be written using catch block. - ```javascript work("") @@ -481,6 +523,7 @@ function check(data) { ``` + # Futures and Promises in Action -- cgit v1.2.3 From 0f59ea090aef37374634b7400a6ebd73ec9782a8 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 22:56:55 -0500 Subject: added more details: Image --- chapter/2/images/p-1.png | Bin 0 -> 39600 bytes chapter/2/images/p-2.png | Bin 0 -> 40084 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 chapter/2/images/p-1.png create mode 100644 chapter/2/images/p-2.png diff --git a/chapter/2/images/p-1.png b/chapter/2/images/p-1.png new file mode 100644 index 0000000..7061fe3 Binary files /dev/null and b/chapter/2/images/p-1.png differ diff --git a/chapter/2/images/p-2.png b/chapter/2/images/p-2.png new file mode 100644 index 0000000..ccc5d09 Binary files /dev/null and b/chapter/2/images/p-2.png differ -- cgit v1.2.3 From 0868f49162590810be1876aae04c8d08e08db442 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 23:10:33 -0500 Subject: added more details: Image --- chapter/2/images/1.png | Bin 14176 -> 41235 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chapter/2/images/1.png b/chapter/2/images/1.png index 1d98f19..569c326 100644 Binary files a/chapter/2/images/1.png and b/chapter/2/images/1.png differ -- cgit v1.2.3 From f8c4ee8046a830f62d27d1d591bf1410a71f0164 Mon Sep 17 00:00:00 2001 From: Kisalaya Date: Fri, 16 Dec 2016 23:25:24 -0500 Subject: added more details --- _bibliography/futures.bib | 5 ++--- chapter/2/futures.md | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/_bibliography/futures.bib b/_bibliography/futures.bib index eec1c79..13e2f19 100644 --- a/_bibliography/futures.bib +++ b/_bibliography/futures.bib @@ -406,7 +406,6 @@ } @misc{47, - title={Are JavaScript Promises swallowing your errors?}, - author={James K Nelson}, - url = {http://jamesknelson.com/are-es6-promises-swallowing-your-errors/} + title={futures}, + url = {https://www.ps.uni-saarland.de/alice/manual/futures.html} } diff --git a/chapter/2/futures.md b/chapter/2/futures.md index ff32d19..0075773 100644 --- a/chapter/2/futures.md +++ b/chapter/2/futures.md @@ -266,11 +266,35 @@ Alice also allows for lazy evaluation of expressions. Expressions preceded with We define Implicit promises as ones where we don’t have to manually trigger the computation vs Explicit promises where we have to trigger the resolution of future manually, either by calling a start function or by requiring the value. This distinction can be understood in terms of what triggers the calculation : With Implicit promises, the creation of a promise also triggers the computation, while with Explicit futures, one needs to triggers the resolution of a promise. This trigger can in turn be explicit, like calling a start method, or implicit, like lazy evaluation where the first use of a promise’s value triggers its evaluation. -The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. Also, lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. +The idea for explicit futures were introduced in the Baker and Hewitt paper. They’re a little trickier to implement, and require some support from the underlying language, and as such they aren’t that common. The Baker and Hewitt paper talked about using futures as placeholders for arguments to a function, which get evaluated in parallel, but when they’re needed. MultiLisp also had a mechanism to delay the evaluation of the future to the time when it's value is first used, using the defer construct. Lazy futures in Alice ML have a similar explicit invocation mechanism, the first thread touching a future triggers its evaluation. + +An example for Explicit Futures would be (from AliceML): + +``` +fun enum n = lazy n :: enum (n+1) + +``` + +This example generates an infinite stream of integers and if stated when it is created, will compete for the system resources. Implicit futures were introduced originally by Friedman and Wise in a paper in 1978. The ideas presented in that paper inspired the design of promises in MultiLisp. Futures are also implicit in Scala and Javascript, where they’re supported as libraries on top of the core languages. Implicit futures can be implemented this way as they don’t require support from language itself. Alice ML’s concurrent futures are also an example of implicit invocation. -In Scala, although the futures are implicit, Promises can be used to have an explicit-like behavior. This is useful in a scenario where we need to stack up some computations and then resolve the Promise. +For example + +```scala + +val f = Future { + Http("http://api.fixer.io/latest?base=USD").asString +} + +f onComplete { + case Success(response) => println(response.body) + case Failure(t) => println(t) +} + +``` + +This sends the HTTP call as soon as it the Future is created. In Scala, although the futures are implicit, Promises can be used to have an explicit-like behavior. This is useful in a scenario where we need to stack up some computations and then resolve the Promise. An Example : -- cgit v1.2.3 From 79f787876a4e71b54e2d6724b58f6c056c8d2512 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 15:56:23 -0500 Subject: Fixes, fix up conclusion --- chapter/7/langs-consistency.md | 86 ++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 0b10c56..edaee53 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -8,7 +8,7 @@ by: "James Larisch" ## What's the problem? In many ways, web developers deal with distributed systems problems every day: your client and your server are in two different geographical locations, and thus, some coordination between computers is required. - As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network is simply not as reliable as, say, memory - and waiting for responses can result in a lack of timeliness for the application's client. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the properties of our systems. As stated, we can't perfectly emulate a single computer using multiple machines, but once we accept that fact and learn to work with it... there are plenty of things we *can* do! + As Aviral discussed in the previous section, many computer scientists have done a lot of thinking about the nature of distributed systems problems. As such, we realize that it's impossible to completely emulate the behavior of a single computational machine using multiple machines. For example, the network is simply not as reliable as, say, memory - and waiting for responses can result in untimeliness for the application's userbase. After discussing the Consistency/Availability/Partition-tolerance theorem, Section 6 discussed how we can make drill down into the CAP pyramid and choose the necessary and unnecessary properties of our systems. As stated, we can't perfectly emulate a single computer using multiple machines, but once we accept that fact and learn to work with it, there are plenty of things we *can* do! ## The Shopping Cart Let's bring all these theorem talk back to reality. Let's say you're working at a new e-commerce startup, and you'd like to revolutionize the electronic shopping cart. You'd like to give the customer the ability to do the following: @@ -24,18 +24,18 @@ How can we ensure that the client sees the same cart at every point in her trip? If you only had one user of your website, this wouldn't be too hard. You could manually, constantly modify and check on all of your servers and personally make sure the state of the customer's shopping cart is consistent across every single server. But what happens when you have millions of customers and thus millions of shopping carts? That would be impossible to keep track of personally. Luckily, you're a programmer - this can be automated! You simply need to make sure that all of your computers stay in-sync, so if the customer checks her cart in Beijing, then in Paris, she sees the same thing. -But as Section 6 already explained, this is not so trivial. Messages between your servers in Beijing and Paris could get dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). +But as Section 6 has already explained, this is not so trivial. Messages between your servers in Beijing and Paris could be dropped, corrupted, reordered, duplicated, or delayed. Servers can crash. Sharks can cut the network cables between countries. Since you have no guarantees about when you'll be able to synchronize state between two servers, it's possible that the customer could see two different cart-states depending on which country she's in (which server she asks). -It's possible to implement "consensus" protocols such as Paxos and 3-Phase-Commit that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable*. For some applications, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart distributed systems experienced a failure, it means users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? +It's possible to implement "consensus" protocols such as Paxos and 3-Phase-Commit that provide coordination between your machines. When failure happens, such as a network shark-attack, the protocol detects a lack of consistency and becomes *unavailable* - at least until it is consistent once more. For applications in which inconsistent state is dangerous, this is appropriate. For a shopping cart, this seems like overkill. If our shopping cart system experienced a failure and became unavailable, users would not be able to add or remove things from the cart. They also couldn't check out. This means our startup would lose money! Perhaps it's not so important that our clients' shopping carts be completely synchronized across the entire world at all times. After all, how often are people going to be doing such wanderlust shopping? -This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be *available*. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. +This is an important moment. By thinking about our specific problem, we've realized a compromise we're willing to make: our users always need to be able to add things, remove things, and checkout. In other words, our service needs to be as *available* as possible. Servers don't necessarily need to agree all the time. We'd like them to, but the system shouldn't shut down if they don't. We'll find a way to deal with it. Turns out there's a company out there called Amazon.com - and they've been having a similar problem. Amazon sells things on their website too, and users can add and remove things from their cart. Amazon has lots of servers spread out across the world. They also have quite a few customers. They need to ensure their customers' carts are robust: if/when servers fail or lose communication with one another, a "best-effort" should be made to display the customer's cart. Amazon acknowledges that failure, latency, or HyperLoop-traveling users can cause inconsistent cart data, depending on which server you ask. How does Amazon resolve these issues? ## Dynamo -Amazon built DynamoDB {% cite Dynamo --file langs-consistency %}, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations and performs another write or a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. +Amazon built DynamoDB {% cite Dynamo --file langs-consistency %}, which is basically a big distributed hash table. In other words, it's a hashmap spread across multiple computers. A user's cart would be stored as a value under the user's username as the key. (`{'james': {'candle', 'skateboard'}}`) When a user adds a new item to her cart, the cart data is replicated across a multiple machines within the network. If the client changes locations then performs another write, or if a few machines fail and later recover, it's possible for different machines to have different opinions about the state of a given user's cart. -Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts and push it all up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. +Dynamo has a rather unique way of dealing with these types of conflicts. Since Dynamo always wants to be available for both writes and reads (add/removes, viewing/checkouts, resp) it must have a way of combining inconsistent data. Dynamo chooses to perform this resolution at read time. When a client performs a `get()` on the user's cart, Dynamo will take the multiple conflicting carts and push them up to the application! Huh? I thought Dynamo resolves this for the programmer!? Actually, Dynamo is a generic key-value store. It detects inconsistencies in the data - but once it does, it simply tells the application (in this case the application is the shopping cart code) that there are some conflicts. The application (shopping cart, in this case) is free to resolve these inconsistencies as it pleases. How should Amazon's shopping cart procede with resolution? It may be fed two cart states like so: @@ -59,9 +59,9 @@ Green Umbrella It's important to understand that Amazon has multiple machines storing the contents of your cart. These machines are asynchronously communicating in order to tell each other about updates they've received. Conflicts like this can happen when you try to read before the nodes have had time to gossip about your cart. More likely, however, is the situation in which one of the machines holding your cart goes offline and missing some updates. When it comes back online, you try to read, and this resolution process must occur. ### Good & Bad -What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures+conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you'd like to use DynamoDB you need to consider your resolution strategy. The database doesn't provide a general solution. +What do we love about Dynamo? It's a highly available key-value store. It replicates data well, and according to the paper, has high uptime and low latency. We love that it's *eventually consistent*. Nodes are constantly gossiping, so given enough time (and assuming failures are resolved), nodes' states will eventually converge. However, this property is *weak*. It's weak because when failures & conflicts occur, and [and they will occur](https://www.youtube.com/watch?v=JG2ESDGwHHY), it's up to the application developer to figure out how to handle it. Given a conflict, there isn't a one-size-fits-all solution for resolving them. In the case of the shopping cart, it's relatively trivial. But as a programmer, every time you use DynamoDB for a different purpose you need to consider your resolution strategy. The database doesn't provide a general solution. -Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed general-purpose data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. +Instead of constructing an all-purpose database and forcing the burden of resolution on programmers, what if we constructed multi-purpose (read: multi, not *all*) data structures that required no manual resolution? These data structures would resolve conflicts inherently, themselves, and depending on your application you could choose which data structure works best for you. Let's try this transfiguration on the shopping cart. Let's strip it down: how does Amazon handle resolution, really? It treats shopping cart versions as sets of items. In order to perform resolution, Amazon unions the two sets. @@ -71,7 +71,6 @@ Let's try this transfiguration on the shopping cart. Let's strip it down: how do Cool. Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. - (Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.) ### Example @@ -233,20 +232,20 @@ Node 2: { } This is pretty nasty. Jerry has come along and with a few lines of code he's obliterated our nice strong eventually consistent code. Surely there's a better way. ### Guarantees -The original Javascript we wrote down exhibits the property from Section 6 known as *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. +The original Javascript we wrote down exhibits the property from Section 6 known as logical *monotonicity*. The union operation ensures that a given node's state is always "greater than or equal to" the states of the other nodes. However, how can we be *sure* that this property is maintained throughout the development of this program? As we've seen, there's nothing stopping an intern from coming along, making a mindless change, and destroying this wonderful property. Ideally, we want to make it impossible (or at least very difficult) to write programs that violate this property. Or, at the very least, we want to make it very easy to write programs that maintain these types of properties. But where should these guarantees live? In the above Javascript example, the guarantees aren't guarantees at all, really. There's no restriction on what the programmer is allowed to do - the programmer has simply constructed a program that mirrors guarantees that she has modeled in her brain. In order to maintain properties such as *monotonicity*, she must constantly check the model in her brain against the code. We haven't really helped the programmer out that much - she has a lot of thinking to do. -Databases such as PostgreSQL have issues like this as well, though they handle them quite differently, masters may need to ensure that write have occurred on every slave before the database becomes available for reading. A database system like this has pushed consistency concerns to the IO-level, completely out of the users control. They are enforced on system reads and system writes. This approach gives programmers no flexibility: as demonstrated with our shopping cart example, there's no need for these type of restrictions; we can tolerate inconsistency in order to maintain availability. +Databases such as PostgreSQL have issues like this as well, though they handle them quite differently, masters may need to ensure that writes have occurred on every slave before the database becomes available for reading. A database system like this has pushed consistency concerns to the IO-level, completely out of the users control. They are enforced on system reads and system writes. This approach gives programmers no flexibility: as demonstrated with our shopping cart example, there's no need for these type of restrictions; we can tolerate inconsistency in order to maintain availability. -Why not push the consistency guarantees in between the IO-level and the application-level? {% cite ConsistencyWithoutBorders --file langs-consistency %} { Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guarunteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and their mental model. +Why not push the consistency guarantees in between the IO-level and the application-level? {% cite ConsistencyWithoutBorders --file langs-consistency %} Is there any reason why you as the programmer couldn't program using tools that facilitate these types of monotonic programs? If you're familiar with formal systems -- why not construct a formal system (programming language / library) in which every theorem (program) is formally guaranteed to be monotonic? If it's *impossible* to express a non-monotonic program, the programmer needn't worry about maintaining a direct mapping between their code and his or her mental model. Wouldn't it be great if tools like this existed? ### Bloom Before talking about such tools, I'd like you to forget almost everything you know about programming for a second (unless of course you've never programmed in a Von Neumann-based language in which you sequentially update pieces of memory; which, by the way, you have). -Imagine the following scenario: you are "programming" a node in a cluster of computers. All of the other computers work as expected. When you receive a message (all messages will include an integer), your task is to save the message, increment the integer, and resend the message back to its originator. You must also send messages you've received from `stdin`. Unfortunately, the programming environment isn't like anything you've encountered before. +Imagine the following scenario: you are "programming" a node in a cluster of computers. All of the other computers work as expected. When you receive a message (all messages will include an integer), your task is to save the message, increment the integer, and resend the message back to its originator. You must also send messages you've received from `stdin`. Unfortunately, the programming environment is a little strange. You have access to five buffers: * Messages you have received in the last 5 seconds * Inputs you've received from `stdin` in the last 5 seconds @@ -288,6 +287,7 @@ onFiveSecondInterval(function() { savedBuffer.push(msg); // save message let newMsg = msg.clone() newMsg.integer++; // increment recv'd message + newMsg.flipSourceDestination() sendBuffer.push(newMsg); // send it out }); @@ -305,6 +305,7 @@ on_five_second_interval do saved_buffer << msg new_msg = msg.clone new_msg.integer += 1 + new_msg.flip_source_destination send_buffer << new_msg end @@ -314,7 +315,7 @@ on_five_second_interval do end ``` -We have expressed this model using an event-driven programming style: the main event is `t % 5 = 0`: when the buffers populate & flush. +We have expressed this model using an event-driven programming style: the callbacks are triggered when `t % 5 = 0`: when the buffers populate & flush. Notice we perform a few "copies". We read something from one buffer and place it into another one, perhaps after applying some modification. Perhaps we place a message from a given buffer into two buffers (`recv_buffer` to `saved_buffer` & `send_buffer`). @@ -326,7 +327,8 @@ on_five_second_interval do send_buffer += recv_buffer.map do |msg| # map over the recv_buffer, increment integers, add to send_buffer new_msg = msg.clone new_msg.integer += 1 - new_msg # this block returns new_msg + new_msg.flip_source_destination # send to originator + new_msg # this block returns new_msg end send_buffer += stdin_input_buffer # add stdin messages to the send buffer @@ -335,12 +337,12 @@ end After this block/callback is called, the system automatically flushes & routes messages as described above. -Bloom {% cite Bloom --file langs-consistency %}, a research language developed at UC Berkeley, has a similar programming model to the one described above. Execution is broken up into a series of "timesteps". In the above example, one "timestemp" would be the execution of one `on_five_second_interval` function. Bloom, like the theoretical system above, automatically flushes and populates the buffers before and after each timestep. In the above example, 5 seconds was an arbitrary amount of time. In Bloom, timesteps (rounds of evaluation) are logical tools - they may happen every second, 10 seconds, etc. Logically, it shouldn't affect how your program executes. In reality, Bud's timesteps correspond to evaluation iterations. Your code is evaluated, executed, and the process repeats. +Bloom {% cite Bloom --file langs-consistency %}, a research language developed at UC Berkeley, has a similar programming model to the one described above. Execution is broken up into a series of "timesteps". In the above example, one "timestemp" would be the execution of one `on_five_second_interval` function. Bloom, like the theoretical system above, automatically flushes and populates certain buffers before and after each timestep. In the above example, 5 seconds was an arbitrary amount of time. In Bloom, timesteps (rounds of evaluation) are logical tools - they may happen every second, 10 seconds, etc. Logically, it shouldn't affect how your program executes. In reality, Bud's timesteps correspond to evaluation iterations. Your code is evaluated, executed, and the process repeats. So what does a Bloom program look like? Bloom's prototypal implementation is called Bud and is implemented in Ruby. There are two main parts to a Bloom program: 1. User defined buffers: rather than the four buffers I gave you above, Bloom users can define their own buffers. There are different types of buffers depending on the behavior you desire: - * `channel`: Above, `recv_buffer` and `send_buffer` would be considered channels. They facilitate sending network messages to and from other nodes. Like the messages above, messages sent into these channels contain a "location-specifier", which tells Bloom where the message should be sent. If you wanted to send a message to `A`, you could push the message `(@A, 10)` into your send buffer (in Ruby, `["@A", 10]`). The `@` denotes the location-specifier. - * `table`: Above, `saved_buffer` would be considered a table. The contents of tables persist across timesteps. + * `channel`: Above, `recv_buffer` and `send_buffer` would be considered channels. They facilitate sending network messages to and from other nodes. Like the messages above, messages sent into these channels contain a "location-specifier", which tells Bloom where the message should be sent. If you wanted to send a message to `A`, you could push the message `(@A, 10)` into your send buffer (in Ruby, `["@A", 10]`). The `@` denotes the location-specifier. At the end of the timestep (or callback execution in the above example), these buffers are flushed. + * `table`: Above, `saved_buffer` would be considered a table. The contents of tables persist across timesteps, which means tables are never flushed. 2. Code to be executed at each timestep. A Bloom (Bud) program can be seen as the inside of the block passed to `on_five_second_interval`. In fact, it looks very similar, as we'll see. For the purposes of this chapter, let's assume `stdin_input_buffer` is a special kind of channel in which are sent in via `stdin`. Let's also assume this channel exists in all Bloom programs. @@ -359,7 +361,7 @@ module Incrementer end ``` -The first line of `state` means: declare a channel called `network_channel` in which messages are 3-tuples. The first field of the message is called `dst`, the second `src`, and the third is called `integer`. `@` is our location-specifier, so if a program wants to send a message to a node at a given identifier, they will place it in the first `dst` field. +The first line of `state` means: declare a channel called `network_channel` in which messages are 3-tuples. The first field of the message is called `dst`, the second `src`, and the third is called `integer`. `@` is our location-specifier, so if a program wants to send a message to a node at a given identifier, they will place it in the first `dst` field. For example, a message destined for `A` would look like `['A', 'me', 10]`. The `@` denotes the location-specifier within the collection's "schema". The second line means: declare a table (persists) called `saved_buffer` in which messages follow the same format as `network_channel`. There's no location specifier since this collection is not network-connected. @@ -401,7 +403,7 @@ def increment_messages network_channel <~ network_channel.map { |x| [x.src, x.dst, x.integer] } end ``` -Here, we take messages we've received from the network channel and send them back into the network channel. The `<~` operator says "copy all of the elements in the right-hand-side and eventually send them off onto the network in the channel on the left-hand-side". So, we map over the contents of network channel *in the current timestep*: switching the `src` and `dst` fields, and incrementing the integer. This mapped collection is passed back into the network channel. Bud will ensure those messages sent off at some point. +Here, we take messages we've received from the network channel and send them back into the network channel. The `<~` operator says "copy all of the elements in the right-hand-side and eventually send them off onto the network in the channel on the left-hand-side". So, we map over the contents of network channel *in the current timestep*: switching the `src` and `dst` fields, and incrementing the integer. This mapped collection is passed back into the network channel. Bud will ensure that those messages are sent off at some point. ``` declare @@ -411,7 +413,7 @@ end ``` In `save_messages`, we use the `<=` operator. `<=` says "copy all of the elements in the right-hand-side and add them to the table on the left-hand-side." It's important to note that this movement occurs *within the current timestep*. This means if `saved_buffer` is referenced elsewhere in the code, it will include the contents of `network_channel`. If we had used the `<+` operator instead, the contents of `network_channel` would show up in `saved_buffer` in the *next* timestep. The latter is useful if you'd like to operate on the current contents of `saved_buffer` in the current timestep but want to specify how `saved_buffer` should be updated for the next timestep. -Remember, all of this code is executed in each timestep - the separation of code into separate methods is merely for readability. +Remember, all of this code is executed in *each* timestep - the separation of code into separate methods is merely for readability. ``` declare @@ -424,7 +426,7 @@ end #### Details -Examine Bloom's "style". Compare it to your (probably) standard way of programming. Compare it to the Javascript & Ruby examples within this strange "timestep" model. Bloom has a more "declarative" style: what does this mean? Look at our Javascript: +Examine Bloom's "style". Compare it to your standard way of programming. Compare it to the Javascript & Ruby timestep/callback examples. Bloom has a more "declarative" style: what does this mean? Look at our Javascript: ```javascript onFiveSecondInterval(function() { @@ -432,6 +434,7 @@ onFiveSecondInterval(function() { savedBuffer.push(msg); // save message let newMsg = msg.clone() newMsg.integer++; // increment recv'd message + newMsg.flipSourceDestination(); sendBuffer.push(newMsg); // send it out }); @@ -441,11 +444,11 @@ onFiveSecondInterval(function() { }); ``` -"Every five seconds, loop over the received messages. For each one, do this, then that, then that." We are telling the computer each step we'd like it to perform. In Bud, however, we describe the state of tables and channels at either the current or next timestep using operators and other tables and channels. We describe what we'd like our collections to include and look like, rather than what to do. You declare what you'd like the state of the world to be at the current instant and at following instants. +"Every five seconds, loop over the received messages. For each message, do this, then that, then that." We are telling the computer each step we'd like it to perform. In Bud, however, we describe the state of tables and channels at either the current or next timestep using operators and other tables and channels. We describe what we'd like our collections to include and look like, rather than what to do. You declare what you'd like the state of the world to be at the current instant and at following instants. #### Isn't this chapter about consistency? -It's time to implement our shopping cart in Bloom. We are going to introduce one more collection: a `periodic`. For example, `periodic :timer 10` instantiates a new periodic collection. This collection is "not empty" every 10 seconds. Alone, it's not all that useful. However, when `join`'d with another table, it can be used to perform actions every `x` seconds. +It's time to implement our shopping cart in Bloom. We are going to introduce one more collection: a `periodic`. For example, `periodic :timer 10` instantiates a new periodic collection. This collection becomes "populated" every 10 seconds. Alone, it's not all that useful. However, when `join`'d with another table, it can be used to perform actions every `x` seconds. ```ruby module ShoppingCart @@ -480,7 +483,7 @@ end * `send_items`: join our cart with the 10-second timer. Since the timer only "appears" every 10 seconds, this `join` will produce a result every 10 seconds. When it does, send all cart items to all peers via `send_mcast`. * `receive_items`: when we receive a message from a peer, add the item to our cart. -Functionally, this code is equivalent to our working Javascript shopping cart implementation. A few important things to note: +Functionally, this code is equivalent to our working Javascript shopping cart implementation. However, there are a few important things to note: * In our Javascript example, we broadcasted our entire cart to all peers. When a peer received a message, they unioned their current cart with the received one. Here, each node broadcasts each element in the cart. When a node receives an item, it adds it to the current cart. Since tables are represented as sets, repeated or unordered additions do not matter. You can think of `{A, B, C}.add(D)` as equivalent to `{A, B, C}.union({D})`. * You cannot add items twice. Since tables are represented as sets and we simply add items to our set, an item can only ever exist once. This was true of our Javascript example as well. * You still cannot remove items! @@ -491,7 +494,7 @@ Bloom has leveraged the montononic, add-only set and constructed a declarative p Bloom's programming model is built around the set. As Aviral discussed in the previous chapter, however, sets are not the only monotonic data structures. Other CRDTs are incredibly useful for programming eventually consistent distributed programs. Recall that a *bounded join semilattice* (CRDT) can be represented as a 3-tuple: `(S, U, ⊥)`. `S` is the set of all elements within the semilattice. `U` is the `least-upper bound` operation. `⊥` is the "least" element within the set. For example, for add-only sets, `S = the set of all sets`, `U = union` and `⊥ = {}`. Elements of these semilattices, when `U` is applied, can only "stay the same or get larger". Sets can only stay the same size or get larger - they can never rollback. For some element `e` in `S`, `e U ⊥` must equal `e`. -For a semilattice we'll call `integerMax`, `S = the set of all integers`, `U = max(x, y)`, and `⊥ = -Infinity`. +For a semilattice we'll call `integerMax`, `S = the set of all integers`, `U = max(x, y)`, and `⊥ = -Infinity`. Hopefully you can see that elements of this lattice (integers) "merged" with other elements of this lattice never produce a result less than either of the merged elements. These semilattices (and many more!) can be used to program other types of distributed, eventually consistent programs. Although sets are powerful, there might be more expressive ways to describe your program. It's not difficult to imagine using `integerMax` to keep a global counter across multiple machines. @@ -510,7 +513,7 @@ interface Lattice { } ``` -[I am purposely leaving out morphisms & monotones for the sake of simplicity.] +Heather: [I am purposely leaving out morphisms & monotones for the sake of simplicity.] This provides the user with much more freedom in terms of the types of Bloom programs she can write. @@ -518,7 +521,7 @@ This provides the user with much more freedom in terms of the types of Bloom pro Bloom aims to provide a new model for writing distributed programs. And since bloom only allows for monotonic data structures with monotonicity-preserving operations, we're safe from Jerry the intern, right? -Wrong. Unfortunately, I left out an operator from Bloom's set of collection operators. `<-` removes all elements in the right-hand-size from the table in the left-hand-side. As we've seen from Jerry's work on our original Javascript shopping cart implementation, naively attempting to remove elements from a distributed set is not a safe operation. Rollbacks can potentially destroy the properties we worked so hard to achieve. So what gives? Why would the Bloom developers add this operation? +Wrong. Unfortunately, I left out an operator from Bloom's set of collection operators. `<-` removes all elements in the right-hand-size from the table in the left-hand-side. So Bloom's sets are *not* add-only. As we've seen from Jerry's work on our original Javascript shopping cart implementation, naively attempting to remove elements from a distributed set is not a safe operation. Rollbacks can potentially destroy the properties we worked so hard to achieve. So what gives? Why would the Bloom developers add this operation? Despite putting so much emphasis on consistency via logical monotonicity, the Bloom programmers recognize that your program might need *some* coordination. @@ -526,13 +529,13 @@ In our example, we don't require coordination. We accept the fact that a user ma For our shopping cart examples: when a client asks a given node what's in her cart, that node will respond with the information it's received so far. We know this information won't be *incorrect*, but this data could be *stale*. That client might be missing information. -The Bloom team calls these points in your program *points of order*. They are points in your program where coordination may be required. In fact, the Bloom developers provide analysis tools for identifying points of order within your program. There's no reason why you couldn't implement a non-monotonic shopping cart in which all nodes must synchronize before giving a response to the user. The Bloom analysis tool would tell you where the points of order lie in your program and you would need to add coordination. +The Bloom team calls points like the one above, the user asking to checkout the contents at the cart of a given node, *points of order*. These are points in your program where coordination may be required - depending on when and who you ask, you may receive a different response. In fact, the Bloom developers provide analysis tools for identifying points of order within your program. There's no reason why you couldn't implement a non-monotonic shopping cart in which all nodes must synchronize before giving a response to the user. The Bloom analysis tool would tell you where the points of order lie in your program, and you as the programmer could decide whether or not (and how!) to add coordination. So what does Bloom really give us? First off, it demonstrates an unusual and possibly more expressive way to program distributed systems. Consistency-wise, it uses sets under the hood for its collections. As long as you shy away from `<-` operator, you can be confident that your collections will only monotonically grow. Since the order of packets is not guaranteed, structuring these eventually consistent applications is reasonably easy within Bloom. BloomL also gives us the power to define our own monotonic data structures by "implementing" the lattice interface. However, Bloom makes it easy to program non-monotonic distributed programs as well. Applications may require coordination and the `<-` operator in particular can cause serious harm to our desired formal properties. Luckily, Bloom attempts to let the programmer know exactly when coordination may be required within their programs. Whenever an operation may return a stale or non-up-to-date value, Bloom's analysis tools let the programmer know. -Another thing to consider: BloomL's user-defined lattices are just that - user-defined. It's up to the programmer to ensure that the data structures that implement the lattice interface are actually valid lattice structures. If not, Bloom can't help you. +Another thing to consider: BloomL's user-defined lattices are just that - user-defined. It's up to the programmer to ensure that the data structures that implement the lattice interface are actually valid lattice structures. If your structures don't follow the rules, your program will behave in some seemingly strange ways. Currently Bloom exists as a Ruby prototype: Bud. Hypothetically speaking, there's nothing stopping the programmer from writing normal, sequentially evaluated Ruby code within Bud. This can also cause harm to our formal properties. @@ -552,9 +555,28 @@ For those of you unfamiliar with Erlang: a *process* can be thought of as an ind ### Utilization -Lasp is an Erlang library, and for good reason. Remember the initial discussion and reasoning for models such as Bloom and Lasp: we have a specific type of application that doesn't require tight consistency constraints. The constraints that do exist have been formalized, and we can be quite sure that by using a DSL like Lasp, we'll be safe from interns like Jerry. But Lasp can't do everything. More generally, eventual consistency doesn't solve every problem. +Compare Lasp and Bloom: + +Lasp +* An Erlang library, meant to be used in every-day Erlang programs. +* Built-in CRDTs. Does not allow user-defined CRDTs (for now). +* All data structures are CRDTs and all operations are logically monotonic. +* Thus, it's essentially impossible to construct a non-monotonic program *using only the Lasp library*. +* It is possible to use Lasp in a non-monotonic way with disrupting outer Erlang code. +* Follows well-known functional programming patterns and is compatible with optimal Erlang style. + +Bloom: +* Aims to be a full-featured language. Is not meant to be embeddable. +* Built-in set collections only. Allows user-defined CRDTs. +* Its sets are not add-only and thus not exclusively logically monotonic. User-defined lattices carry no formal proofs of their consistency gaurantees. +* It's possible to construct non-monotonic programs. Using the `<-` operator, for example. +* With the prototype, Bud, it's possible to use normal Ruby code to disrupt Bloom's properties. But this is more a result of the prototype implementation, not the design. +* Uses a unique programming model based on temporal logic. +* Contains an analysis tool that tells programmers which points in their code might require coordination, depending on the consistency concerns of the application. + +Remember the initial discussion and reasoning for models such as Bloom and Lasp: we have a specific type of application that doesn't require tight consistency constraints. The constraints that do exist have been formalized, and we can be quite sure that by using a DSL like Lasp, we'll be safe from interns like Jerry. But Lasp can't do everything. More generally, eventual consistency doesn't solve every problem. -PostgreSQL enforce very specific and restrictive IO-level consistency, and this was too much for our needs. But it's certainly not too much for *all* needs. There certainly are applications (take banking, for example) in which consistency is extremely important. You certainly are not allowed to double spend your money depending on how fast you can travel to a different server, so eventual consistency is not enough! All servers must coordinate. +PostgreSQL, for example, enforced very specific and restrictive IO-level consistency, and this was too much for our needs. But it's certainly not too much for *all* needs. There certainly are applications (take banking, for example) in which consistency is extremely important. You certainly are not allowed to double spend your money depending on how fast you can travel to a different server, so eventual consistency is not enough! All servers must coordinate. There's a key principle here, however: distributed programming models that attempt to accomodate everything end up doing nothing well; models that accept compromises and formalize certain properties end up being extremely useful for a subset of domains. -- cgit v1.2.3 From 98a6f1f836dd3be15d552fb7c0a1d05ee05d34d2 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 16:29:01 -0500 Subject: More conclusion fixes --- chapter/7/langs-consistency.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index edaee53..af78294 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -574,15 +574,25 @@ Bloom: * Uses a unique programming model based on temporal logic. * Contains an analysis tool that tells programmers which points in their code might require coordination, depending on the consistency concerns of the application. -Remember the initial discussion and reasoning for models such as Bloom and Lasp: we have a specific type of application that doesn't require tight consistency constraints. The constraints that do exist have been formalized, and we can be quite sure that by using a DSL like Lasp, we'll be safe from interns like Jerry. But Lasp can't do everything. More generally, eventual consistency doesn't solve every problem. +Although they are fundamentally different in many ways, Lasp and Bloom accept a key reality: it's probably impossible to program using eventual consistency gaurantees only. It works for shopping carts, but there will always be situations where coordination between machines will need to occur. Lasp and Bloom's designs reflect the different approaches for dealing with this harsh truth. -PostgreSQL, for example, enforced very specific and restrictive IO-level consistency, and this was too much for our needs. But it's certainly not too much for *all* needs. There certainly are applications (take banking, for example) in which consistency is extremely important. You certainly are not allowed to double spend your money depending on how fast you can travel to a different server, so eventual consistency is not enough! All servers must coordinate. +Lasp, on one hand, plans to be an embeddable eventually-consistent library. If you're an Erlang developer and you recognized a situation in which you can accept eventual consistent properties, you can reach for the Lasp library. Within your existing code, you can add communication mechanisms using Lasp and be confident of the properties advertised by eventual consistent systems. No need to change your entire system or re-write code in a different language. Since Lasp does not allow the expression of non-monotonic programs, you express non-monotonicity *outside* of the Lasp sections in your code. -There's a key principle here, however: distributed programming models that attempt to accomodate everything end up doing nothing well; models that accept compromises and formalize certain properties end up being extremely useful for a subset of domains. +Bloom, on the other hand, aims to be an entirely new model for expressing distributed systems problems. By using CRDT-like sets for their collections, they can encourage a declarative way of programming without enforcing too much coordination. They even let the user define their own lattices with BloomL to further encourage this type of programming. But since there will always be times where coordination is necessary, Bloom allows for operations that may require coordination. They even allow the user to perform non-monotonic operations such as `<-`. Bloom, in a way, must do this. They must provide the user with mechanisms for coordination, since they aim to create a new model for expressing distributed systems programs. Lasp is embeddable, so it can perform one specific job. Bloom is not, so it must allow many types of programs. In order to ameliorate this, Bloom provides the programmer with anaylsis tools to help the programmer identify points in the code that may not be totally safe. The programmer can then decide to coordinate or ignore these "points of order". Most programming languages are "general-use". This works for single machine programming. As the world moves toward distributed programming, programmers must adopt models / languages / libraries that are built for their domain. It forces serious thought on the part of the programmer: what *exactly* am I trying to achieve, and what am I willing to sacrifice? -We've known for quite a while that when we're talking about multiple machines, we can't have it all. Our tools must now reflect this mantra. Our sanity and the safety of our programs depends on it. +Bloom could potentially facilitate distributed systems programming through a new, temporal model. The Bloom developers have designed a language for a specific purpose: distributed programming. The Lasp developers take this philosophy even further: let's design a library for a specific subset of distributed systems programming. Although one goes deeper than the other, the two languages share an idea: languages / models should be build for subsets of the computing domain. Distributed systems produce difficult problems. When we put our heads together and develop tools to facilitate distributed systems programming (Bloom) and always *eventually consistent* distributed systems programming, programming gets easier. Fewer bugs pop up, and it becomes easier to formally reason about the behavior of our programs. + +When a language or model tries to do everything well, it cannot provide formal guarantees or tools to facilitate certain problem solving. Since different domains have totally different needs and issues to deal with, general purpose programming languages simply try to provide the minimum required for a wide variety software problems. + +If we shift our mindset as software developers and begin to develop and look for tools to help us with specific problems and domains of problems, we can leverage computers much more than we do today. Our tools can provide relevant feedback and help us design our systems. They can even provide formal properties that we need not question. + +Critically, it requires a narrowing of our problem domain. It means inspecting our problem and asking what we need, and what's not so important? + +In this chapter, we examined ways in which tools can help us leverage eventually consistent distributed systems. But there's no reason why this philosophy couldn't be applied to other subsections of the CAP pyramid. In fact, there's no reason why this philosophy couldn't be applied to other areas of computing in general. Why are both video games and distributed systems programmed using the same language & models? + +Even if you don't encounter consistency issues in your day-to-day life, this idea applies to many areas of computing and tools in general. Hopefully you can begin to ask yourself and those around you: what tasks are we trying to accomplish, and how can our tools help us accomplish them? ## References -- cgit v1.2.3 From c2771b35ed511917ace99e149d0f5eef56a8c7e4 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 17:21:25 -0500 Subject: Add more Lasp --- chapter/7/langs-consistency.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index af78294..9838876 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -552,6 +552,35 @@ A Simple Lasp Program is defined as either a: For those of you unfamiliar with Erlang: a *process* can be thought of as an independent piece of code executing asynchronously. Processes can receive messages and send messages to other processes. Process can also subscribe (I think) to other processes' messages. +Programming in Erlang is unique in comparison to programming in Ruby or Javascript. Erlang processes are spun off for just about everything - and they are independent "nodes" of code acting independently while communicating with other processes. Naturally, distributed systems programming fits well here. Processes can be distributed within a single computer or distributed across a cluster of computers. So communication between processes may move over the network. + +Distribution of a data structure, then, means the transmission of a data structure across network-distributed processes. If a client asks for the state of the shopping cart in Beijing, the processes located on the computer in Beijing will respond. However, the processes in New York may disagree. Thus, our task is to distribute our data structures (CRDTs, right?) across distributed processes. + +So, what's a "Lasp process"? A Lasp process is a process that operates on lattice elements, or CRDTs. Three popular Lasp processes are `map`, `fold`, and `filter`. + +* `map`: If you're familiar with functional programming, these functions shouldn't appear too foreign. `map` spins off a never-ending process which applies a user-supplied `f` to all the replicas of a given CRDT this processes receives. +* `fold`: Spins off a process that continously folds input CRDT values into another CRDT value using a user-provided function. +* `filter`: Spins off a process that continously picks specific CRDT input values based on a user-provided filtering function. + +Drawing parallels to our mock-Bloom-Ruby-callback implementation, we remember that CRDT modifications and movements can be modeled using functional styles. In Bloom, we dealt with mapping values from "collections" to other "collections". These collections were backed by CRDT-like sets. + +Here, we are mapping "streams" of CRDT instances to other CRDT instances using the same functional programming methods. + +However, here, the stream manipulations occcur within unique processes distributed across a network of computers. These processes consume CRDTs and produce new ones based on functions provided by the user. + +There's one hiccup though: the user can't provide *any* function to these processes. Since our datatypes must obey certain properties, the functions that operate on our datas must preserve these properties. + +Recall that within a lattice, a partial order exists. One element is always `<=` another element. For example, with add-only sets, `{A} <= {A} <= {A, B} <= {A, B} <= {A, B, C}`. A *monotonic* function that operates over the domain of add-only sets must preserve this partial ordering. For example - if `{A} <= {A, B}` and `f` is a monotonic function that operates over add-only sets, `f({A}) <= f({A, B})`. + +This ensures the preservation of our consistency properties across our ever-interacting processes. + +#### A Library + +Remember that Lasp is an Erlang *library*. Within your existing Erlang program, you're free to drop in some interacting Lasp-processes. These processes will communicate using CRDTs and functions over CRDTs. As such, your Lasp sub-program is guaranteed to exhibit strong eventual consistency properties. + +However, the rest of your Erlang program is not. Since Lasp is embeddable, it has no control over the rest of your Erlang program. You must be sure to use Lasp in a safe way. But since it doesn't provide the programmer with the ability to perform non-monotonic operations within the Lasp-context, the programmer can have significant confidence in the eventual consistency of the Lasp portion of the program. + +Bloom provided a new model for distributed programming, where Lasp aims to provide existing distributed systems with a drop-in solution for adding eventually consistent parts to their systems. ### Utilization -- cgit v1.2.3 From bf4cd5b0534edfcdca171ccc0f3bc142cd50bf64 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 17:23:21 -0500 Subject: jerry the intern --- chapter/7/langs-consistency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 9838876..50f3926 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -578,7 +578,7 @@ This ensures the preservation of our consistency properties across our ever-inte Remember that Lasp is an Erlang *library*. Within your existing Erlang program, you're free to drop in some interacting Lasp-processes. These processes will communicate using CRDTs and functions over CRDTs. As such, your Lasp sub-program is guaranteed to exhibit strong eventual consistency properties. -However, the rest of your Erlang program is not. Since Lasp is embeddable, it has no control over the rest of your Erlang program. You must be sure to use Lasp in a safe way. But since it doesn't provide the programmer with the ability to perform non-monotonic operations within the Lasp-context, the programmer can have significant confidence in the eventual consistency of the Lasp portion of the program. +However, the rest of your Erlang program is not. Since Lasp is embeddable, it has no control over the rest of your Erlang program. You must be sure to use Lasp in a safe way. But since it doesn't provide the programmer with the ability to perform non-monotonic operations within the Lasp-context, the programmer can have significant confidence in the eventual consistency of the Lasp portion of the program. We still aren't totally safe from Jerry the intern, since Jerry can modify our outer-Erlang to do some dangerous things. Bloom provided a new model for distributed programming, where Lasp aims to provide existing distributed systems with a drop-in solution for adding eventually consistent parts to their systems. -- cgit v1.2.3 From 7aa8cd97fc451f28dbdb9d95af808e27c598054f Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 17:24:40 -0500 Subject: remove cool --- chapter/7/langs-consistency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapter/7/langs-consistency.md b/chapter/7/langs-consistency.md index 50f3926..b19ba23 100644 --- a/chapter/7/langs-consistency.md +++ b/chapter/7/langs-consistency.md @@ -69,7 +69,7 @@ Let's try this transfiguration on the shopping cart. Let's strip it down: how do { Red Candle, Blue Skateboard } U { Red Candle, Green Umbrella } == { Red Candle, Blue Skateboard, Green Umbrella } ``` -Cool. Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. +Using this knowledge, let's try to construct our own shopping cart that automatically resolves conflicts. (Unfortunately Amazon has a leg up on our startup. Their programmers have figured out a way to add multiple instances of a single item into the cart. Users on our website can only add one "Red Candle"" to their shopping cart. This is due to a fundamental limitation in the type of CRDT I chose to exemplify. It's quite possible to have a fully functional cart. Take a look at LWW-Sets.) -- cgit v1.2.3 From 84a9fbf592cc376a41600d0d0fdc7bf18fb4dd30 Mon Sep 17 00:00:00 2001 From: James Larisch Date: Sat, 17 Dec 2016 17:30:37 -0500 Subject: forgot comma --- _bibliography/langs-consistency.bib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_bibliography/langs-consistency.bib b/_bibliography/langs-consistency.bib index 2b1165f..f944784 100644 --- a/_bibliography/langs-consistency.bib +++ b/_bibliography/langs-consistency.bib @@ -19,7 +19,7 @@ @inproceedings{Lasp, author = {Christopher Meiklejohn, Peter Van Roy}, title = {Lasp: A Language for Distributed, Coordination-Free Programming}, - booktitle = {Proceedings of the 17th International Symposium on Principles and Practice of Declarative Programming} + booktitle = {Proceedings of the 17th International Symposium on Principles and Practice of Declarative Programming}, series = {PPDP}, year = {2015}, url = {https://pdfs.semanticscholar.org/c0ad/b28526a29f0765669167d3201e2b3605895e.pdf} -- cgit v1.2.3