Mark & LayerCrafter
Mark Mark
Наткнулся на проблему с гонкой состояний в старом API, проявляется только при определённой последовательности запросов. Поможешь разобраться?
LayerCrafter LayerCrafter
Конечно, но я не тот, кто быстро всё починит. Пришли мне точную последовательность запроса, код, который её обрабатывает, и твою текущую схему блокировок. Как разберёмся с доступом к общей памяти, применишь нужную синхронизацию и запустим тест заново. Сохраняй логи максимально подробными, чтобы поймать точный момент, когда возникает гонка данных. Если начнёт мне самому писать письма – значит, само собой решилось.
Mark Mark
Вот минимальный сценарий, воспроизводящий проблему: 1. GET /api/status (первое чтение) 2. POST /api/start (создает задачу и сохраняет UUID) 3. GET /api/job/<uuid> (второе чтение, ожидается, что задача существует) 4. POST /api/finish/<uuid> (устанавливает статус в "завершено") Обработчик находится в job.go: func handleStart(w http.ResponseWriter, r *http.Request) { id := uuid.New().String() job := &Job{ID: id, State: “running”} mu.Lock() jobs[id] = job mu.Unlock() json.NewEncoder(w).Encode(job) } func handleStatus(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() // чтение jobs map } Блокировка `mu` – это простой sync.Mutex. Гонка возникает, когда POST /api/start происходит сразу после GET /api/status, но до GET /api/job. Запись задачи ещё отсутствует в map, поэтому второе чтение блокируется до освобождения блокировки, но из-за синхронизации чтение статуса завершается раньше, и тогда чтение задачи видит nil pointer. Усиление блокировки вокруг чтения map или добавление блокировки чтения-записи решит проблему. Также стоит перейти на sync.RWMutex и обернуть чтение статуса с помощью RLock, чтобы снизить конкуренцию. Храни логи внутри блокировки и выводи время и ID горутины, чтобы точно знать, когда происходит доступ к map. Этого достаточно, чтобы увидеть сбой и исправить его.
LayerCrafter LayerCrafter
Используй RWMutex и RLock для обработчика статуса, Lock для записи, а чтение заданий защити проверкой на nil. Это уберёт момент, когда чтение видит нулевой элемент. Добавь небольшую запись в лог после поиска в карте, чтобы убедиться в последовательности, и обязательно проверяй ID задания перед записью в ответ. Вот и всё, этим ты поправишь гонку.
Mark Mark
Звучит отлично. Я поменяю мьютекс, добавлю проверку на `nil` и буду убирать метку времени после каждого запроса. Давай запустим.
LayerCrafter LayerCrafter
Отлично, звучит как план. Только перепроверь, чтобы проверка на нулевые значения выполнялась до обращения к задаче, иначе паника не уйдет. И следи за RWMutex во всех обработчиках — не смешивай блокировки, а то будет незаметная взаимная блокировка. Как только все это будет настроено, гонки прекратятся. Давай посмотрим результаты.
Mark Mark
Окей, поставлю проверку на null прямо перед обращением к памяти, и убежусь, что все обработчики используют один и тот же RWMutex. Как только это будет сделано, должна пропасть эта авария, и логи покажут нам точную последовательность событий. Запускаем и посмотрим, что будет.
LayerCrafter LayerCrafter
Отлично, это должно исправить проблему с таймингом. Как запустишь, логи покажут, что больше нет обращения к null, и последовательность завершится без сбоя. Если снова возникнут проблемы, покопаемся в порядке захвата блокировок, но похоже, что гонка устранена. Давай запускай.
Mark Mark
Понял. Запущу тест, уберу метки времени, и посмотрим, не вылезет ли снова эта паника. Если пропала – дело закрыто; если нет – вернемся к порядку захвата локов. Давай запускать.
LayerCrafter LayerCrafter
Запусти и проверь. Если паника не исчезнет, убедись, что ни один другой горутин не удерживает блокировку записи, когда ты читаешь статус. Если временные метки совпадают, проблема с гонкой должна быть решена. Если нет – поищем скрытую запись, которая проскальзывает мимо RWMutex. Но скорее всего, нил-гард и правильный тип блокировки сработают. Удачи.
Mark Mark
Окей, заплатка применилась, очередь тестов пуста. Сейчас запускаю последовательность и вытаскиваю логи прямо из вывода сервера. Если вылезет ошибка доступа – значит, где-то замок сорвало. В остальном должно всё чисто завершиться. Посмотрим, что скажут метки времени.
LayerCrafter LayerCrafter
Сбрось мне лог записи с отметками времени для чтения статуса, создания задания и чтения задания. Если время чтения задания позже времени записи и ты ни разу не увидел ошибку с нулевым указателем, то патч сработал. Если время чтения все равно предшествует записи, нам придется еще раз поработать над порядком блокировок.