8.3.0.10

Lecture 31: Remote Proxying

The lecture is a brief introduction to turning a monolithic system into a distributed one using the remote proxy pattern.

Here is a simplistic monolithic system:
(define server%
  (class object% (init-field client)
    (super-new)
 
    (define/public (go)
      (ping)
      (define x (pong))
      (when (< x 10) (go)))
 
    (define/public (ping)
      (send client ding 'ping))
 
    (define/public (pong)
      (send client dong 'pong))))

    

(define client%
  (class object%
    (super-new)
 
    (field [counter 0])
 
    (define/public (ding x)
      (displayln `[ding received ,x])
      (set! counter (+ counter 1))
      counter)
 
    (define/public (dong y)
      (displayln `[dong received ,y])
      (set! counter (+ counter 1))
      counter)))

    Server               Client

      |                    |

      |                    |

      |   ding('ping)      |  ping the client

      | -----------------> |

      |   N                |  the client returns

      | <================= |  a strictly increasing

      |                    |  sequence of naturals

      |                    |

      |                    |

      |                    |

      |   dong('pong)      |  pong the client

      | -----------------> |

      |   N                |

      | <================= |

      .                    .

      .                    .

      .                    .

    

      |   dong('pong)      |

      | -----------------> |

      |   N                |

      | <================= |

      |                    |  Stop when result >= 10

    -----                ----

    

Figure 1: The Local Protocol

Let’s get the system started:
(define (monolithic-launch)
  (define client (new client%))
  (define server (new server% [client client]))
 
  (send server go)
  "all done")

Now imagine we would like to distribute this system across two different machines. That is, the server and the client no longer run in the same program (or even on the same machine), but in two different places. One way to imagine this goal is to cut the method calls between the two, as in figure 2.

    Server                                   Client

      |                                         |

      |                                         |

      |   ding('ping)                           |  ping the client

      | -----------------                 --->  |

      |   N                                     |  the client returns

      | <================                 ====  |  a strictly increasing

      |                                         |  sequence of naturals

      |                                         |

      |                                         |

      |                                         |

      |   dong('pong)                           |  pong the client

      | -----------------                 --->  |

      |   N                                     |

      | <================                 ====  |

      .                                         .

      .                                         .

      .                                         .

    

      |   dong('pong)                           |

      | -----------------                 --->  |

      |   N                                     |

      | <================                 ====  |

      |                                         |  Stop when result >= 10

    -----                                     ----

Figure 2: The Local Protocol, with Connections Cut

We know that we need to fill the gap with a TCP message. The question is “how,” because simply putting a squiggly line there to indicate a TCP connection is not enough:

    |   dong('pong)                           |

    | ----------------- ~~~~~~~~~~~~~~~ --->  |

    |   N                                     |

    | <================ ~~~~~~~~~~~~~~~ ====  |

What is needed is one mechanism that translates the method call into a TCP message and decodes the resulting response and another mechanism that decodes a TCP message, turns into a method call, and turns the result into a TCP response. We need a bridge between two dangling ends.

The so-called remote-proxy design pattern is the easiest way to implement this bridge. The remote-proxy design calls for the insertion of two life lines:
  1. one that replaces and simulates the client on the server side;

  2. another one that replaces and simulates the server on the client side.

The first one is obviously an object with the same interface as the client but with TCP translation capabilities. The second one calls for a bit more thinking. Anyways, figure 3 shows the resulting protocol.

 Server                ClProxy          SeProxy Client

   |                    |                  |      |

   |   ding('ping)      |                  |      |  ping the client

   | -----------------> | ~~~~~~~~~~~~~~~~ | ---> |

   |   N                |                         |  the client returns

   | <====-============ | <~~~~~~~~~~~~~~~ | <=== |  a strictly increasing

   |                    |                  |      |

   |                    |                  |      |  sequence of naturals

   |   dong('pong)      |                  |      |  pong the client

   | -----------------> | ~~~~~~~~~~~~~~ > | ---> |

   |   N                                          |

   | <================  | <~~~~~~~~~~~~~~~ | <=== |

   |                    |                  |      |

   .                                              .

   .                                              .

   .                                              .

   |                    |                  |      |

   |   dong('pong)      |                  |      |

   | -----------------> | ~~~~~~~~~~~~~~~> | ---> |

   |   N                                          |

   | <================ | <~~~~~~~~~~~~~~~~ | <=== |

   |                                              |  Stop when result >= 10

 -----                                           ---

Figure 3: The Remote Protocol Cut

(define proxy-client%
  (class object% (init-field in out)
    (super-new)
 
    (define/private (serialize l) (~a l))
    (define/private (deserialize x) x)
 
    (define/public (ding x)
      (send-message `["ding" ,(serialize x)] out)
      (deserialize (read-message in)))
 
    (define/public (dong y)
      (send-message `["dong" ,(serialize y)] out)
      (deserialize (read-message in)))))

    

(define proxy-server%
  (class object% [init-field client in out]
    (super-new)
 
    (define/private (serialize l) l)
    (define/private (deserialize x)
      (string->symbol x))
 
    (define/public (go)
      (define result
        (match (read-message in)
          [`["ding" ,x]
           (send client ding (deserialize x))]
          [`["dong" ,y]
           (send client dong (deserialize y))]
          [v (error "something bad happened: ~e" v)]))
      (send-message (serialize result) out)
      (go))))

To launch these two independently, let’s use two different launch functions:
(define (server-launch)
  (define listener (tcp-listen 12345 30 #true))
  ; accept TCP connection from client,
  ; set up input/output streams:
  (define-values (client-in client-out)
    (tcp-accept listener))
  (define client
    (new proxy-client%
      [in client-in]
      [out client-out]))
  (define server
    (new server% [client client]))
  (send server go)
  "all done")

    

(define (client-launch)
  ; connect client to server (locally),
  ; open input/output streams
  (define-values (server-in server-out)
    (tcp-connect "127.0.0.1" 12345))
  (define client
    (new client%))
  (define server
    (new proxy-server%
      [client client]
      [in server-in]
      [out server-out]))
  (send server go))

Puzzle When should the client really shut down?

Resources

The SwDev/Testing/communication is a public github repo, if you wish to experiment with the code.

image

09:15am

Code Walk 1:

Presenters: Adam Melhuish, Gabrielle Bruck

 

Head Reader: Jaana Tabalon

Assistant Reader: Graham Preston

Secretary: Jake Howard

01:35pm

Code Walk 1:

Presenters: Anmol Sakarda, Hunter Fingado

 

Head Reader: Cameron Scoons

Assistant Reader: Samuel Richards

Secretary: Benjamin Knower

Code Walk 2:

Presenters: Benjamin Blaustein, Elijah Olson

 

Head Reader: Stephen Jayne

Assistant Reader: Yuheng Dong

Secretary: Jason Enright

04:35pm

Code Walk 1:

Presenters: Ken Zou, Robert Yin

 

Head Reader: Jacob Chvatal

Assistant Reader: Sean Pomerantz

Secretary: Christian Yiu

Code Walk 2:

Presenters: Kyle Posluns, Michaela Perrotta

 

Head Reader: Harshal Nawade

Assistant Reader: Thomas Sarni

Secretary: Zain Aaban