Principles of Computer Systems
Autumn 2019
Stanford University
Computer Science Department
Instructors: Chris Gregg
Philip Levis
read(2
) write(2
)waitpid(2
)pthread_mutex_lock(2
)read(2
) connect(2
)read(2
)accept(2
)waitpid(2
)pthread_mutex_lock(2
)user
stack
kernel
kernel
stack
system call
user CPU context
accept()
user
stack
kernel
kernel
stack
system call
user CPU context
blocks
also
blocks
accept()
notify_all()
waitpid
with WNOHANG
sigsuspend
class session {
public:
session(boost::asio::io_service& io_service) :
socket_(io_service) {} // construct a TCP-socket from io_service
tcp::socket& socket(){return socket_;}
void start(){
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred){
if (!error)
// initiate asynchronous write; handle_write() is callback-function
boost::asio::async_write(socket_,
boost::asio::buffer(data_,bytes_transferred),
boost::bind(&session::handle_write,this,
boost::asio::placeholders::error));
else
delete this;
}
void handle_write(const boost::system::error_code& error){
if (!error)
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
else
delete this;
}
boost::asio::ip::tcp::socket socket_;
enum { max_length=1024 };
char data_[max_length];
};
start
async_read_some
handle_read
async_write
handle_write
class session {
public:
session(boost::asio::io_service& io_service) :
socket_(io_service) {} // construct a TCP-socket from io_service
tcp::socket& socket(){return socket_;}
void start(){
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred){
if (!error)
// initiate asynchronous write; handle_write() is callback-function
boost::asio::async_write(socket_,
boost::asio::buffer(data_,bytes_transferred),
boost::bind(&session::handle_write,this,
boost::asio::placeholders::error));
else
delete this;
}
void handle_write(const boost::system::error_code& error){
if (!error)
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
else
delete this;
}
boost::asio::ip::tcp::socket socket_;
enum { max_length=1024 };
char data_[max_length];
};
start
handle_read
async_write
handle_write
code returns here, can do other work
async_read_some
1995
1995
2002
1995
2002
2003
class session {
public:
session(boost::asio::io_service& io_service) :
socket_(io_service) {} // construct a TCP-socket from io_service
tcp::socket& socket(){return socket_;}
void start(){
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred){
if (!error)
// initiate asynchronous write; handle_write() is callback-function
boost::asio::async_write(socket_,
boost::asio::buffer(data_,bytes_transferred),
boost::bind(&session::handle_write,this,
boost::asio::placeholders::error));
else
delete this;
}
void handle_write(const boost::system::error_code& error){
if (!error)
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
else
delete this;
}
boost::asio::ip::tcp::socket socket_;
enum { max_length=1024 };
char data_[max_length];
};
start
handle_read
async_write
handle_write
Any state needed across asynchronous calls must be stored in object (e.g., data_).
async_read_some
Of these seven tradeoff points, 5 of them (greyed out) relate to programming challenges and programmer reasoning: they can be ameliorated through software design, testing, and methodology. They're questions of taste.
2 of them (bold) are unavoidable performance issues.
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
data to read
no data
edge trigger
edge trigger
level trigger
level trigger
static const unsigned short kDefaultPort = 33333;
int main(int argc, char **argv) {
int server = createServerSocket(kDefaultPort);
if (server == kServerSocketFailure) {
cerr << "Failed to start server. Port " << kDefaultPort << " is probably already in use." << endl;
return 1;
}
cout << "Server listening on port " << kDefaultPort << endl;
runServer(server);
return 0;
}
static void runServer(int server) {
setAsNonBlocking(server); // fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL) | O_NONBLOCK)
int ws = buildInitialWatchSet(server);
static const int kMaxEvents = 64;
static int buildInitialWatchSet(int server) {
int ws = epoll_create1(0);
struct epoll_event info = {.events = EPOLLIN | EPOLLET, .data = {.fd = server}};
epoll_ctl(ws, EPOLL_CTL_ADD, server, &info);
return ws;
}
struct epoll_event events[kMaxEvents];
while (true) {
int numEvents = epoll_wait(ws, events, kMaxEvents, /* timeout = */ -1);
for (int i = 0; i < numEvents; i++) {
if (events[i].data.fd == server) {
acceptNewConnections(ws, server);
}
} else if (events[i].events & EPOLLIN) { // we're still reading the client's request
consumeAvailableData(ws, events[i].data.fd);
}
} else { // events[i].events & EPOLLOUT
publishResponse(events[i].data.fd);
}
}
}
}
static void acceptNewConnections(int ws, int server) {
while (true) {
int clientSocket = accept4(server, NULL, NULL, SOCK_NONBLOCK);
if (clientSocket == -1) return;
struct epoll_event info = {.events = EPOLLIN | EPOLLET, .data = {.fd = clientSocket}};
epoll_ctl(ws, EPOLL_CTL_ADD, clientSocket, &info);
}
}
static const size_t kBufferSize = 1;
static const string kRequestHeaderEnding("\r\n\r\n");
static void consumeAvailableData(int ws, int client) {
static map<int, string> requests; // tracks what's been read in thus far over each client socket
size_t pos = string::npos;
while (pos == string::npos) {
char buffer[kBufferSize];
ssize_t count = read(client, buffer, kBufferSize);
if (count == -1 && errno == EWOULDBLOCK) return; // not done reading everything yet, so return
if (count <= 0) { close(client); break; } // passes? then bail on connection, as it's borked
requests[client] += string(buffer, buffer + count);
pos = requests[client].find(kRequestHeaderEnding);
if (pos == string::npos) continue;
Notice the static map<> variable inside the function. This map persists across all calls to the function, and so it tracks partially read data for each client (stack ripping).
cout << "Num Active Connections: " << requests.size() << endl;
cout << requests[client].substr(0, pos + kRequestHeaderEnding.size()) << flush;
struct epoll_event info = {.events = EPOLLOUT | EPOLLET, .data = {.fd = client}};
epoll_ctl(ws, EPOLL_CTL_MOD, client, &info); // MOD == modify existing event
}
requests.erase(client);
}
static const string kResponseString("HTTP/1.1 200 OK\r\n\r\n"
"<b>Thank you for your request! We're working on it! No, really!</b><br/>"
"<br/><img src=\"http://vignette3.wikia.nocookie.net/p__/images/e/e0/"
"Agnes_Unicorn.png/revision/latest?cb=20160221214120&path-prefix=protagonist\"/>");
static void publishResponse(int client) {
static map<int, size_t> responses;
responses[client]; // insert a 0 if key isn't present
while (responses[client] < kResponseString.size()) {
ssize_t count = write(client, kResponseString.c_str() + responses[client],
kResponseString.size() - responses[client]);
if (count == -1 && errno == EAGAIN) return;
if (count == -1) break;
assert(count > 0);
responses[client] += count;
}
responses.erase(client);
close(client);
}
class session {
public:
session(boost::asio::io_service& io_service) :
socket_(io_service) {} // construct a TCP-socket from io_service
tcp::socket& socket(){return socket_;}
void start(){
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred){
if (!error)
// initiate asynchronous write; handle_write() is callback-function
boost::asio::async_write(socket_,
boost::asio::buffer(data_,bytes_transferred),
boost::bind(&session::handle_write,this,
boost::asio::placeholders::error));
else
delete this;
}
void handle_write(const boost::system::error_code& error){
if (!error)
// initiate asynchronous read; handle_read() is callback-function
socket_.async_read_some(boost::asio::buffer(data_,max_length),
boost::bind(&session::handle_read,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
else
delete this;
}
boost::asio::ip::tcp::socket socket_;
enum { max_length=1024 };
char data_[max_length];
};
start
handle_read
async_write
handle_write
async_read_some
boost::asio::spawn(my_strand, do_echo);
void do_echo(boost::asio::yield_context yield) {
try {
char data[128];
boost::system::error_code ec;
for (;;) {
std::size_t length =
my_socket.async_read_some(boost::asio::buffer(data), yield);
if (ec == boost::asio::error::eof) {
break; //connection closed cleanly by peer
else if (ec) {
throw boost::system::system_error(ec); //some other error
}
boost::asio::async_write(my_socket, boost::asio::buffer(data, length), yield);
if (ec == boost::asio::error::eof) {
break; //connection closed cleanly by peer
else if (ec) {
throw boost::system::system_error(ec); //some other error
}
}
}
catch (std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<"\n";
}
}
session
async_write
async_read_some
dboper.insertDocument(db, { name: "Test", description: "Test"},
"test", (result) => {
console.log("Insert Document:\n", result.ops);
dboper.findDocuments(db, "test", (docs) => {
console.log("Found Documents:\n", docs);
dboper.updateDocument(db, { name: "Test" },
{ description: "Updated Test" }, "test",
(result) => {
console.log("Updated Document:\n", result.result);
dboper.findDocuments(db, "test", (docs) => {
console.log("Found Updated Documents:\n", docs);
db.dropCollection("test", (result) => {
console.log("Dropped Collection: ", result);
client.close();
});
});
});
});
});
database.insertDocument(db, { name: "Test",
description: "Chill Out! Its just a test program!"},
"test")
.then((result) => {
return database.findDocuments(db, "test");
})
.then((documents) => {
console.log("Found Documents:\n", documents);
return database.updateDocument(db, { name: "Test" },
{ description: "Updated Test" }, "test");
})
.then((result) => {
console.log("Updated Documents Found:\n", result.result);
return database.findDocuments(db, "test");
})
.then((docs) => {
console.log("The Updated Documents are:\n", docs);
return db.dropCollection("test");
})
.then((result) => {
return client.close();
})
.catch((err) => alert(err));
})
.catch((err) => alert(err));
var i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout("timedCount()",500);
}
timedCount();
<!DOCTYPE html>
<html>
<body>
<p>Count numbers: <output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
<script>
var w;
function startWorker() {
if (typeof(Worker) !== "undefined") {
if (typeof(w) == "undefined") {
w = new Worker("demo_workers.js");
}
w.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data;
};
} else {
document.getElementById("result").innerHTML = "Sorry! No Web Worker support.";
}
}
function stopWorker() {
w.terminate();
w = undefined;
}
</script>
</body>
</html>
demo_workers.js
web page
starts a background worker
and registers an event handler
for its messages
extern crate futures;
extern crate future_by_example;
fn main() {
use futures::Future;
use futures::future::ok;
use future_by_example::new_example_future;
let future1 = new_example_future();
let future2 = new_example_future();
// Can do more computation here
let joined = future1.join(future2);
let (value1, value2) = joined.wait().unwrap();
assert_eq!(value1, value2);
}