| 
      
      
      From: Matthew B. <ma...@by...> - 2002-07-05 14:00:19
      
     | 
| On Friday 05 July 2002 14:37, Michael Neumann wrote:
> Matthew Bloch wrote:
> > Hello;
> >
> > I've just noticed an undesirable interaction between multithreaded Ruby
> > applications and the MySQL C API (if not other DB APIs); my web spider
> > application was running along fine but suddenly after an unpredictable
> > number of minutes, hours etc. it would stop dead.  I think the problem is
> > that I use a "lock tables" statement which, if used with Ruby threads,
> > blocks the whole Ruby process until the table is unlocked.  Trouble is if
> > the 'thread' holding the lock is an internal Ruby thread in the same
> > process it'll never get run to unlock it :-/
> >
> > Is there a general way around this problem with the mysql_ API?  My
> > impression is no, since I can't find any way of telling mysql to use
> > non-blocking semantics through its C API.  Anyhow even if we leave it
> > like this it's probably worth adding a note about this pitfall to the
> > docs.
>
> If only your Ruby application accesses the Mysql database and you want
> to lock a table only for one thread, use a Mutex instead of locking the
> whole table. But if you lock a table because other processes (outside the
> Ruby world) should not modify the table while one Ruby thread modifies it,
> I see no other chance than using processes (start new Ruby interpreter)
> instead of threads.
I'm using multiple Ruby processes over multiple machines talking to one 
database, so the database really should be the co-ordination point for 
synchronization.  In the end this solution seemed to work quite nicely:
    private
    def lock_central
      begin
        # A bit of a bodge, but a necessary one since calling blocking Mysql
	# statements blocks *all* of our threads.
	#
	timeout(60) {
          backoff = 0.25
	  begin
            row = @dbi_lock.select_one("select get_lock('central', 1)")
	    sleep backoff
	    backoff = backoff < 5 ? backoff + rand : 0.25
	  end while row[0].to_i == 0
	}
	yield
      ensure
        @dbi_lock.select_one("select release_lock('central')")
      end
    end
Where @dbi_lock is a database connection separate from the one that accesses 
the data.  So for an overhead of an extra database connection per process and 
a slightly mucky spin lock, I think this is the best way of fixing the 
problem.
-- 
Matthew Bloch                 Bytemark Computer Consulting
                                http://www.bytemark.co.uk/
                                  tel. +44 (0) 8707 455026
 |