magent 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,7 +28,7 @@ module Magent
28
28
 
29
29
  def channel
30
30
  @channel ||= begin
31
- Channel.new(self.channel_name)
31
+ ActorChannel.new(self.channel_name)
32
32
  end
33
33
  end
34
34
 
@@ -75,4 +75,8 @@ module Magent
75
75
  def self.current_actor
76
76
  @current_actor
77
77
  end
78
+
79
+ def self.current_channel
80
+ self.current_actor.channel
81
+ end
78
82
  end
@@ -0,0 +1,34 @@
1
+ module Magent
2
+ class ActorChannel < GenericChannel
3
+ def push(message, args)
4
+ enqueue([message, args])
5
+ end
6
+
7
+ def process!(message)
8
+ @actor = Magent.current_actor
9
+
10
+ @method, @payload = message
11
+ return false if @method.nil?
12
+
13
+ sucess = @actor._run_tasks
14
+
15
+ $stderr.puts "#{@actor.class}##{@method}(#{@payload.inspect})"
16
+ if @actor.class.can_handle?(@method)
17
+ @actor.send(@method, @payload)
18
+ return true
19
+ else
20
+ $stderr.puts "Unknown action: #{@method} (payload=#{@payload.inspect})"
21
+ end
22
+
23
+ @method, @payload = nil
24
+
25
+ sucess
26
+ end
27
+
28
+ def on_shutdown
29
+ if @method
30
+ self.enqueue(@method, @payload)
31
+ end
32
+ end
33
+ end # Channel
34
+ end
@@ -0,0 +1,65 @@
1
+ # Example
2
+ # class Processor
3
+ # include Magent::Async
4
+ # [..snip..]
5
+ #
6
+ # def process
7
+ # puts "Processing #{@params}"
8
+ # end
9
+ # end
10
+ #
11
+ # async call:
12
+ # Processor.find(id).async(:my_queue).process.commit!(priority)
13
+ # chained methods:
14
+ # Processor.async(:my_queue, true).find(1).process.commit!(priority)
15
+ #
16
+
17
+ module Magent
18
+ module Async
19
+ def self.included(base)
20
+ base.class_eval do
21
+ include Methods
22
+ extend Methods
23
+ end
24
+ end
25
+
26
+ class Proxy
27
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
28
+
29
+ def initialize(queue, target, test = false)
30
+ @queue = queue
31
+ @method_chain = []
32
+ @target = target
33
+ @test = test
34
+
35
+ @channel = Magent::AsyncChannel.new(@queue)
36
+ end
37
+
38
+ def commit!(priority = 3)
39
+ @channel.push(@target, @method_chain, priority)
40
+
41
+ if @test
42
+ target = @target
43
+ @method_chain.each do |c|
44
+ target = target.send(c[0], *c[1])
45
+ end
46
+
47
+ target
48
+ end
49
+ end
50
+
51
+ def method_missing(m, *args, &blk)
52
+ raise ArgumentError, "ruby blocks are not supported yet" if !blk.nil?
53
+ @method_chain << [m, args]
54
+ self
55
+ end
56
+ end
57
+
58
+ module Methods
59
+ # @question.async(:judge).on_view_question.commit!(1)
60
+ def async(queue = :default, test = false)
61
+ Magent::Async::Proxy.new(queue, self, test)
62
+ end
63
+ end
64
+ end # Async
65
+ end
@@ -0,0 +1,38 @@
1
+ module Magent
2
+ class AsyncChannel < GenericChannel
3
+ def push(target, method_chain, priority)
4
+ if target.kind_of?(Class)
5
+ enqueue([target.to_s, nil, method_chain], priority)
6
+ elsif target.class.respond_to?(:find) && target.respond_to?(:id)
7
+ enqueue([target.class.to_s, target.id, method_chain], priority)
8
+ else
9
+ raise ArgumentError, "I don't know how to handle #{target.inspect}"
10
+ end
11
+ end
12
+
13
+ def process!(message)
14
+ klass, id, method_chain = message
15
+
16
+ return false if method_chain.nil?
17
+
18
+ target = resolve_target(klass, id)
19
+
20
+ method_chain.each do |c|
21
+ target = target.send(c[0], *c[1])
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ private
28
+ def resolve_target(klass, id)
29
+ klass = Object.module_eval(klass) if klass.kind_of?(String)
30
+
31
+ if id
32
+ klass.find(id)
33
+ else
34
+ klass
35
+ end
36
+ end
37
+ end # AsyncChannel
38
+ end
@@ -1,11 +1,7 @@
1
1
  module Magent
2
- class Channel < GenericChannel
3
- def enqueue(message, args)
4
- super([message, args])
5
- end
6
-
2
+ module Failure
7
3
  def failed(info)
8
- error_collection.save(info.merge({:_id => generate_uid, :channel => @name, :created_at => Time.now.utc}))
4
+ error_collection.save(info.merge({:_id => generate_uid, :channel => @name, :channel_class => self.class.to_s, :created_at => Time.now.utc}))
9
5
  end
10
6
 
11
7
  def error_count
@@ -26,12 +22,12 @@ module Magent
26
22
  end
27
23
 
28
24
  def retry_error(error)
25
+ process!(error["message"])
29
26
  remove_error(error["_id"])
30
- enqueue(error["method"], error["payload"])
31
27
  end
32
28
 
33
29
  def error_collection
34
- @error_collection ||= Magent.database.collection("#{@name}-errors")
30
+ @error_collection ||= Magent.database.collection("#{@name}.errors")
35
31
  end
36
- end # Channel
32
+ end
37
33
  end
@@ -1,13 +1,15 @@
1
1
  module Magent
2
2
  class GenericChannel
3
+ include Magent::Failure
4
+
3
5
  attr_reader :name
4
6
 
5
7
  def initialize(name)
6
- @name = name
8
+ @name = "magent.#{name}"
7
9
  end
8
10
 
9
- def enqueue(message)
10
- collection.save({:_id => generate_uid, :message => message, :priority => 3, :created_at => Time.now.to_i})
11
+ def enqueue(message, priority = 3)
12
+ collection.save({:_id => generate_uid, :message => message, :priority => priority, :created_at => Time.now.to_i})
11
13
  end
12
14
 
13
15
  def message_count
@@ -25,12 +27,8 @@ module Magent
25
27
  end
26
28
 
27
29
  def next_message
28
- rec = Magent.database.command(BSON::OrderedHash[:findandmodify, @name,
29
- :sort, [{:priority => -1}, {:created_at => 1}],
30
- :remove, true
31
- ]) rescue {}
32
-
33
- rec["value"]
30
+ collection.find_and_modify(:sort => [[:priority, Mongo::ASCENDING], [:created_at, Mongo::DESCENDING]],
31
+ :remove => true) rescue {}
34
32
  end
35
33
 
36
34
  def collection
@@ -1,16 +1,14 @@
1
1
  module Magent
2
2
  class Processor
3
- attr_reader :actor
4
-
5
- def initialize(actor)
6
- @actor = actor
3
+ def initialize(channel)
4
+ @channel = channel
7
5
  @shutdown = false
8
6
 
9
- @actor.class.actions.each do |action|
10
- if !@actor.respond_to?(action)
11
- raise ArgumentError, "action '#{action}' is not defined"
12
- end
13
- end
7
+ # @actor.class.actions.each do |action|
8
+ # if !@actor.respond_to?(action)
9
+ # raise ArgumentError, "action '#{action}' is not defined"
10
+ # end
11
+ # end
14
12
  end
15
13
 
16
14
  def run!
@@ -23,46 +21,34 @@ module Magent
23
21
  loop do
24
22
  break if @shutdown
25
23
 
26
- delay = 0 if @actor._run_tasks
27
-
28
- @method, @payload = @actor.class.channel.dequeue
29
-
30
- if @method.nil?
31
- delay += 0.1 if delay <= 5
32
- else
33
- delay = 0
34
- $stderr.puts "#{@actor.class}##{@method}(#{@payload.inspect})"
35
- begin
36
- if @actor.class.can_handle?(@method)
37
- processed_messages += 1
38
- @actor.send(@method, @payload)
39
-
40
- if processed_messages > 20
41
- processed_messages = 0
42
- GC.start
43
- end
44
- else
45
- $stderr.puts "Unknown action: #{@method} (payload=#{@payload.inspect})"
24
+ message = @channel.dequeue
25
+ begin
26
+ if message && @channel.process!(message)
27
+ puts "Processed #{message.inspect}"
28
+ delay = 0
29
+ processed_messages += 1
30
+ if processed_messages > 20
31
+ processed_messages = 0
32
+ GC.start
46
33
  end
47
- rescue SystemExit
48
- rescue Exception => e
49
- $stderr.puts "Error while executing #{@method.inspect} #{@payload.inspect}"
50
- $stderr.puts "#{e.to_s}\n#{e.backtrace.join("\t\n")}"
51
- @actor.class.channel.failed(:message => e.message, :method => @method, :payload => @payload, :backtrace => e.backtrace, :date => Time.now.utc)
52
- ensure
53
- @method, @payload = nil
34
+ else
35
+ delay += 0.1 if delay <= 5
54
36
  end
37
+ rescue SystemExit
38
+ rescue Exception => e
39
+ $stderr.puts "Error processing #{message.inspect} => #{e.message}"
40
+ @channel.failed(:error => e.message, :message => message, :backtrace => e.backtrace, :date => Time.now.utc)
41
+ ensure
55
42
  end
43
+
56
44
  sleep delay
57
45
  end
58
46
  end
59
47
 
60
48
  def shutdown!
61
49
  @shutdown = true
50
+ @channel.on_shutdown if @channel.respond_to?(:on_shutdown)
62
51
  $stderr.puts "Shutting down..."
63
- if @method
64
- @actor.class.channel.enqueue(@method, @payload)
65
- end
66
52
  end
67
53
  end #Processor
68
54
  end
@@ -1,10 +1,10 @@
1
1
  module Magent
2
2
  def self.push(channel_name, method, *args)
3
- self.channel(channel_name.to_s).enqueue(method, args)
3
+ self.channel(channel_name.to_s).push(method, args)
4
4
  end
5
5
 
6
6
  def self.channel(name)
7
- self.channels[name] ||= Channel.new(name)
7
+ self.channels[name] ||= ActorChannel.new(name)
8
8
  end
9
9
 
10
10
  def self.channels
@@ -0,0 +1,10 @@
1
+ require 'magent'
2
+ require 'rails'
3
+
4
+ module MyPlugin
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load "tasks/magent.rake"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Magent
2
+ class WebSocketChannel < Magent::GenericChannel
3
+ def self.push(message)
4
+
5
+ self.instance.enqueue(message)
6
+
7
+ self.instance
8
+ end
9
+
10
+ def self.next_message
11
+ self.instance.next_message
12
+ end
13
+
14
+ def self.instance
15
+ @channel ||= self.new(Magent.config["websocket_channel"]||"magent.websocket")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,95 @@
1
+ require 'magent/web_socket_channel'
2
+ module Magent
3
+ class WebSocketServer
4
+ def initialize(options = {})
5
+ options = {:host => "0.0.0.0", :port => 34567}.merge(options)
6
+
7
+ $stdout.puts ">> Server running and up! #{options}}" if options[:debug]
8
+
9
+ EventMachine.run do
10
+ @channel = EM::Channel.new
11
+ @channels = {}
12
+ @channel_ids = {}
13
+ @sids = {}
14
+
15
+ EventMachine.add_periodic_timer(options.delete(:interval)||10) do
16
+ while v = Magent::WebSocketChannel.next_message
17
+ message = v["message"]
18
+ if message && (channel = @channels[message["channel_id"]])
19
+ channel.push(message.to_json)
20
+ end
21
+ end
22
+ end
23
+
24
+ EventMachine::WebSocket.start(options) do |ws|
25
+ ws.onopen do
26
+ ws.onmessage do |msg|
27
+ data = JSON.parse(msg) rescue {}
28
+
29
+ case data["id"]
30
+ when 'start'
31
+ if data["channel_id"].present? && (channel_id = validate_channel_id(data["channel_id"]))
32
+ key = generate_unique_key(data["key"])
33
+ @channels[channel_id] ||= EM::Channel.new
34
+ @channel_ids[key] = channel_id
35
+
36
+ sid = @channels[channel_id].subscribe { |msg| ws.send(msg) }
37
+
38
+ @sids[key] = sid
39
+ ws.onclose do
40
+ @channel_ids.delete(key)
41
+ @channels[channel_id].unsubscribe(sid)
42
+ @sids.delete(key)
43
+ end
44
+
45
+ ws.send({:id => "ack", :key => key}.to_json)
46
+ else
47
+ ws.close_connection
48
+ end
49
+ when 'chatmessage'
50
+ key = data["key"]
51
+ return invalid_key if key.blank? || @sids[key].blank?
52
+
53
+ channel_id = @channel_ids[key]
54
+
55
+ if channel_id
56
+ @channels[channel_id].push({:id => 'chatmessage', :from => user_name(key, @sids[key]), :message => data["message"]}.to_json)
57
+ else
58
+ ws.send({:id => 'announcement', :type => "error", :message => "cannot find the channel"}.to_json)
59
+ end
60
+ else
61
+ handle_message(ws, data)
62
+ end
63
+ end
64
+ end
65
+ end # EM::WebSocket
66
+ end
67
+ end
68
+
69
+ def self.start(options = {})
70
+ self.new(options)
71
+ end
72
+
73
+ protected
74
+ def invalid_key(ws)
75
+ ws.send({:id => 'announcement', :type => "error", :message => "you must provide your unique key"}.to_json)
76
+ end
77
+
78
+ def handle_message(ws, data)
79
+ ws.send({:id => 'announcement', :type => "error", :message => "unhandled message: #{data.inspect}"}.to_json)
80
+ end
81
+
82
+ protected
83
+ def generate_unique_key(default = nil)
84
+ default || UUIDTools::UUID.random_create.hexdigest
85
+ end
86
+
87
+ def validate_channel_id(id)
88
+ return id if !id.blank?
89
+ end
90
+
91
+ def user_name(socket_key, sid)
92
+ "Guest #{sid}"
93
+ end
94
+ end # Class
95
+ end