State of the art in the fault-tolerance web application design for collaborative sound art
Taking a web browser as a platform for distributing sound art projects brings great possibilities for
making deep immersive collaborative experiences between artists and their audience, both online as
well as offline, even on a stage. And specifically for the web browser architecture, such rich
multimedia web applications are using a browser’s sandbox backed by JavaScript VM runtime. Being a
multi-paradigm language, while mixing everything together, JavaScript mostly loses its functional
roots purity and becomes a fertile environment for failures and errors to occur. Using traps for
catching application errors in forms of throw
or try & catch
don’t save the day. The
situation is getting even worse, when websocket network layers or realtime databases and async
resources are added to the application. If such realtime web applications are supposed to be used on
stage, it becomes crucial to bring a fault tolerance as a core principle to its design. In other
words, a web application must follow the “let it crash” philosophy, which means that instead of
trying to handle every possible error in your code, you should let your processes crash and rely on
other processes to detect and recover from failures. This core principle covers web applications
from simple collaborative web GUIs for synth controllers to live coding editors and immersive
virtual worlds running on end-user devices.
doesNotUnderstand
One of the most famous and first implementations of the “let it crash” philosophy is in transforming
any possible “crash” just into language side “does not understand” problem, without stopping main
application execution. That mechanism is implemented in the family of pure message-passing languages
like Smalltalk or Erlang.
When a Smalltalk object is sent a message for a method it has not defined, the runtime system turns
the message-send into an object (with accessors for things like the selector and arguments) and
sends #doesNotUnderstand:
to the original
receiver with this message-send object as argument. By default, the #doesNotUnderstand:
method raises an
exception, but the receiver can override it (in the usual way) to forward the message-send to a
different receiver. In effect, someone can partially redefine the semantics of message-sending.
Since almost everything in the Smalltalk language is done by sending messages, this is a very
general and powerful feature. It has become a standard idiom to use #doesNotUnderstand:
to implement forwarding,
delegation, proxies etc. For example in Smalltalk, nil
is the distinguished object which is
typically used as the initial value of all variables. It answers true to the message #isNil
, and throws a “does not understand”
exception when almost any other message is sent to it. You can try this by yourself by running a Squeak/Smalltalk just in a web browser. Squeak
stands alone as a practical Smalltalk in which anybody can examine source code for every part of the
system, including audio, graphics primitives and the virtual machine itself, and make changes
immediately and without needing to see or deal with any language other than Smalltalk. It also runs
bitidentical images across its wide portability base and includes GUI browsers for static methods in
the inheritance hierarchy and dynamic contexts for debugging in the runtime environment. There is an
example of live coding environment for web browsers Caffeine, based on Squeak.js. It features a
bi-directional JavaScript bridge, enabling Smalltalk methods to send messages to JavaScript objects,
and provide Smalltalk block closures as JavaScript promises or callback functions.
In some cases Smalltalk language with message-passing can be implemented on top of JavaScript using Ohm.js framework. Here are several examples, that include such implementations: Lively Web - a personal programming kit, that emphasizes liveness, directness and interactivity. Croquet OS - multiplayer platform for Web and Unity, Krestianstvo - open source implementation of the Croquet application architecture in Functional Reactive Paradigm.
Debugging and distributed programming
Squeak/Smalltalk compiler is written in itself, thus provides just unlimited runtime debugging possibilities. For those who are more acquainted with functional programming style and Lisp, like Scheme, I would recommend looking at the Pyret language. It can be run just in a web browser. Pyret is fully-fledged enough to self-host its compiler. What that means is, when you run Pyret in your browser, it loads JavaScript code that implements a Pyret-to-JavaScript compiler (i.e., it compiles the Pyret you type into JavaScript and runs it in the browser). This compiler was produced by the Pyret-to-JavaScript compiler by compiling the Pyret-to-JavaScript compiler. Many other languages expose the crippling limitations of JavaScript’s run-time to users (e.g., can’t halt a long-running computation, can’t yield control to the event loop, etc.). In contrast, Pyret is uncompromising, essentially event-loops are first-class entities in the language.
Now, let’s look at architectures that are suitable for distributed programming with fault
tolerance.
Erlang and Elixir programming languages showed how
message-passing can be scaled onto the network, by introducing a concurrent process-based
application structure with strong isolation between concurrent tasks. Nor Erlang, nor Elixir can run
on a web platform unfortunately. There is a project Lunatic, that proposes itself as a
WebAssembly runtime inspired by Erlang. But bringing the lunatic runtime to the browser is still a
challenging task.
Another project worthy of attention is the Goblins by Spritely and its distributed object programming environment. Goblins provides an intuitive security model, automatic local transactions for locally synchronous operations, and an easy to use and efficient asynchronous programming for encapsulated objects which can live anywhere on the network. Its networking model abstracts away these details so the programmer can focus on object programming rather than protocol architecture.
Spritely’s core layers of abstraction make building secure peer-to-peer applications as natural as any other programming model. Spritely provides an integrated system for distributed asynchronous programming, transactional error handling, time-travel debugging, and safe serialization. All this under a security model resembling ordinary reference passing, reducing most considerations to a simple slogan: “If you don’t have it, you can’t use it.”
Spritely Goblins’ network layer means users can perform asynchronous programming against objects that live anywhere. You can even interact with objects written in a completely different programming language! A distributed debugger inspired by E’s Causeway, complete with message-tracing mechanisms allowing programmers to debug a program in the state of an error when it occurred.
Goblins architecture takes its roots from a pure object model of secure distributed persistent computation known as E language. Which also has its own descendants known as Endo - a JavaScript platform for secure communication among objects within one process and distributed between mutually suspicious machines.
Effects and Structured concurrency
Another implementation of the “let it crash” philosophy is based on Effects and structured concurrency. In a real application, you will be launching a lot of coroutines. Coroutines follow a principle of structured concurrency which means that new coroutines can only be launched in a specific coroutine scope which delimits the lifetime of the coroutine. Structured concurrency ensures that they are not lost and do not leak. An outer scope cannot complete until all its children coroutines complete. Structured concurrency also ensures that any errors in the code are properly reported and are never lost. There are several web frameworks available for running in web browsers. Effection - structured concurrency and effects for JavaScript. Unlike Promises and async/await, Effection is fundamentally synchronous in nature, which means you have full control over the event loop and operations requiring synchronous setup remain race condition free. Another one is Effect - a powerful TypeScript library designed to help developers easily create complex, synchronous, and asynchronous programs. There is a great documentation on how it can handle errors.
Concerning distributed programming and live coding like a laptop orchestra, I would recommend looking at Electric Clojure. Electric is a reactive and network-aware Clojure/Script DSL that fully abstracts over client/server state sync at the programming language layer, in order to achieve strong composition across the frontend/backend boundary in dynamic web apps. Network-transparent Electric functions are true functions. They follow function laws and work at the Clojure/Script REPL. You have lambda, recursion, HOFs, closures, dynamic scope, macros, etc: the full undamaged composition power of Lisp. In several lines of code one can create a fully functional collaborative, distributed web application for controlling SuperCollider synth (SuperCollider -> Clojure/Overtone -> ClojureScript/Electric application). So, that recursive distributed lambda can be run on client devices running web browsers. Data from sensors, which are available on a client’s devices could be accessible to an artist on the stage inside united Electric application.
Conclusion and discussions
“Let it crash” philosophy can definitely be achieved inside a web browser using JavaScript runtime. But, for now there is still no all-in-one solution, that will combine time-travel debugging, functional reactive programming, distributed lambda, message-passing, structure concurrency, effect handlers, capability-based security etc. But, the mentioned architectures and frameworks are already well suited for wrapping an existing web audio applications, thus bringing them closer to conform fault tolerance design.
To Top ↑