FIX: better handle race condition when a channel is deleted (#30403)

NOTE: I wasn't able to reproduce locally, so that's my best guess as to what happens based on the production error logs.
It's also the reason why I haven't changed/added any tests...

Earlier today, we started seeing a growing number of errors in the `register_presence_channel_prefix("chat-reply")` handler of the chat plugin.
It was all coming from a Discourse where they make a heavy use of chat channels. They create and **delete** category channels regularly.

If a user has a thread in one of the channels that just got deleted, the client application might not be aware (just yet), asks the server to be connected to the "presence" bus of that channel, and BOOOM.

The following [line](fa0ad0306c/plugins/chat/plugin.rb (L325)) explodes because `chat_channel` is `nil`

```ruby
config.allowed_group_ids = chat_channel.allowed_group_ids
```

And why is `chat_channel` `nil`? Because when we [do](fa0ad0306c/plugins/chat/plugin.rb (L319))

```ruby
chat_channel = Chat::Thread.find_by!(id: thread_id, channel_id: channel_id).channel
```

The thread is still in the database, but the associated channel has been deleted.

A proper fix would most likely be to delete all the `Chat::Thread` associated to a deleted `Chat::Channel` but this might have more technical & business implications.
This commit is contained in:
Régis Hanol
2024-12-21 00:49:21 +01:00
committed by GitHub
parent fa0ad0306c
commit ebb6f1c2d2

View File

@ -37,6 +37,7 @@ module ::Chat
chat_channel_retention_days: :dismissed_channel_retention_reminder, chat_channel_retention_days: :dismissed_channel_retention_reminder,
chat_dm_retention_days: :dismissed_dm_retention_reminder, chat_dm_retention_days: :dismissed_dm_retention_reminder,
} }
PRESENCE_REGEXP = %r{^/chat-reply/(\d+)(?:/thread/(\d+))?$}
end end
require_relative "lib/chat/engine" require_relative "lib/chat/engine"
@ -303,32 +304,29 @@ after_initialize do
end end
register_presence_channel_prefix("chat") do |channel_name| register_presence_channel_prefix("chat") do |channel_name|
next nil unless channel_name == "/chat/online" next if channel_name != "/chat/online"
config = PresenceChannel::Config.new PresenceChannel::Config.new.tap { |config| config.allowed_group_ids = Chat.allowed_group_ids }
config.allowed_group_ids = Chat.allowed_group_ids
config
end end
register_presence_channel_prefix("chat-reply") do |channel_name| register_presence_channel_prefix("chat-reply") do |channel_name|
if ( channel_id, thread_id = Chat::PRESENCE_REGEXP.match(channel_name)&.captures
channel_id, thread_id =
channel_name.match(%r{^/chat-reply/(\d+)(?:/thread/(\d+))?$})&.captures next if channel_id.blank?
)
chat_channel = nil chat_channel =
if thread_id if thread_id.present?
chat_channel = Chat::Thread.find_by!(id: thread_id, channel_id: channel_id).channel Chat::Thread.find_by(id: thread_id, channel_id:)&.channel
else else
chat_channel = Chat::Channel.find(channel_id) Chat::Channel.find_by(id: channel_id)
end end
PresenceChannel::Config.new.tap do |config| next if chat_channel.nil?
config.allowed_group_ids = chat_channel.allowed_group_ids
config.allowed_user_ids = chat_channel.allowed_user_ids PresenceChannel::Config.new.tap do |config|
config.public = !chat_channel.read_restricted? config.allowed_group_ids = chat_channel.allowed_group_ids
end config.allowed_user_ids = chat_channel.allowed_user_ids
config.public = !chat_channel.read_restricted?
end end
rescue ActiveRecord::RecordNotFound
nil
end end
register_push_notification_filter do |user, payload| register_push_notification_filter do |user, payload|