Merge lp:~openerp-dev/openobject-server/6.0-bug-992525-cto into lp:openobject-server/6.0

Proposed by Samus CTO (OpenERP)
Status: Merged
Merged at revision: 3655
Proposed branch: lp:~openerp-dev/openobject-server/6.0-bug-992525-cto
Merge into: lp:openobject-server/6.0
Diff against target: 125 lines (+60/-42)
1 file modified
bin/osv/osv.py (+60/-42)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/6.0-bug-992525-cto
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+148174@code.launchpad.net

Description of the change

Automatically retry the typical transaction serialization errors

To post a comment you must log in.
3656. By Samus CTO (OpenERP)

[FIX] osv: Bad error message

3657. By Samus CTO (OpenERP)

[FIX] osv: Remove lookup for backward compatibility with psycopg2 < 2.0.14

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/osv/osv.py'
2--- bin/osv/osv.py 2012-04-03 06:35:56 +0000
3+++ bin/osv/osv.py 2013-02-13 17:13:26 +0000
4@@ -28,14 +28,19 @@
5 import pooler
6 import copy
7 import logging
8-from psycopg2 import IntegrityError, errorcodes
9+from psycopg2 import IntegrityError, OperationalError, errorcodes
10 from tools.func import wraps
11 from tools.translate import translate
12+import time
13+import random
14
15 module_list = []
16 module_class_list = {}
17 class_pool = {}
18
19+PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
20+MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
21+
22 class except_osv(Exception):
23 def __init__(self, name, value, exc_type='warning'):
24 self.name = name
25@@ -116,47 +121,60 @@
26 def _(src):
27 return tr(src, 'code')
28
29- try:
30- if not pooler.get_pool(dbname)._ready:
31- raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
32- return f(self, dbname, *args, **kwargs)
33- except orm.except_orm, inst:
34- if inst.name == 'AccessError':
35- self.logger.debug("AccessError", exc_info=True)
36- self.abortResponse(1, inst.name, 'warning', inst.value)
37- except except_osv, inst:
38- self.abortResponse(1, inst.name, inst.exc_type, inst.value)
39- except IntegrityError, inst:
40- osv_pool = pooler.get_pool(dbname)
41- for key in osv_pool._sql_error.keys():
42- if key in inst[0]:
43- self.abortResponse(1, _('Constraint Error'), 'warning',
44- tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0])
45- if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
46- msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
47- self.logger.debug("IntegrityError", exc_info=True)
48- try:
49- errortxt = inst.pgerror.replace('«','"').replace('»','"')
50- if '"public".' in errortxt:
51- context = errortxt.split('"public".')[1]
52- model_name = table = context.split('"')[1]
53- else:
54- last_quote_end = errortxt.rfind('"')
55- last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
56- model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
57- model = table.replace("_",".")
58- model_obj = osv_pool.get(model)
59- if model_obj:
60- model_name = model_obj._description or model_obj._name
61- msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
62- except Exception:
63- pass
64- self.abortResponse(1, _('Integrity Error'), 'warning', msg)
65- else:
66- self.abortResponse(1, _('Integrity Error'), 'warning', inst[0])
67- except Exception:
68- self.logger.exception("Uncaught exception")
69- raise
70+ tries = 0
71+ while True:
72+ try:
73+ if not pooler.get_pool(dbname)._ready:
74+ raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
75+ return f(self, dbname, *args, **kwargs)
76+ except OperationalError, e:
77+ # Automatically retry the typical transaction serialization errors
78+ if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
79+ raise
80+ if tries >= MAX_TRIES_ON_CONCURRENCY_FAILURE:
81+ self.logger.warning("%s, maximum number of tries reached" % e.pgcode)
82+ raise
83+ wait_time = random.uniform(0.0, 2 ** tries)
84+ tries += 1
85+ self.logger.info("%s, retrying %d/%d in %.04f sec..." % (e.pgcode, tries, MAX_TRIES_ON_CONCURRENCY_FAILURE, wait_time))
86+ time.sleep(wait_time)
87+ except orm.except_orm, inst:
88+ if inst.name == 'AccessError':
89+ self.logger.debug("AccessError", exc_info=True)
90+ self.abortResponse(1, inst.name, 'warning', inst.value)
91+ except except_osv, inst:
92+ self.abortResponse(1, inst.name, inst.exc_type, inst.value)
93+ except IntegrityError, inst:
94+ osv_pool = pooler.get_pool(dbname)
95+ for key in osv_pool._sql_error.keys():
96+ if key in inst[0]:
97+ self.abortResponse(1, _('Constraint Error'), 'warning',
98+ tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0])
99+ if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
100+ msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
101+ self.logger.debug("IntegrityError", exc_info=True)
102+ try:
103+ errortxt = inst.pgerror.replace('«','"').replace('»','"')
104+ if '"public".' in errortxt:
105+ context = errortxt.split('"public".')[1]
106+ model_name = table = context.split('"')[1]
107+ else:
108+ last_quote_end = errortxt.rfind('"')
109+ last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
110+ model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
111+ model = table.replace("_",".")
112+ model_obj = osv_pool.get(model)
113+ if model_obj:
114+ model_name = model_obj._description or model_obj._name
115+ msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
116+ except Exception:
117+ pass
118+ self.abortResponse(1, _('Integrity Error'), 'warning', msg)
119+ else:
120+ self.abortResponse(1, _('Integrity Error'), 'warning', inst[0])
121+ except Exception:
122+ self.logger.exception("Uncaught exception")
123+ raise
124
125 return wrapper
126