# Buggy code
async def delete_pusher_by_app_id_pushkey_user_id(
    self, app_id: str, pushkey: str, user_id: str
) -> None:
    def delete_pusher_txn(txn, stream_id):
        self._invalidate_cache_and_stream(  # type: ignore
            txn, self.get_if_user_has_pusher, (user_id,)
        )

        self.db_pool.simple_delete_one_txn(
            txn,
            "pushers",
            {"app_id": app_id, "pushkey": pushkey, "user_name": user_id},
        )

        # it's possible for us to end up with duplicate rows for
        # (app_id, pushkey, user_id) at different stream_ids, but that
        # doesn't really matter.
        self.db_pool.simple_insert_txn(
            txn,
            table="deleted_pushers",
            values={
                "stream_id": stream_id,
                "app_id": app_id,
                "pushkey": pushkey,
                "user_id": user_id,
            },
        )

    async with self._pushers_id_gen.get_next() as stream_id:
        await self.db_pool.runInteraction("delete_pusher", delete_pusher_txn, stream_id)


# Traceback (synapse.api.errors.StoreError)
2019-04-26 13:17:52,980 - synapse.push.httppusher - 144 - ERROR - httppush.process-34525- Exception processing notifs
Traceback (most recent call last):
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/push/httppusher.py", line 142, in _process
yield self._unsafe_process()
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/push/httppusher.py", line 178, in _unsafe_process
self.clock.time_msec()
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/pusher.py", line 307, in update_pusher_last_stream_ordering_and_success
desc="update_pusher_last_stream_ordering_and_success",
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/_base.py", line 393, in runInteraction
*args, **kwargs
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/_base.py", line 442, in runWithConnection
inner_func, *args, **kwargs
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/threadpool.py", line 250, in inContext
result = inContext.theWork()
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/threadpool.py", line 266, in <lambda>
inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/context.py", line 122, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/context.py", line 85, in callWithContext
return func(*args,**kw)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/enterprise/adbapi.py", line 306, in _runWithConnection
compat.reraise(excValue, excTraceback)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/python/compat.py", line 464, in reraise
raise exception.with_traceback(traceback)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/twisted/enterprise/adbapi.py", line 297, in _runWithConnection
result = func(conn, *args, **kw)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/_base.py", line 438, in inner_func
return func(conn, *args, **kwargs)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/_base.py", line 314, in _new_transaction
r = func(txn, *args, **kwargs)
File "/opt/venvs/matrix-synapse/lib/python3.5/site-packages/synapse/storage/_base.py", line 1031, in _simple_update_one_txn
raise StoreError(404, "No row found (%s)" % (table,))
synapse.api.errors.StoreError: 404: No row found (pushers)

# Fixed code
async def delete_pusher_by_app_id_pushkey_user_id(
    self, app_id: str, pushkey: str, user_id: str
) -> None:
    def delete_pusher_txn(txn, stream_id):
        self._invalidate_cache_and_stream(  # type: ignore
            txn, self.get_if_user_has_pusher, (user_id,)
        )

        # It is expected that there is exactly one pusher to delete, but
        # if it isn't there (or there are multiple) delete them all.
        self.db_pool.simple_delete_txn(
            txn,
            "pushers",
            {"app_id": app_id, "pushkey": pushkey, "user_name": user_id},
        )

        # it's possible for us to end up with duplicate rows for
        # (app_id, pushkey, user_id) at different stream_ids, but that
        # doesn't really matter.
        self.db_pool.simple_insert_txn(
            txn,
            table="deleted_pushers",
            values={
                "stream_id": stream_id,
                "app_id": app_id,
                "pushkey": pushkey,
                "user_id": user_id,
            },
        )

    async with self._pushers_id_gen.get_next() as stream_id:
        await self.db_pool.runInteraction("delete_pusher", delete_pusher_txn, stream_id)