Iris through Go

Bringing simplicity back to distributed services

Péter Szilágyi

European Institute for Innovation and Technology

Eötvös Loránd University, Budapest, Hungary

Babeş-Bolyai University, Cluj-Napoca, Romania

Note, these are the offline slides of the presentation. For executable codes, please check playground availability at http://iris.karalabe.com/talks.

What the heck is Iris?

Decentralized messaging framework

What does it do?

It communicates, of course!

Show me the code!

Challenge #1 – Group chat

Implement an IRC style group chat system:

Solution #1 – Broadcast

// Print all received broadcast messages
func (e *echo) HandleBroadcast(msg []byte) {
    fmt.Println(string(msg))
}

func main() {
    // Connect to the Iris network
    conn, err := iris.Connect(55555, "go-lobby", new(echo))
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Send a dummy message every second
    for i := 1; i <= 100; i++ {
        conn.Broadcast("go-lobby", []byte(fmt.Sprint("Alice: message #", i)))
        time.Sleep(time.Second)
    }
}

Hint: Rename Alice in a second window 😉

Broadcast highlights

iris.Connect(xyz int, app string, handler iris.ConnectionHandler) → iris.Connection
func (iris.Connection) Broadcast(app string, msg []byte)
func (iris.ConnectionHandler) HandleBroadcast(msg []byte)

Challenge #2 – Web requests

Implement a system for handling web requests:

Solution #2 – Browser

func main() {
    // Connect to the Iris network
    conn, err := iris.Connect(55555, "browser", nil)
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Issue a dummy request every second
    for i := 1; i <= 100; i++ {
        rep, err := conn.Request("server", []byte(fmt.Sprint("Request #", i)), time.Second)
        if err != nil {
            fmt.Println("Request failed:", err)
        } else {
            fmt.Println("Web reply: ", string(rep))
        }
        time.Sleep(time.Second)
    }
}

Hint: Start some webservers and check back 😉

Solution #2 – Web server

// Format each request a bit and return as the reply
func (s *server) HandleRequest(req []byte) []byte {
    return []byte(fmt.Sprint("www-", s.id, ": ", string(req)))
}

func main() {
    // Connect to the Iris network
    conn, err := iris.Connect(55555, "server", &server{id: rand.Intn(100)})
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Serve a while, then quit
    fmt.Println("Waiting for requests...")
    time.Sleep(100 * time.Second)
}

The presentation supports only one active demo process per window. Open new tab?

Request / Reply highlights

func (iris.Connection) Request(app string, req []byte, timeout int) → []byte
func (iris.ConnectionHandler) HandleRequest(req []byte) → []byte

Challenge #3 – Aperture Science Enrichment Center 😈

Implement the comlink for Aperture Laboratories¹:

¹ http://en.wikipedia.org/wiki/Portal_(video_game)

Solution #3 – GLaDOS

func main() {
    // Connect the network
    conn, err := iris.Connect(55555, "GLaDOS", nil)
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Publish the nice wishes
    fmt.Println("GLaDOS is online...")
    for {
        // Pick a random wish
        idx := rand.Intn(len(wishes))

        conn.Publish("official", []byte("GLaDOS: "+wishes[idx]))
        time.Sleep(5 * time.Second)
    }
}

Hint: Boot GLaDOS and let the experiment begin 😉

Solution #3 – Chell

func (e *echo) HandleEvent(msg []byte) {
    fmt.Printf("%s\n\n", string(msg))
}

func main() {
    // Connect to the network
    conn, err := iris.Connect(55555, "Chell", nil)
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Subscribe to some topics
    fmt.Println("Tuning in to Aperture channels...")
    for _, topic := range []string{"official", "portal"} {
        conn.Subscribe(topic, &echo{topic})
    }
    time.Sleep(100 * time.Second)
}

Hint: Maybe there is an "unofficial" channel? 😉

Publish / Subscribe highlights

func (iris.Connection) Subscribe(topic string, handler iris.SubscriptionHandler)
func (iris.Connection) Publish(topic string, msg []byte)
func (iris.SubscriptionHandler) HandleEvent(msg []byte)

Challenge #4 – Transactions

Implement a distributed transaction system:

Solution #4 – Client

    // Open an outbound tunnel to a database
    tun, err := conn.Tunnel("database", time.Second)
    if err != nil {
        fmt.Println("Database connection failed:", err)
        return
    }
    // Send a multipart transaction
    for i := 0; i < 10; i++ {
        tun.Send([]byte(fmt.Sprint("Part #", i)), time.Second)
    }
    // Retrieve the database replies
    for i := 0; i < 10; i++ {
        msg, _ := tun.Recv(time.Second)
        fmt.Println(string(msg))
    }
    tun.Close()

Hint: Start some database processes 😉

Solution #4 – Database

func (d *db) HandleTunnel(tun iris.Tunnel) {
    // Make sure tunnel is cleaned up
    defer tun.Close()
    tx := d.tx
    d.tx++

    // Echo back the arriving data until the tunnel is closed
    for {
        msg, err := tun.Recv(2 * time.Second)
        if err != nil {
            break
        }
        reply := fmt.Sprintf("db-%d (tx-%d): %s", d.id, tx, string(msg))
        tun.Send([]byte(reply), time.Second)
    }
}

The presentation supports only one active demo process per window. Open new tab?

Tunnel highlights

func (iris.Connection) Tunnel(app string) → iris.Tunnel
func (iris.Tunnel) Send(msg []byte, timeout int)
func (iris.Tunnel) Recv(timeout int) → []byte
func (iris.Tunnel) Close()

So how does this all work?

Sneak behind the scenes

Iris nodes do the heavy lifting (one/host):

Thin clients bathe in the glory:

Finally, my promise (xyz in iris.Connect):

Thank you

Péter Szilágyi

European Institute for Innovation and Technology

Eötvös Loránd University, Budapest, Hungary

Babeş-Bolyai University, Cluj-Napoca, Romania