Debugger Proxy

The debugger proxy (green block below) sits between the running process and the debugger implementation. It is responsible for connecting to the running process either directly, through a debug server or in another way. The debug socket starts the proxy and manages the connection to the debugger implementation. The proxy process is a long-running process that can survive a user session and accepts connections from the debug socket.

The debugger implementation determines the code that is run as the proxy process via the getProxySource() function. You can either use the default proxy as the example in the linked article shows, or you can implement your own proxy as done by the GDB debugger implementation.

818818

Function of the Proxy

The debugger proxy has three primary functions as outlined below. Beyond these functions you are free to add any logic that helps increase the performance by performing logic closer to the debugger rather than on a client.

The simplest example is the default proxy that is available via debug.proxySource. You can check out the source code here.

Listening for Cloud9 Clients

The proxy must accept connections from multiple clients on a port that is equal to the debugport of the runner + 1. The simplest way is to create a TCP server that listens to that port and then forward any data in both directions to the debugger. The proxy should stay around even after all clients have disconnected.

❗️

The default proxy that is used currently doesn't support multiple clients. This will be updated in the near future. If you are creating your own proxy we advise you to take this into account and allow multiple Cloud9 clients to connect.

Connecting to the Debugger

The second responsibility is to connect to the debugger. This is particular to the debugger and is completely up to your implementation.

Here's a table describing how three different implementations handle this.

DebuggerWay the Proxy connects
v8Tries to connect the debugport of the runner with an interval of 300ms for each try and it tries 100 times.
gdbStarts the GDB Server and interacts with it over stdin/stdout.
xdebugStarts a listening TCP server on the debugport of the runner and waits until xdebug connects.

Sending the Ready-State to the Debug Socket

Once the proxy connects to the debugger and it has created a TCP server to accept client connections, the proxy should notify the socket that it can start listening.

To do this send the character ß as the only output on stdout. This will trigger the debug socket to connect.

Debugger Proxy Life Cycle

It is up to you to decide how to manage the life cycle of the socket and the proxy process. These are the most common strategies:

One proxy per debugger session

This is the strategy chosen by the v8 debugger. Each time the running process is stopped the debugger proxy will terminate itself. This means that the next time a debugger socket is created it automatically starts a new debugger proxy.

There is some overhead related to attaching the debugger this way, mostly related to the latency of the connection:

1 roundtrip for the net.connect() that fails
1 roundtrip + bandwidth for the node process to start
the time it takes for the node process to start
half of a roundtrip for the start signal
1 roundtrip for the net.connect() that succeeds

This can be < 100ms for connections with latency of 30ms or less. When latency is between 100 - 200ms this can quickly add up to over half a second.

One proxy for multiple debugger session

This is the strategy chosen by the xdebug debugger. The debugger proxy stays around after it is started and keeps listening for a new xdebug process. This means the initial connection time is similar to the one described above and subsequent connection times only take the time of 1 roundtrip.

The main difficulty with this strategy is determining when to kill the proxy. This could be done after a long timeout or never. This choice is up to you.