kulesa-celluloid 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,151 @@
1
+ module Celluloid
2
+ # Simple finite state machines with integrated Celluloid timeout support
3
+ # Inspired by Erlang's gen_fsm (http://www.erlang.org/doc/man/gen_fsm.html)
4
+ #
5
+ # Basic usage:
6
+ #
7
+ # class MyMachine
8
+ # include Celluloid::FSM # NOTE: this does NOT pull in the Celluloid module
9
+ # end
10
+ #
11
+ # Inside an actor:
12
+ #
13
+ # #
14
+ # machine = MyMachine.new(current_actor)
15
+ module FSM
16
+ class UnattachedError < StandardError; end # Not attached to an actor
17
+
18
+ DEFAULT_STATE = :default # Default state name unless one is explicitly set
19
+
20
+ # Included hook to extend class methods
21
+ def self.included(klass)
22
+ klass.send :extend, ClassMethods
23
+ end
24
+
25
+ module ClassMethods
26
+ # Obtain or set the default state
27
+ # Passing a state name sets the default state
28
+ def default_state(new_default = nil)
29
+ if new_default
30
+ @default_state = new_default.to_sym
31
+ else
32
+ defined?(@default_state) ? @default_state : DEFAULT_STATE
33
+ end
34
+ end
35
+
36
+ # Obtain the valid states for this FSM
37
+ def states
38
+ @states ||= {}
39
+ end
40
+
41
+ # Declare an FSM state and optionally provide a callback block to fire
42
+ # Options:
43
+ # * to: a state or array of states this state can transition to
44
+ def state(*args, &block)
45
+ if args.last.is_a? Hash
46
+ # Stringify keys :/
47
+ options = args.pop.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
48
+ else
49
+ options = {}
50
+ end
51
+
52
+ args.each do |name|
53
+ name = name.to_sym
54
+ default_state name if options['default']
55
+ states[name] = State.new(name, options['to'], &block)
56
+ end
57
+ end
58
+ end
59
+
60
+ attr_reader :actor
61
+
62
+ # Be kind and call super if you must redefine initialize
63
+ def initialize(actor = nil)
64
+ @state = self.class.default_state
65
+ @actor = actor
66
+ @actor ||= Celluloid.current_actor if Celluloid.actor?
67
+ end
68
+
69
+ # Obtain the current state of the FSM
70
+ attr_reader :state
71
+
72
+ # Attach this FSM to an actor. This allows FSMs to wait for and initiate
73
+ # events in the context of a particular actor
74
+ def attach(actor)
75
+ @actor = actor
76
+ end
77
+ alias_method :actor=, :attach
78
+
79
+ # Transition to another state
80
+ # Options:
81
+ # * delay: don't transition immediately, wait the given number of seconds.
82
+ # This will return a Celluloid::Timer object you can use to
83
+ # cancel the pending state transition.
84
+ #
85
+ # Note: making additional state transitions will cancel delayed transitions
86
+ def transition(state_name, options = {})
87
+ state_name = state_name.to_sym
88
+ current_state = self.class.states[@state]
89
+
90
+ return if current_state && current_state.name == state_name
91
+
92
+ if current_state and not current_state.valid_transition? state_name
93
+ valid = current_state.transitions.map(&:to_s).join(", ")
94
+ raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
95
+ end
96
+
97
+ new_state = self.class.states[state_name]
98
+
99
+ unless new_state
100
+ return if state_name == self.class.default_state
101
+ raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
102
+ end
103
+
104
+ if options[:delay]
105
+ raise UnattachedError, "can't delay unless attached" unless @actor
106
+ @delayed_transition.cancel if @delayed_transition
107
+
108
+ @delayed_transition = @actor.after(options[:delay]) do
109
+ transition! new_state.name
110
+ new_state.call(self)
111
+ end
112
+
113
+ return @delayed_transition
114
+ end
115
+
116
+ if defined?(@delayed_transition) and @delayed_transition
117
+ @delayed_transition.cancel
118
+ @delayed_transition = nil
119
+ end
120
+
121
+ transition! new_state.name
122
+ new_state.call(self)
123
+ end
124
+
125
+ # Immediate state transition with no sanity checks. "Dangerous!"
126
+ def transition!(state_name)
127
+ @state = state_name
128
+ end
129
+
130
+ # FSM states as declared by Celluloid::FSM.state
131
+ class State
132
+ attr_reader :name, :transitions
133
+
134
+ def initialize(name, transitions = nil, &block)
135
+ @name, @block = name, block
136
+ @transitions = Array(transitions).map { |t| t.to_sym } if transitions
137
+ end
138
+
139
+ def call(obj)
140
+ obj.instance_eval(&@block) if @block
141
+ end
142
+
143
+ def valid_transition?(new_state)
144
+ # All transitions are allowed unless expressly
145
+ return true unless @transitions
146
+
147
+ @transitions.include? new_state.to_sym
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,110 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # Celluloid::Future objects allow methods and blocks to run in the
5
+ # background, their values requested later
6
+ class Future
7
+ # Create a future bound to a given receiver, or with a block to compute
8
+ def initialize(*args, &block)
9
+ @mutex = Mutex.new
10
+ @ready = false
11
+ @result = nil
12
+ @forwards = nil
13
+
14
+ if block
15
+ @call = SyncCall.new(self, :call, args)
16
+ InternalPool.get do
17
+ begin
18
+ @call.dispatch(block)
19
+ rescue
20
+ # Exceptions in blocks will get raised when the value is retrieved
21
+ end
22
+ end
23
+ else
24
+ @call = nil
25
+ end
26
+ end
27
+
28
+ # Execute the given method in future context
29
+ def execute(receiver, method, args, block)
30
+ @mutex.synchronize do
31
+ raise "already calling" if @call
32
+ @call = SyncCall.new(self, method, args, block)
33
+ end
34
+
35
+ receiver << @call
36
+ end
37
+
38
+ # Obtain the value for this Future
39
+ def value(timeout = nil)
40
+ ready = result = nil
41
+
42
+ begin
43
+ @mutex.lock
44
+ raise "no call requested" unless @call
45
+
46
+ if @ready
47
+ ready = true
48
+ result = @result
49
+ else
50
+ case @forwards
51
+ when Array
52
+ @forwards << Thread.mailbox
53
+ when NilClass
54
+ @forwards = Thread.mailbox
55
+ else
56
+ @forwards = [@forwards, Thread.mailbox]
57
+ end
58
+ end
59
+ ensure
60
+ @mutex.unlock
61
+ end
62
+
63
+ unless ready
64
+ result = Thread.receive(timeout) do |msg|
65
+ msg.is_a?(Future::Result) && msg.future == self
66
+ end
67
+ end
68
+
69
+ if result
70
+ result.value
71
+ else
72
+ raise "Timed out"
73
+ end
74
+ end
75
+ alias_method :call, :value
76
+
77
+ # Signal this future with the given result value
78
+ def signal(value)
79
+ result = Result.new(value, self)
80
+
81
+ @mutex.synchronize do
82
+ raise "the future has already happened!" if @ready
83
+
84
+ if @forwards
85
+ @forwards.is_a?(Array) ? @forwards.each { |f| f << result } : @forwards << result
86
+ end
87
+
88
+ @result = result
89
+ @ready = true
90
+ end
91
+ end
92
+ alias_method :<<, :signal
93
+
94
+ # Inspect this Celluloid::Future
95
+ alias_method :inspect, :to_s
96
+
97
+ # Wrapper for result values to distinguish them in mailboxes
98
+ class Result
99
+ attr_reader :future
100
+
101
+ def initialize(result, future)
102
+ @result, @future = result, future
103
+ end
104
+
105
+ def value
106
+ @result.value
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,90 @@
1
+ module Celluloid
2
+ # Supervise collections of actors as a group
3
+ class Group
4
+ include Celluloid
5
+ trap_exit :restart_actor
6
+
7
+ class << self
8
+ # Actors or sub-applications to be supervised
9
+ def members
10
+ @members ||= []
11
+ end
12
+
13
+ # Start this application (and watch it with a supervisor)
14
+ alias_method :run!, :supervise
15
+
16
+ # Run the application in the foreground with a simple watchdog
17
+ def run
18
+ loop do
19
+ supervisor = run!
20
+
21
+ # Take five, toplevel supervisor
22
+ sleep 5 while supervisor.alive?
23
+
24
+ Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
25
+ end
26
+ end
27
+
28
+ # Register an actor class or a sub-application class to be launched and
29
+ # supervised while this application is running. Available options are:
30
+ #
31
+ # * as: register this application in the Celluloid::Actor[] directory
32
+ # * args: start the actor with the given arguments
33
+ def supervise(klass, options = {})
34
+ members << Member.new(klass, options)
35
+ end
36
+ end
37
+
38
+ # Start the group
39
+ def initialize
40
+ @actors = {}
41
+
42
+ # This is some serious lolcode, but like... start the supervisors for
43
+ # this group
44
+ self.class.members.each do |member|
45
+ actor = member.start
46
+ @actors[actor] = member
47
+ end
48
+ end
49
+
50
+ # Terminate the group
51
+ def finalize
52
+ @actors.each do |actor, _|
53
+ begin
54
+ actor.terminate
55
+ rescue DeadActorError
56
+ end
57
+ end
58
+ end
59
+
60
+ # Restart a crashed actor
61
+ def restart_actor(actor, reason)
62
+ member = @actors.delete actor
63
+ raise "a group member went missing. This shouldn't be!" unless member
64
+
65
+ # Ignore supervisors that shut down cleanly
66
+ return unless reason
67
+
68
+ actor = member.start
69
+ @actors[actor] = member
70
+ end
71
+
72
+ # A member of the group
73
+ class Member
74
+ def initialize(klass, options = {})
75
+ @klass = klass
76
+
77
+ # Stringify keys :/
78
+ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
79
+
80
+ @name = options['as']
81
+ @args = options['args'] ? Array(options['args']) : []
82
+ end
83
+
84
+ def start
85
+ actor = @klass.new_link(*@args)
86
+ Actor[@name] = actor if @name
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,62 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # Maintain a thread pool FOR SPEED!!
5
+ module InternalPool
6
+ @pool = []
7
+ @mutex = Mutex.new
8
+
9
+ # TODO: should really adjust this based on usage
10
+ @max_idle = 16
11
+
12
+ class << self
13
+ attr_accessor :max_idle
14
+
15
+ # Get a thread from the pool, running the given block
16
+ def get(&block)
17
+ @mutex.synchronize do
18
+ begin
19
+ if @pool.empty?
20
+ thread = create
21
+ else
22
+ thread = @pool.shift
23
+ end
24
+ end until thread.status # handle crashed threads
25
+
26
+ thread[:queue] << block
27
+ thread
28
+ end
29
+ end
30
+
31
+ # Return a thread to the pool
32
+ def put(thread)
33
+ @mutex.synchronize do
34
+ if @pool.size >= @max_idle
35
+ thread[:queue] << nil
36
+ else
37
+ @pool << thread
38
+ end
39
+ end
40
+ end
41
+
42
+ # Create a new thread with an associated queue of procs to run
43
+ def create
44
+ queue = Queue.new
45
+ thread = Thread.new do
46
+ while proc = queue.pop
47
+ begin
48
+ proc.call
49
+ rescue => ex
50
+ Logger.crash("thread crashed", ex)
51
+ end
52
+
53
+ put thread
54
+ end
55
+ end
56
+
57
+ thread[:queue] = queue
58
+ thread
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # Thread safe storage of inter-actor links
5
+ class Links
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @links = {}
10
+ @lock = Mutex.new
11
+ end
12
+
13
+ # Add an actor to the current links
14
+ def <<(actor)
15
+ @lock.synchronize do
16
+ @links[actor.mailbox.address] = actor
17
+ end
18
+ actor
19
+ end
20
+
21
+ # Do links include the given actor?
22
+ def include?(actor)
23
+ @lock.synchronize do
24
+ @links.has_key? actor.mailbox.address
25
+ end
26
+ end
27
+
28
+ # Remove an actor from the links
29
+ def delete(actor)
30
+ @lock.synchronize do
31
+ @links.delete actor.mailbox.address
32
+ end
33
+ actor
34
+ end
35
+
36
+ # Iterate through all links
37
+ def each
38
+ @lock.synchronize do
39
+ @links.each { |_, actor| yield(actor) }
40
+ end
41
+ end
42
+
43
+ # Map across links
44
+ def map
45
+ result = []
46
+ each { |actor| result << yield(actor) }
47
+ result
48
+ end
49
+
50
+ # Send an event message to all actors
51
+ def send_event(event)
52
+ each { |actor| actor.mailbox.system_event event }
53
+ end
54
+
55
+ # Generate a string representation
56
+ def inspect
57
+ links = self.map(&:inspect).join(',')
58
+ "#<#{self.class}[#{links}]>"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,53 @@
1
+ module Celluloid
2
+ module Logger
3
+ @exception_handlers = []
4
+ module_function
5
+
6
+ # Send a debug message
7
+ def debug(string)
8
+ Celluloid.logger.debug(string) if Celluloid.logger
9
+ end
10
+
11
+ # Send a info message
12
+ def info(string)
13
+ Celluloid.logger.info(string) if Celluloid.logger
14
+ end
15
+
16
+ # Send a warning message
17
+ def warn(string)
18
+ Celluloid.logger.warn(string) if Celluloid.logger
19
+ end
20
+
21
+ # Send an error message
22
+ def error(string)
23
+ Celluloid.logger.error(string) if Celluloid.logger
24
+ end
25
+
26
+ # Handle a crash
27
+ def crash(string, exception)
28
+ string << "\n" << format_exception(exception)
29
+ error string
30
+
31
+ @exception_handlers.each do |handler|
32
+ begin
33
+ handler.call(exception)
34
+ rescue => ex
35
+ error "EXCEPTION HANDLER CRASHED:\n" << format_exception(ex)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Format an exception message
41
+ def format_exception(exception)
42
+ str = "#{exception.class}: #{exception.to_s}\n"
43
+ str << exception.backtrace.join("\n")
44
+ end
45
+
46
+ # Define an exception handler
47
+ # NOTE: These should be defined at application start time
48
+ def exception_handler(&block)
49
+ @exception_handlers << block
50
+ nil
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,134 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ class MailboxError < StandardError; end # you can't message the dead
5
+ class MailboxShutdown < StandardError; end # raised if the mailbox can no longer be used
6
+
7
+ # Actors communicate with asynchronous messages. Messages are buffered in
8
+ # Mailboxes until Actors can act upon them.
9
+ class Mailbox
10
+ include Enumerable
11
+
12
+ # A unique address at which this mailbox can be found
13
+ alias_method :address, :object_id
14
+
15
+ def initialize
16
+ @messages = []
17
+ @mutex = Mutex.new
18
+ @dead = false
19
+ @condition = ConditionVariable.new
20
+ end
21
+
22
+ # Add a message to the Mailbox
23
+ def <<(message)
24
+ @mutex.lock
25
+ begin
26
+ raise MailboxError, "dead recipient" if @dead
27
+
28
+ @messages << message
29
+ @condition.signal
30
+ nil
31
+ ensure
32
+ @mutex.unlock rescue nil
33
+ end
34
+ end
35
+
36
+ # Add a high-priority system event to the Mailbox
37
+ def system_event(event)
38
+ @mutex.lock
39
+ begin
40
+ unless @dead # Silently fail if messages are sent to dead actors
41
+ @messages.unshift event
42
+ @condition.signal
43
+ end
44
+ nil
45
+ ensure
46
+ @mutex.unlock rescue nil
47
+ end
48
+ end
49
+
50
+ # Receive a message from the Mailbox
51
+ def receive(timeout = nil, &block)
52
+ message = nil
53
+
54
+ @mutex.lock
55
+ begin
56
+ raise MailboxError, "attempted to receive from a dead mailbox" if @dead
57
+
58
+ begin
59
+ message = next_message(&block)
60
+
61
+ unless message
62
+ if timeout
63
+ now = Time.now
64
+ wait_until ||= now + timeout
65
+ wait_interval = wait_until - now
66
+ return if wait_interval <= 0
67
+ else
68
+ wait_interval = nil
69
+ end
70
+
71
+ @condition.wait(@mutex, wait_interval)
72
+ end
73
+ end until message
74
+
75
+ message
76
+ ensure
77
+ @mutex.unlock rescue nil
78
+ end
79
+ end
80
+
81
+ # Retrieve the next message in the mailbox
82
+ def next_message
83
+ message = nil
84
+
85
+ if block_given?
86
+ index = @messages.index do |msg|
87
+ yield(msg) || msg.is_a?(SystemEvent)
88
+ end
89
+
90
+ message = @messages.slice!(index, 1).first if index
91
+ else
92
+ message = @messages.shift
93
+ end
94
+
95
+ raise message if message.is_a? SystemEvent
96
+ message
97
+ end
98
+
99
+ # Shut down this mailbox and clean up its contents
100
+ def shutdown
101
+ @mutex.lock
102
+ begin
103
+ messages = @messages
104
+ @messages = []
105
+ @dead = true
106
+ ensure
107
+ @mutex.unlock rescue nil
108
+ end
109
+
110
+ messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
111
+ true
112
+ end
113
+
114
+ # Is the mailbox alive?
115
+ def alive?
116
+ !@dead
117
+ end
118
+
119
+ # Cast to an array
120
+ def to_a
121
+ @mutex.synchronize { @messages.dup }
122
+ end
123
+
124
+ # Iterate through the mailbox
125
+ def each(&block)
126
+ to_a.each(&block)
127
+ end
128
+
129
+ # Inspect the contents of the Mailbox
130
+ def inspect
131
+ "#<#{self.class}:#{object_id.to_s(16)} @messages=[#{map { |m| m.inspect }.join(', ')}]>"
132
+ end
133
+ end
134
+ end