mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 15:50:08 +09:00
Delete a room's messages in a job, one transaction each
Room#destroy destroyed every message inside the room's own transaction, which holds SQLite's write lock until the last one: on a room with many messages, every other write in the app waited and failed. The request now takes the room away from its members and leaves the rest to Room::DestroyJob, which destroys the messages one at a time, each in its own short transaction, and then the room. An open room is closed in the request, so that someone who joins the account before the job ends isn't given it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bj8KnxpTf9sj2Ysa8aLAVa
This commit is contained in:
@@ -30,8 +30,54 @@ class RoomTest < ActiveSupport::TestCase
|
||||
assert Rooms::Closed.new.closed?
|
||||
end
|
||||
|
||||
test "an open room destroyed later lets no one in who joins the account before the job runs" do
|
||||
room = rooms(:pets)
|
||||
|
||||
room.destroy_later
|
||||
newcomer = User.create!(name: "Newcomer", email_address: "newcomer@example.com", password: "secret123456")
|
||||
|
||||
assert_not newcomer.memberships.exists?(room_id: room.id)
|
||||
perform_enqueued_jobs only: Room::DestroyJob
|
||||
assert_not Room.exists?(room.id)
|
||||
end
|
||||
|
||||
test "destroying one message at a time leaves nothing of the room behind" do
|
||||
room = rooms(:designers)
|
||||
searchable = room.messages.create!(body: "Kept in the search index", creator: users(:david))
|
||||
searchable.attachment.attach io: StringIO.new("hello"), filename: "hello.txt", content_type: "text/plain"
|
||||
message_ids = room.messages.ids
|
||||
assert Boost.where(message_id: message_ids).exists?
|
||||
assert_equal 1, search_index_rows(searchable)
|
||||
|
||||
room.destroy_one_message_at_a_time
|
||||
|
||||
assert_not Room.exists?(room.id)
|
||||
assert_empty Message.where(id: message_ids)
|
||||
assert_empty Boost.where(message_id: message_ids)
|
||||
assert_empty ActionText::RichText.where(record_type: "Message", record_id: message_ids)
|
||||
assert_empty ActiveStorage::Attachment.where(record_type: "Message", record_id: message_ids)
|
||||
assert_enqueued_jobs 1, only: ActiveStorage::PurgeJob
|
||||
assert_equal 0, search_index_rows(searchable)
|
||||
end
|
||||
|
||||
test "each message is destroyed in its own transaction, so other writes get through in between" do
|
||||
room = rooms(:designers)
|
||||
messages = room.messages.count
|
||||
transactions = 0
|
||||
count_transactions = ->(*, payload) { transactions += 1 if payload[:sql].start_with?("RELEASE SAVEPOINT") }
|
||||
|
||||
ActiveSupport::Notifications.subscribed(count_transactions, "sql.active_record") { room.destroy_one_message_at_a_time }
|
||||
|
||||
assert_equal messages + 1, transactions
|
||||
end
|
||||
|
||||
test "default involvement for new users" do
|
||||
room = Rooms::Closed.create_for({ name: "Hello!", creator: users(:david) }, users: [ users(:kevin), users(:david) ])
|
||||
assert room.memberships.all? { |m| m.involved_in_mentions? }
|
||||
end
|
||||
|
||||
private
|
||||
def search_index_rows(message)
|
||||
Message.connection.select_value("select count(*) from message_search_index where rowid = #{message.id}")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user