core (memory, sqlite): added fileUpdate method to update "extra" and "access" avoiding race condition

This commit is contained in:
Goffi 2018-03-16 17:03:46 +01:00
commit 97f19b2e52
2 changed files with 54 additions and 0 deletions

View file

@ -798,6 +798,48 @@ class SqliteStorage(object):
d.addErrback(lambda failure: log.error(_(u"Can't save file metadata for [{profile}]: {reason}".format(profile=client.profile, reason=failure))))
return d
def _fileUpdate(self, cursor, file_id, column, update_cb):
query = 'SELECT {column} FROM files where id=?'.format(column=column)
for i in xrange(5):
cursor.execute(query, [file_id])
try:
older_value_raw = cursor.fetchone()[0]
except TypeError:
raise exceptions.NotFound
value = json.loads(older_value_raw)
update_cb(value)
value_raw = json.dumps(value)
update_query = 'UPDATE files SET {column}=? WHERE id=? AND {column}=?'.format(column=column)
update_args = (value_raw, file_id, older_value_raw)
try:
cursor.execute(update_query, update_args)
except sqlite3.Error:
pass
else:
if cursor.rowcount == 1:
break;
log.warning(_(u"table not updated, probably due to race condition, trying again ({tries})").format(tries=i+1))
else:
log.error(_(u"Can't update file table"))
def fileUpdate(self, file_id, column, update_cb):
"""update a column value using a method to avoid race conditions
the older value will be retrieved from database, then update_cb will be applied
to update it, and file will be updated checking that older value has not been changed meanwhile
by an other user. If it has changed, it tries again a couple of times before failing
@param column(str): column name (only "access" or "extra" are allowed)
@param update_cb(callable): method to update the value of the colum
the method will take older value as argument, and must update it in place
update_cb must not care about serialization,
it get the deserialized data (i.e. a Python object) directly
Note that the callable must be thread-safe
@raise exceptions.NotFound: there is not file with this id
"""
if column not in ('access', 'extra'):
raise exceptions.InternalError('bad column name')
return self.dbpool.runInteraction(self._fileUpdate, file_id, column, update_cb)
##Helper methods##
def __getFirstResult(self, result):