Auto-tools/Projects/SUTAgentImplementation

From MozillaWiki
< Auto-tools‎ | Projects
Revision as of 15:54, 7 August 2012 by Mcote (talk | contribs) (Created page with "= Overall Design = The SUTAgent must handle multiple connections and concurrent events. It is expected, however, that often only one or two connections will be established. Thre...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Overall Design

The SUTAgent must handle multiple connections and concurrent events. It is expected, however, that often only one or two connections will be established. Threading, therefore, is considered overkill and too big a potential source of bugs. We shall instead use a reactor-based model for concurrency.

The majority of SUTAgent commands are very simple and can easily be executed synchronously, that is, returning a response immediately in the scope of a single event-loop iteration. Therefore, they don't need to keep state, and we can simplify our command-handler code.

There are a few exceptions to this, namely, exec, pull, push, and possibly hash. To service these requests, we will use specific event handlers that override the main command event handler for the duration of the command.

For platform independence, we will use NSPR.

Components

BufferedSocket

BufferedSocket is just a wrapper around a socket that allows buffering in userspace. Significantly, it supports an unget() method that can return data to the buffer, in case a handler overreads.

Reactor

The Reactor is the central event dispatcher. It uses PR_Poll() from NSPR. Since PR_Poll() only listens to events on sockets, we will need to use polling via timeouts to check subprocesses launched via exec rather than pipe handles. Waiting on filesystem events is unsupported on most operating systems anyway.

The Reactor owns EventHandlers and calls handle_event() on them when a socket event is detected or a timeout is reached. The Reactor deletes a handler when it is closed.

EventHandler

An abstract class with three functions, all used by the Reactor:

  • get_handles(): returns any events for which the handler would like notifications
  • handle_events(): takes a list of events for which the Reactor received a notification in the last iteration. The Handler does application-specific processing here. This function returns 'true' if the EventHandler did anything with the data, or 'false' otherwise. This is used to see if the loop should iterate again before going back to waiting for events (when all EventHandlers' handle_events() return false).
  • closed(): indicates if a handler is finished, which means it should be deleted by the Reactor (or proxy, see below).

SocketAcceptorEventHandler

An EventHandler that listens for connections on the given socket and creates BufferedSockets and SessionEventHandlers, passing the latter to the Reactor.

SessionEventHandler

A SessionEventHandler represents one session, e.g. the duration of a connection. Because connections can have two states--commands and data--it is essentially a proxy to a stack of EventHandlers, which will normally be one or two. It redirects all requests for handles and event notifications to the top-most EventHandler. It also maintains any session state, such as the current working directory.

When created, it immediately creates a CommandEventHandler and pushes it into the EventHandler stack. The CommandEventHandler takes a pointed to the SessionEventHandler so that it can alter the current working directory and push other EventHandlers onto its stack.

Its handle_events() implementation passes the notifications onto the top-most EventHandler on its stack. It also flushes the BufferedSocket output buffer, if needed.

Similarly, its get_handles() implementation returns the handles from the top-most EventHandler on its stack. It also adds a write event for the BufferedSocket, if there is buffered, unsent data.

If it detects that an EventHandler is closed, it deletes it. When the stack is empty, and all buffered data has been sent, it closes itself so that the Reactor can delete it.

CommandEventHandler

Looks for socket read events, and reads a line from the socket, breaks it up into command and arguments, and services the command. For shorter commands, it writes the response directly to the BufferedSocket (which may cause it to be buffered, in which case the SessionEventHandler is responsible for flushing). For longer commands, it pushes a new EventHandler onto the stack. In any case, if it reads more than a line of data, it unget()s the extra data.

DataEventHandler

Represents a data stream. Used for push and pull commands. Maintains some state, i.e. how much of the file has been received or sent. When finished, closes itself, so that the SessionEventHandler pops it off the stack and deletes it, returning to regular command processing. As with the CommandEventHandler, if it reads too much data, it unget()s the extra data.

SubprocessEventHandler

Represents a subprocess. Its get_handles() just returns a timeout, since, for platform-independence, we have to poll subprocesses to determine when they are complete. It may also write data to the BufferedSocket. It is expected that this will have to be subclassed for each platform and probably created via a platform-specific factory object. When the process exits, this EventHandler closes, so that the SessionEventHandler can return to command processing.