Skip to content

Multiple Client Instances & Orphaned Threads  #143

@tonyta

Description

@tonyta

We ran into this issue where instantiating a client can lead to an orphaned thread (and possibly multiple persistent connection to the LD server). We've found a workaround for our implementation, but it seems like unintended behavior. Hope this helps!

Describe the bug

LaunchDarkly::Client seems to leave orphaned threads if client instances are garbage collected before closing.

We originally encountered this in Rails, when autoloading reloading made us lose the reference to our original client and we instantiated another one. We noticed that there were multiple connections to LaunchDarkly involving multiple threads. These threads and connections remain until the ruby process is closed.

To reproduce

Here is a contrived reproduction script:

require "ldclient-rb"

puts "Ruby Version: #{RUBY_VERSION}"
puts "LaunchDarkly Version: #{LaunchDarkly::VERSION}"

puts
puts "Creating 10 clients..."
clients = Array.new(10) { LaunchDarkly::LDClient.new("") }
puts "clients: #{ObjectSpace.each_object(LaunchDarkly::LDClient).count}"
puts "threads: #{Thread.list.count}"

puts
puts "Closing every other client..."
clients.each_with_index { |c, i| c.close if i.even? }
puts "clients: #{ObjectSpace.each_object(LaunchDarkly::LDClient).count}"
puts "threads: #{Thread.list.count}"

puts
puts "Clearing client references and garbage collecting..."
clients.clear
GC.start
sleep 1
puts "clients: #{ObjectSpace.each_object(LaunchDarkly::LDClient).count}"
puts "threads: #{Thread.list.count}"

puts
puts "Remaining threads..."
puts Thread.list

The above script initializes 10 clients. The threads created by the closed clients are eventually terminated. The threads created by the other clients remain indefinitely even after those clients are garbage collected.

Note: I'm aware that I'm initializing with an invalid SDK key, but this didn't seem to have an effect on the outcome.

Ruby Version: 2.5.5
LaunchDarkly Version: 5.6.0

Creating clients...
E, [2019-10-24T15:48:44.765951 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:44.766084 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:45.105761 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:45.105904 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:45.399326 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:45.399652 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:45.729868 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:45.730277 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:46.132979 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:46.133164 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:46.436607 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:46.436749 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:46.727070 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:46.727248 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:47.055064 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:47.055177 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:47.464489 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:47.464632 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
E, [2019-10-24T15:48:47.774941 #41465] ERROR -- : [LDClient] HTTP error 401 (invalid SDK key) for streaming connection - giving up permanently
E, [2019-10-24T15:48:47.775231 #41465] ERROR -- : [LDClient] LaunchDarkly client initialization failed
clients: 10
threads: 12

Closing every other client...
clients: 10
threads: 7

Clearing client references and garbage collecting...
clients: 0
threads: 7

Remaining threads...
#<Thread:0x00007fdae4065c40 run>
#<Thread:0x00007fdae392a1b0@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:317 sleep>
#<Thread:0x00007fdae40fb880@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/launchdarkly-server-sdk-5.6.0/lib/ldclient-rb/events.rb:172 sleep_forever>
#<Thread:0x00007fdae3601060@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/launchdarkly-server-sdk-5.6.0/lib/ldclient-rb/events.rb:172 sleep_forever>
#<Thread:0x00007fdae35d3408@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/launchdarkly-server-sdk-5.6.0/lib/ldclient-rb/events.rb:172 sleep_forever>
#<Thread:0x00007fdae358ad98@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/launchdarkly-server-sdk-5.6.0/lib/ldclient-rb/events.rb:172 sleep_forever>
#<Thread:0x00007fdae3550030@/Users/tonyta/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/launchdarkly-server-sdk-5.6.0/lib/ldclient-rb/events.rb:172 sleep_forever>

Expected behavior
If the expected use is to have a single client instance, this gem take a more control of its client initialization (a la newrelic_rpm), with stricter control over its threads.

Otherwise, if multiple clients are expected, it'd be nice to keep track and terminate orphaned threads. I'm not sure if multiple connections to the LaunchDarkly server is created, but is so, those should be closed too.

Logs
none

SDK version
LaunchDarkly Version: 5.6.0

Language version, developer tools
Ruby Version: 2.5.5

OS/platform
Platform: x86_64-darwin18

Additional context
none

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions