[python-sybase] FreeTDS + Sybase Module = MS SQL 2000

Dave Cole djc at object-craft.com.au
11 Jul 2002 19:33:38 +1000


On Wed, 10 Jul 2002, Kevin Jacobs wrote:

> > Here is the current issue I'm working on.  My app is
> > single-threaded, so I'm a little confused as to why this happens:
> > 
> > Traceback:
> >       Traceback (most recent call last):
> [...]
> >       AssertionError: release() of un-acquire()d lock
> 
> Never mind -- after looking at the code, it is clear why it is
> blowing up.  Can you explain what the unlock in the try-except block
> is supposed to be doing?  Can that if-statement be safely deleted?
> 
>     def close(self):
>         '''DB-API Cursor.close()
>         '''
>         self._lock()
>         try:
>             if self._state == _CUR_CLOSED:
>                 self._raise_error(Error, 'cursor is closed')
>             if self._state != _CUR_IDLE:
>                 status = self._cmd.ct_cancel(CS_CANCEL_ALL)
>                 if status == CS_SUCCEED:
>                     self._unlock()
>             self._cmd = None
>             self._state = _CUR_CLOSED
>         finally:
>             self._unlock()

The Python locks are being used in a reentrant manner to make the
cursors thread safe.

Each method which alters the cursor object protects itself by
obtaining the lock at the start of the method and releasing at the
end.  Some sequences of cursor operations must be keep the cursor
under the control of the same thread for the entire sequence of
operations.  If you look at the callproc() method you will see the
beginning of such a sequence:

    if self._state == _CUR_IDLE:
        # At the start of a command acquire an extra lock -
        # when the cursor is idle again the extra lock will be
        # released.
        self._lock()

And again in the execute() method:

    # At the start of a command acquire an extra lock - when
    # the cursor is idle again the extra lock will be
    # released.
    self._lock()

If you look through the rest of the cursor code you will see places
where the balancing _unlock() method is called.  Once all of the
results have been fetched.  Following it through:

* Each row is fetched via the fetchone() method via the function
  _fetch_rows().  If no row could be fetched (None returned) the
  cursor has reached the end of a result set.

* At the end of result set the number of rows fetched is determined
  via the _fetch_rowcount() method.  If this method discovers that
  there are no more result sets (CS_END_RESULTS from ct library) it
  sets the cursor idle and performs the balancing _unlock()

Most of the other places in the code with do the balancing _unlock()
are variants of this.  When you close() a cursor while there are still
outstanding results then the code does the _unlock().  Likewise when
there is some sort of error returned by the Sybase ct_ library.

Hope this makes things a bit more clear.

- Dave

-- 
http://www.object-craft.com.au