hovel / pybbm

Django forum solution. Tested, documented, shipped with example project.
BSD 2-Clause "Simplified" License
225 stars 151 forks source link

Code to handle mysql transactions atomically for Topic/Forum ReadTracker broken of postgresql with read replicas #291

Open siovene opened 4 years ago

siovene commented 4 years ago

In models.py, you have some code to attempt a transaction to create a TopicReadTracker or ForumReadTracker, and rollback if it fails.

The comment mentions that it's a workaround because get_or_create can fail on MySQL.

I have been running pybbm for a few years on PostgreSQL and when I configured my cluster to have read replicas, I ran into this problem:

File "/src/pybbm/pybb/views.py" in mark_read
  385.             topic_mark, new = TopicReadTracker.objects.get_or_create_tracker(topic=self.topic, user=self.request.user)

File "/src/pybbm/pybb/models.py" in get_or_create_tracker
  423.             transaction.savepoint_commit(sid)

File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py" in savepoint_commit
  74.     get_connection(using).savepoint_commit(sid)

File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py" in savepoint_commit
  363.         self._savepoint_commit(sid)

File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py" in _savepoint_commit
  312.             cursor.execute(self.ops.savepoint_commit_sql(sid))

File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/operations.py" in savepoint_commit_sql
  355.         return "RELEASE SAVEPOINT %s" % self.quote_name(sid)

File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql/operations.py" in quote_name
  111.         if name.startswith('"') and name.endswith('"'):

Exception Type: AttributeError at /forum/c/astrobin/annoucements/db-upgrade-coming-soon-thank-you-for-your-patience/
Exception Value: 'NoneType' object has no attribute 'startswith'

To solve it, you can only do the transaction "trick" if 'mysql' in connection.vendor (where connection is imported from django.db). For other engines, just fallback to the regular get_or_create of the manager's super class.