Hadean

 

Following the path trodden by many tutorials, we will also begin by building a "Hello World" program. This example will offer a bit more functionality, as simply printing "Hello, world!" to the console in your language of choice is not desperately interesting. Because Hadean is meant to provide a platform for building distributed applications, our example will be a "distributed" Hello World. That said, let's start by having a look at some code:

A "Hello World" Example

    #include <stdio.h>
    #include <string.h>
    #include <hadean.h>

    void run(void *arg, size_t arglen) {
        printf("hello: running\n");
        HadeanChannel parent_chan;
        hadean_accept(&parent_chan);
        printf("accept successful\n");
        size_t len;
        hadean_channel_recv(&parent_chan, &len, sizeof(size_t));
        char buf[len];
        hadean_channel_recv(&parent_chan, buf, len);
        printf("Child received '%s'(%zd) from parent\n", buf, len);
        const char msg[] = "Hello from Child!";
        len = sizeof(msg);
        hadean_channel_send(&parent_chan, &len, sizeof(size_t));
        hadean_channel_send(&parent_chan, msg, len);
        printf("child send successful\n");
        hadean_channel_close(&parent_chan);
    }
    
    int main(int argc, const char **argv) {
        printf("entered main\n");
        hadean_init();
        printf("init successful\n");
        HadeanPid child;
        unsigned long argval = 0;
        hadean_spawn(run, &argval, sizeof(argval), &child);
        printf("spawn successful\n");
        HadeanChannel child_chan;
        hadean_connect(&child_chan, child);
        printf("connect successful\n");
        const char msg[] = "Hello from Parent!";
        size_t len = sizeof(msg);
        hadean_channel_send(&child_chan, &len, sizeof(size_t));
        hadean_channel_send(&child_chan, msg, len);
        printf("parent send successful\n");
        printf("receiving from child\n");
        hadean_channel_recv(&child_chan, &len, sizeof(size_t));
        char buf[len];
        hadean_channel_recv(&child_chan, buf, len);
        printf("Parent received '%s'(%zd) from child\n", buf, len);
        return 0;
    }
    extern crate process as hadean;
    extern crate poly_channel;
    #[macro_use] extern crate log;
    
    fn run((): ()) {
        let mut parent = poly_channel::accept();
        let x: String = parent.recv();
        println!("Child received {} from parent", x);
        parent.send("Hello from Child!");
    }
    
    fn main() {
        hadean::init();
        let pid = hadean::spawn(run, ());
        let mut child = poly_channel::connect(pid);
        child.send("Hello from Parent!");
        let x: String = child.recv();
        println!("Parent received {} from child", x);
    }

 

At a high level, what this program does is:

Parent Step 1: Initialize the Hadean framework
 hadean_init();
 hadean::init();

 

hadean_init() must be one of the first things that your main() function calls. If the Hadean process calling this function is a root process nothing happens, but if the calling Hadean process is a child process, this is where the program’s control-flow is intercepted and execution continues from the function specified in the spawn() call that created the process (explained further below).

Parent Step 2:  "Spawn" a child Hadean process. Spawn() is effectively a fork() syscall that can (and often will) create the new process on a different machine
    HadeanPid child;
    unsigned long argval = 0;
    hadean_spawn(run, &argval, sizeof(argval), &child); 
    
 let pid = hadean::spawn(run, ());

 

The spawn() call in Hadean creates a new Hadean process, allocates resources (CPU and RAM) for it, and starts it running. Where this new Child process will physically run is up to the Hadean framework and will be decided by the Scheduler. The caller passes a function reference and a list of arguments to spawn(), which will be used when the Child process (eventually) calls hadean_init().

This is where Hadean does a lot of the “heavy lifting” for a user of the framework. Without Hadean, a user would need to: 

  1. Allocate (via a cloud provider) or claim (via some external mechanism) a new compute resource, a machine called M.
  2. Transfer the binary, command-line arguments, and environment (i.e., all the `env` variables) to M.
  3. Remotely execute the transferred binary on M using a wrapper of some sort to capture `stdout`, `stderr`, and the eventual exit status of the process.

Note that this is just the spawn() itself, and does not include any provisions for communicating with this new child process that is running on M.

Parent Step 3: Create a communication channel to the child process created in Parent Step 2
    HadeanChannel child_chan;
    hadean_connect(&child_chan, child); 
    
 let mut child = poly_channel::connect(pid);

 

Within the Hadean framework, there are two ways to create a communication channel between processes:

  1. Channel connect(pid) - actively connect to the process represented by pid, then return the resulting Channel object.
  2. Channel accept() - wait for another process to connect() to our process, then return the resulting Channel object.

An important thing to note here is that Hadean provides two types of channels:

  1. ByteStream channel - exactly what you would expect given the name: it provides a channel abstraction that is just a simple stream of bytes. Hadean provides no endianness guarantees, etc.
  2. PolyType channel - a higher-level abstraction that allows you to send arbitrary (as long as they serializable) data types over the channel. Hadean ensures that your data item is serialized, sent, and deserialized at the receiving end.

Here is another place where the Hadean framework does a lot of work for its users. Each process is identified by a Hadean PID and that is all the information that is needed to open a channel to the process. Developers using Hadean do not need to worry about whether their communicating processes are running on the same machine or different machines, what their IP addresses are, what ports are open/available, whether the machine(s) have multiple IP addresses (e.g., public/private), etc. When you spawn a Hadean process, the returned PID, is the only reference you’ll ever need. Additionally, the PolyType channels make exchanging data between processes running on arbitrary machines even simpler. Thinking about network byte-ordering or serialization issues is no longer a concern. 

Parent Step 4: Send data ("Hello from Parent!") to the child process 
    const char msg[] = "Hello from Parent!";
    size_t len = sizeof(msg);
    hadean_channel_send(&child_chan, &len, sizeof(size_t));
    hadean_channel_send(&child_chan, msg, len); 
    
    child.send("Hello from Parent!");const char msg[] = "Hello from Parent!";
    size_t len = sizeof(msg);
    hadean_channel_send(&child_chan, &len, sizeof(size_t));
    hadean_channel_send(&child_chan, msg, len);

 

<C/C++>

Hadean’s PolyType channels are not available through our C API, so Hadean C programs must rely on ByteStream channels. As such, there is a bit of “boilerplate” work to establish a simplistic protocol. These processes simply send an integer containing the size of the data type that will be sent.

<RUST>

Because we’re using a PolyType channel here, we don’t need to worry about serializing, network byte-ordering, or anything else. We can simply pass an object to send() and the Hadean framework will handle the rest.

 
Parent Step 5: Receive data from child
    hadean_channel_recv(&child_chan, &len, sizeof(size_t));
    char buf[len];
    hadean_channel_recv(&child_chan, buf, len);
    
    let x: String = child.recv();

 

<C/C++>

Using the simple protocol described in the previous step, the Parent first reads an integer from the ByteStream channel, and then reads that many bytes additional from the channel to receive the actual message.

<RUST>

Once again, the use of a Hadean PolyType channel means we don’t need to worry about trying to determine exactly how many bytes must be received, checking data types, etc. Rust’s type checking system ensures that we will only receive an object of the specified type.

Parent Step 6) Print out the data received from the child:

    printf("Parent received '%s'(%zd) from child\n", buf, len);
    
    println!("Parent received {} from child", x);

 

Finally, we’ve reached the actually “Hello, world” portion of the program. Here we simply print out the string that we received from our Child.

Similarly, the child process does the same communication steps as the parent, but in the reverse order:

Child Step 1: Accept a communication channel from the parent
    HadeanChannel parent_chan;
    hadean_accept(&parent_chan);
    
    let mut parent = poly_channel::accept();

 

Accept a communication channel from Parent As we mentioned in P3 above, there are two ways of creating a channel. Here we see the other side, an accept() call. Because the Parent receives the Child’s PID upon the successful completion of the spawn() call, it is easy for the Parent to call connect(pid) and for the Child to call accept().

Child Step 2: Receive data ("Hello from Parent!") from the parent process
    size_t len;
    hadean_channel_recv(&parent_chan, &len, sizeof(size_t));
    char buf[len];
    hadean_channel_recv(&parent_chan, buf, len);
    
    let x: String = parent.recv();

 

Receive data from Parent Identical to Parent Step 5 above, reversing the roles of Parent and Child.

Child Step 3: Send data ("Hello from Child!") to the parent process
    const char msg[] = "Hello from Child!";
    len = sizeof(msg);
    hadean_channel_send(&parent_chan, &len, sizeof(size_t));
    hadean_channel_send(&parent_chan, msg, len);
    
    parent.send("Hello from Child!");

 

Send data to Parent Identical to Parent Step 4 above, reversing the roles of Parent and Child.

That's it. Hopefully we've provided you with a better understanding of how Hadean can make developing large-scale applications significantly easier using the "Hello World" example.

If you’re interested in learning more or applying for our closed Beta, please sign up for more information here.

Hadean
Hadean © 2018 All rights reserved. Privacy | Terms & Conditions

Hadean is an operating system designed for distribution and scale, its distribution first optimizations allow developers to build, run, and scale real-time applications at hyperscale.

4 Christopher Street
London
EC2A 2BS

8.00 AM - 6:00PM