Пустой `discourse_permalink` вызывает тихие сбои синхронизации комментариев

Окружение

  • Версия WP-Discourse: 2.5.7

  • Версия Discourse: 3.5.0.beta5-dev

  • Версия WordPress: 6.9

  • Хостинг: WP Engine (WordPress multisite)

  • Экземпляр Discourse: Самообслуживаемый / Хостинг Discourse (forum.avweb.com)

Краткое описание

Публикации из WordPress успешно создают темы на Discourse, но поле discourse_permalink сохраняется в мета-данных поста как пустая строка. Это приводит к тому, что все последующие синхронизации комментариев тихо завершаются неудачей, так как функция sync_comments() в файле discourse-comment.php преждевременно завершает работу, если ссылка пустая. Идентификатор темы Discourse и флаг синхронизации веб-хука записываются корректно — отсутствует только ссылка.

Мы обнаружили 174 затронутых поста на одном сайте в нашей сети multisite. Проблема, по-видимому, возникла в середине декабря 2025 года.

Шаги для воспроизведения

  1. Опубликовать пост WordPress, настроенный на создание темы Discourse.

  2. Тема успешно создается на Discourse.

  3. discourse_topic_id сохраняется в мета-данных поста ✓

  4. wpdc_sync_post_comments устанавливается в 1 (веб-хук срабатывает корректно) ✓

  5. discourse_permalink сохраняется как пустая строка ✗

  6. discourse_comments_raw никогда не записывается (синхронизация никогда не завершается)

Ожидаемое поведение

После успешного создания темы поле discourse_permalink должно содержать полный URL темы Discourse (например, The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion).

Фактическое поведение

discourse_permalink существует как ключ мета-данных, но его значение пусто. Каждый последующий вызов sync_comments() попадает в проверку на строке 209 файла lib/discourse-comment.php и преждевременно завершает работу:

if ( ! $discourse_permalink ) {
    return 0;
}

Поскольку синхронизация никогда не завершается, поле discourse_last_sync никогда не записывается, поэтому плагин пытается выполнить синхронизацию при каждой загрузке страницы — и каждый раз терпит неудачу.

Диагностика

Мы проследили проблему по следующему пути выполнения кода:

  1. DiscourseCommentFormatter::format() вызывает do_action('wpdc_sync_discourse_comments'), что запускает DiscourseComment::sync_comments().

  2. sync_comments() проверяет наличие discourse_permalink — находит его пустым и возвращает 0.

  3. format() затем проверяет наличие discourse_comments_raw в пользовательских мета-данных поста — находит его отсутствующим и возвращает bad_response_html().

  4. Комментарии никогда не отображаются для затронутого поста.

Процесс создания темы записывает discourse_topic_id, но не сохраняет ссылку. Нам удалось восстановить правильные ссылки, выполнив запрос к API Discourse по адресу /t/{topic_id}.json и записав их обратно в мета-данные поста, что решило проблему синхронизации для всех 174 постов.

Обходное решение

Мы написали скрипт восстановления WP-CLI, который:

  1. Находит посты, где wpdc_sync_post_comments = 1, но discourse_comments_raw отсутствует.

  2. Получает slug темы из /t/{topic_id}.json.

  3. Восстанавливает и сохраняет ссылку.

  4. Получает и сохраняет комментарии из /t/{slug}/{topic_id}/wordpress.json.

Мы запускаем этот скрипт по расписанию cron как временное решение.


Дополнительные проблемы, обнаруженные в ходе расследования

1. Глобальная блокировка MySQL вызывает тихие сбои синхронизации

Файл: lib/discourse-comment.php, строка 176

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock', 0 ) got_it" );

Функция sync_comments() использует единую глобальную блокировку MySQL (discourse_lock), общую для всех постов всей установки. Тайм-аут равен 0 (неблокирующий режим), поэтому, если какой-либо пост в данный момент синхронизируется, все остальные посты тихо пропускают свою синхронизацию — без логирования и без повторных попыток.

На сайте с высокой нагрузкой, публикующем несколько постов в день, это создает состояние гонки, при котором посты постоянно не могут получить блокировку и никогда не синхронизируются. В сочетании с 10-минутным периодом синхронизации пост может навсегда застрять, если он не получит блокировку в первых нескольких попытках, а затем проблема пустой ссылки предотвратит будущие синхронизации.

Предлагаемое исправление: Использовать блокировку для каждого поста:

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock_{$post_id}', 0 ) got_it" );

С соответствующим освобождением:

$wpdb->get_results( "SELECT RELEASE_LOCK( 'discourse_lock_{$post_id}' )" );

2. Двойное экранирование discourse_comments_raw

Файл: lib/discourse-comment.php, строка 222

update_post_meta( $post_id, 'discourse_comments_raw', esc_sql( $raw_body ) );

Функция update_post_meta() внутренне использует $wpdb->prepare(), поэтому обертывание значения в esc_sql() вызывает двойное экранирование. За несколько циклов синхронизации кавычки в теле JSON накапливают экранирующие символы (\"\\\"\\\\\\\"), пока JSON не станет нечитаемым.

Предлагаемое исправление:

update_post_meta( $post_id, 'discourse_comments_raw', $raw_body );

Влияние

Эти проблемы усугубляют друг друга: пустая ссылка предотвращает начальную синхронизацию, глобальная блокировка препятствует восстановлению, а двойное экранирование может повредить данные постов, которым все же удается синхронизироваться. В нашей установке 174 поста (примерно 2 месяца контента) были затронуты до того, как мы выявили корневую причину проблемы.

Привет! Спасибо за подробный отчёт.

Поскольку плагин WP Discourse не вносил изменений в реализацию функции, с которой у вас возникли проблемы, давайте подумаем, что ещё могло измениться в тот период. Возможно, вы обновили другой плагин в то время?

Одной из первоочередных задач, которую вы можете решить, является обновление плагина WP Discourse до последней версии 2.6.1.

Спасибо. Мы займемся обновлением плагина. К слову, мы используем его на многих наших сайтах, где часто бывает довольно много публикаций и высокий трафик.

Понятно.

Это, вероятно, ключевой момент. Что произошло в середине декабря 2025 года?

Помните, что WordPress по своей природе является составной системой с участием нескольких сторон. Относительно легко, чтобы одна часть вашей системы влияла на другую. Я не утверждаю, что плагин WP Discourse не может иметь проблем, но при прочих равных условиях нам нужно понять, что изменилось и как именно это изменение привело к вашей проблеме, прежде чем мы сможем подумать о решениях.

Я обновил плагин. Посмотрим, стабилизируется ли ситуация. Мы создали пользовательскую команду WP CLI для очистки базы данных. Если хотите посмотреть, я отправил вам приглашение. Спасибо.