> On Dec 4, 2014, at 6:36 PM, HP3 <henddher.pedr...@gmail.com> wrote:
> 
> Hello all,
> 
> We are facing a problem when using history_meta.py recipe.
> 
> It seems like two concurrent transactions read the same (id,version) tuple at 
> "#1" and then each one tries to insert a new row into the pbases_history 
> table with the same pk (id, version) combination (see #2)
> By removing #2, the issue goes away but of course, there is no history kept 
> anywhere.

That’s the correct behavior.  Both transactions load the object at version X, 
then transaction A applies changes, transaction B applies changes.  Only one 
may be committed, and at that point, the other one is invalid; it’s modifying 
an object that no longer exists.

The application needs to anticipate collisions like this and resolve them.  
Usually, retrying the transaction after loading the fresh data is the most 
typical approach, though a pessimistic approach would involve row locking such 
as SELECT FOR UPDATE.



> 
> ####### CODE SNIPPET WHERE THE ISSUE IS TRIGGERED
> 
> history_meta.py:
> 
>     attr['version'] = obj.version # 1
> 
>     hist = history_cls()
> 
>     for key, value in attr.items():
> 
>         setattr(hist, key, value)
> 
>     session.add(hist) # 2
> 
>     obj.version += 1
> 
> 
> 
> #######  STACK TRACE:
> 
> 140, in collection_post
> 
>     p2model.DBSession.flush()
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py",
>  line 150, in do
> 
>     return getattr(self.registry(), name)(*args, **kwargs)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
>  line 1919, in flush
> 
>     self._flush(objects)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
>  line 2037, in _flush
> 
>     transaction.rollback(_capture_exception=True)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py",
>  line 60, in __exit__
> 
>     compat.reraise(exc_type, exc_value, exc_tb)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
>  line 2001, in _flush
> 
>     flush_context.execute()
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
>  line 367, in execute
> 
>     n.execute_aggregate(self, set_)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
>  line 459, in execute_aggregate
> 
>     self.execute(uow)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
>  line 526, in execute
> 
>     uow
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
>  line 65, in save_obj
> 
>     mapper, table, insert)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
>  line 570, in _emit_insert_statements
> 
>     execute(statement, multiparams)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py",
>  line 729, in execute
> 
>     return meth(self, multiparams, params)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/sql/elements.py",
>  line 321, in _execute_on_connection
> 
>     return connection._execute_clauseelement(self, multiparams, params)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py",
>  line 826, in _execute_clauseelement
> 
>     compiled_sql, distilled_params
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py",
>  line 958, in _execute_context
> 
>     context)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py",
>  line 1160, in _handle_dbapi_exception
> 
>     exc_info
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/util/compat.py",
>  line 199, in raise_from_cause
> 
>     reraise(type(exception), exception, tb=exc_tb)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py",
>  line 951, in _execute_context
> 
>     context)
> 
>   File 
> "/home/plannotate/.virtualenvs/P2v0.0.1/local/lib/python2.7/site-packages/sqlalchemy/engine/default.py",
>  line 436, in do_execute
> 
>     cursor.execute(statement, parameters)
> 
> IntegrityError: (IntegrityError) duplicate key value violates unique 
> constraint "pbases_history_pkey"
> 
> DETAIL:  Key (id, version)=(125, 21) already exists.
> 
> 
> 
> ####### MODEL
> 
> The simplified version of our model looks like this:
> 
> class PBase(Versioned, Base):
>     __tablename__ = 'pbases'
> 
>    id = Column(Integer, primary_key=True)
>    uuid = Column(String(50), unique=True)
>    classname = Column(String(50))
> 
>    __mapper_args__ = {
> 
>        'polymorphic_identity' : 'PBase',
> 
>        'polymorphic_on' : classname
> 
>     }
> 
> 
> class PDocument(PBase):
>     __tablename__ = 'pdocuments'
> 
>    id = Column(Integer, ForeignKey('p2bases.id'), primary_key=True)
>    name = Column(String)
> 
> __mapper_args__ = {
> 
>         'polymorphic_identity' : 'P2Document',
> 
>     }
> 
> 
> class PNote(PBase):
>     __tablename__ = 'pnotes'
> 
>      id = Column(Integer, ForeignKey('p2bases.id'), primary_key=True)
>      comment = Column(String)
>     position = Column(Integer)
>      document_id = Column(Integer, ForeignKey('p2documents.id'))
>     document_version = Column(Integer, default=0)
> 
>     document = relationship("PDocument", primaryjoin="PDocument.id == 
> PNode.document_id", 
> 
>                             backref=backref('pages', 
> order_by="PNode.position", collection_class=ordering_list('position')))
> 
>    __mapper_args__ = {
>         'polymorphic_identity' : 'PNote',
> 
>     }
> 
> @event.listens_for(PNode.document_id, 'set', active_history=True)
> 
> def _on_note_set_document_id(note, new_doc_id, old_old_id, initiator):
> 
>     doc = None
> 
>     if new_doc_id:
> 
>         doc = DBSession.query(PDocument).filter(PDocument.id == 
> new_doc_id).one()
> 
>     elif old_doc_id:
> 
>         page = DBSession.query(PDocument).filter(PDocument.id == 
> old_doc_id).one()
> 
>     if doc:
> 
>         note.document_version = doc.version
> 
> 
> 
> #######  SYSTEM CONFIG
> 
> 
> PostgreSQL 9.3.5 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu 
> 4.8.2-19ubuntu1) 4.8.2, 64-bit
> 
> argparse (1.2.1)
> 
> beautifulsoup4 (4.3.2)
> 
> Chameleon (2.16)
> 
> colander (1.0b1)
> 
> cornice (0.17)
> 
> coverage (3.7.1)
> 
> Mako (1.0.0)
> 
> MarkupSafe (0.23)
> 
> nose (1.3.4)
> 
> p2server (0.0)
> 
> PasteDeploy (1.5.2)
> 
> pip (1.5.6)
> 
> psycopg2 (2.5.4)
> 
> Pygments (1.6)
> 
> pyramid (1.5.1)
> 
> pyramid-chameleon (0.3)
> 
> pyramid-debugtoolbar (2.2)
> 
> pyramid-mako (1.0.2)
> 
> pyramid-tm (0.7)
> 
> repoze.lru (0.6)
> 
> requests (2.4.3)
> 
> setuptools (3.6)
> 
> simplejson (3.6.4)
> 
> six (1.8.0)
> 
> SQLAlchemy (0.9.7)
> 
> transaction (1.4.3)
> 
> translationstring (1.1)
> 
> venusian (1.0)
> 
> waitress (0.8.9)
> 
> WebOb (1.4)
> 
> WebTest (2.0.16)
> 
> wsgiref (0.1.2)
> 
> zope.deprecation (4.1.1)
> 
> zope.interface (4.1.1)
> 
> 
> zope.sqlalchemy (0.7.5)
> 
> 
> 
> 
> 
> 
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sqlalchemy+unsubscr...@googlegroups.com 
> <mailto:sqlalchemy+unsubscr...@googlegroups.com>.
> To post to this group, send email to sqlalchemy@googlegroups.com 
> <mailto:sqlalchemy@googlegroups.com>.
> Visit this group at http://groups.google.com/group/sqlalchemy 
> <http://groups.google.com/group/sqlalchemy>.
> For more options, visit https://groups.google.com/d/optout 
> <https://groups.google.com/d/optout>.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to