I was recently working on a small proof-of-concept application with a back-end server written in JRuby. In this proof-of-concept, multiple clients connect to the server to poll for status changes and to request actions be performed as the result of user interactions.
The quickest way to keep the server responsive was to just fire up some threads to handle its various responsibilities. The bulk of the effort that went into the proof-of-concept was in the domain-specific technologies we were working with, not the ability of a server component to handle multiple clients in parallel. In other words, thread-safety was an afterthought.
But, as always seems to be the case when working with threads, I started running into trouble. Occasionally a request would fail and the following error could be found in the logs:
bq. Detected invalid hash contents due to unsynchronized modifications with concurrent users
The server was keeping track of most of its state in a single hash, and apparently if things happened in just the wrong order, one thread would end up updating it while another thread was accessing it. There was already a mechanism in place for preventing multiple writes at the same time, so it was just a matter of preventing a write and a concurrent read from raising the exception.
Rather than try to protect the hash with a “Mutex”:http://ruby-doc.org/core-1.9.3/Mutex.html, or deep cloning a frozen hash whenever a change was made, I decided to give “Hamster’s efficient, immutable, thread-safe”:https://github.com/harukizaemon/hamster data structures a try.
To update a value in a Hamster hash, you need to use the @put@ method, which returns an updated instance of the hash. To convert the codebase, I just needed to find the couple of places where the hash was being updated and change something like this:
def update(key, value) @app_state[key] = value end
into something like this:
def update(key, value) @app_state = @app_state.put(key, value) end
Accessing a Hamster hash is no different than accessing a normal Ruby hash. So besides changing the code that instantiated the hash, no further changes were required.
Benefits of Hamster’s Immutable Data Structure
One of the biggest wins in choosing to use an immutable data structure over using locks or deep cloning is that code that has not been updated to use the @put@ syntax raises an exception when it tries to modify the Hamster hash. This gave me much more confidence that I had not missed anything and that I was not going to run into an unexpected @ConcurrencyError@ during a demo.
My experience with Hamster so far has been great. As I move out of the proof-of-concept phase on this project and into writing production code, I plan to use the Hamster collections right from the beginning.