Skip to content
Snippets Groups Projects
Unverified Commit 11540be5 authored by Erik Johnston's avatar Erik Johnston Committed by GitHub
Browse files

Fix `could not serialize access` errors for `claim_e2e_one_time_keys` (#10504)

parent c2000ab3
No related branches found
No related tags found
No related merge requests found
Reduce errors in PostgreSQL logs due to concurrent serialization errors.
...@@ -755,81 +755,145 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore): ...@@ -755,81 +755,145 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore):
""" """
@trace @trace
def _claim_e2e_one_time_keys(txn): def _claim_e2e_one_time_key_simple(
sql = ( txn, user_id: str, device_id: str, algorithm: str
"SELECT key_id, key_json FROM e2e_one_time_keys_json" ) -> Optional[Tuple[str, str]]:
" WHERE user_id = ? AND device_id = ? AND algorithm = ?" """Claim OTK for device for DBs that don't support RETURNING.
" LIMIT 1"
Returns:
A tuple of key name (algorithm + key ID) and key JSON, if an
OTK was found.
"""
sql = """
SELECT key_id, key_json FROM e2e_one_time_keys_json
WHERE user_id = ? AND device_id = ? AND algorithm = ?
LIMIT 1
"""
txn.execute(sql, (user_id, device_id, algorithm))
otk_row = txn.fetchone()
if otk_row is None:
return None
key_id, key_json = otk_row
self.db_pool.simple_delete_one_txn(
txn,
table="e2e_one_time_keys_json",
keyvalues={
"user_id": user_id,
"device_id": device_id,
"algorithm": algorithm,
"key_id": key_id,
},
) )
fallback_sql = ( self._invalidate_cache_and_stream(
"SELECT key_id, key_json, used FROM e2e_fallback_keys_json" txn, self.count_e2e_one_time_keys, (user_id, device_id)
" WHERE user_id = ? AND device_id = ? AND algorithm = ?"
" LIMIT 1"
) )
result = {}
delete = [] return f"{algorithm}:{key_id}", key_json
used_fallbacks = []
for user_id, device_id, algorithm in query_list: @trace
user_result = result.setdefault(user_id, {}) def _claim_e2e_one_time_key_returning(
device_result = user_result.setdefault(device_id, {}) txn, user_id: str, device_id: str, algorithm: str
txn.execute(sql, (user_id, device_id, algorithm)) ) -> Optional[Tuple[str, str]]:
otk_row = txn.fetchone() """Claim OTK for device for DBs that support RETURNING.
if otk_row is not None:
key_id, key_json = otk_row Returns:
device_result[algorithm + ":" + key_id] = key_json A tuple of key name (algorithm + key ID) and key JSON, if an
delete.append((user_id, device_id, algorithm, key_id)) OTK was found.
else: """
# no one-time key available, so see if there's a fallback
# key # We can use RETURNING to do the fetch and DELETE in once step.
txn.execute(fallback_sql, (user_id, device_id, algorithm)) sql = """
fallback_row = txn.fetchone() DELETE FROM e2e_one_time_keys_json
if fallback_row is not None: WHERE user_id = ? AND device_id = ? AND algorithm = ?
key_id, key_json, used = fallback_row AND key_id IN (
device_result[algorithm + ":" + key_id] = key_json SELECT key_id FROM e2e_one_time_keys_json
if not used: WHERE user_id = ? AND device_id = ? AND algorithm = ?
used_fallbacks.append( LIMIT 1
(user_id, device_id, algorithm, key_id) )
) RETURNING key_id, key_json
"""
# drop any one-time keys that were claimed
sql = ( txn.execute(
"DELETE FROM e2e_one_time_keys_json" sql, (user_id, device_id, algorithm, user_id, device_id, algorithm)
" WHERE user_id = ? AND device_id = ? AND algorithm = ?"
" AND key_id = ?"
) )
for user_id, device_id, algorithm, key_id in delete: otk_row = txn.fetchone()
log_kv( if otk_row is None:
{ return None
"message": "Executing claim e2e_one_time_keys transaction on database."
} key_id, key_json = otk_row
) return f"{algorithm}:{key_id}", key_json
txn.execute(sql, (user_id, device_id, algorithm, key_id))
log_kv({"message": "finished executing and invalidating cache"}) results = {}
self._invalidate_cache_and_stream( for user_id, device_id, algorithm in query_list:
txn, self.count_e2e_one_time_keys, (user_id, device_id) if self.database_engine.supports_returning:
# If we support RETURNING clause we can use a single query that
# allows us to use autocommit mode.
_claim_e2e_one_time_key = _claim_e2e_one_time_key_returning
db_autocommit = True
else:
_claim_e2e_one_time_key = _claim_e2e_one_time_key_simple
db_autocommit = False
row = await self.db_pool.runInteraction(
"claim_e2e_one_time_keys",
_claim_e2e_one_time_key,
user_id,
device_id,
algorithm,
db_autocommit=db_autocommit,
)
if row:
device_results = results.setdefault(user_id, {}).setdefault(
device_id, {}
) )
# mark fallback keys as used device_results[row[0]] = row[1]
for user_id, device_id, algorithm, key_id in used_fallbacks: continue
self.db_pool.simple_update_txn(
txn, # No one-time key available, so see if there's a fallback
"e2e_fallback_keys_json", # key
{ row = await self.db_pool.simple_select_one(
table="e2e_fallback_keys_json",
keyvalues={
"user_id": user_id,
"device_id": device_id,
"algorithm": algorithm,
},
retcols=("key_id", "key_json", "used"),
desc="_get_fallback_key",
allow_none=True,
)
if row is None:
continue
key_id = row["key_id"]
key_json = row["key_json"]
used = row["used"]
# Mark fallback key as used if not already.
if not used:
await self.db_pool.simple_update_one(
table="e2e_fallback_keys_json",
keyvalues={
"user_id": user_id, "user_id": user_id,
"device_id": device_id, "device_id": device_id,
"algorithm": algorithm, "algorithm": algorithm,
"key_id": key_id, "key_id": key_id,
}, },
{"used": True}, updatevalues={"used": True},
desc="_get_fallback_key_set_used",
) )
self._invalidate_cache_and_stream( await self.invalidate_cache_and_stream(
txn, self.get_e2e_unused_fallback_key_types, (user_id, device_id) "get_e2e_unused_fallback_key_types", (user_id, device_id)
) )
return result device_results = results.setdefault(user_id, {}).setdefault(device_id, {})
device_results[f"{algorithm}:{key_id}"] = key_json
return await self.db_pool.runInteraction( return results
"claim_e2e_one_time_keys", _claim_e2e_one_time_keys
)
class EndToEndKeyStore(EndToEndKeyWorkerStore, SQLBaseStore): class EndToEndKeyStore(EndToEndKeyWorkerStore, SQLBaseStore):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment