ruote-beanstalk 2.1.10
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/CHANGELOG.txt +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +90 -0
- data/TODO.txt +20 -0
- data/doc/storages.graffle/QuickLook/Preview.pdf +0 -0
- data/doc/storages.graffle/QuickLook/Thumbnail.tiff +0 -0
- data/doc/storages.graffle/data.plist +809 -0
- data/doc/storages.graffle/image1.png +0 -0
- data/doc/storages.graffle/image2.png +0 -0
- data/doc/storages.png +0 -0
- data/lib/ruote-beanstalk.rb +3 -0
- data/lib/ruote/beanstalk.rb +8 -0
- data/lib/ruote/beanstalk/fork.rb +56 -0
- data/lib/ruote/beanstalk/participant.rb +147 -0
- data/lib/ruote/beanstalk/receiver.rb +149 -0
- data/lib/ruote/beanstalk/storage.rb +308 -0
- data/lib/ruote/beanstalk/version.rb +7 -0
- data/ruote-beanstalk.gemspec +85 -0
- data/serve.rb +11 -0
- data/test/functional/base.rb +17 -0
- data/test/functional/ft_0_participant.rb +102 -0
- data/test/functional/ft_1_receiver.rb +112 -0
- data/test/functional/test.rb +9 -0
- data/test/functional_connection.rb +19 -0
- data/test/test.rb +9 -0
- metadata +165 -0
Binary file
|
Binary file
|
data/doc/storages.png
ADDED
Binary file
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module Ruote
|
27
|
+
module Beanstalk
|
28
|
+
|
29
|
+
OPT_KEYS = { :address => 'l', :port => 'p', :binlog => 'b', :user => 'u' }
|
30
|
+
|
31
|
+
# :address
|
32
|
+
# :port
|
33
|
+
# :binlog
|
34
|
+
# :user
|
35
|
+
#
|
36
|
+
def self.fork (opts={})
|
37
|
+
|
38
|
+
quiet = opts.delete(:quiet)
|
39
|
+
no_kill = opts.delete(:no_kill_at_exit)
|
40
|
+
|
41
|
+
opts = opts.inject([]) { |a, (k, v)| a << "-#{OPT_KEYS[k]} #{v}" }.join(' ')
|
42
|
+
|
43
|
+
cpid = Process.fork do
|
44
|
+
puts "beanstalkd #{opts}" unless quiet
|
45
|
+
exec "beanstalkd #{opts}"
|
46
|
+
end
|
47
|
+
|
48
|
+
unless no_kill
|
49
|
+
at_exit { Process.kill(9, cpid) }
|
50
|
+
end
|
51
|
+
|
52
|
+
cpid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,147 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'beanstalk-client'
|
26
|
+
|
27
|
+
#require 'ruote/part/local_participant'
|
28
|
+
|
29
|
+
|
30
|
+
module Ruote
|
31
|
+
module Beanstalk
|
32
|
+
|
33
|
+
#
|
34
|
+
# This participant emits workitems towards a beanstalk queue.
|
35
|
+
#
|
36
|
+
# engine.register_participant(
|
37
|
+
# :heavy_labour,
|
38
|
+
# :reply_by_default => true, :beanstalk => '127.0.0.1:11300')
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# == workitem format
|
42
|
+
#
|
43
|
+
# Workitems are encoded in the format
|
44
|
+
#
|
45
|
+
# [ 'workitem', workitem.to_h ]
|
46
|
+
#
|
47
|
+
# and then serialized as JSON strings.
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# == cancel items
|
51
|
+
#
|
52
|
+
# Like workitems, but the format is
|
53
|
+
#
|
54
|
+
# [ 'cancelitem', fei.to_h, flavour.to_s ]
|
55
|
+
#
|
56
|
+
# where fei is the FlowExpressionId of the expression getting cancelled
|
57
|
+
# (and whose workitems are to be retired) and flavour is either 'cancel' or
|
58
|
+
# 'kill'.
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# == extending this participant
|
62
|
+
#
|
63
|
+
# Extend and overwrite encode_workitem and encode_cancelitem or
|
64
|
+
# simply re-open the class and change those methods.
|
65
|
+
#
|
66
|
+
#
|
67
|
+
# == :beanstalk
|
68
|
+
#
|
69
|
+
# Indicates which beanstalk to talk to
|
70
|
+
#
|
71
|
+
# engine.register_participant(
|
72
|
+
# 'alice'
|
73
|
+
# Ruote::Beanstalk::BsParticipant,
|
74
|
+
# 'beanstalk' => '127.0.0.1:11300')
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# == :tube
|
78
|
+
#
|
79
|
+
# Most of the time, you want the workitems (or the cancelitems) to be
|
80
|
+
# emitted over/in a specific tube
|
81
|
+
#
|
82
|
+
# engine.register_participant(
|
83
|
+
# 'alice'
|
84
|
+
# Ruote::Beanstalk::BsParticipant,
|
85
|
+
# 'beanstalk' => '127.0.0.1:11300',
|
86
|
+
# 'tube' => 'ruote-workitems')
|
87
|
+
#
|
88
|
+
#
|
89
|
+
# == :reply_by_default
|
90
|
+
#
|
91
|
+
# If the participant is configured with 'reply_by_default' => true, the
|
92
|
+
# participant will dispatch the workitem over to Beanstalk and then
|
93
|
+
# immediately reply to its ruote engine (letting the flow resume).
|
94
|
+
#
|
95
|
+
# engine.register_participant(
|
96
|
+
# 'alice'
|
97
|
+
# Ruote::Beanstalk::BsParticipant,
|
98
|
+
# 'beanstalk' => '127.0.0.1:11300',
|
99
|
+
# 'reply_by_default' => true)
|
100
|
+
#
|
101
|
+
class BsParticipant
|
102
|
+
|
103
|
+
include Ruote::LocalParticipant
|
104
|
+
|
105
|
+
def initialize (opts)
|
106
|
+
|
107
|
+
@opts = opts
|
108
|
+
end
|
109
|
+
|
110
|
+
def consume (workitem)
|
111
|
+
|
112
|
+
connection.put(encode_workitem(workitem))
|
113
|
+
|
114
|
+
reply(workitem) if @opts['reply_by_default']
|
115
|
+
end
|
116
|
+
|
117
|
+
def cancel (fei, flavour)
|
118
|
+
|
119
|
+
connection.put(encode_cancelitem(fei, flavour))
|
120
|
+
end
|
121
|
+
|
122
|
+
def encode_workitem (workitem)
|
123
|
+
|
124
|
+
Rufus::Json.encode([ 'workitem', workitem.to_h ])
|
125
|
+
end
|
126
|
+
|
127
|
+
def encode_cancelitem (fei, flavour)
|
128
|
+
|
129
|
+
Rufus::Json.encode([ 'cancelitem', fei.to_h, flavour.to_s ])
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def connection
|
135
|
+
|
136
|
+
con = ::Beanstalk::Connection.new(@opts['beanstalk'])
|
137
|
+
|
138
|
+
if tube = @opts['tube']
|
139
|
+
con.use(tube)
|
140
|
+
end
|
141
|
+
|
142
|
+
con
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'beanstalk-client'
|
26
|
+
|
27
|
+
#require 'ruote/receiver/base'
|
28
|
+
|
29
|
+
|
30
|
+
module Ruote
|
31
|
+
module Beanstalk
|
32
|
+
|
33
|
+
#
|
34
|
+
# An error class for error emitted by the "remote side" and received here.
|
35
|
+
#
|
36
|
+
class BsReceiveError < RuntimeError
|
37
|
+
|
38
|
+
attr_reader :fei
|
39
|
+
|
40
|
+
def initialize (fei)
|
41
|
+
@fei = fei
|
42
|
+
super("for #{Ruote::FlowExpressionId.to_storage_id(fei)}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Whereas BsParticipant emits workitems (and cancelitems) to a Beanstalk
|
48
|
+
# queue, the receiver watches a Beanstalk queue/tube.
|
49
|
+
#
|
50
|
+
# An example initialization :
|
51
|
+
#
|
52
|
+
# Ruote::Beanstalk::BsReceiver.new(
|
53
|
+
# engine, '127.0.0.1:11300', :tube => 'out')
|
54
|
+
#
|
55
|
+
#
|
56
|
+
# == workitem format
|
57
|
+
#
|
58
|
+
# BsParticipant and BsReceiver share the same format :3
|
59
|
+
#
|
60
|
+
# [ 'workitem', workitem_as_a_hash ]
|
61
|
+
# # or
|
62
|
+
# [ 'error', error_details_as_a_string ]
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# == extending this receiver
|
66
|
+
#
|
67
|
+
# Feel free to extend this class and override the listen or the process
|
68
|
+
# method.
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# == :tube
|
72
|
+
#
|
73
|
+
# Indicates to the receiver which beanstalk tube it should listen to.
|
74
|
+
#
|
75
|
+
# Ruote::Beanstalk::BsReceiver.new(
|
76
|
+
# engine, '127.0.0.1:11300', :tube => 'out')
|
77
|
+
#
|
78
|
+
class BsReceiver < Ruote::Receiver
|
79
|
+
|
80
|
+
# cwes = context, worker, engine or storage
|
81
|
+
#
|
82
|
+
def initialize (cwes, beanstalk, options={})
|
83
|
+
|
84
|
+
super(cwes, options)
|
85
|
+
|
86
|
+
Thread.new do
|
87
|
+
listen(beanstalk, options['tube'] || options[:tube] || 'default')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def listen (beanstalk, tube)
|
94
|
+
|
95
|
+
con = ::Beanstalk::Connection.new(beanstalk)
|
96
|
+
con.watch(tube)
|
97
|
+
con.ignore('default') unless tube == 'default'
|
98
|
+
|
99
|
+
loop do
|
100
|
+
|
101
|
+
job = con.reserve
|
102
|
+
job.delete
|
103
|
+
process(job)
|
104
|
+
end
|
105
|
+
|
106
|
+
rescue EOFError => ee
|
107
|
+
# over
|
108
|
+
end
|
109
|
+
|
110
|
+
# Is meant to return a hash with a first element that is either
|
111
|
+
# 'workitem', 'error' or 'launchitem' (a type).
|
112
|
+
# The second element depends on the type.
|
113
|
+
# It's mappend on Ruote::Beanstalk::BsParticipant anyway.
|
114
|
+
#
|
115
|
+
def decode (job)
|
116
|
+
|
117
|
+
Rufus::Json.decode(job.body)
|
118
|
+
end
|
119
|
+
|
120
|
+
def process (job)
|
121
|
+
|
122
|
+
type, data = decode(job)
|
123
|
+
|
124
|
+
if type == 'workitem'
|
125
|
+
|
126
|
+
# data holds a workitem (as a Hash)
|
127
|
+
|
128
|
+
reply(data)
|
129
|
+
|
130
|
+
elsif type == 'error'
|
131
|
+
|
132
|
+
# data holds a fei (FlowExpressionId) (as a Hash)
|
133
|
+
|
134
|
+
@context.error_handler.action_handle(
|
135
|
+
'dispatch', data, BsReceiveError.new(data))
|
136
|
+
|
137
|
+
elsif type == 'launchitem'
|
138
|
+
|
139
|
+
pdef, fields, variables = data
|
140
|
+
|
141
|
+
launch(pdef, fields, variables)
|
142
|
+
|
143
|
+
#else simply drop
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
@@ -0,0 +1,308 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'fileutils'
|
26
|
+
require 'beanstalk-client'
|
27
|
+
#require 'ruote/storage/base'
|
28
|
+
|
29
|
+
|
30
|
+
module Ruote
|
31
|
+
module Beanstalk
|
32
|
+
|
33
|
+
#
|
34
|
+
# An error class just for BsStorage.
|
35
|
+
#
|
36
|
+
class BsStorageError < RuntimeError
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# This ruote storage can be used in two modes : client and server.
|
41
|
+
#
|
42
|
+
# Beanstalk is the medium.
|
43
|
+
#
|
44
|
+
# == client
|
45
|
+
#
|
46
|
+
# The storage is pointed at a beanstalk queue
|
47
|
+
#
|
48
|
+
# engine = Ruote::Engine.new(
|
49
|
+
# Ruote::Worker.new(
|
50
|
+
# Ruote::Beanstalk::BsStorage.new('127.0.0.1:11300', opts)))
|
51
|
+
#
|
52
|
+
# All the operations (put, get, get_many, ...) of the storage are done
|
53
|
+
# by a server, connected to the same beanstalk queue.
|
54
|
+
#
|
55
|
+
# == server
|
56
|
+
#
|
57
|
+
# The storage point to a beanstalk queue and receives orders from clients
|
58
|
+
# via the queue.
|
59
|
+
#
|
60
|
+
# Ruote::Beanstalk::BsStorage.new(':11300', 'ruote_work', :fork => true)
|
61
|
+
#
|
62
|
+
# Note the directory passed as a string. When in server mode, this storage
|
63
|
+
# uses an embedded Ruote::FsStorage for the actual storage.
|
64
|
+
#
|
65
|
+
# The :fork => true lets the storage start and adjacent OS process containing
|
66
|
+
# the Beanstalk server. The storage takes care of stopping the beanstalk
|
67
|
+
# server when the Ruby process exits.
|
68
|
+
#
|
69
|
+
class BsStorage
|
70
|
+
|
71
|
+
include Ruote::StorageBase
|
72
|
+
|
73
|
+
def initialize (uri, directory=nil, options=nil)
|
74
|
+
|
75
|
+
@uri, address, port = split_uri(uri)
|
76
|
+
|
77
|
+
directory, @options = if directory.nil?
|
78
|
+
[ nil, {} ]
|
79
|
+
elsif directory.is_a?(Hash)
|
80
|
+
[ nil, directory ]
|
81
|
+
else
|
82
|
+
[ directory, options || {} ]
|
83
|
+
end
|
84
|
+
|
85
|
+
@cloche = nil
|
86
|
+
|
87
|
+
if directory
|
88
|
+
#
|
89
|
+
# run embedded Ruote::FsStorage
|
90
|
+
|
91
|
+
require 'rufus/cloche'
|
92
|
+
|
93
|
+
FileUtils.mkdir_p(directory)
|
94
|
+
|
95
|
+
@cloche = Rufus::Cloche.new(
|
96
|
+
:dir => directory, :nolock => @options['cloche_nolock'])
|
97
|
+
end
|
98
|
+
|
99
|
+
if fork_opts = @options[:fork]
|
100
|
+
#
|
101
|
+
# run beanstalk in a forked process
|
102
|
+
|
103
|
+
fork_opts = fork_opts.is_a?(Hash) ? fork_opts : {}
|
104
|
+
fork_opts = { :address => address, :port => port }.merge(fork_opts)
|
105
|
+
|
106
|
+
Ruote::Beanstalk.fork(fork_opts)
|
107
|
+
|
108
|
+
sleep 0.1
|
109
|
+
end
|
110
|
+
|
111
|
+
put_configuration
|
112
|
+
|
113
|
+
serve if @cloche
|
114
|
+
end
|
115
|
+
|
116
|
+
def put (doc, opts={})
|
117
|
+
|
118
|
+
doc.merge!('put_at' => Ruote.now_to_utc_s)
|
119
|
+
|
120
|
+
return @cloche.put(doc, opts) if @cloche
|
121
|
+
|
122
|
+
r = operate('put', [ doc ])
|
123
|
+
|
124
|
+
return r unless r.nil?
|
125
|
+
|
126
|
+
doc['_rev'] = (doc['_rev'] || -1) + 1 if opts[:update_rev]
|
127
|
+
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def get (type, key)
|
132
|
+
|
133
|
+
return @cloche.get(type, key) if @cloche
|
134
|
+
|
135
|
+
operate('get', [ type, key ])
|
136
|
+
end
|
137
|
+
|
138
|
+
def delete (doc)
|
139
|
+
|
140
|
+
return @cloche.delete(doc) if @cloche
|
141
|
+
|
142
|
+
operate('delete', [ doc ])
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_many (type, key=nil, opts={})
|
146
|
+
|
147
|
+
return @cloche.get_many(type, key, opts) if @cloche
|
148
|
+
|
149
|
+
operate('get_many', [ type, key, opts ])
|
150
|
+
end
|
151
|
+
|
152
|
+
def ids (type)
|
153
|
+
|
154
|
+
return @cloche.ids(type) if @cloche
|
155
|
+
|
156
|
+
operate('ids', [ type ])
|
157
|
+
end
|
158
|
+
|
159
|
+
def purge!
|
160
|
+
|
161
|
+
if @cloche
|
162
|
+
FileUtils.rm_rf(@cloche.dir)
|
163
|
+
else
|
164
|
+
operate('purge!', [])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def dump (type)
|
169
|
+
|
170
|
+
get_many(type)
|
171
|
+
end
|
172
|
+
|
173
|
+
def shutdown
|
174
|
+
|
175
|
+
Thread.list.each do |t|
|
176
|
+
t.keys.each do |k|
|
177
|
+
next unless k.match(/^BeanstalkConnection\_/)
|
178
|
+
t[k].close
|
179
|
+
t[k] = nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Mainly used by ruote's test/unit/ut_17_storage.rb
|
185
|
+
#
|
186
|
+
def add_type (type)
|
187
|
+
|
188
|
+
# nothing to do
|
189
|
+
end
|
190
|
+
|
191
|
+
# Nukes a db type and reputs it (losing all the documents that were in it).
|
192
|
+
#
|
193
|
+
def purge_type! (type)
|
194
|
+
|
195
|
+
if @cloche
|
196
|
+
@cloche.purge_type!(type)
|
197
|
+
else
|
198
|
+
operate('purge_type!', [ type ])
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
CONN_KEY = '__ruote_beanstalk_connection'
|
205
|
+
TUBE_NAME = 'ruote-storage-commands'
|
206
|
+
|
207
|
+
def split_uri (uri)
|
208
|
+
|
209
|
+
uri = ':' if uri == ''
|
210
|
+
|
211
|
+
address, port = uri.split(':')
|
212
|
+
address = '127.0.0.1' if address.strip == ''
|
213
|
+
port = 11300 if port.strip == ''
|
214
|
+
|
215
|
+
[ "#{address}:#{port}", address, port ]
|
216
|
+
end
|
217
|
+
|
218
|
+
def connection
|
219
|
+
|
220
|
+
c = Thread.current[CONN_KEY]
|
221
|
+
return c if c
|
222
|
+
|
223
|
+
c = ::Beanstalk::Connection.new(@uri, TUBE_NAME)
|
224
|
+
c.ignore('default')
|
225
|
+
|
226
|
+
Thread.current[CONN_KEY] = c
|
227
|
+
end
|
228
|
+
|
229
|
+
# Don't put configuration if it's already in
|
230
|
+
#
|
231
|
+
# (avoid storages from trashing configuration...)
|
232
|
+
#
|
233
|
+
def put_configuration
|
234
|
+
|
235
|
+
return if get('configurations', 'engine')
|
236
|
+
|
237
|
+
put({ '_id' => 'engine', 'type' => 'configurations' }.merge(@options))
|
238
|
+
end
|
239
|
+
|
240
|
+
def operate (command, params)
|
241
|
+
|
242
|
+
client_id = "BsStorage-#{Thread.current.object_id}-#{$$}"
|
243
|
+
timestamp = Time.now.to_f.to_s
|
244
|
+
|
245
|
+
con = connection
|
246
|
+
|
247
|
+
con.put(Rufus::Json.encode([ command, params, client_id, timestamp ]))
|
248
|
+
|
249
|
+
con.watch(client_id)
|
250
|
+
con.ignore(TUBE_NAME)
|
251
|
+
|
252
|
+
result = nil
|
253
|
+
|
254
|
+
# NOTE : what about a timeout ?
|
255
|
+
|
256
|
+
loop do
|
257
|
+
|
258
|
+
job = con.reserve
|
259
|
+
job.delete
|
260
|
+
|
261
|
+
result, ts = Rufus::Json.decode(job.body)
|
262
|
+
|
263
|
+
break if ts == timestamp # hopefully
|
264
|
+
end
|
265
|
+
|
266
|
+
if result.is_a?(Array) && result.first == 'error'
|
267
|
+
raise ArgumentError.new(result.last) if result[1] == 'ArgumentError'
|
268
|
+
raise BsStorageError.new(result.last)
|
269
|
+
end
|
270
|
+
|
271
|
+
result
|
272
|
+
end
|
273
|
+
|
274
|
+
COMMANDS = %w[ put get get_many delete ids purge! purge_type! dump ]
|
275
|
+
|
276
|
+
def serve
|
277
|
+
|
278
|
+
con = connection
|
279
|
+
|
280
|
+
loop do
|
281
|
+
|
282
|
+
job = con.reserve
|
283
|
+
job.delete
|
284
|
+
|
285
|
+
command, params, client_id, timestamp = Rufus::Json.decode(job.body)
|
286
|
+
|
287
|
+
result = begin
|
288
|
+
|
289
|
+
if COMMANDS.include?(command)
|
290
|
+
send(command, *params)
|
291
|
+
else
|
292
|
+
[ 'error', 'UnknownCommand', command ]
|
293
|
+
end
|
294
|
+
|
295
|
+
rescue Exception => e
|
296
|
+
#p e
|
297
|
+
#e.backtrace.each { |l| puts l }
|
298
|
+
[ 'error', e.class.to_s, e.to_s ]
|
299
|
+
end
|
300
|
+
|
301
|
+
con.use(client_id)
|
302
|
+
con.put(Rufus::Json.encode([ result, timestamp ]))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|