Skip to content

Distribution

Simon Fowler edited this page Jun 25, 2017 · 2 revisions

Distribution Overview

Links has for some time incorporated process-based, actor-ish concurrency, and more recently, has incorporated session-typed channels. Both of these have worked on the client, and on the server.

However, while the abstractions themselves imply location transparency (for example, if we have a process ID, we should be able to send to it irrespective of where the process is located), there has until now existed a "barrier" between different concurrency runtimes. The only way of doing client-server communication has been RPC calls, which are very handy for some things, but less so for others.

Bringing Distribution to Links

The distribution patches aim to break this barrier. It is now possible to use channel- and actor-based processes across this gap.

Minimal example: Processes

Here is a program which spawns a loop on the server, which response with a "Hello" value to any request it receives.

module Client {

  fun mainPage(serverPid) {
    var _ = spawnClient {
      fun loop(n) {
        var ourPid = self();
        if (n > 0) {
          serverPid ! Hi(ourPid);
          receive { case x -> print("Received hello!"); loop(n - 1) }
        } else {
          print("Done!");
        }
      }
      loop(5)
    };
    page
      <html>
        <h1>Hi!</h1>
      </html>
  }

}

module Server {

  fun go() {
    spawn {
      fun loop() {
        receive {
          case Hi(pid) -> pid ! Hello; loop()
        }
      }
      loop()
    }
  }

}

fun main() {
  var serverPid = Server.go();
  addRoute("/", fun (_, _) { Client.mainPage(serverPid) } );
  serveWebsockets();
  servePages()
}

main()

The Client module has a mainPage which creates a page, given a server process ID. spawnClient ensures a process is spawned on the client. Here, we spawn a process which loops 5 times: the process retrieves its own PID using self(), sends a message to the server, and blocks waiting for a response. The server hears the response and the PID, and sends back a Hello message. The client receives the response, decrements the counter, and loops.

The main extra bit of work to be done here is to call serveWebsockets() before servePages(), in order to initialise the websocket logic which underpins this.

Minimal Example: Sessions

module Sessions {
  typename Ping = [| Ping |];
  typename Pong = [| Pong |];
  typename PingPong =
    ?(Ping).!(Pong). [&| Again: PingPong, Stop: End |&];
}

open Sessions

module Client {

  fun mainPage(ap) {
    var _ = spawnClient {
      var c = request(ap);

      fun loop(n, c) {
        var ourPid = self();
        var c = send (Ping, c);
        var (_, c) = receive(c);
        print("Received pong!");
        if (n > 0) {
          var c = select Again c;
          loop(n - 1, c)
        } else {
          var _ = select Stop c;
          print("Done!");
        }
      }
      loop(5, c)
    };
    page
      <html>
        <h1>Hi!</h1>
      </html>
  }

}

module Server {

  fun clientLoop(c) {
    var (_, c) = receive(c);
    print("Received ping!");
    var c = send(Pong, c);
    offer(c) {
      case Again(c) -> clientLoop(c);
      case Stop(c) -> ()
    }
  }

  fun go(ap) {
    spawn {
      fun loop() {
        var c = accept(ap);
        clientLoop(c);
        loop()
      }
      loop()
    }
  }

}

fun main() {
  var ap = (new() : AP(PingPong));
  var _ = Server.go(ap);
  addRoute("/", fun (_, _) { Client.mainPage(ap) } );
  serveWebsockets();
  servePages()
}

main()

We can adapt the previous example to use session types. We firstly describe (in the Sessions module) a session type (from the view of the server) which receives a ping, receives a pong, and then receives a decision whether to loop and go again, or to stop.

In main(), we create an "access point" called ap using the new command. Note that while we choose to give this a type annotation, this isn't actually necessary---it helps with documentation and debugging, however. An access point is a "matchmaking service". In this case, the server gets the "server end" by passing ap as an argument to accept in the go function, whereas the client (running in the browser) gets the "client end" by passing ap as an argument to request.

More involved examples

You can find a distributed chat server in examples/distribution/chatserver. Run it from that directory to get styles to work.

Still to do

  • We don't do anything fancy when a client goes away mid-session. This is the subject of ongoing research, but we're looking to repurpose Affine Sessions (Mostrous & Vasconcelos, 2014).

  • Access points for sessions can only reside on the server at the moment.

  • Multiparty sessions

Clone this wiki locally