19

我正在寻找加载一些库,让它们做一些工作,然后做相反的事情require以避免以后出现兼容性错误。我不想转储到文件并重新启动 shell,因为创建的对象(例如data)可以由我的其他库很好地处理,而不是在我想要卸载的早期库中。

任何人有任何建议或知道这是否可能?2006 年的一次对话除了“看起来 Webrick 设法以某种方式做到这一点”外,并没有得出太多的结论。

有问题的库是Google_drive 和 Nokogiri(电子表格处理库 Roo 依赖于 Google_drive 进行在线电子表格读取/写入,如该链接所述)。

4

4 回答 4

12

就像@Alex 说的那样,您可以使用Kernel#fork创建一个新的 ruby​​ 进程来存放您require的库。新的分叉进程将有权访问父进程中加载​​的数据:

def talk(msg)
  # this will allow us to see which process is
  # talking
  puts "#{Process.pid}: #{msg}"
end

# this data was loaded on the parent process
# and will be use in the child (and in the parent)
this_is_data = ["a", "b", "c"]

talk "I'm the father process, and I see #{this_is_data}"

# this will create a new ruby process
fork{
  talk "I'm another process, and I also see #{this_is_data}"
  talk "But when I change `this_is_data`, a new copy of it is created"
  this_is_data << "d"
  talk "My own #{this_is_data}"
}

# let's wait and give a chance to the child process
# finishes before the parent
sleep 3

talk "Now, in the father again, data is: #{this_is_data}"

这个执行的结果在你的机器上会有所不同,Process.id会返回不同的值,但它会像这样:

23520: I'm the father process, and I see ["a", "b", "c"]
23551: I'm another process, and I also see ["a", "b", "c"]
23551: But when I change `this_is_data`, a new copy of it is created
23551: My own ["a", "b", "c", "d"]
23520: Now, in the father again, data is: ["a", "b", "c"]

这很好!创建的每个进程fork都是操作系统级别的进程,并在其自己的内存空间中运行。

您可以以某种方式管理通过加载文件创建的全局变量的另一件事是替换requireby的使用load。这种方法并不能解决已经指出的所有问题,但确实可以提供帮助。请参阅以下规格:

require "minitest/autorun"

describe "Loading files inside a scope" do

  def create_lib_file(version)
    libfile = <<CODE
      class MyLibrary#{version}
        VERSION = "0.0.#{version}"
      end

      class String
        def omg_danger!
        end
      end

      puts "loaded \#{MyLibrary#{version}::VERSION}"
    CODE

    File.write("my_library.rb", libfile)
  end

  after do
    File.delete("my_library.rb") if File.exists?("my_library.rb")
  end

  describe "loading with require" do
    it "sees the MyLibrary definition" do
      create_lib_file("1")
      require_relative "my_library.rb"
      MyLibrary1::VERSION.must_be :==, "0.0.1"
      "".respond_to?(:omg_danger!).must_be :==, true
    end
  end

  describe "loading with #load " do
    describe "without wrapping" do
      it "sees the MyLibrary definition" do
        create_lib_file("2")
        load "my_library.rb"
        MyLibrary2::VERSION.must_be :==, "0.0.2"
        "".respond_to?(:omg_danger!).must_be :==, true
      end
    end

    describe "using anonymous module wraping" do
      it "doesn't sees MyLibrary definition" do
        create_lib_file("3")
        load "my_library.rb", true
        ->{ MyLibrary3 }.must_raise NameError
        "".respond_to?(:omg_danger!).must_be :==, false
      end
    end
  end
end

以及执行结果:

Run options: --seed 16453

# Running tests:

loaded 0.0.3
.loaded 0.0.2
.loaded 0.0.1
.

Finished tests in 0.004707s, 637.3486 tests/s, 1274.6973 assertions/s.

3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
于 2013-11-03T17:02:13.817 回答
5

我不知道有什么方法可以卸载文件,但是您可以将精心挑选的全局变量重置为 nil 并取消定义常量(足够接近):

class Foo; end
Object.constants.include?(:Foo)
Object.send(:remove_const, :Foo)
Object.constants.include?(:Foo)
Foo                              # NameError: uninitialized constant Foo

根据您的冲突是什么,您还可以暂时重命名冲突的类:

Bar = Foo
Object.send(:remove_const, :Foo)
do_stuff
Foo = Bar
于 2013-11-03T13:17:59.080 回答
5

尽管通常所说的那样,但可以使用此过程取消要求/卸载包。

  1. 假设所需的文件存储为d:/foo.rb这个简单的内容:
class Foo

end
  1. 由于任何类、模块或方法在 Ruby 中都被定义为常量,您可以先取消链接:
irb(main):001:0> require 'd:/foo.rb'
=> true
irb(main):002:0> defined? Foo
=> "constant"
irb(main):003:0> Object.send(:remove_const, :Foo)
=> Foo
irb(main):004:0> defined? Foo
=> nil
  1. 已经需要/加载的文件记录在全局 var$"中,然后您需要从中清除您已经需要的内容:
irb(main):005:0> $".select{|r| r.include? 'foo.rb'}
=> ["d:/foo.rb"]
irb(main):006:0> $".delete('d:/foo.rb')
=> "d:/foo.rb"
irb(main):007:0> $".select{|r| r.include? 'foo.rb'}
=> []
  1. 现在您可以再次要求您的文件,所有内容都将刷新并可用。
irb(main):008:0> require 'd:/foo.rb'
=> true
irb(main):009:0> $".select{|r| r.include? 'foo.rb'}
=> ["d:/foo.rb"]
irb(main):010:0> defined? Foo
=> "constant"
irb(main):011:0> Foo.new
=> #<Foo:0x000000033ff8d8>
于 2019-10-02T06:42:10.513 回答
3

不幸的是,Ruby 的一些特性最终与您希望干净地“卸载”库有关。首先,“加载”一个 Ruby 库可以运行任意 Ruby 代码。其次,现有的常量和方法可以在 Ruby 中动态地重新定义。

如果一个 Ruby 库只定义了新的类和模块,你可以像@Denis 指出的那样简单地取消定义它们。但是,在这种情况下,即使您保持原样,“兼容性错误”也不太可能发生。如果一个库猴子修补核心 Ruby 类、创建信号处理程序或设置跟踪挂钩或挂钩,那么跟踪所有已更改的内容并干净地逆转更改at_exit非常非常困难。

您最好的选择是首先加载您的数据,然后使用类似Process#forkfork 的新外壳,然后加载库。完成后,杀死子外壳并返回父外壳。您的数据仍然存在。

https://github.com/burke/zeushttps://github.com/jonleighton/spring使用类似的技术来避免重复等待 Rails 加载。也许你可以改编他们的一些作品。

于 2013-11-03T14:51:28.433 回答