Edgewall Software

Ticket #2170: trac_no_writer_contention.2.patch

File trac_no_writer_contention.2.patch, 4.2 KB (added by cboos, 6 years ago)

Second iteration of the patch, integrating cmlenz feedback, previous patch survived 1 day of intensive use, let's see how it goes with this one :)

  • trac/db.py

    diff -r 3263f36aa50a -r 6893628e22fe trac/db.py
    a b  
    2424except ImportError: 
    2525    import dummy_threading as threading 
    2626    threading._get_ident = lambda: 0 
     27import weakref 
    2728 
    2829from trac.core import TracError 
    2930 
     
    183184                    del self._active[tid] 
    184185                    if cnx not in self._dormant: 
    185186                        cnx.rollback() 
    186                         self._dormant.append(cnx) 
     187                        if cnx.poolable: 
     188                            self._dormant.append(cnx) 
     189                        else: 
     190                            self._cursize -= 1 
    187191                        self._available.notify() 
    188192        finally: 
    189193            self._available.release() 
     
    203207 
    204208    class PyFormatCursor(sqlite.Cursor): 
    205209        def execute(self, sql, args=None): 
    206             if args: 
    207                 sql = sql % (('?',) * len(args)) 
    208             sqlite.Cursor.execute(self, sql, args or []) 
     210            try: 
     211                if args: 
     212                    sql = sql % (('?',) * len(args)) 
     213                sqlite.Cursor.execute(self, sql, args or []) 
     214            except sqlite.OperationalError, e: 
     215                self.cnx.rollback() 
     216                raise e 
    209217        def executemany(self, sql, args=None): 
    210             if args: 
    211                 sql = sql % (('?',) * len(args[0])) 
    212             sqlite.Cursor.executemany(self, sql, args or []) 
     218            try: 
     219                if args: 
     220                    sql = sql % (('?',) * len(args[0])) 
     221                sqlite.Cursor.executemany(self, sql, args or []) 
     222            except sqlite.OperationalError, e: 
     223                    self.cnx.rollback() 
     224                    raise e 
    213225        def _convert_row(self, row): 
    214226            return tuple([(isinstance(v, unicode) and [v.encode('utf-8')] or [v])[0] 
    215227                          for v in row]) 
     
    224236            rows = sqlite.Cursor.fetchall(self) 
    225237            return rows != None and [self._convert_row(row) 
    226238                                     for row in rows] or None 
     239 
    227240                 
    228241except ImportError: 
    229242    try: 
     
    236249class SQLiteConnection(ConnectionWrapper): 
    237250    """Connection wrapper for SQLite.""" 
    238251 
    239     __slots__ = ['cnx'] 
     252    __slots__ = ['cnx', 'active_cursors'] 
     253 
     254    poolable = False 
     255 
     256    poolable = False 
    240257 
    241258    def __init__(self, path, params={}): 
    242259        assert have_pysqlite > 0 
     
    254271                                 'directory it is located in.' \ 
    255272                                 % (getuser(), path) 
    256273 
    257         timeout = int(params.get('timeout', 10000)) 
    258274        if have_pysqlite == 2: 
     275            self.active_cursors = weakref.WeakKeyDictionary() 
     276            timeout = int(params.get('timeout', 10.0)) 
    259277            # Convert unicode to UTF-8 bytestrings. This is case-sensitive, so 
    260278            # we need two converters 
    261279            sqlite.register_converter('text', str) 
    262280            sqlite.register_converter('TEXT', str) 
    263281 
    264282            cnx = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES, 
    265                                  check_same_thread=False, timeout=timeout) 
     283                                 timeout=timeout) 
    266284        else: 
     285            timeout = int(params.get('timeout', 10000)) 
    267286            cnx = sqlite.connect(path, timeout=timeout) 
    268287        ConnectionWrapper.__init__(self, cnx) 
    269288 
    270289    if have_pysqlite == 2: 
    271290        def cursor(self): 
    272             return self.cnx.cursor(PyFormatCursor) 
     291            cursor = self.cnx.cursor(PyFormatCursor) 
     292            self.active_cursors[cursor] = True 
     293            cursor.cnx = self 
     294            return cursor 
    273295    else: 
    274296        def cursor(self): 
    275297            return self.cnx.cursor() 
     298 
     299    def _finalize_cursors(self): 
     300        cursors = self.active_cursors.keys() 
     301        for c in cursors: 
     302            c.close() 
     303 
     304    def rollback(self): 
     305        self._finalize_cursors() 
     306        self.cnx.rollback() 
    276307 
    277308    def cast(self, column, type): 
    278309        return column 
     
    331362    """Connection wrapper for PostgreSQL.""" 
    332363 
    333364    __slots__ = ['cnx'] 
     365 
     366    poolable = True 
    334367 
    335368    def __init__(self, path, user=None, password=None, host=None, port=None, 
    336369                 params={}):