AtomizeJS is a JavaScript library for writing distributed programs, that run in the browser, without having to write any application specific logic on the server.

Introduction

Writing concurrent programs is tricky. Languages tend to go down one of two routes: either threads are allowed to access the same data-structures directly, and it's left up to the programmer to decide how to manage locking; or the language presents a model whereby each thread has its own specific memory, and data is passed between threads through message passing. The former approach gives the programmer a slightly bigger gun to shoot themselves with: the ease of writing code that deadlocks is unrivalled. The latter is conceptually much simpler and more intuitive, but is relatively confined: languages such as Erlang or dedicated actor frameworks such as Akka are about as mainstream as it gets.

Writing distributed programs is trickier still. In addition to the issues of writing concurrent programs, you have further difficulties of dealing with the safe access or distribution of data across the system, combined with the increased potential for partial failures.

AtomizeJS is a project which aims to make it easy to write programs in JavaScript that can be both concurrent and distributed. It does this by implementing Distributed Software Transactional Memory (DSTM).

The mental model you should have when using AtomizeJS is as follows:

  • Assume there is an object graph that gets distributed automatically to every browser looking at your site.
  • To make safe changes to this object graph, you write functions which change the objects as desired. These functions are then run by AtomizeJS as transactions. AtomizeJS ensures that these transaction functions are run:
    • atomically: Transactions are atomic (all or nothing).
    • consistently: Transactions preserve the object graph's consistency. That is, a transaction transforms a consistent state of the object graph into another consistent state, without necessarily preserving consistency at all intermediate points.
    • in isolation: Transactions are isolated from one another. That is, even though in general there will be many transactions running concurrently, any given transaction's updates are concealed from all the rest, until that transaction commits. Another way of saying that same thing is that, for any two distinct transactions T1 and T2, T1 might see T2's updates (after T2 has committed) or T2 might see T1's updates (after T1 has committed), but certainly not both.

Transactions cannot deadlock: when a transaction function is run, AtomizeJS detects if other transactions have modified the same objects that are being altered by this function. If so, this function is automatically restarted with the updated current state-of-the-world. If a transaction is restarted then it will be because some other transaction has committed, thus the system as a whole has made progress.

Transactions allow programmers to safely manipulate data-structures shared between many threads, or in the case of AtomizeJS, clients (such as web browsers). You don't need to write distribution mechanisms as you would with message-passing: all the code you write assumes the data is local - you never write send or have receive call-backs.

Writing applications with transactions

Many applications do more and more work on the client-side - i.e. in the browser. But currently, if a client wants to modify some global state, the usual practice is to make some sort of request to the server, whether it's via XHR or WebSockets or some other means. The server receives the message and acts on it. This means you have to write both client-side code, and server-side code. But with AtomizeJS, you can just write client-side code, and rely on AtomizeJS to distribute the changes to all clients.

Indeed, I have written an entire multi-player game - bomberman that has no server-side code at all beyond the AtomizeJS server.

Beyond plain transactions

If a transaction allows you to safely modify global data, what about waiting for someone else to modify a value? If you can wait for someone else to modify a value then you can very easily implement the observer pattern, which, across global distributed data gets you broadcast and other general-purpose communication mechanisms, amongst other things. Well, AtomizeJS supports both the retry and orElse functions of STM, which gives you the ability to force a transaction to suspend and be restarted only when values which have been read as part of the transaction have been changed.

AtomizeJS also supports nested transactions.

On the server, a NodeJS server can also be a client to the AtomizeJS system. This means that you can, when you need or want to, write server side JavaScript that also uses AtomizeJS to safely modify global state that is shared with clients. There are no API changes at all.