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).