Окружение
-
Версия 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 года.
Шаги для воспроизведения
-
Опубликовать пост WordPress, настроенный на создание темы Discourse.
-
Тема успешно создается на Discourse.
-
discourse_topic_idсохраняется в мета-данных поста ✓ -
wpdc_sync_post_commentsустанавливается в1(веб-хук срабатывает корректно) ✓ -
discourse_permalinkсохраняется как пустая строка ✗ -
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 никогда не записывается, поэтому плагин пытается выполнить синхронизацию при каждой загрузке страницы — и каждый раз терпит неудачу.
Диагностика
Мы проследили проблему по следующему пути выполнения кода:
-
DiscourseCommentFormatter::format()вызываетdo_action('wpdc_sync_discourse_comments'), что запускаетDiscourseComment::sync_comments(). -
sync_comments()проверяет наличиеdiscourse_permalink— находит его пустым и возвращает 0. -
format()затем проверяет наличиеdiscourse_comments_rawв пользовательских мета-данных поста — находит его отсутствующим и возвращаетbad_response_html(). -
Комментарии никогда не отображаются для затронутого поста.
Процесс создания темы записывает discourse_topic_id, но не сохраняет ссылку. Нам удалось восстановить правильные ссылки, выполнив запрос к API Discourse по адресу /t/{topic_id}.json и записав их обратно в мета-данные поста, что решило проблему синхронизации для всех 174 постов.
Обходное решение
Мы написали скрипт восстановления WP-CLI, который:
-
Находит посты, где
wpdc_sync_post_comments = 1, ноdiscourse_comments_rawотсутствует. -
Получает slug темы из
/t/{topic_id}.json. -
Восстанавливает и сохраняет ссылку.
-
Получает и сохраняет комментарии из
/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 месяца контента) были затронуты до того, как мы выявили корневую причину проблемы.