Skip to content

Commit

Permalink
[feat] revamped client.close to release resources
Browse files Browse the repository at this point in the history
- close will now attempt to properly shutdown the client
- in case a user closes the client manually finalizer is wiped
- finalization redesigned to not require multiple finalizers
  • Loading branch information
kares committed Jun 1, 2022
1 parent 683bfb7 commit 58843d8
Showing 1 changed file with 48 additions and 10 deletions.
58 changes: 48 additions & 10 deletions lib/manticore/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ def newThread(runnable)
# @option options [boolean] ssl[:track_state] (false) Turn on or off connection state tracking. This helps prevent SSL information from leaking across threads, but means that connections
# can't be shared across those threads. This should generally be left off unless you know what you're doing.
def initialize(options = {})
@finalizers = []
self.class.shutdown_on_finalize self, @finalizers
@finalizer = Finalizer.new
self.class.shutdown_on_finalize self, @finalizer

builder = client_builder
builder.set_user_agent options.fetch(:user_agent, "Manticore #{VERSION}")
Expand Down Expand Up @@ -360,29 +360,67 @@ def execute!
end
end

# Free resources associated with the CloseableHttpClient
def close
@client.close if @client
# Release resources held by this client, namely:
# - close the internal http client
# - shutdown the connection pool
# - stops accepting async requests in the executor
#
# After this call the client is no longer usable.
# @note In versions before 0.9 this method only closed the underlying `CloseableHttpClient`
def close(await: nil)
ObjectSpace.undefine_finalizer(self)
@finalizer.call # which does ~ :
# @executor&.shutdown rescue nil
# @client&.close
# @pool&.shutdown rescue nil
@async_requests.close
case await
when false, nil
@executor&.shutdown_now rescue nil
when Numeric
millis = java.util.concurrent.TimeUnit::MILLISECONDS
@executor&.await_termination(await * 1000, millis) rescue nil
else
nil
end
end

# @private
class Finalizer

def initialize
@_objs = []
end

def push(obj, args)
@_objs.unshift [java.lang.ref.WeakReference.new(obj), Array(args)]
end

def call(id = nil) # when called on finalization an object id arg is passed
@_objs.each { |obj, args| obj.get&.send(*args) rescue nil }
end

end
private_constant :Finalizer

# Get at the underlying ExecutorService used to invoke asynchronous calls.
def executor
@executor ||= create_executor
end

def self.shutdown_on_finalize(client, objs)
ObjectSpace.define_finalizer client, -> {
objs.each { |obj, args| obj.send(*args) rescue nil }
}
# @private
def self.shutdown_on_finalize(client, finalizer)
ObjectSpace.define_finalizer(client, finalizer)
end

protected

# Takes an object and a message to pass to the object to destroy it. This is done rather than
# a proc to avoid creating a closure that would maintain a reference to this client, which
# would prevent the client from being cleaned up.
# @private
def finalize(object, args)
@finalizers << [WeakRef.new(object), Array(args)]
@finalizer.push(object, args)
end

def url_as_regex(url)
Expand Down

0 comments on commit 58843d8

Please sign in to comment.