# HG changeset patch # User Adam Chlipala # Date 1238945095 14400 # Node ID 9b29ce0babb8715a7de3ce2d209d5719b00742b5 # Parent 755a71c99be591b569dd38838cd704c4aa241a58 RoundTrip demo diff -r 755a71c99be5 -r 9b29ce0babb8 demo/prose --- a/demo/prose Sun Apr 05 10:48:11 2009 -0400 +++ b/demo/prose Sun Apr 05 11:24:55 2009 -0400 @@ -246,3 +246,13 @@

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 -r 755a71c99be5 -r 9b29ce0babb8 demo/roundTrip.ur --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demo/roundTrip.ur Sun Apr 05 11:24:55 2009 -0400 @@ -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 -r 755a71c99be5 -r 9b29ce0babb8 demo/roundTrip.urp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demo/roundTrip.urp Sun Apr 05 11:24:55 2009 -0400 @@ -0,0 +1,5 @@ +database dbname=test +sql roundTrip.sql + +buffer +roundTrip diff -r 755a71c99be5 -r 9b29ce0babb8 demo/roundTrip.urs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demo/roundTrip.urs Sun Apr 05 11:24:55 2009 -0400 @@ -0,0 +1,1 @@ +val main : unit -> transaction page diff -r 755a71c99be5 -r 9b29ce0babb8 src/c/driver.c --- a/src/c/driver.c Sun Apr 05 10:48:11 2009 -0400 +++ b/src/c/driver.c Sun Apr 05 11:24:55 2009 -0400 @@ -67,15 +67,14 @@ 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 @@ 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 *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 initialize() { - uw_context ctx = uw_init(); + uw_context ctx = new_context(); - uw_db_init(ctx); + if (!ctx) + exit(1); + if (uw_initialize(ctx) != SUCCESS) { printf("Failed to initialize database!\n"); uw_db_rollback(ctx); diff -r 755a71c99be5 -r 9b29ce0babb8 src/c/urweb.c --- a/src/c/urweb.c Sun Apr 05 10:48:11 2009 -0400 +++ b/src/c/urweb.c Sun Apr 05 11:24:55 2009 -0400 @@ -322,7 +322,7 @@ 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 -r 755a71c99be5 -r 9b29ce0babb8 src/rpcify.sml --- a/src/rpcify.sml Sun Apr 05 10:48:11 2009 -0400 +++ b/src/rpcify.sml Sun Apr 05 11:24:55 2009 -0400 @@ -59,7 +59,9 @@ ["get", "set", "alert", - "recv"]) + "recv", + "sleep", + "spawn"]) type state = { cpsed : int IM.map,