Search This Blog

2011-11-17

Slavic mentality: laugh or cry


I would like to share an incredible story I have observed in my life. 
The story took place in the early 90s, during the high point of turmoil in Ukraine, and at that time I was studying in the elementary school. 
I lived in a long block of flats, together with my grandparents. Despite the large number of tenants, the people were generally polite and friendly except for some... strange neighbors. As you might guess, they are the heroes of the story.
My neighbors from the eighth floor were common workers on a confectionery. Those were difficult times so people were often paid in barter. Neighbors were lucky and received a lot of candies for their job and that is why I knew them :-). 
In order to protect the property they also had a German shepherd called Nayda. Nayda was a good and kind dog, but to the great misfortune, by breaking all rules, it was fed with chocolates earned by her hosts... Nayda was really a fat dog and weighed over 60 kg because of such a diet. Nayda lived 6 years of a sweet life. 
It was quiet and warm spring holiday, the grass was green, the birds were singing and people enjoyed with a nice weather. Nayda died that day. It was so sad... and neighbors laid it to rest according to our customs, accompanying the ritual with drinking vodka...
After the third bottle a “brilliant” idea was born in their heads - to bury the dog on our backyard. The problem is that the dog was heavy and the block house was so long that they would have to drag the body of about 400 meters to get around it. Their next brilliant idea was: "Well, Nayda is already dead so we can throw the body off the balcony and gravity will do everything for us!" Damn, how did they come up with this?!
Without any hesitations, they dragged the body onto the balcony and threw it through a window... Then they just got dressed, took a shovel and went out to dig a grave; but when they got to the point they discovered there was no dog! It just disappeared! So they started looking for a dog in the yard, swearing at the same time.
The real reason they realized a little later. 
Some balconies in apartment houses were equipped with special rails with stretched ropes that stick out two feet from the outside balcony; they are designed for drying clothes.
It turned out that the dog's body collided with ropes in flight in front of the third floor and jumped inside into the open window... OMG
The neighbor from the third floor was at home that day and he heard some strange noise, so he decided to find out the reason. When he opened the door of the balcony - he was stunned by what he saw...
When he collected his thoughts together and looked out of the window - he saw drunken neighbors, who were calling for their dead dog. 
After long abuses and battering, other neighbors called the police to investigate the situation. When police arrived, the house turned into a loony bin: people behaved inappropriately and evidences were incredible. However, finally, experienced police officers of the Soviet hardening dragged the body of the dog in the yard and buried. Drunken neighbors had been fined for disturbing public order, but the moral injury of the neighbors on the third floor has not healed to this day.

2011-10-31

jQuery Deferred queue

Since jQuery 1.5 had been released some very good feature called Deferred Object appeared for better callbacks handling and other kinds of synchronizing calls.
Deferred API has "jQuery.when()" which allows to multiplex deferreds (waiting on multiple deferreds at the same time)... and has "pipe()" to create a pipeline or chain of deferreds (since version 1.6). While this is very nice if all deferreds are available at the same point, it doesn't really convenient if there is a collections of deferreds coming from multiple sources (which may not be aware of one another). This plugin allows to solve the problem at some point. But it was based on previous version... before pipe() feature.
My simple solution also tries to handle multiplexing deferreds from different sources using shared Queue object.
Idea is to have a queue that is based on the principle of FIFO multiple-producers multiple-consumers tasks queues.
Let's start from unit test to describe how it works:
module("Test.Queue.js");

asyncTest("Test of appending to the queue ", 6, function () {
  var queue = new Queue();
  queue.append(function () {
   ok("func 1");

   setTimeout(function () {
      ok("func 1 nested");
      start(); // Continue async test. 
      this.resolve(1); // return some value for the subscribers.
    }.bind(this), 500); // Here I would like to have a control over Deferred through 'this'.
  })
  .done(function (arg) { // on done callback
    ok("func 1 done callback");
    equal(arg, 1);
  });

  queue.append(function (arg) {
    equal(arg, "test arg data");
    this.reject(); // operation was failed.
  }, "test arg data")
  .fail(function (funcArg) { // on fail callback
    ok("func 2 fail callback");
  });
});
So here we have a queue object and two calls of independent functions that might come from the multiple sources. Each function itself might have a callbacks or chain so "queue.append" should return a promise.
The assert trace should look like this: "func 1", "func 1 nested", "func 1 done callback", "func 2" and "func 2 fail callback".
And the code itself:
Queue = function () {
  this.promise = jQuery(this).promise(); // Stub promise
};

// Magic with arguments is needed to pass arguments to the called function
Queue.prototype.append = function () {
  var args = arguments;

  var fn = args[0];
  if (!fn || !jQuery.isFunction(fn)) {
    throw new TypeError('1st parameter should be a function');
  }

  var self = this;
  args = Array.prototype.slice.call(args, 1);
  return this.promise = this.promise.pipe(function () {
    return jQuery.Deferred(function () {
      try {
        return fn.apply(this, args);
      } catch (ex) {
        // log exception
        this.reject(ex);
        return self.promise = jQuery(self).promise();
      }
    }).promise();
  });
};
Implementation idea is very simple: Class just has a reference to the head of a queue and push new function to the Deferred pipeline.

2011-10-05

log4net appender for Raven DB

Recently I have started to look at noSQL databases and Document-Oriented DBs particularly for the .NET platform to solve some issues within our solution. One of the brilliant examples of such databases is Raven DB by Ayende. IMHO, one of the benefits of Raven DB is easy to start way. It has almost all I need out of the box: simple deploy, native .NET client with Linq, Map/Reduces indexes, RESTful API and so on and so forth.
Raven DB itself uses NLog by default and our solution uses log4net like thousands of other ones. So I decided to experiment with custom log4net appender for our needs because one of the first steps of moving to noSQL direction might be logging, due to simple implementation. Such an approach is far more flexible than logging to text files and might significantly reduce load on SQL servers in case of using ADO.NET appender.
So let's start.
First we need to decide what we are going to store. Since logging event  is simple and independent document  we can just wrap LoggingEvent.
// The log entry document entity that will be stored to the database.
public class Log : INamedDocument
{
  ...

  public Log(LoggingEvent logEvent)
  {
    if (logEvent == null)
    {
    throw new ArgumentNullException("logEvent");
    }

    this.LoggerName = logEvent.LoggerName;
    this.Domain = logEvent.Domain;
    this.Identity = logEvent.Identity;
    this.ThreadName = logEvent.ThreadName;
    this.UserName = logEvent.UserName;
    this.MessageObject = logEvent.MessageObject;
    this.TimeStamp = logEvent.TimeStamp;
    this.Exception = logEvent.ExceptionObject;
    this.Message = logEvent.RenderedMessage;
    this.Fix = logEvent.Fix.ToString();

    ...

    // Raven doesn't serialize unknown types like log4net's PropertiesDictionary
    this.Properties = logEvent.Properties.GetKeys().ToDictionary(key => key, key => logEvent.Properties[key].ToString());
  }

  public string LoggerName { get; set; }
  public string Domain { get; set; }
  public string Identity { get; set; }
  public string ThreadName { get; set; }
  public string UserName { get; set; }
  public object MessageObject { get; set; }
  public DateTime TimeStamp { get; set; }
  public object Exception { get; set; }
  public string Message { get; set; }
  public string Fix { get; set; }
  public IDictionary Properties { get; set; }
  ....
}
Next step will be choosing which appender template we need and which additional settings Raven requires. I think using of buffering appender makes sense in order not to drive database connection up the wall. As for additional setting of appender we might need connection string (or connection string name) and Raven's settings about max number of request per document session.
So our ".config" file would have something like this:


  
Ok, we have all the prerequisites and we can write some code.
Implementation is simple: on SendBuffer method it adds log events to a document session and saves changes to a DB server. On ActivateOptions it will initialize DB connection, i.e. create document store instance.
  public class RavenAppender : BufferingAppenderSkeleton
  {
    private readonly object lockObject = new object();

    private IDocumentStore documentStore;

    private IDocumentSession documentSession;

    #region Appender configuration properties

    /// 
    /// Gets or sets the connection string.
    /// 
    /// 
    /// The connection string.
    /// 
    public string ConnectionString { get; set; }

    /// 
    /// Gets or sets the max number of requests per session.
    /// By default the number of remote calls to the server per session is limited to 30.
    /// 
    /// 
    /// The max number of requests per session.
    /// 
    public int MaxNumberOfRequestsPerSession { get; set; }

    #endregion

    /// 
    /// Initializes a new instance of the  class.
    /// 
    public RavenAppender() {}

    /// 
    /// Initializes a new instance of the  class. 
 /// Can be used for unit testing
    /// 
    /// 
The document store.
    public RavenAppender(IDocumentStore documentStore)
    {
      if (documentStore == null)
      {
        throw new ArgumentNullException("documentStore");
      }

      this.documentStore = documentStore;
    }

    protected override void SendBuffer(LoggingEvent[] events)
    {
      if (events == null || !events.Any())
      {
        return;
      }

      this.CheckSession();

      foreach (var entry in events.Where(e => e != null).Select(e => new Log(e)))
      {
        this.documentSession.Store(entry);
      }

      this.Commit();
    }

    public override void ActivateOptions()
    {
      try
      {
        this.InitServer();
      }
      catch (Exception exception)
      {
        ErrorHandler.Error("Exception while initializing Raven Appender", exception, ErrorCode.GenericFailure);
        // throw;
      }
    }

    protected override void OnClose()
    {
      this.Flush();
      this.Commit();

      try
      {
        if (this.documentSession != null)
        {
          this.documentSession.Dispose();
        }

        if (this.documentStore != null && !this.documentStore.WasDisposed)
        {
          this.documentStore.Dispose();
        }
      }
      catch (Exception e)
      {
        ErrorHandler.Error("Exception while closing Raven Appender", e, ErrorCode.GenericFailure);
      }

      base.OnClose();
    }

    protected virtual void Commit()
    {
      if (this.documentSession == null)
      {
        return;
      }

      try
      {
        this.documentSession.SaveChanges();
      }
      catch (Exception e)
      {
        ErrorHandler.Error("Exception while commiting to the Raven DB", e, ErrorCode.GenericFailure);
      }
    }

    private void CheckSession()
    {
      if (this.documentSession != null)
      {
        return;
      }

      lock (this.lockObject)
      {
        if (this.documentSession != null)
        {
          if (this.documentSession.Advanced.NumberOfRequests >= this.documentSession.Advanced.MaxNumberOfRequestsPerSession)
          {
            this.Commit();
            this.documentSession.Dispose();
          }
          else
          {
            return;
          }
        }

        this.documentSession = this.documentStore.OpenSession();
        this.documentSession.Advanced.UseOptimisticConcurrency = true;

        if (this.MaxNumberOfRequestsPerSession > 0)
        {
          this.documentSession.Advanced.MaxNumberOfRequestsPerSession = this.MaxNumberOfRequestsPerSession;
        }
      }
    }

    private void InitServer()
    {
      if (this.documentStore != null)
      {
        return;
      }

      if (string.IsNullOrEmpty(this.ConnectionString))
      {
        var exception = new InvalidOperationException("Connection string is not specified.");
        ErrorHandler.Error("Connection string is not specified.", exception, ErrorCode.GenericFailure);

        return;
      }

      this.documentStore = new DocumentStore
      {
        ConnectionStringName = this.ConnectionString
      };

      this.documentStore.Initialize();
    }
  }
One detail we should care about is max number of requests per session. RavenDB's default limitation is 30 requests per session. On the one hand we have moved this option to the config file on the other hand we just recreate a session when max number of requests is reached.
So this is it: simple but effective. With RavenDB indexes it is possible to have extra-analysis or statistics of logged events, for instance, number of error per day / per module, new errors or warnings, etc.
Sources are available on GitHub.
NuGet package: http://nuget.org/List/Packages/log4net.Raven

Update:
The screenshot of the test Log documents