Replace advisory lock by SKIP LOCKED in queue pulling
It is simpler and closer to standard SQL. issue8910 review288701002
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Replace advisory lock by SKIP LOCKED in queue pulling
|
||||
* Load WSGI middleware from configuration
|
||||
* Add help text to Dict keys
|
||||
* Update button_action with returned values
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from collections import namedtuple
|
||||
from sql import For
|
||||
|
||||
DatabaseIntegrityError = None
|
||||
DatabaseOperationalError = None
|
||||
@@ -164,6 +165,10 @@ class DatabaseInterface(object):
|
||||
"Return if database supports FOR UPDATE/SHARE clause in SELECT."
|
||||
return False
|
||||
|
||||
def get_select_for_skip_locked(self):
|
||||
"Return For class with skip locked"
|
||||
return For
|
||||
|
||||
def has_window_functions(self):
|
||||
"Return if database supports window functions."
|
||||
return False
|
||||
|
||||
@@ -30,7 +30,7 @@ from psycopg2 import IntegrityError as DatabaseIntegrityError
|
||||
from psycopg2 import OperationalError as DatabaseOperationalError
|
||||
from psycopg2.extras import register_default_json, register_default_jsonb
|
||||
|
||||
from sql import Flavor, Cast
|
||||
from sql import Flavor, Cast, For
|
||||
from sql.functions import Function
|
||||
from sql.operators import BinaryOperator
|
||||
|
||||
@@ -67,6 +67,12 @@ class LoggingCursor(cursor):
|
||||
cursor.execute(self, sql, args)
|
||||
|
||||
|
||||
class ForSkipLocked(For):
|
||||
def __str__(self):
|
||||
assert not self.nowait, "Can not use both NO WAIT and SKIP LOCKED"
|
||||
return super().__str__() + (' SKIP LOCKED' if not self.nowait else '')
|
||||
|
||||
|
||||
class Unaccent(Function):
|
||||
__slots__ = ()
|
||||
_function = 'unaccent'
|
||||
@@ -132,6 +138,7 @@ class Database(DatabaseInterface):
|
||||
_search_path = None
|
||||
_current_user = None
|
||||
_has_returning = None
|
||||
_has_select_for_skip_locked = None
|
||||
_has_unaccent = {}
|
||||
flavor = Flavor(ilike=True)
|
||||
|
||||
@@ -413,6 +420,20 @@ class Database(DatabaseInterface):
|
||||
def has_select_for(self):
|
||||
return True
|
||||
|
||||
def get_select_for_skip_locked(self):
|
||||
if self._has_select_for_skip_locked is None:
|
||||
connection = self.get_connection()
|
||||
try:
|
||||
# SKIP LOCKED clause is available since PostgreSQL 9.5
|
||||
self._has_select_for_skip_locked = (
|
||||
self.get_version(connection) >= (9, 5))
|
||||
finally:
|
||||
self.put_connection(connection)
|
||||
if self._has_select_for_skip_locked:
|
||||
return ForSkipLocked
|
||||
else:
|
||||
return For
|
||||
|
||||
def has_window_functions(self):
|
||||
return True
|
||||
|
||||
|
||||
+18
-13
@@ -93,15 +93,20 @@ class Queue(ModelSQL):
|
||||
order_by=[
|
||||
queue.scheduled_at.nulls_first,
|
||||
queue.expected_at.nulls_first]))
|
||||
selected = With('id', query=candidates.select(
|
||||
candidates.id,
|
||||
where=((candidates.scheduled_at <= CurrentTimestamp())
|
||||
| (candidates.scheduled_at == Null))
|
||||
& database.lock_id(candidates.id),
|
||||
order_by=[
|
||||
candidates.scheduled_at.nulls_first,
|
||||
candidates.expected_at.nulls_first],
|
||||
limit=1))
|
||||
selected = queue.select(
|
||||
queue.id,
|
||||
where=((queue.name == name) if name else Literal(True))
|
||||
& (queue.dequeued_at == Null)
|
||||
& ((queue.scheduled_at <= CurrentTimestamp())
|
||||
| (queue.scheduled_at == Null)),
|
||||
order_by=[
|
||||
queue.scheduled_at.nulls_first,
|
||||
queue.expected_at.nulls_first],
|
||||
limit=1)
|
||||
if database.has_select_for():
|
||||
For = database.get_select_for_skip_locked()
|
||||
selected.for_ = For('UPDATE')
|
||||
|
||||
next_timeout = With('seconds', query=candidates.select(
|
||||
Min(Extract('second',
|
||||
candidates.scheduled_at - CurrentTimestamp())
|
||||
@@ -111,8 +116,8 @@ class Queue(ModelSQL):
|
||||
task_id, seconds = None, None
|
||||
if database.has_returning():
|
||||
query = queue.update([queue.dequeued_at], [CurrentTimestamp()],
|
||||
where=queue.id == selected.select(selected.id),
|
||||
with_=[candidates, selected, next_timeout],
|
||||
where=queue.id.in_(selected),
|
||||
with_=[candidates, next_timeout],
|
||||
returning=[
|
||||
queue.id, next_timeout.select(next_timeout.seconds)])
|
||||
cursor.execute(*query)
|
||||
@@ -121,8 +126,8 @@ class Queue(ModelSQL):
|
||||
task_id, seconds = row
|
||||
else:
|
||||
query = queue.select(queue.id,
|
||||
where=queue.id == selected.select(selected.id),
|
||||
with_=[candidates, selected])
|
||||
where=queue.id.in_(selected),
|
||||
with_=[candidates])
|
||||
cursor.execute(*query)
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
|
||||
Reference in New Issue
Block a user