magent 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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