Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts

November 19, 2010

Recipe: handling database communication breakdown in Rails and Django

This recipe is for users who manually handle persistent (shared) database connection, with Oracle, although it's probably easy to adapt for other database backends with small adjustments. I needed to implement this both in Ruby (Rails' ActiveRecord) and Python (django.db) daemons, both working in the same setting. I thought the solution was elegant enough to be cleaned up into a recipe.

Automated database connection pooling for Python (nor Ruby) is not supported with Oracle 10g. Only 11g introduced Database Resident Connection Pooling (DRCP). It probably makes this recipe redundant for Python users with 11g. Oracle has official support for using Python with Oracle Database 11g, using the cx_Oracle driver. I'm not sure but I think Oracle doesn't have any form of official support for Ruby, but the de-facto ruby-oci8 driver works well with ActiveRecord.

Without automated connection pooling you have two decent options. Either open and close a new connection for each request, or save open connections into shared memory. The latter has obvious performance benefits if the expected request rate is high, but exposes a number of other problems this recipe aims to address. A connection can abruptly terminate because of e.g. a network glitch or maintenance downtime, and the sysadmins deploying your software will be happier having to deal with less services to reboot.

The handler should catch exceptions only related to network or database resource failures, whereas in other cases (SQL typo?) the original exception should not be reacted upon. Figuring out the proper ORA error codes to use was an exciting task, I hope I got them right ;). Those guys at Oracle sure love to make things easy. Since I use a regular expression to match the error message string, a multitude of errors can be matched with a single rule. Fortunately, all errors dealing with Transparent Network Substrate are marked by prefix "TNS". In my tests most errors were indeed caught with the single "ORA-.....: TNS" regexp.

Python and Ruby have much in common, both could let me write this very expressively with about the same LOC. There is one small detail I'd like to bring up. These two expressions both compare the codes listed with the exception (error message in variable "message"), and return true if the message matches to one of the given ORA error codes.

Python

# Example message="ORA-03113: end-of-file on communication channel"
any(filter(lambda oracode: re.search('ORA-'+oracode,message), re.split(' *\n *| +(?=\d)',"""
    .....: TNS
    01000 01001 01014 01033 01034 01037 01089 01090
    011.. 015.. 016..
    031..
    28547
    30678
    """)[1:-1]))
Ruby
# Example message="ORA-01034: ORACLE not available"
%w{ .....:\ TNS
    01000 01001 01014 01033 01034 01037 01089 01090
    011.. 015.. 016..
    031..
    28547
    30678
}.select {|oracode| message[/ORA-#{oracode}/]}.any?
Especially take notice of Ruby's %w{multiline string} versus Python's re.split(' *\n *| +(?=\d)',"""multiline string""")[1:-1]. This just for the sake of the visual appearance of input data on the screen. Ruby makes it easier to write aesthetic code. Perhaps Python has an idiom for this, though, it just didn't pass my mind.

example_oci_exception_handler.py

# -*- coding: utf-8 -*-
"""
You may use this file under the terms of the BSD license as follows:

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of copyright holder.
"""
import re
import sys
from time import sleep
from myapp.settings import settings # your django settings module
from django.core.management import setup_environ
setup_environ(settings)
import cx_Oracle
from django.db import connection
from django.db.utils import DatabaseError
from logging import getLogger
log = getLogger(__name__)

class ExampleOCIExceptionHandler():
    """Recipe for handling lost Oracle database connection.
    
    Depends on Django ORM, and that proper settings.py exist and is initialized properly.
    
    Selects the network and connection errors, waits until the server is reachable again,
    and calls the method again during upon which the exception happened.
    
    This is very useful if the connection, for some reason, is shared between requests.
    Oracle 10g does not support connection pooling with cx_Oracle.
    """

    @staticmethod
    def dispatch(message_data):
        """Processes request.

        In case of database connection failure, the waitForOCIConnection() loop is called
        and connection should be re-established. In case of other Oracle errors,
        the error is raised again.
        """
        try:
            # do some database operations...

        except (cx_Oracle.Error, DatabaseError):
            """
            Catch lost database connection.

            Handles all TNS errors:
              ORA-xxxxx: TNS errors

            And all errors from these ranges:
              ORA-011xx: Database file errors
              ORA-015xx: Execution errors
              ORA-016xx: Execution errors
              ORA-031xx: communication errors

            Along with these specific errors:
              ORA-01000: maximum open cursors exceeded
              ORA-01001: invalid cursor
              ORA-01014: ORACLE shutdown in progress
              ORA-01033: ORACLE initialization or shutdown in progress
              ORA-01034: ORACLE not available
              ORA-01037: cannot allocate sort work area cursor; too many cursors
              ORA-01089: immediate shutdown in progress - no operations are permitted
              ORA-01090: shutdown in progress - connection is not permitted
              ORA-28547: connection to server failed, probable Oracle Net admin error
              ORA-30678: too many open connections

            """
            msg = str(sys.exc_info()[1]).rstrip()

            # is the error about broken connection?
            if any(filter(lambda oracode: re.search('ORA-'+oracode,msg), re.split(' *\n *| +(?=\d)',"""
                .....: TNS
                01000 01001 01014 01033 01034 01037 01089 01090
                011.. 015.. 016..
                031..
                28547
                30678
                """)[1:-1]) # ignore outermost items as they are empty strings
            ):
                log.error(msg)
                ExampleOCIExceptionHandler.waitForOCIConnection()
                # enter recursion and call this method again..
                sleep(2)
                return ExampleOCIExceptionHandler.dispatch(message_data)

            else:
                log.debug(msg)
                log.debug("Error seems to be not about lost connection, raising again ..")
                raise

    @staticmethod
    def waitForOCIConnection(time_to_wait=60):
        """Creates a new database connection, loops until one is established."""
        # nullify the connection first, since it can't discover that the socket is gone
        connection.connection = None
        while not connection._valid_connection():
            try:
                log.info("Attempt to establish OCI connection to %s ..." % [
                    settings.DATABASES['default']['NAME']])
                # in django parlance, calling _cursor() opens the database connection
                cursor = connection._cursor()
                sleep(2)
                return connection._valid_connection()

            except (cx_Oracle.Error, DatabaseError):
                exctype, message = sys.exc_info()[:2]
                log.error(str(message).rstrip())
                log.info("Retrying OCI connection in %i seconds" % time_to_wait)
                sleep(time_to_wait)
                return ExampleOCIExceptionHandler.waitForOCIConnection(time_to_wait)


example_oci_exception_handler.rb
# encoding: utf-8
=begin
You may use this file under the terms of the BSD license as follows:

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of copyright holder.
=end
require 'rubygems'
require 'active_record'

# Recipe for handling lost Oracle database connection with ActiveRecord.
#
# Depends on RAILS_ROOT and RAILS_ENV variables to be set and RAILS_ROOT/config/database.yml file to exist.
#
# Selects the network and connection errors, waits until the server is reachable again,
# and calls the method again during upon which the exception happened.
#
# This is very useful if the connection, for some reason, is shared between requests.
class ExampleOCIExceptionHandler

  # Processes request.
  #
  # In case of database connection failure, the waitForOCIConnection() loop is called
  # and connection should be re-established. In case of other Oracle errors,
  # the error is raised again.
  def self.dispatch(message_data)
    begin
      # do some database operations...

    # Catch lost database connection.
    #
    # Handles all TNS errors:
    #   ORA-xxxxx: TNS errors
    #
    # And all errors from these ranges:
    #   ORA-011xx: Database file errors
    #   ORA-015xx: Execution errors
    #   ORA-016xx: Execution errors
    #   ORA-031xx: communication errors
    #
    # Along with these specific errors:
    #   ORA-01000: maximum open cursors exceeded
    #   ORA-01001: invalid cursor
    #   ORA-01014: ORACLE shutdown in progress
    #   ORA-01033: ORACLE initialization or shutdown in progress
    #   ORA-01034: ORACLE not available
    #   ORA-01037: cannot allocate sort work area cursor; too many cursors
    #   ORA-01089: immediate shutdown in progress - no operations are permitted
    #   ORA-01090: shutdown in progress - connection is not permitted
    #   ORA-28547: connection to server failed, probable Oracle Net admin error
    #   ORA-30678: too many open connections
    #
    rescue OCIError, ActiveRecord::StatementInvalid
      msg = $!.message
      
      # is the error about broken connection?
      if %w{
        .....: TNS
        01000 01001 01014 01033 01034 01037 01089 01090
        011.. 015.. 016..
        031..
        28547
        30678
      }.select{|oracode| msg[/ORA-#{oracode}/]}.any?
      
        logger.error msg
        waitForOCIConnection()
        # enter recursion and call this method again..
        return dispatch(message_data)

      else
        logger.debug msg
        logger.debug("Error seems to be not about lost connection, raising again ..")
        raise $!
      end
    end
  end


  # Attempts to establish database connection.
  # Loops until ActiveRecord is connected.
  #
  # Set time_to_wait in seconds.
  def self.waitForOCIConnection(time_to_wait=60)
    begin
      database_configuration = YAML.load_file(File.join(RAILS_ROOT,"config","database.yml"))
      ActiveRecord::Base.configurations = database_configuration
      logger.info(
        "Attempt to establish OCI connection to %s ..." % database_configuration[RAILS_ENV]['database'])
      ActiveRecord::Base.establish_connection(database_configuration[RAILS_ENV])
      ActiveRecord::Base.connection # essential to open connection
      return ActiveRecord::Base.connected?

    rescue OCIError, ActiveRecord::StatementInvalid
      logger.error($!.message)
      logger.info("Retrying OCI connection in %i seconds" % time_to_wait)
      sleep time_to_wait
      return waitForOCIConnection(time_to_wait)
    end
  end
end

February 14, 2010

Introduction to Haskell and Erlang

I am reading Masterminds of Programming, by O'Reilly. It is a very good book I will get back to in other posts. Reading up on the thoughts of Haskell core language developers made me very interested in the language and functional programming in general. Haskell and Erlang are functional languages, so it takes a while to get into the mindset but seems like it's all worth the while.

I looked up some Haskell resources: http://www.haskell.org/haskellwiki/Haskell_in_5_steps

Then I saw the Haskell "Hello World"

Prelude> "Hello, World!"
"Hello, World!"
And immediately fell in love. Is there a simpler way to say "Hello, World!"?

This Haskell hacking video is also a great source of inspiration :)

There is also a nice guidebook called Learn You a Haskell for Great Good! with cutesy pictures and writing that takes you by the hand. Very nice. Reminds me of the Poignant Guide to Ruby. :) Erlang has got another one named Learn You Some Erlang for Great Good!, and you can't go wrong with one that has got a bearded squid on the cover =D

Erlang indeed looks to be a very nice language. This is an example from the book:

1> Weather = [
  {toronto, rain},
  {montreal, storms},
  {london, fog},
  {paris, sun},
  {boston, fog},
  {vancouver, snow}].

2> FoggyPlaces = [X || {X, fog} <- Weather].
[london,boston]

In fact this is even more compact syntax than in Ruby. Erlang atoms behave much in the same way as Ruby symbols. In which the equivalent would be something like:

>> weather = [
  {'Toronto' => :rain},
  {'Montreal' => :storms},
  {'London' => :fog},
  {'Paris' => :sun},
  {'Boston' => :fog},
  {'Vancouver' => :snow}]

>> foggy_places = weather.collect{|x| x.keys[0] if x.values[0]==:fog }.compact
=> ["London", "Boston"]
Or..
>> foggy_places = weather.select{|x| x.values[0]==:fog }.collect(&:keys).flatten
=> ["London", "Boston"]

Either case, I cannot think of a better way to say this in Ruby and Erlang has a cleaner way.

Joe Armstrong is one of the original Ericsson engineers who designed Erlang and wrote the first implementation. He describes using Erlang to feed data onto the page by web sockets. I am completely astonished. I need dig deeper into this...

January 29, 2010

EJB to Ruby proxying and a little bit on programming languages

This essay goes quite technical at times but the presented idea is pretty simple.

Java and the J2EE stack seem to be used in large-scale enterprise web solutions. Ruby is an odd hippie programming language that is not so often met (yet!) in this enterprise world. Ruby on Rails does not quite have the native equivalent in Java web development toolpack, and there never will be such. One key fundamental difference in Java and Ruby, is their measure of abstractness. Higher abstraction in some cases help to describe and solve the problem in just a few code blocks. Ruby lets you express ideas outside the micromanagement level - right from the beginning of your introduction to the language.

JRuby helps to break a major technical barrier, making possible to leverage the massive amount of Java libraries that now can be used through the Ruby interpreter. This is a description of a real world scenario where Ruby expression describes and solves the problem quite beautifully.

To understand what is happening next you need to know that an EJB is an Enterprise Java Bean. You can imagine that the Bean is an exporter in Brazil. The Java transaction layer RemoteMethodInvocation is built on top TCP/IP infrastructure. Regular citizen Java clients need to contact their national supplier.

It is quite a bureaucratic process. It takes some knowledge of Java to understand how each object should behave. EJB3 makes this a bit easier but to efficiently use the developer tools will take a long time to learn. This is an example of the Java enterprise business model where you develop on elaborate IDEs and large companies have lots of people doing object-oriented chunks of code. After having that much Java your dreams will become XML configurable for a while..

Ruby has a similar technique, DRb - Distributed Ruby. It is built into the Ruby core language. It provides a way to share data objects over the wire. As we will see, it takes only a few lines, perfectly writable even from the vi editor.

Now, let's inspect the supply chain.

bean_context =
      {
        'java.naming.factory.initial'      => 'com.evermind.server.rmi.RMIInitialContextFactory',
        'java.naming.security.principal'   => security_principal,
        'java.naming.security.credentials' => security_credentials,
        'java.naming.provider.url'         => provider_url
      }
This is the Brazil Bean exporter who has the brew I like. His contact is found by jndi_name from the @context. Home represents my national office who will be creating me this @stub guy who actually does the delivery of my order.
@context = InitialContext.new(bean_context)
  @home = @context.lookup(jndi_name)
  @stub = @home.create
EJB home is an object that implements the create() method for requesting the remote EJB and returning the stub object that acts as the EJB interface. It acts a bit like a database table model.

Let's say we want to know more of the company..

@stub.companyID
  => "430146"

  @stub.companyName
  => "Brazil Bean Provider"
Mmmh. So let's make an order..
@order = @stub.createOrder(uid,"Coffea arabica, 1 kg sample")
  => #<#:0x62ee558f @java_object=BBPServices stateless session>  
Ah! It returned an object! One important aspect of the dynamic nature of Ruby (and thus JRuby) is that the callable methods need not be specified beforehand. The method call to the Ruby object is dispatched to the EJB, the return value as well and if possible, JRuby casts it to Ruby native.
@order.items
  => ["Coffea arabica, 1 kg sample"]
This could be usable in Rails, but the architecture should allow dynamic translation of method calls so that the solution would have the most leverage and also to stay clean out of implementation details. Ruby has the sort of inner beauty that is capable of this.

method_missing() is a powerful Ruby kernel method, which has its counterpart in Python and Lisp, possibly other dynamic languages are growing into that direction too. Unless you are not familiar with method_missing(), I will explain it so that when Ruby calls a method on the EjbObject's instance (there is this @stub), and the Ruby object does not find the method, it calls method_missing, and here the call is redirected to the @stub, alas the EJB interface and along the network over TCP/IP. In other words, Ruby does not care what kind of EJB it gets.

This is the core of EjbObject class:

class EjbObject
    def context_environment
      {
        'java.naming.factory.initial'      => 'com.evermind.server.rmi.RMIInitialContextFactory',
        'java.naming.security.principal'   => security_principal,
        'java.naming.security.credentials' => security_credentials,
        'java.naming.provider.url'         => provider_url
      }
    end

    def initialize
      @context = InitialContext.new(context_environment)
      @home = @context.lookup(jndi_name)
      @stub = @home.create
    end

    def method_missing(method, *args, &block)
      @stub.send(method, *args, &block)
    end
  end
EjbObject is used by subclassing it:
class MyEJB < EjbObject
    set_provider_url 'rmi://....'
  end

And so, this essentially is the JRuby engine in ejb2rb. The solution wouldn't be complete without the Rails counterpart, ActiveEJB. Conceptually DRb and RMI are very similar.

Ok - even if the code didn't speak to you, think it this way - now JRuby can talk to the remote Java beans, call their methods, and use the return values as other objects that have methods. Some object classes are mapped directly to corresponding Ruby classes, such as Strings, Arrays and Fixnums. The Java-cast beatnik flower child JRuby can talk enterprise jargon. The EJBDispatcher::Instance enables him to open a channel to one EJB for other Ruby VMs that are equally valuable as Ruby implementations but have not developed their Java-fu.

This is how the EJB stub object is shared for DRb clients (the thread is started later by Hydra):

module EJBDispatcher
    class Instance

      self.config = {
        'hostname' => 'localhost',
        'port'     => '9876',
        'class'    => 'MyEJB'
      }

      def initialize
        @uri = "druby://%s:%s" % [config['hostname'],config['port']]
        DRb.start_service(@uri, Kernel.const_get(config['class']).new)
        return DRb.thread
      end
    end
  end
What happens on the Ruby end?
module ActiveEJB
    class Entity < DRb::DRbObject
      def initialize
        uri = 'druby://%s:%i' % [drb_server,drb_port]
        super(nil,uri)
      end
    end
  end
ActiveEJB::Entity is used by subclassing it:
class MyEJB < ActiveEJB::Entity
    include Singleton

    set_drb_server 'localhost'
    set_drb_port   '9876'
  end

So, in the end, you can use this in native MRI Ruby Rails:

profile = MyEJB.getUserProfile(@username, "PDS")

  contacts = MyEJB2.getCountryContacts(profile.getAddress().getCountry())
  
  contacts.each do |contact|
    ...
  end
Or something .. usually more or less involving enterprise business logic.. The actual implementation can handle multiple concurrent EJB instances.

But, in the end, if we look at the power and sheer expressfulness of Ruby, I think this is a fine example of it working to solve a real world problem. The source code is available to help rubyists with the leverage this technology may give them trying to survive under the serviance of J2EE.

Oh, yes, the order:

@order.delivery.weight
  => 1.108

  BrewerEJB.makeCoffee(
    @order.delivery,
    :espresso,
    :milk => true,
    :sugar => false)

October 15, 2008

jdbcpostgres-adapter and the numeric datatype

Vanilla Ruby with the postgres-pr adapter stores the 'numeric' datatype to BigDecimal. Unlike expected, the column has to explicitly define 'precision' and 'scale' when used in JRuby with the JDBC adapter, otherwise the scale is assumed to be 0 and all decimals are dropped. I have a column 'weight' in a table, in the original migration:

t.column :weight, :decimal, :null => false

This was corrected with the migration:
def self.up
  change_column :weights, :weight, :decimal, :null => false, :precision => 4, :scale => 1
end

def self.down
  change_column :weights, :weight, :decimal, :null => false
end

July 7, 2008

Localizing dates with Gettext in Rails

I came into a problem where I needed to localize Time and Date in my Rails app. I found Samuel Lown's gettext patch which worked really well for my needs. However, my application's primary language is not English, for practical reasons, I translated the strings to the same locale as my app. After putting this into my lib/ directory, I can use these methods to localize Date and Time:
Time.now.strftime('%c')
=> Maa Heinä 7 21:21:52 2008

Time.now.to_localised_formatted_s
=> Maa Heinä 07 21:21:52 EEST 2008

GettextDate::Conversions.monthnames(Time.now.month)
=> Heinäkuu