require 'postgres'
require 'drb'
require 'thread'

class PGconnGroup
  include DRbUndumped

public

  DEFAULT_SIZE  = 8
  DEFAULT_SLEEP = 0.01
  MAX_PASSES    = 2

  attr_reader :size
  attr_reader :pghost, :pgport, :pgoptions, :pgtty, 
	      :pgdbname ,:pglogin ,:pgpassword

  alias db pgdbname
  alias host pghost
  alias user pglogin
  alias options pgoptions
  alias port pgport
  alias tty pgtty

  def connection
    passes = 0
    loop do
      pgconns.each do |pgconn|
	if pgconn.try_lock 
	  begin
	    return(yield(pgconn))
	  ensure
	    pgconn.unlock
	  end
	end
      end
      passes += 1
      Thread.current.priority += 1 

      (passes > MAX_PASSES) ? sleep(DEFAULT_SLEEP) : Thread.pass
    end
  end

  def exec sql
    connection do |conn|
      return conn.async_exec(sql)
    end
  end

  def query sql
    connection do |conn|
      return conn.async_query(sql)
    end
  end

  def reset_all
    each_connection do |conn|
      conn.reset
    end
  end

  def close_all
    each_connection do |conn|
      conn.close
    end
  end

private

  attr_writer :size
  attr_writer :pghost, :pgport, :pgoptions, :pgtty, 
	      :pgdbname, :pglogin, :pgpassword

  alias db= pgdbname=
  alias host= pghost=
  alias user= pglogin=
  alias options= pgoptions=
  alias port= pgport=
  alias tty= pgtty=

  attr_accessor :pgconns

  
  def initialize args = nil 
    args.each {|k,v| self.send "#{k}=", v} if args
    @size ||= DEFAULT_SIZE
    @pgconns = []
    size.times do 
      @pgconns << (PGconn.new *pgconn_args)
    end
    c = @pgconns[0]
    @pghost, @pgport, @pgoptions, @pgtty, @pgdbname, @pglogin = 
    c.host, c.port, c.options, c.tty, c.db, c.user 
  end

  def pgconn_args
    return pghost, pgport, pgoptions, pgtty, pgdbname, 
	   pglogin, pgpassword
  end

  def each_connection
    pgconns.each do |pgconn|
      sem = pgconn.sem
      Thread.pass while not pgconn.try_lock
      begin
	yield pgconn
      ensure
	pgconn.unlock
      end
    end
  end

  class PGconn < ::PGconn
    include DRbUndumped
  public
    attr_reader :sem
    def lock
      @sem.lock
    end
    def locked?
      @sem.locked?
    end
    def synchronize
      @sem.synchronize
    end
    def try_lock
      @sem.try_lock
    end
    def unlock
      @sem.unlock
    end
  private
    attr_writer :sem
    def initialize pghost, pgport, pgoptions, pgtty, 
		   pgdbname, pglogin, pgpassword
      @sem = Mutex.new
      super
    end
  end
 end
