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>&lt;body&gt;</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 *));
--- 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,