From 37eeae6bc2503281d1b806c85aa0e70645fd9966 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 5 Apr 2009 11:24:55 -0400 Subject: RoundTrip demo --- demo/prose | 10 ++++++++++ demo/roundTrip.ur | 29 +++++++++++++++++++++++++++++ demo/roundTrip.urp | 5 +++++ demo/roundTrip.urs | 1 + src/c/driver.c | 28 +++++++++++++++++++--------- src/c/urweb.c | 2 +- src/rpcify.sml | 4 +++- 7 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 demo/roundTrip.ur create mode 100644 demo/roundTrip.urp create mode 100644 demo/roundTrip.urs diff --git a/demo/prose b/demo/prose index 80113c3e..45b7be00 100644 --- a/demo/prose +++ b/demo/prose @@ -246,3 +246,13 @@ threads.urp

We specify some client-side code to run on page load using the onload attribute of <body>. The onload code in this example spawns two separate threads running the loop code with different prefixes, update intervals, and starting counters.

Old hands at concurrent programming may be worried at the lack of synchronization in this program. Ur/Web uses cooperative multi-threading, not the more common preemptive multi-threading. Only one thread runs at a time, and only particular function calls can trigger context switches. In this example, sleep is the only such function that appears.

+ +roundTrip.urp + +

So far, we've seen examples of client-side code triggering the execution of server-side code. Such remote calls only happen in response to client-side events. It is often useful to allow a client to trigger events on other clients, and Ur/Web facilitates this with a simple asynchronous message-passing facility. The current example introduces the basics of message-passing with a trivial use case, and the next example shows a more realistic case where several clients can communicate.

+ +

We are going to provide a silly service where a client can send messages to the server, which the server then echoes back to the client. The SQL table channels stores a mapping from client IDs to message channels. The abstract type client holds unique client IDs, which Ur/Web generates automatically as needed. A channel T is a channel to which messages of type T can be sent. Every channel belongs to a single client; anyone can send to a channel, but only the channel's owner can read the messages. Every client is associated with a particular open page on a particular web browser somewhere. Since web browsing sessions are ephemeral, clients and their channels are garbage-collected automatically as the web server loses contact with browsers. When a client is garbage-collected, any database row mentioning it or one of its channels is deleted. It's also possible to include option clients (and likewise for channels) in databases, in which case such columns are merely nulled out when they refer to dead clients.

+ +

The main function begins by retrieving the current client ID, allocating a new channel, and associating that channel with the current client in the database. Next, we allocate a buffer and return the page, which in its onload attribute starts two loops running in parallel. In contrast to in the last example, here we only use spawn with the call to the first loop, since every client-side event handler is implicitly started in a new thread. + +

The first loop, receiver, repeatedly reads messages from the channel and writes them to the buffer. The second loop, sender, periodically sends messages to the channel. Client code can't send messages directly. Instead, we must use server-side functions to do the sending. Clients aren't trusted to pass channels to the server, so our server-side function writeBack instead keys off of the client ID, looking up the corresponding channel in the database.

diff --git a/demo/roundTrip.ur b/demo/roundTrip.ur new file mode 100644 index 00000000..a2be8083 --- /dev/null +++ b/demo/roundTrip.ur @@ -0,0 +1,29 @@ +table channels : { Client : client, Channel : channel (string * int * float) } + +fun writeBack v = + me <- self; + r <- oneRow (SELECT channels.Channel FROM channels WHERE channels.Client = {[me]}); + send r.Channels.Channel v + +fun main () = + me <- self; + ch <- channel; + dml (INSERT INTO channels (Client, Channel) VALUES ({[me]}, {[ch]})); + + buf <- Buffer.create; + + let + fun receiver () = + v <- recv ch; + Buffer.write buf ("(" ^ v.1 ^ ", " ^ show v.2 ^ ", " ^ show v.3 ^ ")"); + receiver () + + fun sender s n f = + sleep 2000; + writeBack (s, n, f); + sender (s ^ "!") (n + 1) (f + 1.23) + in + return + + + end diff --git a/demo/roundTrip.urp b/demo/roundTrip.urp new file mode 100644 index 00000000..37de3812 --- /dev/null +++ b/demo/roundTrip.urp @@ -0,0 +1,5 @@ +database dbname=test +sql roundTrip.sql + +buffer +roundTrip diff --git a/demo/roundTrip.urs b/demo/roundTrip.urs new file mode 100644 index 00000000..6ac44e0b --- /dev/null +++ b/demo/roundTrip.urs @@ -0,0 +1 @@ +val main : unit -> transaction page diff --git a/src/c/driver.c b/src/c/driver.c index 52999da3..44465cc3 100644 --- a/src/c/driver.c +++ b/src/c/driver.c @@ -67,15 +67,14 @@ static int try_rollback(uw_context ctx) { return r; } -static void *worker(void *data) { - int me = *(int *)data, retries_left = MAX_RETRIES; +static uw_context new_context() { uw_context ctx = uw_init(); - + int retries_left = MAX_RETRIES; + while (1) { failure_kind fk = uw_begin_init(ctx); if (fk == SUCCESS) { - uw_db_init(ctx); printf("Database connection initialized.\n"); break; } else if (fk == BOUNDED_RETRY) { @@ -94,12 +93,19 @@ static void *worker(void *data) { uw_free(ctx); return NULL; } else { - printf("Unknown uw_handle return code!\n"); + printf("Unknown uw_begin_init return code!\n"); uw_free(ctx); return NULL; } } + return ctx; +} + +static void *worker(void *data) { + int me = *(int *)data, retries_left = MAX_RETRIES; + uw_context ctx = new_context(); + while (1) { char buf[uw_bufsize+1], *back = buf, *s; int sock, dont_close = 0; @@ -278,8 +284,10 @@ static void *worker(void *data) { } static void *client_pruner(void *data) { - uw_context ctx = uw_init(); - uw_db_init(ctx); + uw_context ctx = new_context(); + + if (!ctx) + exit(1); while (1) { uw_prune_clients(ctx); @@ -297,9 +305,11 @@ static void sigint(int signum) { } static void initialize() { - uw_context ctx = uw_init(); + uw_context ctx = new_context(); + + if (!ctx) + exit(1); - uw_db_init(ctx); if (uw_initialize(ctx) != SUCCESS) { printf("Failed to initialize database!\n"); uw_db_rollback(ctx); diff --git a/src/c/urweb.c b/src/c/urweb.c index 9915219d..1b3cfb1d 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -322,7 +322,7 @@ uw_context uw_init() { buf_init(&ctx->outHeaders, 0); buf_init(&ctx->page, 0); buf_init(&ctx->heap, 0); - buf_init(&ctx->script, 0); + buf_init(&ctx->script, 1); ctx->script.start[0] = 0; ctx->inputs = calloc(uw_inputs_len, sizeof(char *)); diff --git a/src/rpcify.sml b/src/rpcify.sml index dc8ecc52..846cc6c5 100644 --- a/src/rpcify.sml +++ b/src/rpcify.sml @@ -59,7 +59,9 @@ val csBasis = SS.addList (SS.empty, ["get", "set", "alert", - "recv"]) + "recv", + "sleep", + "spawn"]) type state = { cpsed : int IM.map, -- cgit v1.2.3