Wiki

Clone wiki

chut / Home

Chut

Chut is an experimental core for a distributed chat system in Erlang. The system aims to support long-polling connections from many tabs in a browser, but could easily be adapted to a desktop client or anything really.

What's done

The system is currently pretty minimal. It includes the following:

  • User-to-user communication
  • Multiple clients for a single user is possible
  • Session timeouts
  • Distribution on multiple nodes
  • Basic web-server implementation
  • Javascript JSONP client
  • Basic history logs from previous conversations
  • General test suite
  • Should be a standard OTP application
  • Benchmarks of Chut's core

How it works

The first concept to understand is the idea of clients and users. A client is basically a connection from a socket, web server, browser tab, whatever. The user is a bunch of processes that represent a real world user. Clients connect to a user and can route messages through them for another user, which in turns routes them to that user's clients.

client-users

Users are composed of a supervisor, a gen_fsm and a gen_event:

Supervisor

The supervision tree of the application goes as follow:

supervision-tree

Here, chut_sup is the whole application's supervisor.

The chut_user_supersup acts as an entry point to the user hierarchy. A user can be killed by terminating then deleting the children from it. The chut_user_sup supervisor adopts a one-for-all restart strategy for the FSM and event manager, making sure the user can't exist without them. After that, there's only chut_webserver_sup, responsible for monitoring mochiweb's server.

gen_event

The event manager is responsible for routing messages from user to user, and also from a user to each of its clients. Each message sent from or to a given user must pass through the event manager.

The messages have the following format:

  • {received, From, Message}: Another user sent you a message
  • {sent, To, Message}: Your user sent someone else a message

Different event handlers attached to the event manager will be dealing with routing messages:

  • chut_user_dispatch_handler: Takes messages you send and routes them to another user. There is only one of these per user.
  • chut_user_listen_handler: Takes any kind of message received and transmits it to the client. The format of the message is altered to be {{UserId, HandlerId}, {received, From, Message} } or {{UserId, HandlerId}, {sent, To, Message} } so a given process can hold more than one client connection. There is one handler of this kind per client and each of them is added with gen_event:add_sup_handler/3, which guarantees that if the client dies, the handler also dies (the opposite is also true, except for a normal exit).

Another handler, namely chut_user_history_handler, exists with the sole purpose of accumulating the messages sent through a user. When the notification {From, history} (where From is a PID) is sent, this handler will send back a list of recent messages routed through the user.

These three handlers are enough to do all the message routing and history keeping. Additional handlers could be added to do logging, presence updates, etc.

gen_fsm

The finite state machine acts as a manager for state. It tracks the addition and deletion of chut_user_listen_handlers to the user. When there are no listeners left on the user, the state machine falls into a waiting state. If no handler is added within the given time limit (determined when starting the supervisor), the FSM kills the supervisor by asking chut_user_supersup to terminate it, bringing down the user itself.

This time delay allows a user with only 1 client in a browser to refresh pages and stay connected.

The user as a whole can be illustrated that way: user

Addressing

Each user is thus made of 3 processes individually registered with the global module. The names used are UserId, {manager, UserId} and {monitor, UserId} for the supervisor, event manager and the finite state machine, respectively.

The global module was used instead of solutions like mnesia for reasons such as automatic monitoring of registered processes, the possibility to define procedures in case of netsplits when the network comes back up, a local lookup for names, etc. This system allows to have instant distribution over many nodes.

Web Server and javascript implementation

The current implementation works with a basic mochiweb server implementation. It supports three basic operations: listen, message and history and responds to JSONP requests only.

The javascript client has been rewritten to rely purely on jQuery and uses JSONP to avoid stack overflows and to bypass the browser limit in connections to a single domain. It also lets the implementation circumvent the same origin policy. Visually speaking, the client now supports one separate conversation block for each person someone might be talking to. Such a block is automatically added on the reception of a message or when there were entries in the recent user history with that person. Here's what it looks like:

Chut's UI

This ugly duckling is of course meant to be re-skinned in CSS by any site to fit in with its design. All that's provided here is basic style.

JSONP has the downside of appearing as if the page wasn't done loading in many browsers. The two ways there are to go around this issue are to either use iframes or use fake subdomains that increment in every client (facebook does it this way). basic JSONP was chosen for now given there are many issues to solve before that one. The facebook approach is something Chut is unlikely to implement because of the comparatively complex setup requirements.

So far the client seems to work fine with FF3.0.x, FF3.5+, Opera 10.x, Chrome, Safari 4.0.x, IE6, IE7 and IE8.

What's left to be done

  • Authentication for users
  • Adding logging to messages sent and received
  • Function type annotations (-specs for dialyzer)
  • Notifying the user when someone he's talking to disconnects
  • Specifying functions to deal with netsplits and user registration
  • Add more unit tests
  • Additional Benchmarks (with mochiweb)
  • Making the conversation history more flexible
  • Adding wiki entries about how to build Chut
  • Clean up the feedback loops with an intermediary process handling the resources (use transients simple_one_for_one?)
  • And much more!

Note that Javascript/HTML features are not seen as a priority given the integration of such a chat client would be left in the hands of any site integrating it. The JS side isn't likely to be made much better with time unless I'm satisfied enough with the Erlang core.

How it's gonna be done

Because Chut is meant to be plugged into an existing system, the maximum that will be done about authentication will be to do callbacks to a server that will care about all the authentication-related stuff. Chut will require a session ID (to be set in the user's cookies by the other site) and a session token (to be set in the page itself). On each call coming from the site, Chut's JS system will need to send the session token to the server with the cookie. The server will then act only after validating these. This will help protect against CSRF and identity theft, but will leave the burden of user authentication and storage on the main site, where it belongs. A cache with values for session-id and username could be added to avoid repeated calls to the main site.

For the logging system, a simple event handler listening to every message sent and logging them on disk somewhere (or in a database) is trivial to add. That's all that needs to be done, given we have a database table already open.

To notify a user someone he's talking to has disconnected, two options are possible. The first one is to add a lookup on each message sent (from the dispatch handler), which would then report that event to all connected clients. The second option is that when a discussion starts between two users, the FSM starts monitoring the other user. Whenever the FSM receives a message mentioning the death of a monitored user, the clients are made aware of it. This last option would require the potential addition of new functions to usr.erl, but would reduce the amount of lookups made within the global module and would let the status of the other user be known in real time rather than the sending of a message.

More unit tests should be one of the top priority also. So far, only a general common test suite exists, testing the general behavior of the chat users and client modules. A finer grained testing approach could help optimize some potential bottlenecks and increase confidence in the code. Load and performance testing is also needed, in order to know what load the prototype can support and if using global the way it is used might become a bottleneck when under heavy load.

As for the conversation history, it should be trivial to make it better (only keeping x messages in memory) to something more flexible. The history message could be changed to take the form {From, {history, LastId}} where LastId might just be {now(), make_ref()} and would refer to the last message read. Otherwise, having a conversation history that could be dispatched depending on the receiving party would also be useful. However, given what's left to do in other areas, these ameliorations will be delayed a bit.

Updated