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
|
||||
Reference in New Issue
Block a user