gooderp18绿色标准版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
7.7KB

  1. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  2. import logging
  3. import random
  4. import threading
  5. import time
  6. from collections.abc import Mapping, Sequence
  7. from functools import partial
  8. from psycopg2 import IntegrityError, OperationalError, errorcodes, errors
  9. import odoo
  10. from odoo.exceptions import UserError, ValidationError
  11. from odoo.http import request
  12. from odoo.models import check_method_name
  13. from odoo.modules.registry import Registry
  14. from odoo.tools import DotDict, lazy
  15. from odoo.tools.translate import translate_sql_constraint
  16. from . import security
  17. _logger = logging.getLogger(__name__)
  18. PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
  19. PG_CONCURRENCY_EXCEPTIONS_TO_RETRY = (errors.LockNotAvailable, errors.SerializationFailure, errors.DeadlockDetected)
  20. MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
  21. def dispatch(method, params):
  22. db, uid, passwd = params[0], int(params[1]), params[2]
  23. security.check(db, uid, passwd)
  24. threading.current_thread().dbname = db
  25. threading.current_thread().uid = uid
  26. registry = Registry(db).check_signaling()
  27. with registry.manage_changes():
  28. if method == 'execute':
  29. res = execute(db, uid, *params[3:])
  30. elif method == 'execute_kw':
  31. res = execute_kw(db, uid, *params[3:])
  32. else:
  33. raise NameError("Method not available %s" % method)
  34. return res
  35. def execute_cr(cr, uid, obj, method, *args, **kw):
  36. # clean cache etc if we retry the same transaction
  37. cr.reset()
  38. env = odoo.api.Environment(cr, uid, {})
  39. recs = env.get(obj)
  40. if recs is None:
  41. raise UserError(env._("Object %s doesn't exist", obj))
  42. result = retrying(partial(odoo.api.call_kw, recs, method, args, kw), env)
  43. # force evaluation of lazy values before the cursor is closed, as it would
  44. # error afterwards if the lazy isn't already evaluated (and cached)
  45. for l in _traverse_containers(result, lazy):
  46. _0 = l._value
  47. return result
  48. def execute_kw(db, uid, obj, method, args, kw=None):
  49. return execute(db, uid, obj, method, *args, **kw or {})
  50. def execute(db, uid, obj, method, *args, **kw):
  51. # TODO could be conditionnaly readonly as in _call_kw_readonly
  52. with Registry(db).cursor() as cr:
  53. check_method_name(method)
  54. res = execute_cr(cr, uid, obj, method, *args, **kw)
  55. if res is None:
  56. _logger.info('The method %s of the object %s can not return `None`!', method, obj)
  57. return res
  58. def _as_validation_error(env, exc):
  59. """ Return the IntegrityError encapsuled in a nice ValidationError """
  60. unknown = env._('Unknown')
  61. model = DotDict({'_name': 'unknown', '_description': unknown})
  62. field = DotDict({'name': 'unknown', 'string': unknown})
  63. for _name, rclass in env.registry.items():
  64. if exc.diag.table_name == rclass._table:
  65. model = rclass
  66. field = model._fields.get(exc.diag.column_name) or field
  67. break
  68. match exc:
  69. case errors.NotNullViolation():
  70. return ValidationError(env._(
  71. "The operation cannot be completed:\n"
  72. "- Create/update: a mandatory field is not set.\n"
  73. "- Delete: another model requires the record being deleted."
  74. " If possible, archive it instead.\n\n"
  75. "Model: %(model_name)s (%(model_tech_name)s)\n"
  76. "Field: %(field_name)s (%(field_tech_name)s)\n",
  77. model_name=model._description,
  78. model_tech_name=model._name,
  79. field_name=field.string,
  80. field_tech_name=field.name,
  81. ))
  82. case errors.ForeignKeyViolation():
  83. return ValidationError(env._(
  84. "The operation cannot be completed: another model requires "
  85. "the record being deleted. If possible, archive it instead.\n\n"
  86. "Model: %(model_name)s (%(model_tech_name)s)\n"
  87. "Constraint: %(constraint)s\n",
  88. model_name=model._description,
  89. model_tech_name=model._name,
  90. constraint=exc.diag.constraint_name,
  91. ))
  92. if exc.diag.constraint_name in env.registry._sql_constraints:
  93. return ValidationError(env._(
  94. "The operation cannot be completed: %s",
  95. translate_sql_constraint(env.cr, exc.diag.constraint_name, env.context.get('lang', 'en_US'))
  96. ))
  97. return ValidationError(env._("The operation cannot be completed: %s", exc.args[0]))
  98. def retrying(func, env):
  99. """
  100. Call ``func`` until the function returns without serialisation
  101. error. A serialisation error occurs when two requests in independent
  102. cursors perform incompatible changes (such as writing different
  103. values on a same record). By default, it retries up to 5 times.
  104. :param callable func: The function to call, you can pass arguments
  105. using :func:`functools.partial`:.
  106. :param odoo.api.Environment env: The environment where the registry
  107. and the cursor are taken.
  108. """
  109. try:
  110. for tryno in range(1, MAX_TRIES_ON_CONCURRENCY_FAILURE + 1):
  111. tryleft = MAX_TRIES_ON_CONCURRENCY_FAILURE - tryno
  112. try:
  113. result = func()
  114. if not env.cr._closed:
  115. env.cr.flush() # submit the changes to the database
  116. break
  117. except (IntegrityError, OperationalError) as exc:
  118. if env.cr._closed:
  119. raise
  120. env.cr.rollback()
  121. env.reset()
  122. env.registry.reset_changes()
  123. if request:
  124. request.session = request._get_session_and_dbname()[0]
  125. # Rewind files in case of failure
  126. for filename, file in request.httprequest.files.items():
  127. if hasattr(file, "seekable") and file.seekable():
  128. file.seek(0)
  129. else:
  130. raise RuntimeError(f"Cannot retry request on input file {filename!r} after serialization failure") from exc
  131. if isinstance(exc, IntegrityError):
  132. raise _as_validation_error(env, exc) from exc
  133. if not isinstance(exc, PG_CONCURRENCY_EXCEPTIONS_TO_RETRY):
  134. raise
  135. if not tryleft:
  136. _logger.info("%s, maximum number of tries reached!", errorcodes.lookup(exc.pgcode))
  137. raise
  138. wait_time = random.uniform(0.0, 2 ** tryno)
  139. _logger.info("%s, %s tries left, try again in %.04f sec...", errorcodes.lookup(exc.pgcode), tryleft, wait_time)
  140. time.sleep(wait_time)
  141. else:
  142. # handled in the "if not tryleft" case
  143. raise RuntimeError("unreachable")
  144. except Exception:
  145. env.reset()
  146. env.registry.reset_changes()
  147. raise
  148. if not env.cr.closed:
  149. env.cr.commit() # effectively commits and execute post-commits
  150. env.registry.signal_changes()
  151. return result
  152. def _traverse_containers(val, type_):
  153. """ Yields atoms filtered by specified ``type_`` (or type tuple), traverses
  154. through standard containers (non-string mappings or sequences) *unless*
  155. they're selected by the type filter
  156. """
  157. from odoo.models import BaseModel
  158. if isinstance(val, type_):
  159. yield val
  160. elif isinstance(val, (str, bytes, BaseModel)):
  161. return
  162. elif isinstance(val, Mapping):
  163. for k, v in val.items():
  164. yield from _traverse_containers(k, type_)
  165. yield from _traverse_containers(v, type_)
  166. elif isinstance(val, Sequence):
  167. for v in val:
  168. yield from _traverse_containers(v, type_)
上海开阖软件有限公司 沪ICP备12045867号-1