class SpiderGazelle::Spider

Attributes

logger[R]
threads[R]

Public Class Methods

new() click to toggle source
# File lib/spider-gazelle/spider.rb, line 28
def initialize
    @logger = Logger.instance
    @signaller = Signaller.instance
    @thread = @signaller.thread

    # Gazelle pipe connection management
    @gazelles = {
        # thread: [],
        # inline: gazelle_instance
    }
    @counts = {
        # process: number
        # thread: number
    }
    @loading = {}     # mode => load defer
    @bindings = {}    # port => binding
    @iterators = {}   # mode => gazelle round robin iterator
    @iterator_source = {}   # mode => gazelle thread array (iterator source)

    @running = true
    @loaded = false
    @bound = false
    @delay_port_binding = false

    @load_complete = @thread.defer
end

Public Instance Methods

bind_application_ports() click to toggle source
# File lib/spider-gazelle/spider.rb, line 115
def bind_application_ports
    if @delay_port_binding && !@loaded
        loaded.finally { bind_application_ports }
        return
    end
    @bound = true

    @options.each do |options|
        @logger.verbose { "Loading rackup #{options}" }
        iterator = @iterators[options[:mode]]

        binding = @bindings[options[:port]] = Binding.new(iterator, options)
        binding.bind
    end
end
delay_port_binding() click to toggle source
# File lib/spider-gazelle/spider.rb, line 131
def delay_port_binding
    @delay_port_binding = true
end
in_mode?(mode) click to toggle source

Applications can query the availability of various modes to share resources

# File lib/spider-gazelle/spider.rb, line 21
def in_mode?(mode)
    !!@loading[mode.to_sym]
end
load() click to toggle source

Load a second application (requires a new port binding)

# File lib/spider-gazelle/spider.rb, line 98
def load

end
loaded() click to toggle source

This allows applications to recieve a callback once the server has completed loading the applications. Port binding is in progress

# File lib/spider-gazelle/spider.rb, line 16
def loaded
    @load_complete.promise
end
ready() click to toggle source

Load gazelles and make the required bindings

# File lib/spider-gazelle/spider.rb, line 66
def ready
    load_promise = load_applications
    load_promise.then do
        # Check a shutdown request didn't occur as we were loading
        if @running
            @logger.verbose "All gazelles running"

            # This happends on the master thread so we don't need to check
            # for the shutdown events here
            @loaded = true
            bind_application_ports unless @delay_port_binding
        else
            @logger.warn "A shutdown event occured while loading"
            perform_shutdown
        end
    end

    # Provide applications with a load complete callback
    @load_complete.resolve(load_promise)
end
run!(options) click to toggle source
# File lib/spider-gazelle/spider.rb, line 55
def run!(options)
    @options = options
    @logger.verbose { "Spider Pid: #{Process.pid} started" }
    if options[0][:isolate]
        ready
    else
        @signaller.authenticate(options[0][:spider])
    end
end
shutdown(finished) click to toggle source

Shutdown after current requests have completed

# File lib/spider-gazelle/spider.rb, line 103
def shutdown(finished)
    @shutdown_defer = finished

    @logger.verbose { "Spider Pid: #{Process.pid} shutting down (loaded #{@loaded})" }

    if @loaded
        perform_shutdown
    else
        @running = false
    end
end
update() click to toggle source

Pass existing bindings to the master process

# File lib/spider-gazelle/spider.rb, line 93
def update

end
wait() click to toggle source

Load gazelles and wait for the bindings to be sent

# File lib/spider-gazelle/spider.rb, line 88
def wait

end

Protected Instance Methods

load_applications() click to toggle source
# File lib/spider-gazelle/spider.rb, line 139
def load_applications
    loaded = []
    @logger.info "Environment: #{ENV['RACK_ENV']} on #{RUBY_ENGINE || 'ruby'} #{RUBY_VERSION}"

    # Load the different types of gazelles required
    @options.each do |app|
        @logger.info "Loading: #{app[:rackup]}" if app[:rackup]
        AppStore.load(app[:rackup], app)

        mode = app[:mode]
        loaded << load_gazelles(mode, app[:count], @options) unless @loading[mode]
    end

    # Return a promise that resolves when all the gazelles are loaded
    # Gazelles will only load the applications that apply to them based on the application type
    @thread.all(*loaded)
end
load_gazelle_thread(reactor, thread, mode, options, loading) click to toggle source
# File lib/spider-gazelle/spider.rb, line 204
def load_gazelle_thread(reactor, thread, mode, options, loading)
    # Log any unhandled errors
    thread.notifier { |*args| reactor.log(*args) }
    # Give current requests 5 seconds to complete
    thread.on_program_interrupt do
        timer = thread.timer {
            puts "Forcing gazelle exit"
            thread.stop
        }
        timer.unref
        timer.start(5000)
    end
    thread.run do |thread|
        # Start the gazelle
        gaz = ::SpiderGazelle::Gazelle.new(thread, :thread)
        thread.next_tick do
            loading.resolve(gaz)
        end
        gaz.run!(options)
    end
end
load_gazelles(mode, count, options) click to toggle source
# File lib/spider-gazelle/spider.rb, line 158
def load_gazelles(mode, count, options)
    defer = @thread.defer
    @loading[mode] = defer

    if mode == :inline
        # Start the gazelle
        require 'spider-gazelle/gazelle'
        gaz = ::SpiderGazelle::Gazelle.new(@thread, mode).run!(options)
        @gazelles[:inline] = gaz

        # Setup the round robin
        itr = [gaz]
        @iterator_source[mode] = [gaz]
        @iterators[mode] = [gaz.thread].cycle

        defer.resolve(true)
    else
        require 'thread'

        count = @counts[mode] = count || ::Libuv.cpu_count || 1
        @logger.info "#{mode.to_s.capitalize} count: #{count}"

        require 'spider-gazelle/gazelle'
        reactor = Reactor.instance

        @threads = []
        loaded = []
        count.times do
            loading = @thread.defer
            loaded << loading.promise

            thread = ::Libuv::Reactor.new
            @threads << thread

            Thread.new { load_gazelle_thread(reactor, thread, mode, options, loading) }
        end

        defer.resolve(@thread.all(*loaded).then { |gazelles|
            @iterator_source[mode] = gazelles
            @iterators[mode] = gazelles.map { |gaz| gaz.thread }.cycle
        })
    end

    defer.promise
end
perform_shutdown() click to toggle source

Shutdown Management


# File lib/spider-gazelle/spider.rb, line 230
def perform_shutdown
    if @bound
        # Unbind any ports we are bound to
        ports = []
        @bindings.each do |port, binding|
            ports << binding.unbind
        end

        # Shutdown once the ports are all closed
        @thread.finally(*ports).then do
            shutdown_gazelles
        end
    else
        shutdown_gazelles
    end
end
shutdown_gazelles() click to toggle source
# File lib/spider-gazelle/spider.rb, line 247
def shutdown_gazelles
    @bound = false
    @shutdown = true
    promises = []

    @iterator_source.each do |mode, gazelles|
        # End communication with the gazelle threads
        gazelles.each do |gazelle|
            defer = @thread.defer
            gazelle.shutdown(defer)
            promises << defer.promise
        end
    end

    # Finish shutdown after all signals have been sent
    @shutdown_defer.resolve(@thread.finally(*promises))
end