We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Race conditions in muting topics and users
Our pattern in [muting topics](https://github.com/zulip/zulip/blob/b4075b78eb6e128bce7ef3d36b86d176ef2ecfa5/zerver/views/muting.py#L39-L42) is to check if the topic is muted, and if not then to add a row:
```py3
    if topic_is_muted(user_profile, stream.id, topic_name):
        raise JsonableError(_("Topic already muted"))

    do_mute_topic(user_profile, stream, topic_name, date_muted)
    return json_success()
```

This pattern is inherently prone to race conditions.  Luckily, we catch those due to database constraints, in the form of `UserTopic.objects.create` raising an IntegrityError, but those bubble up as 500's, not 400's.

We should catch those IntegrityError's and re-raise them as `JsonableError(_("Topic already muted"))`.  That applies to the mute-topic codepath, as well as the mute-user codepath.

Though it doesn't affect correctness in this case, since the duplicate row is the first database change operation, these actions should be done inside of transactions.

Un-muting is technically also subject to this race, though it doesn't matter -- both processes calling `.delete()` on the same object is a mostly-silent no-op for the second process.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/views/muting.py`
Content:
```
1 import datetime
2 from typing import Optional
3 
4 from django.http import HttpRequest, HttpResponse
5 from django.utils.timezone import now as timezone_now
6 from django.utils.translation import gettext as _
7 
8 from zerver.lib.actions import do_mute_topic, do_mute_user, do_unmute_topic, do_unmute_user
9 from zerver.lib.exceptions import JsonableError
10 from zerver.lib.request import REQ, has_request_variables
11 from zerver.lib.response import json_success
12 from zerver.lib.streams import (
13     access_stream_by_id,
14     access_stream_by_name,
15     access_stream_for_unmute_topic_by_id,
16     access_stream_for_unmute_topic_by_name,
17     check_for_exactly_one_stream_arg,
18 )
19 from zerver.lib.topic_mutes import topic_is_muted
20 from zerver.lib.user_mutes import get_mute_object
21 from zerver.lib.users import access_user_by_id
22 from zerver.lib.validator import check_int
23 from zerver.models import UserProfile
24 
25 
26 def mute_topic(
27     user_profile: UserProfile,
28     stream_id: Optional[int],
29     stream_name: Optional[str],
30     topic_name: str,
31     date_muted: datetime.datetime,
32 ) -> None:
33     if stream_name is not None:
34         (stream, sub) = access_stream_by_name(user_profile, stream_name)
35     else:
36         assert stream_id is not None
37         (stream, sub) = access_stream_by_id(user_profile, stream_id)
38 
39     if topic_is_muted(user_profile, stream.id, topic_name):
40         raise JsonableError(_("Topic already muted"))
41 
42     do_mute_topic(user_profile, stream, topic_name, date_muted)
43 
44 
45 def unmute_topic(
46     user_profile: UserProfile,
47     stream_id: Optional[int],
48     stream_name: Optional[str],
49     topic_name: str,
50 ) -> None:
51     error = _("Topic is not muted")
52 
53     if stream_name is not None:
54         stream = access_stream_for_unmute_topic_by_name(user_profile, stream_name, error)
55     else:
56         assert stream_id is not None
57         stream = access_stream_for_unmute_topic_by_id(user_profile, stream_id, error)
58 
59     do_unmute_topic(user_profile, stream, topic_name)
60 
61 
62 @has_request_variables
63 def update_muted_topic(
64     request: HttpRequest,
65     user_profile: UserProfile,
66     stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
67     stream: Optional[str] = REQ(default=None),
68     topic: str = REQ(),
69     op: str = REQ(),
70 ) -> HttpResponse:
71 
72     check_for_exactly_one_stream_arg(stream_id=stream_id, stream=stream)
73 
74     if op == "add":
75         mute_topic(
76             user_profile=user_profile,
77             stream_id=stream_id,
78             stream_name=stream,
79             topic_name=topic,
80             date_muted=timezone_now(),
81         )
82         return json_success(request)
83     elif op == "remove":
84         unmute_topic(
85             user_profile=user_profile,
86             stream_id=stream_id,
87             stream_name=stream,
88             topic_name=topic,
89         )
90         return json_success(request)
91 
92 
93 def mute_user(request: HttpRequest, user_profile: UserProfile, muted_user_id: int) -> HttpResponse:
94     if user_profile.id == muted_user_id:
95         raise JsonableError(_("Cannot mute self"))
96 
97     muted_user = access_user_by_id(
98         user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False
99     )
100     date_muted = timezone_now()
101 
102     if get_mute_object(user_profile, muted_user) is not None:
103         raise JsonableError(_("User already muted"))
104 
105     do_mute_user(user_profile, muted_user, date_muted)
106     return json_success(request)
107 
108 
109 def unmute_user(
110     request: HttpRequest, user_profile: UserProfile, muted_user_id: int
111 ) -> HttpResponse:
112     muted_user = access_user_by_id(
113         user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False
114     )
115     mute_object = get_mute_object(user_profile, muted_user)
116 
117     if mute_object is None:
118         raise JsonableError(_("User is not muted"))
119 
120     do_unmute_user(mute_object)
121     return json_success(request)
122
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/views/muting.py b/zerver/views/muting.py
--- a/zerver/views/muting.py
+++ b/zerver/views/muting.py
@@ -1,6 +1,7 @@
 import datetime
 from typing import Optional
 
+from django.db import IntegrityError
 from django.http import HttpRequest, HttpResponse
 from django.utils.timezone import now as timezone_now
 from django.utils.translation import gettext as _
@@ -39,7 +40,10 @@
     if topic_is_muted(user_profile, stream.id, topic_name):
         raise JsonableError(_("Topic already muted"))
 
-    do_mute_topic(user_profile, stream, topic_name, date_muted)
+    try:
+        do_mute_topic(user_profile, stream, topic_name, date_muted)
+    except IntegrityError:
+        raise JsonableError(_("Topic already muted"))
 
 
 def unmute_topic(

{"golden_diff": "diff --git a/zerver/views/muting.py b/zerver/views/muting.py\n--- a/zerver/views/muting.py\n+++ b/zerver/views/muting.py\n@@ -1,6 +1,7 @@\n import datetime\n from typing import Optional\n \n+from django.db import IntegrityError\n from django.http import HttpRequest, HttpResponse\n from django.utils.timezone import now as timezone_now\n from django.utils.translation import gettext as _\n@@ -39,7 +40,10 @@\n     if topic_is_muted(user_profile, stream.id, topic_name):\n         raise JsonableError(_(\"Topic already muted\"))\n \n-    do_mute_topic(user_profile, stream, topic_name, date_muted)\n+    try:\n+        do_mute_topic(user_profile, stream, topic_name, date_muted)\n+    except IntegrityError:\n+        raise JsonableError(_(\"Topic already muted\"))\n \n \n def unmute_topic(\n", "issue": "Race conditions in muting topics and users\nOur pattern in [muting topics](https://github.com/zulip/zulip/blob/b4075b78eb6e128bce7ef3d36b86d176ef2ecfa5/zerver/views/muting.py#L39-L42) is to check if the topic is muted, and if not then to add a row:\r\n```py3\r\n    if topic_is_muted(user_profile, stream.id, topic_name):\r\n        raise JsonableError(_(\"Topic already muted\"))\r\n\r\n    do_mute_topic(user_profile, stream, topic_name, date_muted)\r\n    return json_success()\r\n```\r\n\r\nThis pattern is inherently prone to race conditions.  Luckily, we catch those due to database constraints, in the form of `UserTopic.objects.create` raising an IntegrityError, but those bubble up as 500's, not 400's.\r\n\r\nWe should catch those IntegrityError's and re-raise them as `JsonableError(_(\"Topic already muted\"))`.  That applies to the mute-topic codepath, as well as the mute-user codepath.\r\n\r\nThough it doesn't affect correctness in this case, since the duplicate row is the first database change operation, these actions should be done inside of transactions.\r\n\r\nUn-muting is technically also subject to this race, though it doesn't matter -- both processes calling `.delete()` on the same object is a mostly-silent no-op for the second process.\n", "before_files": [{"content": "import datetime\nfrom typing import Optional\n\nfrom django.http import HttpRequest, HttpResponse\nfrom django.utils.timezone import now as timezone_now\nfrom django.utils.translation import gettext as _\n\nfrom zerver.lib.actions import do_mute_topic, do_mute_user, do_unmute_topic, do_unmute_user\nfrom zerver.lib.exceptions import JsonableError\nfrom zerver.lib.request import REQ, has_request_variables\nfrom zerver.lib.response import json_success\nfrom zerver.lib.streams import (\n    access_stream_by_id,\n    access_stream_by_name,\n    access_stream_for_unmute_topic_by_id,\n    access_stream_for_unmute_topic_by_name,\n    check_for_exactly_one_stream_arg,\n)\nfrom zerver.lib.topic_mutes import topic_is_muted\nfrom zerver.lib.user_mutes import get_mute_object\nfrom zerver.lib.users import access_user_by_id\nfrom zerver.lib.validator import check_int\nfrom zerver.models import UserProfile\n\n\ndef mute_topic(\n    user_profile: UserProfile,\n    stream_id: Optional[int],\n    stream_name: Optional[str],\n    topic_name: str,\n    date_muted: datetime.datetime,\n) -> None:\n    if stream_name is not None:\n        (stream, sub) = access_stream_by_name(user_profile, stream_name)\n    else:\n        assert stream_id is not None\n        (stream, sub) = access_stream_by_id(user_profile, stream_id)\n\n    if topic_is_muted(user_profile, stream.id, topic_name):\n        raise JsonableError(_(\"Topic already muted\"))\n\n    do_mute_topic(user_profile, stream, topic_name, date_muted)\n\n\ndef unmute_topic(\n    user_profile: UserProfile,\n    stream_id: Optional[int],\n    stream_name: Optional[str],\n    topic_name: str,\n) -> None:\n    error = _(\"Topic is not muted\")\n\n    if stream_name is not None:\n        stream = access_stream_for_unmute_topic_by_name(user_profile, stream_name, error)\n    else:\n        assert stream_id is not None\n        stream = access_stream_for_unmute_topic_by_id(user_profile, stream_id, error)\n\n    do_unmute_topic(user_profile, stream, topic_name)\n\n\n@has_request_variables\ndef update_muted_topic(\n    request: HttpRequest,\n    user_profile: UserProfile,\n    stream_id: Optional[int] = REQ(json_validator=check_int, default=None),\n    stream: Optional[str] = REQ(default=None),\n    topic: str = REQ(),\n    op: str = REQ(),\n) -> HttpResponse:\n\n    check_for_exactly_one_stream_arg(stream_id=stream_id, stream=stream)\n\n    if op == \"add\":\n        mute_topic(\n            user_profile=user_profile,\n            stream_id=stream_id,\n            stream_name=stream,\n            topic_name=topic,\n            date_muted=timezone_now(),\n        )\n        return json_success(request)\n    elif op == \"remove\":\n        unmute_topic(\n            user_profile=user_profile,\n            stream_id=stream_id,\n            stream_name=stream,\n            topic_name=topic,\n        )\n        return json_success(request)\n\n\ndef mute_user(request: HttpRequest, user_profile: UserProfile, muted_user_id: int) -> HttpResponse:\n    if user_profile.id == muted_user_id:\n        raise JsonableError(_(\"Cannot mute self\"))\n\n    muted_user = access_user_by_id(\n        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False\n    )\n    date_muted = timezone_now()\n\n    if get_mute_object(user_profile, muted_user) is not None:\n        raise JsonableError(_(\"User already muted\"))\n\n    do_mute_user(user_profile, muted_user, date_muted)\n    return json_success(request)\n\n\ndef unmute_user(\n    request: HttpRequest, user_profile: UserProfile, muted_user_id: int\n) -> HttpResponse:\n    muted_user = access_user_by_id(\n        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False\n    )\n    mute_object = get_mute_object(user_profile, muted_user)\n\n    if mute_object is None:\n        raise JsonableError(_(\"User is not muted\"))\n\n    do_unmute_user(mute_object)\n    return json_success(request)\n", "path": "zerver/views/muting.py"}], "after_files": [{"content": "import datetime\nfrom typing import Optional\n\nfrom django.db import IntegrityError\nfrom django.http import HttpRequest, HttpResponse\nfrom django.utils.timezone import now as timezone_now\nfrom django.utils.translation import gettext as _\n\nfrom zerver.lib.actions import do_mute_topic, do_mute_user, do_unmute_topic, do_unmute_user\nfrom zerver.lib.exceptions import JsonableError\nfrom zerver.lib.request import REQ, has_request_variables\nfrom zerver.lib.response import json_success\nfrom zerver.lib.streams import (\n    access_stream_by_id,\n    access_stream_by_name,\n    access_stream_for_unmute_topic_by_id,\n    access_stream_for_unmute_topic_by_name,\n    check_for_exactly_one_stream_arg,\n)\nfrom zerver.lib.topic_mutes import topic_is_muted\nfrom zerver.lib.user_mutes import get_mute_object\nfrom zerver.lib.users import access_user_by_id\nfrom zerver.lib.validator import check_int\nfrom zerver.models import UserProfile\n\n\ndef mute_topic(\n    user_profile: UserProfile,\n    stream_id: Optional[int],\n    stream_name: Optional[str],\n    topic_name: str,\n    date_muted: datetime.datetime,\n) -> None:\n    if stream_name is not None:\n        (stream, sub) = access_stream_by_name(user_profile, stream_name)\n    else:\n        assert stream_id is not None\n        (stream, sub) = access_stream_by_id(user_profile, stream_id)\n\n    if topic_is_muted(user_profile, stream.id, topic_name):\n        raise JsonableError(_(\"Topic already muted\"))\n\n    try:\n        do_mute_topic(user_profile, stream, topic_name, date_muted)\n    except IntegrityError:\n        raise JsonableError(_(\"Topic already muted\"))\n\n\ndef unmute_topic(\n    user_profile: UserProfile,\n    stream_id: Optional[int],\n    stream_name: Optional[str],\n    topic_name: str,\n) -> None:\n    error = _(\"Topic is not muted\")\n\n    if stream_name is not None:\n        stream = access_stream_for_unmute_topic_by_name(user_profile, stream_name, error)\n    else:\n        assert stream_id is not None\n        stream = access_stream_for_unmute_topic_by_id(user_profile, stream_id, error)\n\n    do_unmute_topic(user_profile, stream, topic_name)\n\n\n@has_request_variables\ndef update_muted_topic(\n    request: HttpRequest,\n    user_profile: UserProfile,\n    stream_id: Optional[int] = REQ(json_validator=check_int, default=None),\n    stream: Optional[str] = REQ(default=None),\n    topic: str = REQ(),\n    op: str = REQ(),\n) -> HttpResponse:\n\n    check_for_exactly_one_stream_arg(stream_id=stream_id, stream=stream)\n\n    if op == \"add\":\n        mute_topic(\n            user_profile=user_profile,\n            stream_id=stream_id,\n            stream_name=stream,\n            topic_name=topic,\n            date_muted=timezone_now(),\n        )\n        return json_success(request)\n    elif op == \"remove\":\n        unmute_topic(\n            user_profile=user_profile,\n            stream_id=stream_id,\n            stream_name=stream,\n            topic_name=topic,\n        )\n        return json_success(request)\n\n\ndef mute_user(request: HttpRequest, user_profile: UserProfile, muted_user_id: int) -> HttpResponse:\n    if user_profile.id == muted_user_id:\n        raise JsonableError(_(\"Cannot mute self\"))\n\n    muted_user = access_user_by_id(\n        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False\n    )\n    date_muted = timezone_now()\n\n    if get_mute_object(user_profile, muted_user) is not None:\n        raise JsonableError(_(\"User already muted\"))\n\n    do_mute_user(user_profile, muted_user, date_muted)\n    return json_success(request)\n\n\ndef unmute_user(\n    request: HttpRequest, user_profile: UserProfile, muted_user_id: int\n) -> HttpResponse:\n    muted_user = access_user_by_id(\n        user_profile, muted_user_id, allow_bots=False, allow_deactivated=True, for_admin=False\n    )\n    mute_object = get_mute_object(user_profile, muted_user)\n\n    if mute_object is None:\n        raise JsonableError(_(\"User is not muted\"))\n\n    do_unmute_user(mute_object)\n    return json_success(request)\n", "path": "zerver/views/muting.py"}]}