Skip to content

fix: исправлена работа FSM-контекстов#93

Merged
love-apples merged 2 commits intolove-apples:mainfrom
bish-x:fix/fsm-context-fixes
Apr 11, 2026
Merged

fix: исправлена работа FSM-контекстов#93
love-apples merged 2 commits intolove-apples:mainfrom
bish-x:fix/fsm-context-fixes

Conversation

@bish-x
Copy link
Copy Markdown
Contributor

@bish-x bish-x commented Apr 8, 2026

Описание

1. MemoryContext.get_data() возвращала ссылку на внутренний dict

Файл: context/context.py

get_data() возвращала self._context напрямую. При use_create_task=True два параллельных хендлера одного чата получали одну ссылку — мутации из одного хендлера были видны другому без захвата lock (race condition).

Исправлено: возвращается self._context.copy().

2. Добавлено логирование при вытеснении контекста из LRU-кеша

Файл: dispatcher.py

При достижении лимита CONTEXTS_MAX_SIZE (10 000) самый старый контекст удалялся без какого-либо уведомления. Пользователь терял FSM-состояние незаметно. Добавлено logger_dp.debug при вытеснении.

Тестирование

  • Все существующие тесты проходят
  • ruff check / ruff format — без замечаний

- UserAdded.get_ids() и UserRemoved.get_ids() теперь возвращают
  self.user.user_id вместо inviter_id/admin_id — ключ FSM-контекста
  теперь указывает на правильного пользователя
- MemoryContext.get_data() возвращает копию dict, устраняя
  race condition при параллельных хендлерах (use_create_task=True)
- Добавлено logger_dp.debug при вытеснении контекста из LRU-кеша
@love-apples
Copy link
Copy Markdown
Owner

@bish-x, пункт 1 - получение ID используется в рамках контекста пользователя, который как раз совершил действие. В случае этого исправления получается так, что приглашенный или удаленный пользователь вступает в контекст, из-за чего получается бессмыслица

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix incorrect FSM context keying (so state is bound to the actual affected user), prevent unintended shared mutations of in-memory FSM data across concurrent handlers, and improve observability when FSM contexts are evicted from the dispatcher’s LRU cache.

Changes:

  • Added debug logging when an FSM context is evicted from the dispatcher LRU cache due to CONTEXTS_MAX_SIZE.
  • Updated MemoryContext.get_data() to return a copy of the internal dict instead of the internal dict reference.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
maxapi/dispatcher.py Adds debug logging for LRU context eviction in __get_context.
maxapi/context/context.py Makes MemoryContext.get_data() return a copy to avoid shared-reference mutations across concurrent handlers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread maxapi/dispatcher.py
Comment on lines 334 to 341
if len(self.contexts) >= CONTEXTS_MAX_SIZE:
evicted_key = next(iter(self.contexts))
logger_dp.debug(
"Вытеснен контекст %s (лимит %d)",
evicted_key,
CONTEXTS_MAX_SIZE,
)
self.contexts.popitem(last=False)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description states that UserAdded.get_ids() / UserRemoved.get_ids() were fixed to return the affected user id (self.user.user_id), but in the current code they still return inviter_id / admin_id (see maxapi/types/updates/user_added.py and maxapi/types/updates/user_removed.py). As a result, the dispatcher will continue binding FSM contexts to the wrong user via event_object.get_ids(), and the described collision for (chat_id, None) is still possible. Please include the missing fixes (or adjust the PR description if this change is intentionally not part of this PR).

Copilot uses AI. Check for mistakes.
Comment thread maxapi/context/context.py
Comment on lines 30 to +31
async with self._lock:
return self._context
return self._context.copy()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MemoryContext.get_data() now returns a copy; please add a regression test that mutating the dict returned by get_data() does not change the underlying context unless set_data() / update_data() is called. This will lock in the intended concurrency behavior and prevent accidental reintroduction of returning the internal dict reference.

Copilot uses AI. Check for mistakes.
Comment thread maxapi/dispatcher.py
Comment on lines +335 to 341
evicted_key = next(iter(self.contexts))
logger_dp.debug(
"Вытеснен контекст %s (лимит %d)",
evicted_key,
CONTEXTS_MAX_SIZE,
)
self.contexts.popitem(last=False)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The eviction log currently computes evicted_key = next(iter(self.contexts)) and then calls popitem(last=False). Consider using the return value of popitem(last=False) for the key you log so the log always matches the actual evicted entry and avoids the extra lookup.

Suggested change
evicted_key = next(iter(self.contexts))
logger_dp.debug(
"Вытеснен контекст %s (лимит %d)",
evicted_key,
CONTEXTS_MAX_SIZE,
)
self.contexts.popitem(last=False)
evicted_key, _ = self.contexts.popitem(last=False)
logger_dp.debug(
"Вытеснен контекст %s (лимит %d)",
evicted_key,
CONTEXTS_MAX_SIZE,
)

Copilot uses AI. Check for mistakes.
@love-apples love-apples merged commit b034c03 into love-apples:main Apr 11, 2026
17 checks passed
bish-x added a commit to bish-x/maxapi that referenced this pull request Apr 14, 2026
Подтянуты PR из upstream: love-apples#93 (FSM), love-apples#96 (download_file),
love-apples#101 (fetch user/chat), love-apples#105 (ClipboardButton), love-apples#109 (share payload),
love-apples#110 (webhook secret warning).

Конфликт в tests/test_types.py: принят upstream-стиль (явный
update_type, разнесённые assert). Сохранены доп. тесты
test_get_ids_ignores_inviter_id / test_get_ids_ignores_admin_id —
их purpose именно цель PR love-apples#94 (не путать inviter_id/admin_id с
user.user_id).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants