message 0.0.1
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.
- data/README.md +121 -0
- data/lib/message/filters.rb +66 -0
- data/lib/message/in_memory_queue.rb +39 -0
- data/lib/message/job.rb +48 -0
- data/lib/message/q.rb +26 -0
- data/lib/message/worker.rb +79 -0
- data/lib/message.rb +44 -0
- metadata +65 -0
data/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Message
|
|
2
|
+
|
|
3
|
+
Message provides flexible & reliable background/asynchronous job processing mechanism on top of simple queue interface.
|
|
4
|
+
|
|
5
|
+
Any developer can create queue adapter for Message to hook up different messaging/queue system.
|
|
6
|
+
|
|
7
|
+
One in-memory queue is included with Message for you to start development and test,
|
|
8
|
+
and you can easily swap in other queues later.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## How to use
|
|
16
|
+
|
|
17
|
+
### Queuing jobs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Inspired by delayed_job API, call .enq.method(params) on any object and it will be processed in the background.
|
|
21
|
+
|
|
22
|
+
# without message
|
|
23
|
+
@img.resize(36)
|
|
24
|
+
|
|
25
|
+
# with message
|
|
26
|
+
@img.enq.resize(36)
|
|
27
|
+
|
|
28
|
+
### Start worker to process jobs
|
|
29
|
+
|
|
30
|
+
Message.worker.start
|
|
31
|
+
|
|
32
|
+
## Job interface specification
|
|
33
|
+
|
|
34
|
+
Job = a queue + message processor
|
|
35
|
+
|
|
36
|
+
### initialize a job
|
|
37
|
+
|
|
38
|
+
job = Message.job('name') { |msg| ... process msg ... }
|
|
39
|
+
|
|
40
|
+
### enq(msg), alias: <<
|
|
41
|
+
|
|
42
|
+
Queue up a message for processing:
|
|
43
|
+
|
|
44
|
+
job << msg
|
|
45
|
+
|
|
46
|
+
### process(size=1)
|
|
47
|
+
|
|
48
|
+
Process a message in queue by processor defined when initializing the job
|
|
49
|
+
|
|
50
|
+
job.process
|
|
51
|
+
|
|
52
|
+
Process multiple messages
|
|
53
|
+
|
|
54
|
+
job.process(5)
|
|
55
|
+
|
|
56
|
+
## Job filters
|
|
57
|
+
|
|
58
|
+
You can add job filter to add additional functions to enqueue and process job message
|
|
59
|
+
|
|
60
|
+
Message.job.filter(filter_name) do |next_filter, job|
|
|
61
|
+
lambda do |*args, &block|
|
|
62
|
+
next_filter.call(*args, &block)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Filters will be applied as the order initialized.
|
|
67
|
+
Checkout all filters:
|
|
68
|
+
|
|
69
|
+
Message.job.filters
|
|
70
|
+
|
|
71
|
+
### enq filter
|
|
72
|
+
|
|
73
|
+
Message.job.filter(:enq) do |filter, job|
|
|
74
|
+
lambda do |work|
|
|
75
|
+
filter.call(work)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
### process filter
|
|
80
|
+
|
|
81
|
+
Message.job.filter(:process) do |filter, job|
|
|
82
|
+
lambda do |size, &processor|
|
|
83
|
+
filter.call(size) do |msg|
|
|
84
|
+
processor.call(msg)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
## Queue adapters
|
|
90
|
+
|
|
91
|
+
Change queue adapter to change different queue implementation. Default is a in memory queue for testing and development environments.
|
|
92
|
+
|
|
93
|
+
### Change adapter
|
|
94
|
+
|
|
95
|
+
Message.queue.adapter = :sqs
|
|
96
|
+
|
|
97
|
+
### Add a new adapter
|
|
98
|
+
|
|
99
|
+
Message.queue.adapters[:sqs] = Message::SqsQueue
|
|
100
|
+
|
|
101
|
+
## Queue interface specification
|
|
102
|
+
|
|
103
|
+
To hook up a new queue into Message.
|
|
104
|
+
|
|
105
|
+
### name
|
|
106
|
+
|
|
107
|
+
Queue name.
|
|
108
|
+
|
|
109
|
+
### enq(msg), alias: <<
|
|
110
|
+
|
|
111
|
+
Enqueue message, non-blocking.
|
|
112
|
+
|
|
113
|
+
### deq(size, &block)
|
|
114
|
+
|
|
115
|
+
Dequeue message, non-blocking.
|
|
116
|
+
It is up to Queue implementation when to delete the message in queue when deq got called with a block.
|
|
117
|
+
|
|
118
|
+
### size
|
|
119
|
+
|
|
120
|
+
(Approximate) queue size.
|
|
121
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'benchmark'
|
|
2
|
+
|
|
3
|
+
module Message
|
|
4
|
+
class Filters
|
|
5
|
+
attr_reader :config
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@data = Hash.new{|h,k|h[k]=[]}
|
|
9
|
+
@config = {
|
|
10
|
+
:error_handling_callback => lambda {|type, job, msg, e| }
|
|
11
|
+
}
|
|
12
|
+
defaults.each do |t, m|
|
|
13
|
+
self[t] << [m, method(m)]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](type)
|
|
18
|
+
@data[type]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def defaults
|
|
22
|
+
[
|
|
23
|
+
[:process, :error_handling],
|
|
24
|
+
[:enq, :error_handling],
|
|
25
|
+
[:process, :benchmarking]
|
|
26
|
+
]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def error_handling(filter, job)
|
|
30
|
+
lambda do |arg, &processor|
|
|
31
|
+
type = processor ? :process : :enq
|
|
32
|
+
log_error(type, job, (type == :enq ? arg : nil)) do
|
|
33
|
+
if processor
|
|
34
|
+
filter.call(arg) do |msg|
|
|
35
|
+
log_error(type, job, msg) { processor.call(msg) }
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
filter.call(arg)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def benchmarking(filter, job)
|
|
45
|
+
lambda do |size, &processor|
|
|
46
|
+
filter.call(size) do |msg|
|
|
47
|
+
ret = nil
|
|
48
|
+
Message.logger.info { "#{job.name}: processing one message"}
|
|
49
|
+
s = Benchmark.realtime do
|
|
50
|
+
ret = processor.call(msg)
|
|
51
|
+
end
|
|
52
|
+
Message.logger.info { "#{job.name}: processed in #{(1000 * s).to_i}ms" }
|
|
53
|
+
ret
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
def log_error(type, job, msg, &block)
|
|
60
|
+
block.call
|
|
61
|
+
rescue => e
|
|
62
|
+
Message.logger.error {"#{type} #{job.name} message failed, #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"}
|
|
63
|
+
config[:error_handling_callback].call(type, job, msg, e)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module Message
|
|
4
|
+
class InMemoryQueue
|
|
5
|
+
attr :name
|
|
6
|
+
def initialize(name)
|
|
7
|
+
@name = name
|
|
8
|
+
@queue = ::Queue.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def enq(msg)
|
|
12
|
+
@queue << msg
|
|
13
|
+
end
|
|
14
|
+
alias :<< :enq
|
|
15
|
+
|
|
16
|
+
def deq(size=1, &block)
|
|
17
|
+
if size == 1
|
|
18
|
+
__deq__(&block)
|
|
19
|
+
else
|
|
20
|
+
size.times { __deq__(&block) }
|
|
21
|
+
end
|
|
22
|
+
rescue ThreadError
|
|
23
|
+
#no message in queue
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size
|
|
27
|
+
@queue.size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
def __deq__(&block)
|
|
32
|
+
if block_given?
|
|
33
|
+
yield(@queue.deq(true))
|
|
34
|
+
else
|
|
35
|
+
@queue.deq(true)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/message/job.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'message/filters'
|
|
2
|
+
|
|
3
|
+
module Message
|
|
4
|
+
class Job
|
|
5
|
+
class << self
|
|
6
|
+
def filters
|
|
7
|
+
@filters ||= Filters.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def filter(type, name, &block)
|
|
11
|
+
filters[type] << [name, block]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reset
|
|
15
|
+
@filters = nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(queue, &processor)
|
|
20
|
+
@queue = queue
|
|
21
|
+
@processor = processor || lambda {|msg| msg}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name
|
|
25
|
+
@queue.name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def size
|
|
29
|
+
@queue.size
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def enq(msg)
|
|
33
|
+
chain(:enq, @queue.method(:enq)).call(msg)
|
|
34
|
+
end
|
|
35
|
+
alias :<< :enq
|
|
36
|
+
|
|
37
|
+
def process(size=1)
|
|
38
|
+
chain(:process, @queue.method(:deq)).call(size, &@processor)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def chain(type, base)
|
|
43
|
+
Job.filters[type].reverse.inject(base) do |m, f|
|
|
44
|
+
f[1].call(m, self)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/message/q.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'message/in_memory_queue'
|
|
2
|
+
module Message
|
|
3
|
+
module Q
|
|
4
|
+
module_function
|
|
5
|
+
def init(name)
|
|
6
|
+
adapters[adapter].new(name)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def adapters
|
|
10
|
+
@adapters ||= { :in_memory => InMemoryQueue }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def adapter
|
|
14
|
+
@adapter ||= :in_memory
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def adapter=(name)
|
|
18
|
+
@adapter = name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reset
|
|
22
|
+
@adapter = nil
|
|
23
|
+
@adapters = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Message
|
|
4
|
+
class Worker
|
|
5
|
+
DEFAULT_JOB_NAME = 'message-worker-default'
|
|
6
|
+
|
|
7
|
+
module Enqueue
|
|
8
|
+
class Enq
|
|
9
|
+
def initialize(obj, job)
|
|
10
|
+
@obj = obj
|
|
11
|
+
@job = job
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def method_missing(m, *args, &block)
|
|
15
|
+
if block_given?
|
|
16
|
+
raise ArgumentError, "Can't enqueue with block call."
|
|
17
|
+
end
|
|
18
|
+
unless @obj.respond_to?(m)
|
|
19
|
+
raise NoMethodError, "undefined method `#{m}' for #{@obj.inspect}"
|
|
20
|
+
end
|
|
21
|
+
Worker.enq(@job, [@obj, m, args])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def enq(job=DEFAULT_JOB_NAME)
|
|
26
|
+
Enq.new(self, job)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def jobs
|
|
32
|
+
@jobs ||= RUBY_PLATFORM =~ /java/ ? java.util.concurrent.ConcurrentHashMap.new : {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def job(name)
|
|
36
|
+
jobs[name] ||= Message.job(name, &job_processor)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def enq(name, work)
|
|
40
|
+
job(name).enq(YAML.dump(work))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def job_processor
|
|
44
|
+
lambda do |msg|
|
|
45
|
+
obj, m, args = YAML.load(msg)
|
|
46
|
+
obj.send(m, *args)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize(job_name)
|
|
52
|
+
@job_name = job_name || DEFAULT_JOB_NAME
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start(size=10, interval=1)
|
|
56
|
+
Thread.start do
|
|
57
|
+
begin
|
|
58
|
+
log(:info) { "start" }
|
|
59
|
+
loop do
|
|
60
|
+
process(size)
|
|
61
|
+
sleep interval
|
|
62
|
+
end
|
|
63
|
+
log(:info) { "stopped" }
|
|
64
|
+
rescue => e
|
|
65
|
+
log(:error) { "crashed: #{e.message}\n#{e.backtrace.join("\n")}"}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process(size=1)
|
|
71
|
+
Worker.job(@job_name).process(size)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
def log(level, &block)
|
|
76
|
+
Message.logger.send(level) { "[Worker(#{Thread.current.object_id})] #{block.call}" }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/message.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'message/q'
|
|
2
|
+
require 'message/job'
|
|
3
|
+
require 'message/worker'
|
|
4
|
+
require 'logger'
|
|
5
|
+
|
|
6
|
+
module Message
|
|
7
|
+
module_function
|
|
8
|
+
def queue(name=nil)
|
|
9
|
+
if name
|
|
10
|
+
Q.init(name)
|
|
11
|
+
else
|
|
12
|
+
Q
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def job(name=nil, &block)
|
|
17
|
+
if name
|
|
18
|
+
Job.new(queue(name), &block)
|
|
19
|
+
else
|
|
20
|
+
Job
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def worker(job=nil)
|
|
25
|
+
Worker.new(job)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def logger
|
|
29
|
+
@logger ||= Logger.new(STDOUT)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def logger=(logger)
|
|
33
|
+
@logger = logger
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reset
|
|
37
|
+
Message.queue.reset
|
|
38
|
+
Message.job.reset
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
reset
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Object.send(:include, Message::Worker::Enqueue)
|
metadata
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: message
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Xiao Li
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2014-09-09 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: ! 'Message provides flexible & reliable background/asynchronous job processing
|
|
15
|
+
mechanism on top of simple queue interface.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Any developer can create queue adapter for Message to hook up different messaging/queue
|
|
19
|
+
system.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
One in-memory queue is included with Message for you to start development and test,
|
|
23
|
+
|
|
24
|
+
and you can easily swap in other queues later.
|
|
25
|
+
|
|
26
|
+
'
|
|
27
|
+
email:
|
|
28
|
+
- swing1979@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- README.md
|
|
34
|
+
- lib/message/filters.rb
|
|
35
|
+
- lib/message/in_memory_queue.rb
|
|
36
|
+
- lib/message/job.rb
|
|
37
|
+
- lib/message/q.rb
|
|
38
|
+
- lib/message/worker.rb
|
|
39
|
+
- lib/message.rb
|
|
40
|
+
homepage: https://github.com/xli/message
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
none: false
|
|
49
|
+
requirements:
|
|
50
|
+
- - ! '>='
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
|
+
none: false
|
|
55
|
+
requirements:
|
|
56
|
+
- - ! '>='
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubyforge_project:
|
|
61
|
+
rubygems_version: 1.8.23
|
|
62
|
+
signing_key:
|
|
63
|
+
specification_version: 3
|
|
64
|
+
summary: Simplify Ruby messaging/queue/async/job processing.
|
|
65
|
+
test_files: []
|