Hi all,
I've not done a lot of multi-threaded programming before, and I now find 
myself in the middle of writing a multi-threaded application. I'd 
appreciate someone who knows more about thread-safety than me to do a 
review of the code below and tell me where it falls short in that 
respect (as I'm sure it does). I can't immediately see anywhere in the 
code which is likely to cause problems and I've tried to write it so 
that it doesn't have problems, but I haven't used Mutex or any other 
library traditionally associated with thread-safety so I'm feeling a bit 
nervous about it. I learn best by getting my hands dirty, so I've given 
it a shot and I'd like someone to point out my mistakes.

Some background: the application (known as SAMS) is essentially no more 
than a database-backed DRb server. DRb takes care of most of the 
threading, by starting a new thread for each connection for me. Rather 
than starting a separate database connection for each thread, or trying 
to share one connection between all threads, I've tried to write a 
connection pooling class. The other application code generally requests 
a connection from the pool immediately before performing its queries and 
releases it immediately after, so unless the queries go for quite some 
time no one thread will be hanging onto a connection for more than a 
second or so.

Below is the code I have written. The idea is that other application 
code calls SAMS::Database.get_handle{ |handle| handle.execute... } and 
this module takes care of the rest. Also, the application's shutdown 
code will call SAMS::Database.destroy to cleanly disconnect all the handles.

require 'dbi'

module SAMS
   module Database
     # These values will eventually be taken from a configuration file
     MAX_FREE_HANDLES = 10
     MAX_HANDLES = 20

     @handles = []
     @free_handles = []

     @stopped = false

     class << self
       def new_handle
         # The DB connection parameters will eventually be read from a
         # configuration file somewhere
         h = DBI.connect('DBI:pg:sams', 'samsd', 'foo')
         h['AutoCommit'] = false
         @handles << h
         h
       end

       def destroy_handle
         h = @free_handles.shift
         if h
           h.disconnect
           @handles.delete h
         end
       end

       def allocate_handle
         if @free_handles.empty?
           if @handles.length < MAX_HANDLES
             return new_handle
           else
             while @free_handles.empty?
               sleep(1)
             end
             return allocate_handle
           end
         else
           return @free_handles.shift
         end
       end

       private :new_handle, :destroy_handle, :allocate_handle

       def get_handle
         return nil if @stopped
         begin
           h = allocate_handle
           yield h
         ensure # Make sure the handle gets put back in the list
           @free_handles << h
         end
         while @free_handles.length > MAX_FREE_HANDLES
           destroy_handle
         end
       end

       def destroy
         # Don't allocate any more handles
         @stopped = true
         # Destroy all handles, waiting for them to be released first
         while @handles.length > 0
           if @free_handles.length > 0
             destroy_handle
           else
             # Wait for a handle to be released
             sleep(1)
           end
         end
       end
     end
   end
end


-- 
Tim Bates
tim / bates.id.au