【问题标题】:How do I stop the Tempfile created by Creek from being deleted before I'm done with it?如何在完成之前阻止 Creek 创建的 Tempfile 被删除?
【发布时间】:2020-01-21 21:48:26
【问题描述】:

我正在编写一个包含 Creek 和 .xlsx 文件的脚本,并使用它来更新数据库中产品的价格和重量。 .xlsx 文件位于 AWS 服务器上,因此 Creek 将文件复制下来并在使用时将其存储在 Tempfile 中。

问题是,在某些时候 Tempfile 似乎被过早地删除了,并且由于 Creek 会在它遍历工作表时继续调用它,因此脚本会失败。有趣的是,我同事的环境可以很好地运行脚本,尽管我没有发现我们正在运行的环境之间存在差异。

这是我写的脚本:

require 'creek'

class PricingUpdateWorker
  include Sidekiq::Worker

  def perform(filename)
    # This points to the file in the root bucket
    file = bucket.files.get(filename)

    # Make public temporarily to open in Creek
    file.public = true
    file.save

    creek_sheets = Creek::Book.new(file.public_url, remote: true).sheets

    # Close file to public
    file.public = false
    file.save

    creek_sheets.each_with_index do |sheet, sheet_index|
      p "---------- #{sheet.name} ----------"

      sheet.simple_rows.each_with_index do |row, index|
        next if index == 0

        product = Product.find_by_id(row['A'].to_i)
        if product
          if row['D']&.match(/N\/A/) || row['E']&.match(/N\/A/)
            product.delete
            p '*** deleted ***'
          else
            product.price = row['D']&.to_f&.round(2)
            product.weight = row['E']&.to_f
            product.request_for_quote = false
            product.save
            p 'product updated'
          end
        else
          p "#{row['A']} | product not found ***"
        end
      end
    end
  end

  private

  def connection
    @connection ||= Fog::Storage.new(
      provider: 'AWS',
      aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
      aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
    )
  end

  def bucket
    # Grab the file from the bucket
    @bucket ||= connection.directories.get 'my-aws-bucket'
  end
end

还有日志:

"---------- Sheet 1 ----------"
"product updated"
"product updated"
... I've cut out a bunch more of these...
"product updated"
"product updated"
"---------- Sheet 2 ----------"
rails aborted!
Errno::ENOENT: No such file or directory @ rb_sysopen - /var/folders/9m/mfcnhxmn1bqbm6h91rx_rd8m0000gn/T/file20190920-19247-c6x4zw

"/var/folders/9m/mfcnhxmn1bqbm6h91rx_rd8m0000gn/T/file20190920-19247-c6x4zw" 是临时文件,如你所见,已经被收集了,虽然我还在用,我也信仍在范围内。有什么想法可能导致这种情况吗?特别奇怪的是我的同事可以很好地运行它。

如果有帮助,这里有一段来自 Creek 的小代码:

def initialize path, options = {}
      check_file_extension = options.fetch(:check_file_extension, true)
      if check_file_extension
        extension = File.extname(options[:original_filename] || path).downcase
        raise 'Not a valid file format.' unless (['.xlsx', '.xlsm'].include? extension)
      end
      if options[:remote]
        zipfile = Tempfile.new("file")
        zipfile.binmode
        zipfile.write(HTTP.get(path).to_s)
        # I added the line below this one, and it fixes the problem by preventing the file from being marked for garbage collection, though I shouldn't need to take steps like that.
        # ObjectSpace.undefine_finalizer(zipfile)
        zipfile.close
        path = zipfile.path
      end
      @files = Zip::File.open(path)
      @shared_strings = SharedStrings.new(self)
    end

编辑:有人想确切地知道我是如何运行我的代码的,所以就在这里。

我通过在命令行中执行bundle exec rails client:pricing_update[client_updated_prices.xlsx] 来运行以下 rake 任务。

namespace :client do
  desc 'Imports the initial database structure & base data from uploaded .xlsx file'
  task :pricing_update, [:filename] => :environment do |t, args|
    PricingUpdateWorker.new.perform(args[:filename])
  end
end

我还应该提到我正在运行 Rails,因此 Gemfile.lock 使我和我的同事之间的 gem 版本保持一致。我的雾版本是 2.0.0,我的 ruby​​zip 版本是 1.2.2。

【问题讨论】:

  • 如果将# Close file to public 之后的两行移动到循环结束后会发生什么?
  • lacostenycoder 恐怕没有什么不同。
  • 我删除了我的答案,因为在使用公共 xlsx 文件进行了更多测试之后,我能够获得一个更基本的示例来正常工作。我能问一下你是如何运行你的代码的吗?另外,您正在运行什么版本的 Fog 和 ruby​​zip gem?确保它们与 Heroku 或工作机器上运行的相同。
  • @lacostenycoder 我已经用你的问题的答案更新了我的帖子。
  • 如果你是从 rake 运行它而不使用 .perform_async 你甚至需要 include Sidekiq::Worker 吗?但我不认为这是你的问题。

标签: ruby garbage-collection


【解决方案1】:

最后,该错误似乎根本不在 Creek gem 中,而是在 ruby​​zip gem 中,如this issue 中所述,xlsx 文件有问题。这似乎取决于文件源的生成方式。我在 Google 表格中创建了一个简单的 2 页电子表格,它工作正常,但随机 xlsx 文件可能不行。

require 'creek'

def test_creek(url)
  Creek::Book.new(url, remote: true).sheets.each_with_index do |sheet, index|
    p "----------Name: #{sheet.name} Index: #{index} ----------"
    sheet.simple_rows.each_with_index do |row, i|
        puts "#{row} index: #{i}"
    end
  end
end

test_creek 'https://tc-sandbox.s3.amazonaws.com/creek-test.xlsx'
# works fine should output
"----------Name: Sheet1 Index: 0 ----------"
{"A"=>"foo ", "B"=>"sheet", "C"=>"one"} index: 0
"----------Name: Sheet2 Index: 1 ----------"
{"A"=>"bar", "B"=>"sheet", "C"=>"2.0"} index: 0

test_creek 'http://dev-builds.libreoffice.org/tmp/test.xlsx'
# raises error

【讨论】:

  • 我也评论了this issue in the repo
  • 感谢您的回答!不幸的是,这对我不起作用。它也没有解释为什么代码可能在其他人的设置上工作,这是难题的一部分。
  • @Dinopolis 是其他人的设置使用相同版本的所有内容吗?我们不知道您的依赖链。但我可以告诉你,我在 S3 上使用测试 xlsx 文件测试了类似的条带化代码,并在本地创建文件而不是处理 tempfile 对我来说效果很好。
  • 据我所知,是的。他们使用具有相同 gem 规范的相同分支,并且使用相同版本的 Ruby (2.5.1)。该代码在部署到 Heroku 时也可以工作。当我运行您的代码时,我收到一条错误消息,告诉我该文件不是有效格式。
  • 确切的错误是什么?如果它是编码而不是尝试省略 "w:ASCII-8BIT" 并传递 w 来写入文件。而且,在 Heroku 或其他机器上进行测试时,您是否在 S3 上使用相同的文件进行测试?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-05
  • 2019-12-31
  • 1970-01-01
  • 1970-01-01
  • 2014-07-05
  • 2020-02-21
  • 2013-08-06
相关资源
最近更新 更多