Skip to content

ActiveStorage - attached file quietly discarded if record is later locked in the same transaction #53737

Open
@seandilda

Description

Steps to reproduce

  1. Open a transaction (using #transaction or #with_lock)
  2. Attach a file
  3. Call #with_lock while in the same transaction
  4. Close the transaction
  5. Try to download the attachment
# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails", '~> 7.2'
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"
  gem 'securerandom'

  gem "sqlite3"
end

require "active_record/railtie"
require "active_storage/engine"
require "minitest/autorun"

ENV["DATABASE_URL"] = "sqlite3::memory:"

class TestApp < Rails::Application
  config.load_defaults Rails::VERSION::STRING.to_f

  config.root = __dir__
  config.hosts << "example.org"
  config.eager_load = false
  config.session_store :cookie_store, key: "cookie_store_key"
  config.secret_key_base = "secret_key_base"

  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger

  config.active_storage.service = :local
  config.active_storage.service_configurations = {
    local: {
      root: Dir.tmpdir,
      service: "Disk"
    }
  }
end
Rails.application.initialize!

require ActiveStorage::Engine.root.join("db/migrate/20170806125915_create_active_storage_tables.rb").to_s

ActiveRecord::Schema.define do
  CreateActiveStorageTables.new.change

  create_table :users, force: true
end

class User < ActiveRecord::Base
  has_one_attached :profile
end

class BugTest < ActiveSupport::TestCase
  def test_upload_and_download
    user = User.create!
    user.transaction do
      user.profile.attach(content_type: "text/plain",
                          filename: "dummy.txt",
                          io: ::StringIO.new("dummy"))
      user.with_lock {}
    end

    assert_equal "dummy", user.profile.download
  end
end

Expected behavior

File should be properly saved in the backend or an error raised if that is not possible.

Actual behavior

The attachment appears to work. However, when you later call #download on it, you receive an ActiveStorage::FileNotFoundError error.

If you are updating an existing attachment, the old saved file will still be deleted, but the new data will be lost.

System configuration

Rails version: 7.2.1.1

Ruby version: ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux]

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions