about summary refs log tree commit homepage
path: root/lib/rainbows/revactor.rb
blob: fae5f16aaad7e6a3909b86b34a75c1bf2e865100 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# -*- encoding: binary -*-
require 'revactor'
Revactor::VERSION >= '0.1.5' or abort 'revactor 0.1.5 is required'

# Enables use of the Actor model through {Revactor}[http://revactor.org]
# under Ruby 1.9.
#
# \Revactor dormant upstream, so the use of this is NOT recommended for
# new applications.
#
# It spawns one long-lived Actor for every listen socket in the process
# and spawns a new Actor for every client connection accept()-ed.
# +worker_connections+ will limit the number of client Actors we have
# running at any one time.
#
# Applications using this model are required to be reentrant, but do
# not have to worry about race conditions unless they use threads
# internally.  \Rainbows! does not spawn threads under this model.
# Multiple instances of the same app may run in the same address space
# sequentially (but at interleaved points).  Any network dependencies
# in the application using this model should be implemented using the
# \Revactor library as well, to take advantage of the networking
# concurrency features this model provides.
#
# === RubyGem Requirements
# * revactor 0.1.5 or later
module Rainbows::Revactor
  autoload :Client, 'rainbows/revactor/client'
  autoload :Proxy, 'rainbows/revactor/proxy'

  include Rainbows::Base

  # runs inside each forked worker, this sits around and waits
  # for connections and doesn't die until the parent dies (or is
  # given a INT, QUIT, or TERM signal)
  def worker_loop(worker) #:nodoc:
    Client.setup
    init_worker_process(worker)
    nr = 0
    limit = worker_connections
    actor_exit = Case[:exit, Actor, Object]

    revactorize_listeners.each do |l,close,accept|
      Actor.spawn do
        Actor.current.trap_exit = true
        l.controller = l.instance_variable_set(:@receiver, Actor.current)
        begin
          while nr >= limit
            l.disable if l.enabled?
            logger.info "busy: clients=#{nr} >= limit=#{limit}"
            Actor.receive do |f|
              f.when(close) {}
              f.when(actor_exit) { nr -= 1 }
              f.after(0.01) {} # another listener could've gotten an exit
            end
          end

          l.enable unless l.enabled?
          Actor.receive do |f|
            f.when(close) {}
            f.when(actor_exit) { nr -= 1 }
            f.when(accept) do |_, _, s|
              nr += 1
              Actor.spawn_link(s) { |c| Client.new(c).process_loop }
            end
          end
        rescue => e
          Rainbows::Error.listen_loop(e)
        end while Rainbows.alive
        Actor.receive do |f|
          f.when(close) {}
          f.when(actor_exit) { nr -= 1 }
        end while nr > 0
      end
    end

    Actor.sleep 1 while Rainbows.tick || nr > 0
    rescue Errno::EMFILE
      # ignore, let another worker process take it
  end

  def revactorize_listeners #:nodoc:
    LISTENERS.map do |s|
      case s
      when TCPServer
        l = Revactor::TCP.listen(s, nil)
        [ l, T[:tcp_closed, Revactor::TCP::Socket],
          T[:tcp_connection, l, Revactor::TCP::Socket] ]
      when UNIXServer
        l = Revactor::UNIX.listen(s)
        [ l, T[:unix_closed, Revactor::UNIX::Socket ],
          T[:unix_connection, l, Revactor::UNIX::Socket] ]
      end
    end
  end
  # :startdoc:
end