Repository: aiogram/aiogram

Issue:
A problem with event_isolation
### Checklist

- [X] I am sure the error is coming from aiogram code
- [X] I have searched in the issue tracker for similar bug reports, including closed ones

### Operating system

macOS 13.5.2

### Python version

3.11.5

### aiogram version

3.0.0

### Expected behavior

Change a state after sending a message




### Current behavior

I send 10-20 messages to the bot at a time, and it responds to each of them with "Second thing", even though it should have changed its state after the first message.
If I send messages in sequence, everything works fine.


### Steps to reproduce

You can run the code from gist and then send 10-20 messages at a time to the bot (e.g. you can forward it 20 messages at a time)

### Code example

```python3
https://gist.github.com/foozzi/a84ed385616545e68ae75e258f99e46a
```


### Logs

_No response_

### Additional information

_No response_

Discussion:
github-actions[bot]: # :heavy_check_mark: Changelog found.

Thank you for adding a description of the changes
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aiogram/aiogram/pull/1318?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
> Merging [#1318](https://app.codecov.io/gh/aiogram/aiogram/pull/1318?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (09eb3d0) into [dev-3.x](https://app.codecov.io/gh/aiogram/aiogram/commit/8a77939d5bf7778be8a65e3531781220245724ff?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (8a77939) will **not change** coverage.
> The diff coverage is `100.00%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/aiogram/aiogram/pull/1318/graphs/tree.svg?width=650&height=150&src=pr&token=cFqEr3p9MX&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)](https://app.codecov.io/gh/aiogram/aiogram/pull/1318?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)

```diff
@@            Coverage Diff            @@
##           dev-3.x     #1318   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          369       369           
  Lines         9412      9412           
=========================================
  Hits          9412      9412           
```

| [Flag](https://app.codecov.io/gh/aiogram/aiogram/pull/1318/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/aiogram/aiogram/pull/1318/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/aiogram/aiogram/pull/1318?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/fsm/middleware.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1318?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vbWlkZGxld2FyZS5weQ==) | `100.00% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/CHANGES/1317.bugfix.rst b/CHANGES/1317.bugfix.rst
new file mode 100644
index 00000000..dcdf1e47
--- /dev/null
+++ b/CHANGES/1317.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed priority of events isolation, now user state will be loaded only after lock is acquired
diff --git a/aiogram/fsm/middleware.py b/aiogram/fsm/middleware.py
index 6de91a83..d1f1d973 100644
--- a/aiogram/fsm/middleware.py
+++ b/aiogram/fsm/middleware.py
@@ -34,8 +34,10 @@ class FSMContextMiddleware(BaseMiddleware):
         context = self.resolve_event_context(bot, data)
         data["fsm_storage"] = self.storage
         if context:
-            data.update({"state": context, "raw_state": await context.get_state()})
+            # Bugfix: https://github.com/aiogram/aiogram/issues/1317
+            # State should be loaded after lock is acquired
             async with self.events_isolation.lock(key=context.key):
+                data.update({"state": context, "raw_state": await context.get_state()})
                 return await handler(event, data)
         return await handler(event, data)
 


Test patch:
diff --git a/tests/test_issues/test_1317_state_vs_isolation.py b/tests/test_issues/test_1317_state_vs_isolation.py
new file mode 100644
index 00000000..31613fa4
--- /dev/null
+++ b/tests/test_issues/test_1317_state_vs_isolation.py
@@ -0,0 +1,81 @@
+import asyncio
+from datetime import datetime
+
+from aiogram import Dispatcher
+from aiogram.filters import Command
+from aiogram.fsm.context import FSMContext
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.fsm.storage.memory import SimpleEventIsolation
+from aiogram.types import Chat, Message, Update, User
+from tests.mocked_bot import MockedBot
+
+
+class TestStateVSIsolation:
+    async def test_issue(self, bot: MockedBot):
+        dispatcher = Dispatcher(events_isolation=SimpleEventIsolation())
+        first = 0
+        second = 0
+        third = 0
+        stack = []
+
+        class TestState(StatesGroup):
+            foo = State()
+            bar = State()
+            baz = State()
+
+        @dispatcher.message(Command("test"))
+        async def command_top(message: Message, state: FSMContext):
+            nonlocal first
+            first += 1
+            stack.append("command")
+            await state.set_state(TestState.foo)
+
+        @dispatcher.message(TestState.foo)
+        async def handle_foo(message: Message, state: FSMContext):
+            nonlocal second
+            second += 1
+            stack.append("foo")
+            await state.set_state(TestState.bar)
+
+        @dispatcher.message(TestState.bar)
+        async def handle_bar(message: Message, state: FSMContext):
+            nonlocal third
+            third += 1
+            stack.append("bar")
+            await state.set_state(None)
+
+        @dispatcher.message()
+        async def handle_all(message: Message):
+            stack.append("all")
+
+        await asyncio.gather(
+            *(
+                dispatcher.feed_update(bot, update)
+                for update in [
+                    create_message_update(index=1, text="/test"),
+                    create_message_update(index=2, text="foo"),
+                    create_message_update(index=3, text="bar"),
+                    create_message_update(index=4, text="baz"),
+                ]
+            )
+        )
+
+        # Before bug fix:
+        #   first == 1, second == 3, third == 0, stack == ["command", "foo", "foo", "foo"]
+        assert first == 1
+        assert second == 1
+        assert third == 1
+        assert stack == ["command", "foo", "bar", "all"]
+
+
+def create_message_update(index: int, text: str):
+    return Update(
+        update_id=index,
+        message=Message(
+            message_id=index,
+            date=datetime.now(),
+            chat=Chat(id=42, type="private"),
+            from_user=User(id=42, is_bot=False, first_name="Test", username="test"),
+            text=text,
+        ),
+    )