gxg-framework/lib/gxg/gxg_engine.rb
2024-05-05 18:27:22 -07:00

854 lines
No EOL
29 KiB
Ruby

# ---------------------------------------------------------------------------------------------------------------------
# GxG Engine interface: rational memory management, and quota supports
module GxG
#
module Engine
# ok... why?
# example: ::Process::getrlimit(:AS) => [<1Exabyte>,<1Exabyte>]
# Now that is the Amsterdam-style pull on a bong with stirrups - so far as dealing with real memory limits goes.
# Rather - I want my apps to be *able* to deal with reality as the system resources determine
# Eventually provides a *corrected* drop-in replacement for Process module in standard MRI 1.9.2
# See : http://www.ruby-doc.org/core-1.9.3/Process.html
#
@@details = {}
# running effectively in 32 or 64 bits? See: http://www.ruby-forum.com/topic/164583
@@details[:runtime_bits] = ([ -1 ].pack('l!').length * 8)
# default to 32 bit size:
if @@details[:runtime_bits] == 64
@@details[:runtime_rvalue_size] = 40
else
@@details[:runtime_rvalue_size] = 20
end
#
@@details[:files] = {:maximum_file_descriptors => 1024}
# FORNOW : Review : server claims 50% of all system threads, saw a default of 18931 imposed by system quotas.
# @@details[:events] = {:maximum_event_descriptors => (GxG::SYSTEM["kernel.threads_max"]["kernel.threads_max"].to_f * 0.5).to_i , :event_descriptors_threshold => 0.05, :event_descriptors_envelope => (20..100), :event_minimum_threshold => 20}
@@details[:events] = {:maximum_event_descriptors => 18931, :event_descriptors_threshold => 0.05, :event_descriptors_envelope => (20..100), :event_minimum_threshold => 20}
# Fix for Windows: per-process theads max is 2000.
if GxG::SYSTEM.platform[:platform] == :windows
@@details[:files][:maximum_file_descriptors] = 512
@@details[:events][:maximum_event_descriptors] = 2000
end
#
@@details[:runtime] = RUBY_ENGINE.to_sym
@@details[:runtime_version] = GxG::Version.new({:interpret => RUBY_VERSION, :patch_level => RUBY_PATCHLEVEL, :revision => RUBY_REVISION})
# determine the byte size of a Ruby RVALUE (a.k.a :slot_size)
# See: http://www.engineyard.com/blog/2010/mri-memory-allocation-a-primer-for-developers/
#
@@details[:memory_load] = {:high => false, :medium => false, :low => true, :percent => 0.0}
@@details[:engine_load] = {:high => false, :medium => false, :low => true, :percent => 0.0}
#
@@details[:key_map] = []
@@details[:key_map] << {:key => "files.max_descriptors", :path => "/:files/:maximum_file_descriptors"}
@@details[:key_map] << {:key => "events.max_descriptors", :path => "/:events/:maximum_event_descriptors"}
#
@@thread_safety = Mutex.new
@@event_descriptors = 0
#
def self.detail_mapping()
@@details[:key_map]
end
#
def self.[](the_path="")
# Public interface for accessing system keyed values.
# access cached or system-read values on /proc (sysctl-style) keys. (frankly missing from BSD, etc)
result = nil
reference = ::GxG::Engine::detail_mapping()
#
reference.to_enum.each do |mapping|
if mapping[:key] == the_path
@@thread_safety.synchronize {
result = @@details.get_at_path(mapping[:path]).clone
}
break
end
end
# unless result
# # attempt to fetch the data from the system somehow.
# result = get_system_data(the_path)
# unless result.keys.size > 0
# result = nil
# end
# end
#
result
end
#
def self.memory_load_high?()
@@thread_safety.synchronize {
@@details[:memory_load][:high]
}
end
#
def self.memory_load_medium?()
@@thread_safety.synchronize {
@@details[:memory_load][:medium]
}
end
#
def self.memory_load_low?()
@@thread_safety.synchronize {
@@details[:memory_load][:low]
}
end
#
def self.memory_load()
@@thread_safety.synchronize {
@@details[:memory_load][:percent]
}
end
#
def self.engine_load_high?()
@@thread_safety.synchronize {
@@details[:engine_load][:high]
}
end
#
def self.engine_load_medium?()
@@thread_safety.synchronize {
@@details[:engine_load][:medium]
}
end
#
def self.engine_load_low?()
@@thread_safety.synchronize {
@@details[:engine_load][:low]
}
end
#
def self.engine_load()
@@thread_safety.synchronize {
@@details[:engine_load][:percent]
}
end
#
def self.determine_memory_load()
current_load = ((GxG::Engine::memory_used()[:total].to_f / GxG::SYSTEM.memory_limits()[:process].first.to_f) * 100.0)
if (0.0..40.0).include?(current_load)
# low
@@thread_safety.synchronize {
@@details[:memory_load][:high] = false
@@details[:memory_load][:medium] = false
@@details[:memory_load][:low] = true
@@details[:memory_load][:percent] = current_load
}
else
if (40.0..70.0).include?(current_load)
# medium
@@thread_safety.synchronize {
@@details[:memory_load][:high] = false
@@details[:memory_load][:medium] = true
@@details[:memory_load][:low] = false
@@details[:memory_load][:percent] = current_load
}
else
if current_load > 70.0
# high
@@thread_safety.synchronize {
@@details[:memory_load][:high] = true
@@details[:memory_load][:medium] = false
@@details[:memory_load][:low] = false
@@details[:memory_load][:percent] = current_load
}
end
end
end
end
#
def self.determine_engine_load()
max_event_descriptors = ::GxG::Engine::maximum_event_descriptors()
available_events = ::GxG::Engine::available_event_descriptors()
current_load = (((max_event_descriptors - available_events).to_f / max_event_descriptors.to_f) * 100.0)
if (0.0..40.0).include?(current_load)
# low
@@thread_safety.synchronize {
@@details[:engine_load][:high] = false
@@details[:engine_load][:medium] = false
@@details[:engine_load][:low] = true
@@details[:memory_load][:percent] = current_load
}
else
if (40.0..70.0).include?(current_load)
# medium
@@thread_safety.synchronize {
@@details[:engine_load][:high] = false
@@details[:engine_load][:medium] = true
@@details[:engine_load][:low] = false
@@details[:engine_load][:percent] = current_load
}
else
if current_load > 70.0
# high
@@thread_safety.synchronize {
@@details[:engine_load][:high] = true
@@details[:engine_load][:medium] = false
@@details[:engine_load][:low] = false
@@details[:engine_load][:percent] = current_load
}
end
end
end
end
#
def self.engine_gear()
# inverse of load
result = :low
@@thread_safety.synchronize {
if @@details[:engine_load][:medium]
result = :medium
end
if @@details[:engine_load][:low]
result = :high
end
}
result
end
#
def self.memory_gear()
# inverse of load
result = :low
@@thread_safety.synchronize {
if @@details[:memory_load][:medium]
result = :medium
end
if @@details[:memory_load][:low]
result = :high
end
}
result
end
#
def self.gear()
"#{::GxG::Engine::engine_gear().to_s}_#{::GxG::Engine::memory_gear().to_s}".to_sym
end
#
def self.determine_loads()
mem_gear = ::GxG::Engine::memory_gear()
eng_gear = ::GxG::Engine::engine_gear()
{
:engine => {:load => ::GxG::Engine::determine_engine_load(), :gear => eng_gear},
:memory => {:load => ::GxG::Engine::determine_memory_load(), :gear => mem_gear},
:gear => "#{eng_gear.to_s}_#{mem_gear.to_s}".to_sym
}
end
#
include ::Process
# overrides
#
# [:exec, :fork, :spawn, :exit!, :exit, :abort, :kill, :wait, :wait2, :waitpid, :waitpid2, :waitall, :detach, :pid, :ppid,
# .
def self.exec(*args)
::Process::exec(*args)
end
#
def self.fork(*args)
::Process::fork(*args)
end
#
def self.spawn(*args)
::Process::spawn(*args)
end
#
def self.exit!(*args)
::Process::exit!(*args)
end
#
def self.exit(*args)
::Process::exit(*args)
end
#
def self.abort(*args)
::Process::abort(*args)
end
#
def self.kill(*args)
::Process::kill(*args)
end
#
def self.wait(*args)
::Process::wait(*args)
end
#
def self.wait2(*args)
::Process::wait2(*args)
end
#
def self.waitpid(*args)
::Process::waitpid(*args)
end
#
def self.waitpid2(*args)
::Process::waitpid2(*args)
end
#
def self.waitall(*args)
::Process::waitall(*args)
end
#
def self.detach(*args)
::Process::detach(*args)
end
#
def self.pid(*args)
::Process::pid(*args)
end
#
def self.ppid(*args)
::Process::ppid(*args)
end
#
# :getpgrp, :setpgrp, :getpgid, :setpgid, :setsid, :getpriority, :setpriority, :getrlimit, :setrlimit, :uid, :gid, :euid, :egid,
#
def self.getpgrp(*args)
::Process::getpgrp(*args)
end
#
def self.setpgrp(*args)
::Process::setpgrp(*args)
end
#
def self.getpgid(*args)
::Process::getpgid(*args)
end
#
def self.setpgid(*args)
::Process::setpgid(*args)
end
#
def self.setsid(*args)
::Process::setsid(*args)
end
#
def self.getpriority(*args)
::Process::getpriority(*args)
end
#
def self.setpriority(*args)
::Process::setpriority(*args)
end
#
def self.getrlimit(key)
result = nil
# FIX: GxG::Engine::getrlimit : Awaiting rbx bug #2112 fix : https://github.com/rubinius/rubinius/issues/2112
case ::GxG::Engine::profile()[:engine]
when :ruby
raw_data = ::Process::getrlimit(key)
case key
when :AS
if GxG::SYSTEM
memory_limits = GxG::SYSTEM.memory_limits()
result = [raw_data[0],(memory_limits[:process].max)]
else
result = raw_data
end
else
result = raw_data
end
when :jruby
memory_limits = GxG::SYSTEM.memory_limits()
result = [(memory_limits[:process].first),(memory_limits[:process].last)]
when :rbx
memory_limits = GxG::SYSTEM.memory_limits()
result = [(memory_limits[:process].first),(memory_limits[:process].last)]
end
result
end
#
def self.setrlimit(*args)
::Process::setrlimit(*args)
end
#
def self.uid(*args)
::Process::uid(*args)
end
#
def self.gid(*args)
::Process::gid(*args)
end
#
def self.euid(*args)
::Process::euid(*args)
end
#
def self.egid(*args)
::Process::egid(*args)
end
#
# :initgroups, :groups, :maxgroups, :daemon, :times]
#
def self.initgroups(*args)
::Process::initgroups(*args)
end
#
def self.groups(*args)
::Process::groups(*args)
end
#
def self.maxgroups(*args)
::Process::maxgroups(*args)
end
#
def self.daemon(*args)
::Process::daemon(*args)
end
#
def self.times(*args)
::Process::times(*args)
end
#
# extensions
#
def self.profile()
@@thread_safety.synchronize {
{:engine => @@details[:runtime], :version => @@details[:runtime_version], :bits => @@details[:runtime_bits], :slot_size => @@details[:runtime_rvalue_size]}.clone
}
end
#
def self.instance_process(the_class, &block)
if block.respond_to?(:call)
::ObjectSpace.each_object(Class) do |the_object|
if the_object.alive?()
if the_object.is_a?(the_class)
block.call(the_object)
end
end
end
end
end
#
def self.object_memory_used()
#
slots = 0
other = 0
::ObjectSpace.each_object(Class) do |the_object|
slots += 1
if the_object.alive?()
if the_object.is_a?(::String)
other += the_object.bytesize()
end
end
end
#
((::GxG::Engine::profile()[:slot_size].to_i * slots) + other)
end
#
def self.memory_used()
# ok... why in the fly'n F isn't this provided by core ruby?!?
# Attribution: http://stackoverflow.com/questions/7220896/get-current-ruby-process-memory-usage
result = {:actual => 0, :virtual => 0, :total => 0}
#
if ::GxG::SYSTEM
case ::GxG::SYSTEM.platform[:platform]
when :windows
# FORNOW: user object space enumeration
result[:actual] = ::GxG::Engine::object_memory_used()
when :bsd
pid_string = "#{::Process::pid}"
env = self.environment()
case env[:environment]
when :openbsd
# See: http://modman.unixdev.net/?sektion=1&page=procmap&manpath=OpenBSD-3.6
# use `procmap -p #{pid_string}`
# LATER: GxG::Engine::memory_used (OpenBSD) : determine how to cull procmap text data into :actual and :virtual memory usage.
# FORNOW: user object space enumeration
result[:actual] = ::GxG::Engine::object_memory_used()
#
when :freebsd, :dragonfly, :pcbsd
# TODO: GxG::Engine::memory_used: Test with FreeBSD, DragonFlyBSD, and PCBSD.
raw_data = `top -d 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[6] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (netbsd) : Find a reliable way to get result[:virtual] mem used.
break
end
end
when :netbsd
# TODO: GxG::Engine::memory_used: Test with NetBSD.
raw_data = `top -d 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[4] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (netbsd) : Find a reliable way to get result[:virtual] mem used.
break
end
end
when :darwin, :macos
# TODO: GxG::Engine::memory_used: Test with Darwin/MacOSX.
raw_data = `top -l 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[9] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (darwin) : Find a reliable way to get result[:virtual] mem used.
break
end
end
end
when :linux, :solaris
# PS is universal, but unfortunately universally incorrect, See: http://stackoverflow.com/questions/131303/linux-how-to-measure-actual-memory-usage-of-an-application-or-process
# TODO: GxG::Engine::memory_used: create a non-toolbox version for bootstrap memory profiling.
# TODO: GxG::Engine::memory_used: Test with Solaris.
raw_data = `pmap -d #{::Process::pid} | tail -n 1`.split(" ")
result[:actual] = (raw_data[3].to_s << "B").numeric_values()[:byte].to_i
result[:virtual] = (raw_data[1].to_s << "B").numeric_values()[:byte].to_i
# result[:shared] = (raw_data[5] << "B").numeric_values()[:byte].to_i
end
end
result[:total] = (result[:actual] + result[:virtual])
result
end
#
def self.memory_available()
# Memory available for this process
result = {:actual => 0, :virtual => 0, :total => 0}
if ::GxG::SYSTEM
used_memory = ::GxG::Engine::memory_used()
free_memory = ::GxG::SYSTEM.memory()
limits = ::GxG::Engine::getrlimit(:AS)
latitude_total = (limits[0] - used_memory[:actual])
if latitude_total < 0
latitude_total = 0
end
# latitude_virtual = (limits[0] - used_memory[:actual])
# latitude_actual = (limits[0] - used_memory[:virtual])
# indicates available ram vs. paged swap first
if free_memory[:actual][:free].to_i > limits[0]
result[:actual] = latitude_total
result[:virtual] = 0
else
result[:actual] = free_memory[:actual][:free]
# vm calc is crack-monkey, but someplace close to reality.
if free_memory[:virtual][:free] > ((limits[0] - result[:actual]) - used_memory[:total])
result[:virtual] = ((limits[0] - result[:actual]) - used_memory[:total])
unless result[:virtual] > 0
result[:virtual] = 0
end
else
if ((free_memory[:virtual][:free] - result[:actual]) - used_memory[:total]) > 0
result[:virtual] = ((free_memory[:virtual][:free] - result[:actual]) - used_memory[:total])
else
result[:virtual] = 0
end
end
end
#
end
result[:total] = (result[:actual] + result[:virtual])
result
end
#
def self.engine_data_keys(other_platform=nil)
#
result = []
data = []
data << {:key => "files.max_descriptors", :platforms => [:linux], :operation => {:getconf => "OPEN_MAX", :format => :to_int}}
# TODO: Research maximum socket limit under zmq:
# See: http://news.ycombinator.com/item?id=1740823
# Also : http://stackoverflow.com/questions/651665/how-many-socket-connections-possible
# inventory the various platform variables that affect what threshold of max_socket_count a given system would end up with.
# TODO: find a way to ask the OS and ZMQ for: how many descriptors available to this process, and how many in use under zmq 2.x & 3.x respectively?
# ALSO: factor in passed preferences for these settings.
# update available_file_descriptors when all that becomes clear and is a bit settled.
# current_memory = ::GxG::Engine::memory_available()[:actual]
# TODO: GxG::Engine::engine_data_keys : determine precise socket size (32bit/64bit arch) for event zmq sockets used (exact type used).
# GxG::Engine::profile()[:bits]
# zmq_socket_size = 16384
#
# event_allocation = 0.5
#
# TODO: GxG::Engine::engine_data_keys : tie event allocation percentage into defaults / config.file loader.
data << {:key => "events.max_descriptors", :platforms => [:linux], :operation => {:event_allocation => 0.5, :format => :noop}}
# See: http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3
# Sysctl tuning: http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1
# LATER: develop a series of formulas for calculating approx. how many clients one can support under one 'cluster' controller (session manager).
data.to_enum.each do |entry|
if entry[:platforms].include?((other_platform || ::GxG::SYSTEM.platform()[:platform]))
result << entry
end
end
#
result
end
#
def self.get_engine_data(keys=[])
# Support for <SYSTEM>.refresh_data_for(a_path) (private_method)
# some sort of sysctl + config + etc for *all* platforms:
# "sysctl -n net.ipv4.tcp_sack" becomes: "/net/ipv4/tcp/selective_ack" (external use path)
# @details[:memory_limits][:buffers][:socket][:ipc] becomes colllection_point = @details.at_path(":memory_limits/:buffers/:socket/:ipc");collection_point[(key)]=value
# procs for gathering data, and formatting for use.
# by_platform key between sysctl/config-key and @details hash path.
# {:synonyms => ["external-path"], :sysctl => "key-string', :format => :proc-name, :location => "hash-path", :location_key => (:sym or int)}
#
unless keys.is_a?(::Array)
keys = [(keys)]
end
result = {}
#
format_noop = Proc.new do |the_data|
the_data
end
format_to_int = Proc.new do |the_string|
(the_string.numeric_values()[:integer] || 0)
end
format_to_float = Proc.new do |the_string|
(the_string.numeric_values()[:float] || 0.0)
end
format_zerofalse = Proc.new do |the_string|
( format_to_int.call(the_string) > 0 )
end
format_valid_with_initial = Proc.new do |the_string|
raw_data = the_string.split(" ")
{:initial => format_to_int.call(raw_data[1]), :valid => ((format_to_int.call(raw_data[0]))..(format_to_int.call(raw_data[2])))}
end
sysctl_read = Proc.new do |a_key,the_formatter|
( the_formatter.call(`sysctl -n #{a_key.to_s}`.split("\n")[0]) )
end
getconf_read = Proc.new do |a_key, the_formatter|
( the_formatter.call(`getconf #{a_key.to_s}`.split("\n")[0]) )
end
event_allocation = Proc.new do |the_allocation, the_formatter|
# more precise zmq socket byte size goes here.
zmq_socket_size = 16384
#
(((::GxG::Engine::memory_available()[:actual].to_f * the_allocation.to_f).to_i / zmq_socket_size) - 1)
end
#
reference = ::GxG::Engine::engine_data_keys()
#
fetch_data = Proc.new do |the_key|
data = nil
reference.to_enum.each do |entry|
if entry[:key] == the_key
case entry[:operation][:format]
when :noop
the_formatter = format_noop
when :zerofalse
the_formatter = format_zerofalse
when :to_int
the_formatter = format_to_int
when :to_float
the_formatter = format_to_float
when :valid_with_initial
the_formatter = format_valid_with_initial
else
the_formatter = format_noop
end
if entry[:operation][:sysctl]
data = sysctl_read.call(entry[:operation][:sysctl],the_formatter)
else
if entry[:operation][:getconf]
data = getconf_read.call(entry[:operation][:getconf],the_formatter)
else
if entry[:operation][:event_allocation]
data = event_allocation.call(entry[:operation][:event_allocation],the_formatter)
else
#
end
#
end
end
if data
break
end
end
end
data
end
#
keys.to_enum.each do |specifier|
if specifier.is_a?(::String)
# if element is String: serves as result Hash key to hold result value.
# single result with this result key
result[(specifier)] = fetch_data.call(specifier)
else
if specifier.is_a?(::Hash)
# if element is Hash: {ukey => hash_path}, {:key => <keystring>, :path => <hash_path>}
# if hash_path supplied, read the data; set the @details hash element, and return the formatted data
if specifier[:key]
# {:key => <keystring>, :path => <hash_path>}
result[(specifier[:key])] = fetch_data.call(specifier[:key])
if specifier[:path]
@@thread_safety.synchronize {
@@details.set_at_path(specifier[:path],result[(specifier[:key])])
}
end
else
# {ukey => hash_path}
subkeys = specifier.keys
subkeys.to_enum.each do |a_key|
result[(a_key)] = fetch_data.call(a_key)
@@thread_safety.synchronize {
@@details.set_at_path(specifier[(a_key)],result[(a_key)])
}
end
end
end
end
end
#
result
end
#
def self.set_engine_data(keys=[])
# LATER: GxG::Engine::set_engine_data : when you figure out system(sudo) authentication - use this for ops that require elevated privs to set system parameters.
# Define a set of universal_keys that affect what other keys; upon setting any one, unique-list a set of keys to read back in w/ hash paths.
result = nil
if nil
::GxG::Engine::get_engine_data(nil,nil)
# compare hash value to the_value; return true if successful
end
result
end
# ### Descriptors in general
def self.descriptor_limits()
{:events => @@details[:events][:maximum_event_descriptors], :files => @@details[:files][:maximum_file_descriptors]}.clone
end
#
def self.descriptor_heap_used()
result = {:total => 0}
slot_size = ::GxG::Engine::profile[:slot_size]
file_total = 0
GxG::Engine::file_descriptors().to_enum.each do |item|
file_total += item.heap_used()
end
@@thread_safety.synchronize {
result[:files] = file_total
unless ::GxG::Engine::descriptor_limits()[:events] == :files
# see available_file_descriptors : beware counting event descriptors twice (w/ file descriptors)
# TODO: GxG::Engine::descriptor_heap_used : sync w/ more precise event socket size determination
result[:events] = (@@event_descriptors * (slot_size + 4))
end
}
result[:total] = (result[:files].to_i + result[:events].to_i)
#
result
end
# ### File Descriptors
def self.maximum_file_descriptors()
::GxG::Engine::descriptor_limits()[:files]
end
#
def self.file_descriptors()
# Return an Array of currently opened file descriptors.
# About File Descriptors: http://stackoverflow.com/questions/848717/handling-more-than-1024-file-descriptors-in-c-on-linux
#
result = []
exclude_list = []
[::IO, ::IO::descendants()].flatten!.to_enum.each do |the_class|
unless exclude_list.include?(the_class)
::ObjectSpace.each_object(the_class) do |the_io|
unless (the_io.closed? || result.include?(the_io))
result << the_io
end
end
end
end
result
end
#
def self.available_file_descriptors()
(::GxG::Engine::maximum_file_descriptors() - ::GxG::Engine::file_descriptors().size)
end
#
# ### Event Descriptors
def self.maximum_event_descriptors()
::GxG::Engine::descriptor_limits()[:events]
end
#
def self.event_descriptors_threshold()
# Percentage of allocation that must be available for events to be released.
@@thread_safety.synchronize {
@@details[:events][:event_descriptors_threshold].clone
}
end
#
def self.event_minimum_threshold()
# Percentage of allocation that must be available for events to be released.
@@thread_safety.synchronize {
@@details[:events][:event_minimum_threshold].clone
}
end
#
def self.events_in_use()
@@thread_safety.synchronize {
@@event_descriptors
}
end
#
def self.available_event_descriptors()
result = 0
current = 0
@@thread_safety.synchronize {
current = (::GxG::Engine::maximum_event_descriptors() - @@event_descriptors)
}
if current > 0
result = current
end
result
end
#
def self.reserve_event_descriptor()
result = false
if ::GxG::Engine::available_event_descriptors() > 0
@@thread_safety.synchronize {
@@event_descriptors += 1
}
result = true
else
raise Errno::ENOMEM, "maximum number of event descriptors in use (#{GxG::Engine::maximum_event_descriptors().to_s})"
end
result
end
#
def self.release_event_descriptor()
result = false
@@thread_safety.synchronize {
if @@event_descriptors > 0
@@event_descriptors -= 1
end
}
result = true
result
end
#
def self.determine_event_allocations()
#
total = ::GxG::Engine::maximum_event_descriptors()
dispatchers = []
[::GxG::Events::EventDispatcher, ::GxG::Events::EventDispatcher::descendants()].flatten!.each do |the_class|
::ObjectSpace.each_object(the_class) do |the_dispatcher|
if the_dispatcher.alive?()
if the_dispatcher.running?()
dispatchers << the_dispatcher
end
end
end
end
# Note : a ghost-dispatcher representing all other system funcitons is added to the dispatcher count.
maximum_events = (total.to_f / (dispatchers.size + 1).to_f).to_i
threshold = (maximum_events.to_f * ::GxG::Engine::event_descriptors_threshold().to_f).to_i
unless threshold > 0
threshold = 1
end
@@thread_safety.synchronize {
@@details[:events][:event_minimum_threshold] = threshold
@@details[:events][:event_descriptors_envelope] = ((threshold)..(maximum_events))
}
true
end
#
def self.event_allocation_envelope()
result = (20..100)
@@thread_safety.synchronize {
result = @@details[:events][:event_descriptors_envelope]
}
result
end
#
end
# FIX / Review : this appears to set file and event max descriptors to NIL on Winderz. Do I need this?
# ::GxG::Engine::get_engine_data(::GxG::Engine::detail_mapping())
#
end
#