Multiple Io VM's under OpenMP revisited

Filed under: io openmp 

It came up the other day on the Io mailing list that managing multiple Io VM's (each with an HTTP server) was a bit too complicated. Since I'd done a little testing in this area, I decided to see what I could come up with.

First off, we need a bootstrap to launch our VM's. Again I'm utilizing OpenMP to this end:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <getopt.h>
#include <omp.h>
#include "IoState.h"

void usage ( char *program ) {
  fprintf ( stderr, "Usage: %s [-n threads] <script>\n", program );
  exit ( EXIT_FAILURE );
}

char *mmap_src ( char *script ) {
  int src;
  struct stat finfo;
  char *buffer;

  src = open ( script, O_RDONLY );
  fstat ( src, &finfo );
  buffer = mmap ( 0, finfo.st_size, PROT_READ, MAP_SHARED, src, 0 );
  close ( src );

  return buffer;
}

void spawn_vm ( char *buffer ) {
  IoState *self;

  self = IoState_new ( );
  IoState_init ( self );
  IoState_doCString_ ( self, buffer );
  IoState_free ( self );
}

int main ( int argc, char **argv ) {
  int threadcount = 0;
  char *script = NULL, *script_src;
  int opt;

  while ( ( opt = getopt ( argc, argv, "n:" ) ) != -1 ) {
    switch ( opt ) {
    case 'n':
      threadcount = atoi ( optarg );
      break;
    default:
      usage ( argv [ 0 ] );
    }
  }

  if ( ! ( script = argv [ optind ] ) )
    usage ( argv [ 0 ] );

  script_src = mmap_src ( script );

  if ( threadcount < 1 )
    threadcount = omp_get_num_procs ( );
  threadcount++;
  omp_set_num_threads ( threadcount );

#pragma omp parallel
  {
#pragma omp single nowait
    spawn_vm ( mmap_src ( "do_server.io" ) );

    spawn_vm ( script_src );
  }

  return 0;
}

This is called mio.c and is compiled with:

gcc -fopenmp -Wall -I/usr/local/include/io \
      -L/usr/local/lib/io -liovmall -lgomp -o mio mio.c

Next, we need a DistributedObjects server so we can centralize our configuration (and maybe use it for other stuff later on). We'll name this file do_server.io:

Shared := Object clone do (
    vars := Map clone
    ip_addr := "127.0.0.1"
    ip_port := 5000

    put := method ( identifier, value,
        vars atPut ( identifier, value )
        return value
    )

    get := method ( identifier,
        return vars at ( identifier )
    )

    addr := method ( return ip_addr )

    port := method (
        ip_port = ip_port + 1
        return ip_port
    )
)

doServer := DOServer clone do (
    setRootObject ( Shared clone )
    setPort ( 8456 )
    writeln ( "Starting DistributedObject Server at 8456" )
)

doServer start

And finally, our HTTP servers:

loop ( // until we have a connection to the DOServer
    System sleep ( 0.5 )
    shared := nil
    do_conn := DOConnection clone setHost ( "127.0.0.1" ) setPort ( 8456 ) connect
    e := try (
        shared = do_conn serverObject
    )
    if ( shared type == "DOProxy", break )
)

server := HttpServer clone do (
    addr := shared addr
    port := shared port
    setHost ( addr )
    setPort ( port )

    writeln ( "Starting HttpServer at #{addr}:#{port}" interpolate )

    renderResponse := method ( request, response,
        response body = (
            "<html><body>hello, world from #{addr}:#{port}</body></html>" interpolate
        )
    )
)

server start

We'll name this file runserver.io.

Now we can run our servers with something like:

./mio -n 4 runserver.io

Of course, it's not much use to run four separate servers on four separate ports, so we need a proxy. As usual, I'll utilize Nginx, which is extremely fast and lightweight and has built-in load-balancing features:

upstream io_server {
    server 127.0.0.1:5001;
    server 127.0.0.1:5002;
    server 127.0.0.1:5003;
    server 127.0.0.1:5004;
}

server {
    listen       80;
    server_name  _;

    location / {
        proxy_pass http://io_server;
    }
}

(obviously this config is just showing the relevant bits to our application).



0 comments Leave a comment