|
17 | 17 |
|
18 | 18 | from canonicaljson import encode_canonical_json
|
19 | 19 |
|
20 |
| -from synapse.api.errors import Codes, SynapseError |
| 20 | +from synapse.api.errors import Codes, StoreError, SynapseError |
21 | 21 | from synapse.storage._base import SQLBaseStore, db_to_json
|
22 | 22 | from synapse.storage.database import LoggingTransaction
|
23 | 23 | from synapse.types import JsonDict
|
@@ -46,8 +46,6 @@ async def get_user_filter(
|
46 | 46 |
|
47 | 47 | return db_to_json(def_json)
|
48 | 48 |
|
49 |
| - |
50 |
| -class FilteringStore(FilteringWorkerStore): |
51 | 49 | async def add_user_filter(self, user_localpart: str, user_filter: JsonDict) -> int:
|
52 | 50 | def_json = encode_canonical_json(user_filter)
|
53 | 51 |
|
@@ -79,4 +77,23 @@ def _do_txn(txn: LoggingTransaction) -> int:
|
79 | 77 |
|
80 | 78 | return filter_id
|
81 | 79 |
|
82 |
| - return await self.db_pool.runInteraction("add_user_filter", _do_txn) |
| 80 | + attempts = 0 |
| 81 | + while True: |
| 82 | + # Try a few times. |
| 83 | + # This is technically needed if a user tries to create two filters at once, |
| 84 | + # leading to two concurrent transactions. |
| 85 | + # The failure case would be: |
| 86 | + # - SELECT filter_id ... filter_json = ? → both transactions return no rows |
| 87 | + # - SELECT MAX(filter_id) ... → both transactions return e.g. 5 |
| 88 | + # - INSERT INTO ... → both transactions insert filter_id = 6 |
| 89 | + # One of the transactions will commit. The other will get a unique key |
| 90 | + # constraint violation error (IntegrityError). This is not the same as a |
| 91 | + # serialisability violation, which would be automatically retried by |
| 92 | + # `runInteraction`. |
| 93 | + try: |
| 94 | + return await self.db_pool.runInteraction("add_user_filter", _do_txn) |
| 95 | + except self.db_pool.engine.module.IntegrityError: |
| 96 | + attempts += 1 |
| 97 | + |
| 98 | + if attempts >= 5: |
| 99 | + raise StoreError(500, "Couldn't generate a filter ID.") |
0 commit comments