1

I'm building a Promela model in which one process send a request to N other processes, waits for the replies, and then computes a value. Basically a typical map-reduce style execution flow. Currently my model sends requests in a fixed order. I'd like to generalize this to send a non-deterministic order. I've looked at the select statement, but that appears to select a single element non-deterministically.

Is there a good pattern for achieving this? Here the basic structure of what I'm working with:

#define NUM_OBJECTS 2
chan obj_req[NUM_OBJECTS] = [0] of { mtype, chan };

This is the object process that responds to msgtype messages with some value that it computes.

proctype Object(chan request) {
  chan reply;

end:
  do
  :: request ? msgtype(reply) ->
    int value = 23
    reply ! value
  od;
}

This is the client. It sends a request to each of the objects in order 0, 1, 2, ..., and collects all the responses and reduces the values.

proctype Client() {
  chan obj_reply = [0] of { int };
  int value

  // WOULD LIKE NON-DETERMINISM HERE
  for (i in obj_req) {
    obj_req[i] ! msgtype(obj_reply)
    obj_reply ? value
    // do something with value
  }
}

And I start up the system like this

init {
  atomic {
    run Object(obj_req[0]);
    run Object(obj_req[1]);
    run Client();
  }
}
Noah Watkins
  • 5,446
  • 3
  • 34
  • 47

1 Answers1

2

From your question I gather that you want to assign a task to a given process in a randomised order, as opposed to simply assign a random task to an ordered sequence of processes.

All in all, the solution for both approaches is very similar. I don't know whether the one I am going to propose is the most elegant approach, though.

#define NUM_OBJECTS 10

mtype = { ASSIGN_TASK };
chan obj_req[NUM_OBJECTS] = [0] of { mtype, chan, int };

init
{
    byte i;
    for (i in obj_req) {
        run Object(i, obj_req[i]);
    }
    run Client();
};

proctype Client ()
{
    byte i, id;
    int value;
    byte map[NUM_OBJECTS];
    int data[NUM_OBJECTS];
    chan obj_reply = [NUM_OBJECTS] of { byte, int };

    d_step {
        for (i in obj_req) {
            map[i] = i;
        }
    }

    // scramble task assignment map
    for (i in obj_req) {
        byte j;
        select(j : 0 .. (NUM_OBJECTS - 1));
        byte tmp = map[i];
        map[i] = map[j];
        map[j] = tmp;
    }

    // assign tasks
    for (i in obj_req) {
        obj_req[map[i]] ! ASSIGN_TASK(obj_reply, data[i]);
    }

    // out-of-order wait of data
    for (i in obj_req) {
        obj_reply ? id(value);
        printf("Object[%d]: end!\n", id, value);
    }

    printf("client ends\n");
};


proctype Object(byte id; chan request)
{
  chan reply;
  int in_data;

end:
  do
    :: request ? ASSIGN_TASK(reply, in_data) ->
        printf("Object[%d]: start!\n", id)
        reply ! id(id)
  od;
};

The idea is have an array which acts like a map from the set of indexes to the starting position (or, equivalently, to the assigned task). The map is then scrambled through a finite number of swap operations. After that, each object is assigned its own task in parallel, so they can all start more-or-less at the same time.


In the following output example, you can see that:

  • Objects are being assigned a task in a random order
  • Objects can complete the task in a different random order
~$ spin test.pml
              Object[1]: start!
                                              Object[9]: start!
          Object[0]: start!
                                  Object[6]: start!
                  Object[2]: start!
                                          Object[8]: start!
                          Object[4]: start!
                              Object[5]: start!
                      Object[3]: start!
                                      Object[7]: start!
                                                  Object[1]: end!
                                                  Object[9]: end!
                                                  Object[0]: end!
                                                  Object[6]: end!
                                                  Object[2]: end!
                                                  Object[4]: end!
                                                  Object[8]: end!
                                                  Object[5]: end!
                                                  Object[3]: end!
                                                  Object[7]: end!
                                                  client ends
      timeout
#processes: 11
...

If one wants to assign a random task to each object rather than starting them randomly, then it suffices to change:

        obj_req[map[i]] ! ASSIGN_TASK(obj_reply, data[i]);

into:

        obj_req[i] ! ASSIGN_TASK(obj_reply, data[map[i]]);

Obviously, data should be initialised to some meaningful content first.

Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
  • Wow, thanks for in depth explanation. You know I really should maybe been more explicit in my question. I think your solution is close to what I had come up with, but given the existence of `select`, my primary question was: is there a really elegant way to do this that doesn't involve all the looping and tracking of orders. In any case, i think this is a good question to have since this style of communication is quite common in distributed systems. – Noah Watkins Nov 14 '17 at 21:10
  • @NoahWatkins *afaik*, no, but perhaps someone else knows better – Patrick Trentin Nov 14 '17 at 21:15