Mercurial > urweb
changeset 698:9b29ce0babb8
RoundTrip demo
author | Adam Chlipala <adamc@hcoop.net> |
---|---|
date | Sun, 05 Apr 2009 11:24:55 -0400 |
parents | 755a71c99be5 |
children | 4e260887d8f2 |
files | demo/prose demo/roundTrip.ur demo/roundTrip.urp demo/roundTrip.urs src/c/driver.c src/c/urweb.c src/rpcify.sml |
diffstat | 7 files changed, 68 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- 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 @@ <p>We specify some client-side code to run on page load using the <tt>onload</tt> attribute of <tt><body></tt>. The <tt>onload</tt> code in this example spawns two separate threads running the <tt>loop</tt> code with different prefixes, update intervals, and starting counters.</p> <p>Old hands at concurrent programming may be worried at the lack of synchronization in this program. Ur/Web uses <i>cooperative multi-threading</i>, not the more common <i>preemptive</i> multi-threading. Only one thread runs at a time, and only particular function calls can trigger context switches. In this example, <tt>sleep</tt> is the only such function that appears.</p> + +roundTrip.urp + +<p>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.</p> + +<p>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 <tt>channels</tt> stores a mapping from client IDs to message channels. The abstract type <tt>client</tt> holds unique client IDs, which Ur/Web generates automatically as needed. A <tt>channel <i>T</i></tt> is a channel to which messages of type <tt><i>T</i></tt> 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 <tt>option client</tt>s (and likewise for channels) in databases, in which case such columns are merely nulled out when they refer to dead clients.</p> + +<p>The <tt>main</tt> 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 <tt>onload</tt> attribute starts two loops running in parallel. In contrast to in the last example, here we only use <tt>spawn</tt> with the call to the first loop, since every client-side event handler is implicitly started in a new thread.</tt> + +<p>The first loop, <tt>receiver</tt>, repeatedly reads messages from the channel and writes them to the buffer. The second loop, <tt>sender</tt>, 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 <tt>writeBack</tt> instead keys off of the client ID, looking up the corresponding channel in the database.</p>
--- /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 <xml><body onload={spawn (receiver ()); sender "" 0 0.0}> + <dyn signal={Buffer.render buf}/> + </body></xml> + end
--- /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
--- /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
--- 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);
--- 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 *));