Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak in Concurrent::Promises.future #960

Open
leoarnold opened this issue Aug 4, 2022 · 1 comment
Open

Memory leak in Concurrent::Promises.future #960

leoarnold opened this issue Aug 4, 2022 · 1 comment

Comments

@leoarnold
Copy link

leoarnold commented Aug 4, 2022

* Operating system:                linux
* Ruby implementation:             Ruby
* `concurrent-ruby` version:       1.1.10
* `concurrent-ruby-ext` installed: no
* `concurrent-ruby-edge` used:     no

The test script

# frozen_string_literal: true

require 'bundler/inline'

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

  gem 'concurrent-ruby', '1.1.10'
  gem 'memory_profiler', '~> 1'
end

class Thing; end

def report(title, &block)
  puts title

  pp MemoryProfiler.report(&block).retained_memory_by_class
end

report('Warmup') do
  Concurrent::Promises.future { Thing.new }.wait
end

report('When waiting for the Future') do
  Concurrent::Promises.future { Thing.new }.wait
end

report('When waiting for the Future with args') do
  Concurrent::Promises.future(Thing.new) { |o| o }.wait
end

report('When waiting for the Future and actively dereferencing it') do
  x = Concurrent::Promises.future { Thing.new }.wait
  x = nil
end

report('When waiting for the Future with args and actively dereferencing it') do
  x = Concurrent::Promises.future(Thing.new) { |o| o }.wait
  x = nil
end

yields the following output

Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using bundler 2.3.19
Using memory_profiler 1.0.0
Using concurrent-ruby 1.1.10
Warmup
[{:data=>"Thread", :count=>1048992},
 {:data=>"Concurrent::CachedThreadPool", :count=>216},
 {:data=>"Thread::Mutex", :count=>216},
 {:data=>"Array", :count=>200},
 {:data=>"Thread::ConditionVariable", :count=>192},
 {:data=>"Concurrent::Event", :count=>144},
 {:data=>"Proc", :count=>80},
 {:data=>"String", :count=>80},
 {:data=>"Thread::Queue", :count=>76},
 {:data=>"Concurrent::RubyThreadPoolExecutor::Worker", :count=>40}]
When waiting for the Future
[{:data=>"Array", :count=>40}]
When waiting for the Future with args
[{:data=>"Array", :count=>40}]
When waiting for the Future and actively dereferencing it
[{:data=>"Array", :count=>40}]
When waiting for the Future with args and actively dereferencing it
[{:data=>"Array", :count=>40}]

Note that in contrast to #959 the return value of the block is not leaking here.

@leoarnold
Copy link
Author

Maybe this "leak" is intentional, i.e. keeping a pool of sub-threads in a thread-local variable which is then garbage collected when the parent thread is garbage collected 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant