Skip to content

Three endpoints, no connections tutorial

Harrison Ho edited this page Jun 18, 2013 · 4 revisions

Previous tutorials focused on how to coordinate actions between two endpoints, each on a different host. For some applications, you may want to call methods on endpoints besides the one you're directly connected to (eg., to broadcast a message). This tutorial shows you how to call methods between endpoints on the same host.

Bank account example

Waldo provides a special Endpoint type, which encapsulates an endpoint object. A Waldo program can call into any Public method of an Endpoint-typed variable's encapsulated endpoint object. For instance, assume that we specify a Waldo endpoint for an account balance:

BankAccountExample
Endpoint Account;

Account
{
    Number balance;
    Number acct_id;
    onCreate(Number initial_balance, Number in_acct_id)
    {
        balance = initial_balance;
        acct_id = in_acct_id;
    }

    Public Function get_balance() returns Number
    {
        return balance;
    }

    Public Function set_balance(Number new_value)
    {
        balance = new_value;
    }

    Public Function get_id() returns Number
    {
        return acct_id;
    }
}

We can then build a bank composed of these endpoints with the following Waldo file:

BankExample
Endpoint Bank;

Bank
{
    // tracks all accounts that are in the bank.
    Map (from: Number, to: Endpoint) all_endpoints;

    // Pushes endpoint into map
    Public Function add_endpoint (Endpoint endpt)
    {
        Number id = endpt.get_id();
        all_endpoints[id] = endpt;
    }

    // Runs through all accounts on this endpoint and returns
    // how much total money this bank is holding
    Public Function total_money_held () returns Number
    {
        Number total_balance = 0;
        for (Number key in all_endpoints)
        {
            Endpoint endpt = all_endpoints[key];
            total_balance += endpt.get_balance();
        }
        return total_balance;
    }

    // Transfer money from one account to another.  Return True if action completed, 
    // return False otherwise.
    Public Function transfer_money(Number from_id, Number to_id, Number amount)
        returns TrueFalse
    {
         if ((not (from_id in all_endpoints)) or
             (not (to_id in all_endpoints)))
         {
             return False;
         }

         Endpoint from_endpoint = all_endpoints[from_id];
         if (amount > from_endpoint.get_balance())
             return False;

         Endpoint to_endpoint = all_endpoints[to_id];
         from_endpoint.set_balance( from_endpoint.get_balance() - amount);
         to_endpoint.set_balance( to_endpoint.get_balance() + amount);
         return True;
    }
}

Endpoint call memory model

Recall that for a single connection, Waldo's memory model assures the programmer that data cannot change out from under him/her. Ie., there will be no read-write conflicts with other events that the system is processing.

Endpoint calls provide the same guarantee. In the transfer_money method defined on the bank, after checking the amount of money in from_endpoint, the Waldo programmer did not have to hold a lock on from_endpoint's balance or re-check it to ensure that it was still the value that we read before. The Waldo runtime takes care of this for the programmer. Any other event that was simultaneously running and tried to modify the balance at the same time will be rolled back and retried.

Etc.

There's no type checking across endpoint calls. This permits some flexibility: not all endpoints need to be declared before your system runs. But it's also dangerous: if you call a method that does not actually exist on an Endpoint typed variable, you'll get a really ugly runtime error. Please don't do this.