This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Multi # :nodoc
|
||||
# This module contains logic to run a multi.
|
||||
module Operations
|
||||
STARTED_MULTI = "ETHON: started MULTI"
|
||||
PERFORMED_MULTI = "ETHON: performed MULTI"
|
||||
|
||||
# Return the multi handle. Inititialize multi handle,
|
||||
# in case it didn't happened already.
|
||||
#
|
||||
# @example Return multi handle.
|
||||
# multi.handle
|
||||
#
|
||||
# @return [ FFI::Pointer ] The multi handle.
|
||||
def handle
|
||||
@handle ||= FFI::AutoPointer.new(Curl.multi_init, Curl.method(:multi_cleanup))
|
||||
end
|
||||
|
||||
# Initialize variables.
|
||||
#
|
||||
# @example Initialize variables.
|
||||
# multi.init_vars
|
||||
#
|
||||
# @return [ void ]
|
||||
def init_vars
|
||||
if @execution_mode == :perform
|
||||
@timeout = ::FFI::MemoryPointer.new(:long)
|
||||
@timeval = Curl::Timeval.new
|
||||
@fd_read = Curl::FDSet.new
|
||||
@fd_write = Curl::FDSet.new
|
||||
@fd_excep = Curl::FDSet.new
|
||||
@max_fd = ::FFI::MemoryPointer.new(:int)
|
||||
elsif @execution_mode == :socket_action
|
||||
@running_count_pointer = FFI::MemoryPointer.new(:int)
|
||||
end
|
||||
end
|
||||
|
||||
# Perform multi.
|
||||
#
|
||||
# @return [ nil ]
|
||||
#
|
||||
# @example Perform multi.
|
||||
# multi.perform
|
||||
def perform
|
||||
ensure_execution_mode(:perform)
|
||||
|
||||
Ethon.logger.debug(STARTED_MULTI)
|
||||
while ongoing?
|
||||
run
|
||||
timeout = get_timeout
|
||||
next if timeout == 0
|
||||
reset_fds
|
||||
set_fds(timeout)
|
||||
end
|
||||
Ethon.logger.debug(PERFORMED_MULTI)
|
||||
nil
|
||||
end
|
||||
|
||||
# Prepare multi.
|
||||
#
|
||||
# @return [ nil ]
|
||||
#
|
||||
# @example Prepare multi.
|
||||
# multi.prepare
|
||||
#
|
||||
# @deprecated It is no longer necessary to call prepare.
|
||||
def prepare
|
||||
Ethon.logger.warn(
|
||||
"ETHON: It is no longer necessay to call "+
|
||||
"Multi#prepare. Its going to be removed "+
|
||||
"in future versions."
|
||||
)
|
||||
end
|
||||
|
||||
# Continue execution with an external IO loop.
|
||||
#
|
||||
# @example When no sockets are ready yet, or to begin.
|
||||
# multi.socket_action
|
||||
#
|
||||
# @example When a socket is readable
|
||||
# multi.socket_action(io_object, [:in])
|
||||
#
|
||||
# @example When a socket is readable and writable
|
||||
# multi.socket_action(io_object, [:in, :out])
|
||||
#
|
||||
# @return [ Symbol ] The Curl.multi_socket_action return code.
|
||||
def socket_action(io = nil, readiness = 0)
|
||||
ensure_execution_mode(:socket_action)
|
||||
|
||||
fd = if io.nil?
|
||||
::Ethon::Curl::SOCKET_TIMEOUT
|
||||
elsif io.is_a?(Integer)
|
||||
io
|
||||
else
|
||||
io.fileno
|
||||
end
|
||||
|
||||
code = Curl.multi_socket_action(handle, fd, readiness, @running_count_pointer)
|
||||
@running_count = @running_count_pointer.read_int
|
||||
|
||||
check
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
# Return whether the multi still contains requests or not.
|
||||
#
|
||||
# @example Return if ongoing.
|
||||
# multi.ongoing?
|
||||
#
|
||||
# @return [ Boolean ] True if ongoing, else false.
|
||||
def ongoing?
|
||||
easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Get timeout.
|
||||
#
|
||||
# @example Get timeout.
|
||||
# multi.get_timeout
|
||||
#
|
||||
# @return [ Integer ] The timeout.
|
||||
#
|
||||
# @raise [ Ethon::Errors::MultiTimeout ] If getting the timeout fails.
|
||||
def get_timeout
|
||||
code = Curl.multi_timeout(handle, @timeout)
|
||||
raise Errors::MultiTimeout.new(code) unless code == :ok
|
||||
timeout = @timeout.read_long
|
||||
timeout = 1 if timeout < 0
|
||||
timeout
|
||||
end
|
||||
|
||||
# Reset file describtors.
|
||||
#
|
||||
# @example Reset fds.
|
||||
# multi.reset_fds
|
||||
#
|
||||
# @return [ void ]
|
||||
def reset_fds
|
||||
@fd_read.clear
|
||||
@fd_write.clear
|
||||
@fd_excep.clear
|
||||
end
|
||||
|
||||
# Set fds.
|
||||
#
|
||||
# @example Set fds.
|
||||
# multi.set_fds
|
||||
#
|
||||
# @return [ void ]
|
||||
#
|
||||
# @raise [ Ethon::Errors::MultiFdset ] If setting the file descriptors fails.
|
||||
# @raise [ Ethon::Errors::Select ] If select fails.
|
||||
def set_fds(timeout)
|
||||
code = Curl.multi_fdset(handle, @fd_read, @fd_write, @fd_excep, @max_fd)
|
||||
raise Errors::MultiFdset.new(code) unless code == :ok
|
||||
max_fd = @max_fd.read_int
|
||||
if max_fd == -1
|
||||
sleep(0.001)
|
||||
else
|
||||
@timeval[:sec] = timeout / 1000
|
||||
@timeval[:usec] = (timeout * 1000) % 1000000
|
||||
loop do
|
||||
code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
|
||||
break unless code < 0 && ::FFI.errno == Errno::EINTR::Errno
|
||||
end
|
||||
raise Errors::Select.new(::FFI.errno) if code < 0
|
||||
end
|
||||
end
|
||||
|
||||
# Check.
|
||||
#
|
||||
# @example Check.
|
||||
# multi.check
|
||||
#
|
||||
# @return [ void ]
|
||||
def check
|
||||
msgs_left = ::FFI::MemoryPointer.new(:int)
|
||||
while true
|
||||
msg = Curl.multi_info_read(handle, msgs_left)
|
||||
break if msg.null?
|
||||
next if msg[:code] != :done
|
||||
easy = easy_handles.find{ |e| e.handle == msg[:easy_handle] }
|
||||
easy.return_code = msg[:data][:code]
|
||||
Ethon.logger.debug { "ETHON: performed #{easy.log_inspect}" }
|
||||
delete(easy)
|
||||
easy.complete
|
||||
end
|
||||
end
|
||||
|
||||
# Run.
|
||||
#
|
||||
# @example Run
|
||||
# multi.run
|
||||
#
|
||||
# @return [ void ]
|
||||
def run
|
||||
running_count_pointer = FFI::MemoryPointer.new(:int)
|
||||
begin code = trigger(running_count_pointer) end while code == :call_multi_perform
|
||||
check
|
||||
end
|
||||
|
||||
# Trigger.
|
||||
#
|
||||
# @example Trigger.
|
||||
# multi.trigger
|
||||
#
|
||||
# @return [ Symbol ] The Curl.multi_perform return code.
|
||||
def trigger(running_count_pointer)
|
||||
code = Curl.multi_perform(handle, running_count_pointer)
|
||||
@running_count = running_count_pointer.read_int
|
||||
code
|
||||
end
|
||||
|
||||
# Return number of running requests.
|
||||
#
|
||||
# @example Return count.
|
||||
# multi.running_count
|
||||
#
|
||||
# @return [ Integer ] Number running requests.
|
||||
def running_count
|
||||
@running_count ||= nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Multi
|
||||
|
||||
# This module contains the logic and knowledge about the
|
||||
# available options on multi.
|
||||
module Options
|
||||
|
||||
# Sets max_total_connections option.
|
||||
#
|
||||
# @example Set max_total_connections option.
|
||||
# multi.max_total_conections = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def max_total_connections=(value)
|
||||
Curl.set_option(:max_total_connections, value_for(value, :int), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets maxconnects option.
|
||||
#
|
||||
# @example Set maxconnects option.
|
||||
# multi.maxconnects = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def maxconnects=(value)
|
||||
Curl.set_option(:maxconnects, value_for(value, :int), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets pipelining option.
|
||||
#
|
||||
# @example Set pipelining option.
|
||||
# multi.pipelining = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def pipelining=(value)
|
||||
Curl.set_option(:pipelining, value_for(value, :int), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets socketdata option.
|
||||
#
|
||||
# @example Set socketdata option.
|
||||
# multi.socketdata = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def socketdata=(value)
|
||||
Curl.set_option(:socketdata, value_for(value, :string), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets socketfunction option.
|
||||
#
|
||||
# @example Set socketfunction option.
|
||||
# multi.socketfunction = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def socketfunction=(value)
|
||||
Curl.set_option(:socketfunction, value_for(value, :string), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets timerdata option.
|
||||
#
|
||||
# @example Set timerdata option.
|
||||
# multi.timerdata = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def timerdata=(value)
|
||||
Curl.set_option(:timerdata, value_for(value, :string), handle, :multi)
|
||||
end
|
||||
|
||||
# Sets timerfunction option.
|
||||
#
|
||||
# @example Set timerfunction option.
|
||||
# multi.timerfunction = $value
|
||||
#
|
||||
# @param [ String ] value The value to set.
|
||||
#
|
||||
# @return [ void ]
|
||||
def timerfunction=(value)
|
||||
Curl.set_option(:timerfunction, value_for(value, :string), handle, :multi)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return the value to set to multi handle. It is converted with the help
|
||||
# of bool_options, enum_options and int_options.
|
||||
#
|
||||
# @example Return casted the value.
|
||||
# multi.value_for(:verbose)
|
||||
#
|
||||
# @return [ Object ] The casted value.
|
||||
def value_for(value, type, option = nil)
|
||||
return nil if value.nil?
|
||||
|
||||
if type == :bool
|
||||
value ? 1 : 0
|
||||
elsif type == :int
|
||||
value.to_i
|
||||
elsif value.is_a?(String)
|
||||
Ethon::Easy::Util.escape_zero_byte(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Multi
|
||||
|
||||
# This module provides the multi stack behaviour.
|
||||
module Stack
|
||||
|
||||
# Return easy handles.
|
||||
#
|
||||
# @example Return easy handles.
|
||||
# multi.easy_handles
|
||||
#
|
||||
# @return [ Array ] The easy handles.
|
||||
def easy_handles
|
||||
@easy_handles ||= []
|
||||
end
|
||||
|
||||
# Add an easy to the stack.
|
||||
#
|
||||
# @example Add easy.
|
||||
# multi.add(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to add.
|
||||
#
|
||||
# @raise [ Ethon::Errors::MultiAdd ] If adding an easy failed.
|
||||
def add(easy)
|
||||
return nil if easy_handles.include?(easy)
|
||||
|
||||
code = Curl.multi_add_handle(handle, easy.handle)
|
||||
raise Errors::MultiAdd.new(code, easy) unless code == :ok
|
||||
easy_handles << easy
|
||||
end
|
||||
|
||||
# Delete an easy from stack.
|
||||
#
|
||||
# @example Delete easy from stack.
|
||||
#
|
||||
# @param [ Easy ] easy The easy to delete.
|
||||
#
|
||||
# @raise [ Ethon::Errors::MultiRemove ] If removing an easy failed.
|
||||
def delete(easy)
|
||||
if easy_handles.delete(easy)
|
||||
code = Curl.multi_remove_handle(handle, easy.handle)
|
||||
raise Errors::MultiRemove.new(code, handle) unless code == :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user