-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/week04 #4
Feat/week04 #4
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Посмотрите, пожалуйста, комменты по репозиторию jobs и пулу менеджеров 🙂
internal/repositories/jobs/api.go
Outdated
var j *store.Job | ||
|
||
findAndReserve := func(ctx context.Context) error { | ||
tx := store.TxFromContext(ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Важный момент, отталкиваясь от которого надо внести правки – репозиторий ничего не знает про транзакции. Он должен каким-то образом работать, как в режиме выполнения запроса в транзакции, так и в режиме выполнения запроса просто на соединении, и сам он этим управлять не может. Этим управляет юзкейс / сервис.
Могут возникнуть вопросы (хотя по коду ниже видно, что вы в целом понимаете суть, но на всякий случай ещё раз озвучим её). Абстрактный "как это сделать" и вполне конкретный вопрос "как нужно сделать в этом случае".
Как это сделать?
В третьем модуле вы как раз реализовали и использовали метод RunInTx()
структуры Database
, которая из недр пакета store
. Обратите внимание, как там написан код и на что был сделан основной расчет. Теория была в шагах 1, 2 и в ссылках в них, поэтому пройдитесь, пожалуйста, по ним повторно на всякий случай.
Краткая выжимка:
- Для прозрачной передачи транзакции между слоями используется контекст.
- Транзакцию стартует юзкейс / сервис, вызывая метод
RunInTx
. RunInTx
кладет запущенную транзакцию в контекст и отдает контекст в функцию-коллбек, которую будет с этим контекстом вызывать (обратите внимание, что ещё этот метод делает).- Внутри функции-коллбека вызываем методы репозиториев, которые хотим выполнить транзакционно.
- Репозитории уже в своих методах используют структуру
Database
, и вызывают у неё методы для выполнения запрсов. Например,Query()
. - Внутри
Database.Query()
есть вызов ещё одного метода loadClient, который как раз и решает где выполнять запрос: в транзакции или на соединении. И определяет он это как раз по наличию или отсутствию транзакции в контексте, как вы это сделали здесь.
P.S. можем в Телеграмах в канале / личке обсудить, если возникнут вопросы.
Как нужно сделать в этом случае?
Вспомним задачу: атомарно найти незанятую задачу и занять её. Атомарность требует выполнения этого всего либо одним запросом, либо в транзакции несколькими запросами.
Спойлер: мы это сделали одним запросом. Можете посмотреть в сторону CTE. Но и против выполнения в двух запросах не возражаем (возможно, потребуется правка заготовок и тестов).
Поэтому либо делаем одним запросом и тогда транзакции не нужны.
Либо уносим RunInTx
на уровень сервиса и внутри вызываем методы репозитория, которые проделают нужные нам штуки.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Переписал с использованием двух запросов. Про CTE сходу не нашёл как сделать, по этому подсмотрю ваше решение.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Транзакцию стартует юзкейс / сервис, вызывая метод RunInTx.
Вы упустили этот момент 🙂 Т.к. старт RunInTx остался в репозитории.
Не будем вас больше мучать, посмотрите, пожалуйста, в авторское решение
internal/repositories/jobs/api.go
Outdated
findAndReserve := func(ctx context.Context) error { | ||
tx := store.TxFromContext(ctx) | ||
if nil == tx { | ||
return fmt.Errorf("no transaction in context") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если нет аргументов для форматирования, то используйте errors.New()
🙂
internal/repositories/jobs/api.go
Outdated
|
||
j, err = j.Update().AddAttempts(1).SetReservedUntil(until).Save(ctx) | ||
if err != nil { | ||
if isAttemptsExceededError(err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это похоже на "логику", которой в репозитории не место. Вы и так возвращаете job-у с количеством попыток, поэтому на это количество попыток стоит смотреть в сервисе и в зависимости от него что-то делать 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Убрал
case errors.Is(err, freehands.ErrInvalidRequest): | ||
return internalerrors.NewServerError(http.StatusBadRequest, "invalid request", err) | ||
case errors.Is(err, freehands.ErrManagerOverloaded): | ||
return internalerrors.NewServerError(int(ErrorCodeManagerOverloadedError), "manager overloaded", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Лайк
return nil | ||
} | ||
|
||
func (s *Service) Get(_ context.Context) (types.UserID, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: сплошное полотно текста тяжеловато читается. Делите, по возможности, отступами код на смысловые группы
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправил
s.mutex.RLock() | ||
_, exists := s.store[managerID] | ||
s.mutex.RUnlock() | ||
if exists { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кмк, здесь возможна гонка, в результате которой один менеджер окажется дважды в пуле.
Параллельно одновременно вызывается дважды этот метод с одним и тем же методом.
Параллельно дважды захватывается мьютекс на чтение. Оба раза убедились, что менеджера в мапе нет. Проскакиваем 61-ую строку. Дважды добавляем менеджера в мапу.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кмк, здесь и в Get оптимизации с RLock излишни и приводят к усложнению кода. А профит какой от них непонятно
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправил
s.rw.RLock() | ||
_, exists := s.registry[job.Name()] | ||
s.rw.RUnlock() | ||
if exists { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вроде как, аналогичная проблема может быть.
Но мы даже и не парились здесь насчет мьютексов, честно говоря
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправил
internal/services/outbox/service.go
Outdated
|
||
func idle(ctx context.Context, idleTime time.Duration) <-chan struct{} { | ||
ch := make(chan struct{}) | ||
go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А зачем стартовать отдельную горутину, чтобы потом все равно заблокироваться на канале? Кмк, просто селекта и таймера достаточно должно быть
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправил
internal/services/outbox/service.go
Outdated
} | ||
|
||
func (s *Service) handleJob(ctx context.Context, job jobsrepo.Job) error { | ||
s.rw.RLock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тоже особого смысла не имеет, кмк, т.к. в рантайме не предполагается, что зарегистрированные джобы будут удаляться
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправил
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🖖 🖖 🖖
Message Producing
services/outbox
services/msg-producer
services/outbox/jobs/send-client-message
Managers Load
services/manager-pool/in-mem
services/manager-load
Manager API
task gen:api
internal/server
chat-ui-manager
, рольsupport-chat-manager
и пользователь-менеджер с этой ролью/v1/getFreeHandsBtnAvailability
, недостающие юнит-тесты написаны/v1/freeHands
/v1/freeHands
возвращает ошибку с кодом 5000 в случае "перегрузки" менеджера/getFreeHandsBtnAvailability
возвращает более детальный статус, чемtrue
/false
Validation
required
Testing
kcat
task tests
)task tests:integration
)task tests:e2e
)