diff options
author | Gregory P. Smith <greg@mad-scientist.com> | 2003-11-02 09:10:16 (GMT) |
---|---|---|
committer | Gregory P. Smith <greg@mad-scientist.com> | 2003-11-02 09:10:16 (GMT) |
commit | dc113a8a06668c652895f6745808b1b04cbfc103 (patch) | |
tree | 2ec990ec2985c84b3711a644f280e04c41a4ab0b /Lib/bsddb | |
parent | e2767171133ead86ec3aacc205d1d8365a0a2d07 (diff) | |
download | cpython-dc113a8a06668c652895f6745808b1b04cbfc103.zip cpython-dc113a8a06668c652895f6745808b1b04cbfc103.tar.gz cpython-dc113a8a06668c652895f6745808b1b04cbfc103.tar.bz2 |
* Fix the singlethreaded deadlocks occurring in the simple bsddb interface.
* Add support for multiple iterator/generator objects at once on the simple
bsddb _DBWithCursor interface.
Diffstat (limited to 'Lib/bsddb')
-rw-r--r-- | Lib/bsddb/__init__.py | 109 |
1 files changed, 102 insertions, 7 deletions
diff --git a/Lib/bsddb/__init__.py b/Lib/bsddb/__init__.py index 51cc454..778ad29 100644 --- a/Lib/bsddb/__init__.py +++ b/Lib/bsddb/__init__.py @@ -70,20 +70,74 @@ import UserDict class _iter_mixin(UserDict.DictMixin): def __iter__(self): try: - yield self.first()[0] - next = self.next + cur = self.db.cursor() + self._iter_cursors[str(cur)] = cur + + # since we're only returning keys, we call the cursor + # methods with flags=0, dlen=0, dofs=0 + curkey = cur.first(0,0,0)[0] + yield curkey + + next = cur.next while 1: - yield next()[0] + try: + curkey = next(0,0,0)[0] + yield curkey + except _bsddb.DBCursorClosedError: + # our cursor object was closed since we last yielded + # create a new one and attempt to reposition to the + # right place + cur = self.db.cursor() + self._iter_cursors[str(cur)] = cur + # FIXME-20031101-greg: race condition. cursor could + # be closed by another thread before this set call. + try: + cur.set(curkey,0,0,0) + except _bsddb.DBCursorClosedError: + # halt iteration on race condition... + raise _bsddb.DBNotFoundError + next = cur.next except _bsddb.DBNotFoundError: + try: + del self._iter_cursors[str(cur)] + except KeyError: + pass return def iteritems(self): try: - yield self.first() - next = self.next + cur = self.db.cursor() + self._iter_cursors[str(cur)] = cur + + kv = cur.first() + curkey = kv[0] + yield kv + + next = cur.next while 1: - yield next() + try: + kv = next() + curkey = kv[0] + yield kv + except _bsddb.DBCursorClosedError: + # our cursor object was closed since we last yielded + # create a new one and attempt to reposition to the + # right place + cur = self.db.cursor() + self._iter_cursors[str(cur)] = cur + # FIXME-20031101-greg: race condition. cursor could + # be closed by another thread before this set call. + try: + cur.set(curkey,0,0,0) + except _bsddb.DBCursorClosedError: + # halt iteration on race condition... + raise _bsddb.DBNotFoundError + next = cur.next except _bsddb.DBNotFoundError: + try: + del self._iter_cursors[str(cur)] + except KeyError: + pass return """ else: @@ -97,15 +151,53 @@ class _DBWithCursor(_iter_mixin): """ def __init__(self, db): self.db = db - self.dbc = None self.db.set_get_returns_none(0) + # FIXME-20031101-greg: I believe there is still the potential + # for deadlocks in a multithreaded environment if someone + # attempts to use the any of the cursor interfaces in one + # thread while doing a put or delete in another thread. The + # reason is that _checkCursor and _closeCursors are not atomic + # operations. Doing our own locking around self.dbc, + # self.saved_dbc_key and self._iter_cursors could prevent this. + # TODO: A test case demonstrating the problem needs to be written. + + # self.dbc is a DBCursor object used to implement the + # first/next/previous/last/set_location methods. + self.dbc = None + self.saved_dbc_key = None + + # a collection of all DBCursor objects currently allocated + # by the _iter_mixin interface. + self._iter_cursors = {} + + def __del__(self): self.close() + def _get_dbc(self): + return self.dbc + def _checkCursor(self): if self.dbc is None: self.dbc = self.db.cursor() + if self.saved_dbc_key is not None: + self.dbc.set(self.saved_dbc_key) + self.saved_dbc_key = None + + # This method is needed for all non-cursor DB calls to avoid + # BerkeleyDB deadlocks (due to being opened with DB_INIT_LOCK + # and DB_THREAD to be thread safe) when intermixing database + # operations that use the cursor internally with those that don't. + def _closeCursors(self, save=True): + if self.dbc: + c = self.dbc + self.dbc = None + if save: + self.saved_dbc_key = c.current(0,0,0)[0] + c.close() + del c + map(lambda c: c.close(), self._iter_cursors.values()) def _checkOpen(self): if self.db is None: @@ -124,13 +216,16 @@ class _DBWithCursor(_iter_mixin): def __setitem__(self, key, value): self._checkOpen() + self._closeCursors() self.db[key] = value def __delitem__(self, key): self._checkOpen() + self._closeCursors() del self.db[key] def close(self): + self._closeCursors(save=False) if self.dbc is not None: self.dbc.close() v = 0 |