2011-05-22

Legacy code and multi-threading

After a hiatus of a few months, here is an update. What follows is an e-mail that I sent to my collaborators regarding the future technological direction of our organic chemistry product.
* * * *
Apart from resuming my work on importing available chemicals databases, etc., I have spent some time over the last few weeks studying recent developments in making programs multi-threaded, etc. I have written several multi-threaded programs in the past. Usually, the following are the important problems that arise in the context of multi-threaded programs:
  1. identifying the variables (mutable state of data) that need to be accessed from more than one thread,
  2. determining the locking mechanisms needed to protect simultaneous access to such variables from multiple threads, and
  3. using appropriate synchronization mechanisms for proper control flow and cause-effect relationships.
In contrast to the model described above, an alternative approach was proposed by Hewitt, Bishop and Steiger in 1973. It is called the Actor model. It comprises independent objects (all objects are actors) communicating via messages. No other form of interaction exists between independent objects. This communication can be synchronous or asynchronous, but is usually asynchronous by default. Also, actors can create new actors. Actors can also maintain state, designating what their behavior should be in response to future incoming messages.
Yet another alternative was proposed in 1978 by Hoare, called Communicating Sequential Processes (CSP). While being similar in spirit to the Actor model (all objects are communicating processes), CSP differs in a few aspects. For instance, in the Actor model, the sender of a message needs to know the address of the receiver in order to send a message. In CSP, communication happens through channels. A sender need not be aware of the identity of the receiver at the other end of the channel. Also, in CSP, message-passing is in itself a synchronization mechanism: a sender cannot complete the process of sending a message unless the receiver is ready to accept one. In the Actor model, on the other hand, the `system' itself holds the messages that receivers are not yet ready to accept.
Please refer to http://en.wikipedia.org/wiki/Actor_model and http://en.wikipedia.org/wiki/Communicating_sequential_processes for introductory details.
It is pertinent to note that neither the Actor model nor CSP mandates conventional locks or globally-shared data! It is known in the industry that locking and lock-based synchronization are difficult to implement correctly, and even more difficult to scale up to multi-processor scenarios.
About 2-3 years ago, when I used Scala (http://www.scala-lang.org) for a few programs, I utilized the Actor model (provided in Scala through a standard library) to develop a framework for lock-less multi-threading that could utilize all the available processors. That was, of course, before I had heard of Akka. I employed it successfully for processing large amounts of data from the chemical databases of one of my clients.
Some time last year, I became aware of, and interested in, the Go (http://golang.org) language. These days, I use it for most new programs that I develop. Go implements CSP at the language level itself. I have found it to be very useful and convenient for developing multi-threaded programs that operate in a lock-less manner. I have recently written a utility program, using Go, to view and search for molecules in MDL's SDF file format.
Due to C and C++ not having an inherent notion of threading or message passing, there exists no straight-forward way of making ST's main program multi-threaded. We have to employ the Boost library or the MPI(Message-Passing Interface) library or (worse) POSIX threads directly or something similar, in order to wrap certain parts of the current main program. I remember that you looked at MPI (OpenMPI, in particular) several months ago
In my (rather limited) experience, both Boost libraries and MPI present steep learning gradients. They are large, their APIs are large, they are inherently complex, and the very amount of code they require us to write is significantly high. I am, in fact, more concerned about the effort needed to debug and maintain such code in the long run. In contrast, I have found Scala's Actor model and Go's CSP model to be welcome reliefs by way of their simplicity and ease of use.
Scala is a language implemented for the Java Virtual Machine (JVM). This, unfortunately, means that should we choose to employ Scala, our clients have to install Java in their Linux computers. Installation of Java is a decision outside our control, and is made by their respective IT departments.
Go, on the other hand, is similar to C and C++: it compiles to native code, and does not require a virtual machine. Consequently, we can give a new main program to our clients without asking them to install a JVM first. More importantly, we can easily call C code from within Go, making it feasible to re-use select parts of the current main program in a more flexible manner.
In view of the above, after several weeks of study and trials, I think that we should employ Go for making ST's main program multi-threaded. This applies to all areas of the main program that lend themselves to multi-threading, in general, and to the convergent synthesis problem, in particular.
I have written a small part of the new SDF import program in Go. Apart from a particular annoyance that I encountered (because of the absence of generics in Go), I should say that I was delighted to see how easily the program could be written multi-threaded to utilize both the processors in my computer. The effective increase in speed was about 50%, and I think that it can increase even further with some optimization.
The above may be unclear or confusing in parts. Please ask me questions if it is so. However, kindly note that this is an important decision that has to be made, and which cannot be reversed (that easily) later. Accordingly, the more the number of questions that we consider now, the better.

2011-02-28

Scaling databases

Here is an interesting prototype for scaling the database without partitioning. I admit that I have not yet fully grasped the lock-free algorithm for conflict resolution. But, it looks quite promising. Also, note that this is a relational - not a NoSQL - database!

2011-01-26

Equivalence classes -- a recurring pattern

Within the last couple of months, I have found myself applying one pattern in three different scenarios.

(1) Literature on Clinical Trials

Each clinical trial - usually - has multiple trial aliases. A journal article may cite one or more of them. It is, therefore, possible for two articles to refer to the same trial, but using two different aliases. However, as we compile newer articles, it may be possible to cross-link them based on those that cite multiple aliases.

Example:
- Article A1 refers to trial aliases TA1 and TA5.
- Article A2 refers to trial aliases TA2 and TA4.
- Article A3 refers to trial aliases TA1 and TA3.
- Article A4 refers to trial aliases TA4 and TA5.
- Article A5 refers to trial alias TA3.

Based on the above, the program should infer that they are all referring to the same trial.

(2) Stripping Salts of a Molecule

A possible complex may have more than one molecule. Often, it is a combination of a main molecule and one or more salts. Reading from an MDL `.mol' file, the program should recognize the different units, identify the main molecule, and strip the salts. The input is read an atom at a time, followed by a bond at a time.

Obviously, the two atoms participating in a bond belong to one unit. The neighbors of each of them also belong to the same unit, etc.

(3) Fused Rings of a Molecule

A single molecule can have multiple ring systems. Within each such system may exist a set of fused rings. Step 1 is to identify all rings in the system. In Step 2, we have to identify fused rings.

If rings R1 and R2 are fused, they belong to one ring system. The fused neighbors of each of them also belong to the same ring system, etc.

Equivalence Classes

Each of the problems above has the same recurring pattern -- that of equivalence classes! There is a defined equivalence relation on the input set. The program has to utilize that to partition the set. Recognizing that pattern has helped me in streamlining my design and implementation.

2010-11-22

Large files and multiple cores

As part of my product, I have an available chemicals database (ACD) that is compiled from third-party catalogs. Such catalogs are typically in SDF file format, as specified by MDL. I ran into some issues when importing them, resulting in aborted runs. It was getting tedious to locate the troublesome molecule in the vastness of the SD files. After a while, the programmer in me woke up! The consequence is a small (~750 LoC) utility written in Go, which I blandly called sdf.

~ % sdf help

Usage:

sdf help
Prints this usage notes message, and exits.

sdf show in=file [from=m] [to=n]
Fetches and displays molecules from the file 'file',
starting with the molecule numbered 'm', optionally to the
molecule numbered 'n'. If 'from' is not specified, the
first molecule is used as the starting molecule. If 'to'
is not specified, all molecules until the end of file are
displayed. Specifically, to display only the m'th molecule,
you should specify 'from=m to=m'.

sdf copy in=file1 out=file2 [from=m] [to=n]
Similar to 'show' above, difference being that the output
is written to 'file2'. Any existing 'file2' will be
truncated.

sdf searcha in=file [from=m] [to=n] symbol=count [symbol=count] [mx=c]
Performs a search for the first molecule in the given range
that has the specified number of atoms of each element type.
The number of processor cores to use can be specified using
'mx'; default is 2.

sdf searcht in=file [from=m] [to=n] tag=name tagval=value [tag=name tagval=value] [mx=c]
Performs a search for the first molecule in the given range
that has the specified tags and values. The number of
processor cores to use can be specified using 'mx'; default
is 2.

2010-11-13

Java and native code

Last week, one of my clients' projects ran into a problem. The team had introduced a new feature in the latest release. It ran fine in testing, but began having mysterious crashes in production. Upon some investigation, they found that a particular third-party native library (`.so' file) was causing the error. This native library was used to compute the InChI value of a given molecule. The application itself is a Java Web application written using the Struts framework.

Initially, there was a path related problem. They solved it. Then there was a 32-bit vs. 64-bit problem (or so they thought). They solved it. Then there was some version mismatch. They solved it. At that point, they moved it into production. Presently, the mysterious crashes started. Yesterday, I was called in.

Even before I landed there, my thinking was: (a) such molecules could be known corner cases of the third-party library, or (b) there is a memory corruption (e.g. JVM trying to garbage-collect memory allocated by C runtime).

I began by understanding the issue's history. I familiarized myself with the part of the code that was interfacing with the native code. Together with the team, I set up various scenarios to reproduce the crash. After a couple of hours, the first crash did occur. Using trace information, we looked up the forums of the third-party vendor who supplied the native library and its wrapper Java library. The forums had discussions concerning some of the exactly same scenarios that we had set up for testing. They were stated to be known problems. We also saw that a newer version of the vendor library was available. At this point, it appeared as if my first guess was probably correct.

Re-running the scenarios with the updated library caused similar crashes, nevertheless! After a couple of more hours, the situation appeared to have reached a dead end.

Then, I suddenly saw a pattern in the crashes -- they all happened only in multi-user scenarios! Then I remembered my second line of reasoning. It was memory corruption, but probably due to global data in the native library! We quickly verified this by simultaneously launching the said functionality in independent JVM instances. There were no crashes!

So, that indeed was the problem! The native library was not thread-safe! We then re-wrote that part of the application to factor the native part into a separate process to be run in a new instance of JVM. Each client request invoking that particular functionality would run in a separate JVM, handing the results over in a text file, back to the mother program. With thread-safety problem out of the way, the application went back to running happily!

2010-11-04

Happy ending!

I was thinking of filing an issue for the stat problem of Go for Windows. I was about to do that this evening, when I received my daily digest of Go mailing list. I found an announcement therein that a new release of Go was made yesterday night. The release notes said, to my disbelief, that the stat problem in Windows was solved!

I eagerly downloaded the new version for Windows, and re-compiled the SHA-1 summing program with it. I ran it, I admit, with some trepidation. It ran as it should (have) -- smoothly!

Now, that is some coincidence! Happy ending, thusly!

2010-11-03

Fruition :-)

I spent a couple of hours rewriting the Go version of the program in C++ using Qt. This version worked flawlessly, much to my relief!

Qt is a very well-designed library! This is the same feeling that gets reinforced every time I work with it.

I used a class called QDirIterator, whose function is self-describing. While Qt supports STL-style iterators, it recently introduced Java-style iterators. These new iterators support hasNext() and next() combination for traversal through the containers. Needless to say, these are much easier to use than the corresponding STL-style iterators.

In all, I enjoyed the program's rewrite exercise, which also came to fruition!

2010-10-28

Fruitless effort :-(

I wrote a small program to compute SHA-1 sums of directory trees, to easily identify and locate files occurring more than once. I wrote it in Linux using Go language. I tested it on large trees of sizes in the range 1-4 GB. I cross-checked the results - through random sampling - using `sha1sum', and I was happy! I then booted into Windows 7 VMware image and compiled it. Then began the trouble.

Go is not officially supported on Windows yet. There is an unofficial port that closely follows the official releases. It is clearly marked `experimental'. I should have realized that when an OS is not yet officially supported, the weakest areas would be OS interfaces. The inane dud that I am, I did not think enough!

While walking the directory tree, I was doing a `stat' of each entry: (a) to find whether it is a directory or a file, and (b) if it is a file, to know its size in bytes. In Windows, the `stat' call was failing. After some gymnastics, the program was walking the directory tree. But, some directories were getting recognized as files. In addition, I could not make it `stat' the entries whose full path contained spaces. I have exhausted all conventional mechanisms to no avail.

So, I give up! At least, for now. I have begun using Go for serious work. I have not encountered any issues with the runtime so far. But ... that is in Linux. From a few small programs that I run in both Linux and Windows, I see that there are no issues with Go ... as long as OS interfaces are not used in Windows!

My choice of language for this particular job was wrong, leading to this fruitless effort. Sigh!

Modern computing

I came across this in Go mailing list today. Irresistible!
Such is modern computing: everything simple is made too complicated because it's easy to fiddle with; everything complicated stays complicated because it's hard to fix.

— Rob Pike

2010-10-04

Time problems

Last week, I encountered two interesting problems.

Background: The product that I am building has a newly-developed licensing feature. The feature uses hardware information of the user's machine a la Windows activation. In addition, licenses are annual, so the program must commit suicide at the end of one year.

Problem 1: After the first successful run of the product, all attempts at running it again were failing with the error message `Current time older than that of the most recent run.' The time of last run is updated in the license file (which is an encrypted file) at the beginning of each run. After several attempts at debugging, I could find nothing. I switched to other work, and returned a few hours later. To my surprise, the program ran successfully, but just once! It then returned to the now-familiar error message.

I noticed that 4 hours had elapsed between the two successful runs. I suddenly remembered that I was storing the time of last run as UTC, and was signed in into my collaborator's computer at Toronto (UTC-4:00). Upon searching the code for all instances of storing/reading time, I noticed that conversions of local time to and from UTC were inconsistent. I made them consistent, and Problem 1 disappeared.

Problem 2: No sooner had I finished resolving the above than I needed to run the program - in batch - about 6000 times. I wrote a shell script to do that. I tested it for 10 runs. Surprise! The error message was back, only even more confusingly. Jobs 1 and 2 ran successfully. Job 3 failed with the above error. Job 4 ran successfully. Job 5 failed. Jobs 6 and 7 ran successfully. Job 8 failed. Jobs 9 and 10 ran successfully. The actual jobs that ran successfully changed but slightly each time I re-ran the shell script. It really left me scratching my head.

I then noticed that there was a pattern. The jobs before the failed ones completed really quickly. I inserted a `sleep 1' after each job in the shell script. Now, every job completed successfully!

I then checked the license verifier code. One of the important checks was that current time should be greater than that of the last run. However, resolution of time was in seconds. Thus, if a job completed in less than a second, its immediately next job would fail. So, I modified the condition to current time should not be less than that of the last run. The shell script ran happily ever after!