gemerald_beanstalk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ class GemeraldBeanstalk::Connection
2
+
3
+ BEGIN_REQUEST_STATES = [:ready, :multi_part_request_in_progress]
4
+
5
+ attr_reader :beanstalk, :mutex, :tube_used, :tubes_watched
6
+ attr_writer :producer, :waiting, :worker
7
+
8
+
9
+ def alive?
10
+ return @inbound_state != :closed && @oubound_state != :closed
11
+ end
12
+
13
+
14
+ def begin_multi_part_request(multi_part_request)
15
+ return false unless outbound_ready?
16
+ @multi_part_request = multi_part_request
17
+ @outbound_state = :multi_part_request_in_progress
18
+ return true
19
+ end
20
+
21
+
22
+ def begin_request
23
+ return false unless BEGIN_REQUEST_STATES.include?(@outbound_state)
24
+ @outbound_state = :request_in_progress
25
+ return true
26
+ end
27
+
28
+
29
+ def close_connection
30
+ @inbound_state = @outbound_state = :closed
31
+ @connection.close_connection unless @connection.nil?
32
+ end
33
+
34
+
35
+ def complete_request
36
+ return false unless request_in_progress?
37
+ @outbound_state = :ready
38
+ return true
39
+ end
40
+
41
+
42
+ def execute(raw_command)
43
+ command = response = nil
44
+ @mutex.synchronize do
45
+ return if waiting? || request_in_progress?
46
+ puts "#{Time.now.to_f}: #{raw_command}" if ENV['VERBOSE']
47
+ if multi_part_request_in_progress?
48
+ (command = @multi_part_request).body = raw_command
49
+ else
50
+ command = GemeraldBeanstalk::Command.new(raw_command, self)
51
+ if !command.valid?
52
+ response = command.error
53
+ elsif command.multi_part_request?
54
+ return begin_multi_part_request(command)
55
+ end
56
+ end
57
+ begin_request
58
+ end
59
+ puts "#{Time.now.to_f}: #{command.inspect}" if ENV['VERBOSE']
60
+ # Execute command unless parsing already yielded a response
61
+ response ||= beanstalk.execute(command)
62
+ transmit(response) unless response.nil?
63
+ end
64
+
65
+
66
+ def ignore(tube, force = false)
67
+ return nil unless @tubes_watched.length > 1 || force
68
+ @tubes_watched.delete(tube)
69
+ return @tubes_watched.length
70
+ end
71
+
72
+
73
+ def inbound_ready?
74
+ return @inbound_state == :ready
75
+ end
76
+
77
+
78
+ def initialize(beanstalk, connection = nil)
79
+ @beanstalk = beanstalk
80
+ @connection = connection
81
+ @inbound_state = :ready
82
+ @mutex = Mutex.new
83
+ @outbound_state = :ready
84
+ @tube_used = 'default'
85
+ @tubes_watched = Set.new(%w[default])
86
+ end
87
+
88
+
89
+ def multi_part_request_in_progress?
90
+ return @outbound_state == :multi_part_request_in_progress
91
+ end
92
+
93
+
94
+ def outbound_ready?
95
+ return @outbound_state == :ready
96
+ end
97
+
98
+
99
+ def producer?
100
+ return !!@producer
101
+ end
102
+
103
+
104
+ def request_in_progress?
105
+ return @outbound_state == :request_in_progress
106
+ end
107
+
108
+
109
+ def response_received
110
+ return false unless waiting? || timed_out?
111
+ @inbound_state = :ready
112
+ return true
113
+ end
114
+
115
+
116
+ def timed_out?
117
+ return @inbound_state == :timed_out
118
+ end
119
+
120
+
121
+ def transmit(message)
122
+ return if !alive? || @connection.nil?
123
+ puts "#{Time.now.to_f}: #{message}" if ENV['VERBOSE']
124
+ @connection.send_data(message)
125
+ complete_request
126
+ response_received
127
+ return self
128
+ end
129
+
130
+
131
+ def use(tube_name)
132
+ @tube_used = tube_name
133
+ end
134
+
135
+
136
+ def wait(timeout = nil)
137
+ return false unless inbound_ready?
138
+ @wait_timeout = timeout
139
+ @inbound_state = :waiting
140
+ return true
141
+ end
142
+
143
+
144
+ def wait_timed_out
145
+ return false unless @inbound_state == :waiting
146
+ @wait_timeout = nil
147
+ @inbound_state = :timed_out
148
+ return true
149
+ end
150
+
151
+
152
+ def waiting?
153
+ return false unless @inbound_state == :waiting
154
+ return true if @wait_timeout.nil? || @wait_timeout > Time.now.to_f
155
+ wait_timed_out
156
+ return false
157
+ end
158
+
159
+
160
+ def watch(tube)
161
+ @tubes_watched << tube
162
+ return @tubes_watched.length
163
+ end
164
+
165
+
166
+ def worker?
167
+ return !!@worker
168
+ end
169
+
170
+ end
@@ -0,0 +1,229 @@
1
+ class GemeraldBeanstalk::Job
2
+
3
+ MAX_JOB_PRIORITY = 2**32
4
+
5
+ INACTIVE_STATES = [:buried, :delayed]
6
+ RESERVED_STATES = [:deadline_pending, :reserved]
7
+ UPDATE_STATES = [:deadline_pending, :delayed, :reserved]
8
+
9
+ attr_reader :beanstalk, :reserved_at, :reserved_by, :timeout_at
10
+ attr_accessor :priority, :tube_name, :delay, :ready_at, :body,
11
+ :bytes, :created_at, :ttr, :id, :buried_at
12
+
13
+
14
+ def <(other_job)
15
+ return (self <=> other_job) == -1
16
+ end
17
+
18
+
19
+ def <=>(other_job)
20
+ raise 'Cannot compare job with nil' if other_job.nil?
21
+ current_state = state
22
+ raise 'Cannot compare jobs with different states' if current_state != other_job.state
23
+
24
+ case current_state
25
+ when :ready
26
+ return -1 if self.priority < other_job.priority ||
27
+ self.priority == other_job.priority && self.created_at < other_job.created_at
28
+ when :delayed
29
+ return -1 if self.ready_at < other_job.ready_at
30
+ when :buried
31
+ return -1 if self.buried_at < other_job.buried_at
32
+ else
33
+ raise "Cannot compare job with state of #{current_state}"
34
+ end
35
+ return 1
36
+ end
37
+
38
+
39
+ def buried?
40
+ return state == :buried
41
+ end
42
+
43
+
44
+ def bury(connection, priority, *args)
45
+ return false unless reserved_by_connection?(connection)
46
+
47
+ reset_reserve_state
48
+ @state = :buried
49
+ @stats_hash[:'buries'] += 1
50
+ self.priority = priority.to_i
51
+ self.buried_at = Time.now.to_f
52
+ self.ready_at = nil
53
+ return true
54
+ end
55
+
56
+
57
+ # Must look at @state to avoid infinite recursion
58
+ def deadline_approaching(*args)
59
+ return false unless @state == :reserved
60
+ @state = :deadline_pending
61
+ return true
62
+ end
63
+
64
+
65
+ def deadline_pending?
66
+ return state == :deadline_pending
67
+ end
68
+
69
+
70
+ def delayed?
71
+ return state == :delayed
72
+ end
73
+
74
+
75
+ def delete(connection, *args)
76
+ return false if RESERVED_STATES.include?(state) && !reserved_by_connection?(connection)
77
+ @state = :deleted
78
+ return true
79
+ end
80
+
81
+
82
+ def initialize(beanstalk, id, tube_name, priority, delay, ttr, bytes, body)
83
+ priority, delay, ttr = priority.to_i, delay.to_i, ttr.to_i
84
+ @beanstalk = beanstalk
85
+ @stats_hash = Hash.new(0)
86
+ self.id = id
87
+ self.tube_name = tube_name
88
+ self.priority = priority % MAX_JOB_PRIORITY
89
+ self.delay = delay
90
+ self.ttr = ttr == 0 ? 1 : ttr
91
+ self.bytes = bytes
92
+ self.body = body
93
+ self.created_at = Time.now.to_f
94
+ self.ready_at = self.created_at + delay
95
+
96
+ @state = delay > 0 ? :delayed : :ready
97
+ end
98
+
99
+
100
+ def kick(*args)
101
+ return false unless INACTIVE_STATES.include?(state)
102
+
103
+ @state = :ready
104
+ @stats_hash[:'kicks'] += 1
105
+ self.ready_at = Time.now.to_f
106
+ self.buried_at = nil
107
+ return true
108
+ end
109
+
110
+
111
+ def ready?
112
+ return state == :ready
113
+ end
114
+
115
+
116
+ def release(connection, priority, delay, increment_stats = true, *args)
117
+ return false unless reserved_by_connection?(connection)
118
+
119
+ delay = delay.to_i
120
+ reset_reserve_state
121
+ @state = delay > 0 ? :delayed : :ready
122
+ @stats_hash[:'releases'] += 1 if increment_stats
123
+ self.priority = priority.to_i
124
+ self.delay = delay
125
+ self.ready_at = Time.now.to_f + delay
126
+ return true
127
+ end
128
+
129
+
130
+ def reserve(connection, *args)
131
+ return false unless ready?
132
+
133
+ @state = :reserved
134
+ @stats_hash[:'reserves'] += 1
135
+ @reserved_by = connection
136
+ @reserved_at = Time.now.to_f
137
+ @timeout_at = @reserved_at + self.ttr
138
+ return true
139
+ end
140
+
141
+
142
+ def reserved_by_connection?(connection)
143
+ return RESERVED_STATES.include?(state) && self.reserved_by == connection ? true : false
144
+ end
145
+
146
+
147
+ def reset_reserve_state
148
+ @timeout_at = nil
149
+ @reserved_at = nil
150
+ @reserved_by = nil
151
+ end
152
+
153
+
154
+ def state
155
+ return @state unless UPDATE_STATES.include?(@state)
156
+
157
+ now = Time.now.to_f
158
+ if @state == :delayed && self.ready_at <= now
159
+ @state = :ready
160
+ elsif RESERVED_STATES.include?(@state)
161
+ # Rescue from timeout being reset by other thread
162
+ if (now > self.timeout_at rescue false)
163
+ timed_out
164
+ elsif (@state == :reserved && now + 1 > self.timeout_at rescue false)
165
+ deadline_approaching
166
+ end
167
+ end
168
+
169
+ return @state
170
+ end
171
+
172
+
173
+ def stats
174
+ now = Time.now.to_f
175
+ current_state = state
176
+ return {
177
+ 'id' => self.id,
178
+ 'tube' => self.tube_name,
179
+ 'state' => current_state == :deadline_pending ? 'reserved' : current_state.to_s,
180
+ 'pri' => self.priority,
181
+ 'age' => (now - self.created_at).to_i,
182
+ 'delay' => self.delay.to_i,
183
+ 'ttr' => self.ttr,
184
+ 'time-left' => time_left(now),
185
+ 'file' => 0,
186
+ 'reserves' => @stats_hash[:'reserves'],
187
+ 'timeouts' => @stats_hash[:'timeouts'],
188
+ 'releases' => @stats_hash[:'releases'],
189
+ 'buries' => @stats_hash[:'buries'],
190
+ 'kicks' => @stats_hash[:'kicks'],
191
+ }
192
+ end
193
+
194
+
195
+ def time_left(current_time = Time.now.to_f)
196
+ if self.timeout_at
197
+ time_left = self.timeout_at - current_time
198
+ elsif self.ready_at
199
+ time_left = self.ready_at - current_time
200
+ end
201
+ return time_left.to_i
202
+ end
203
+
204
+
205
+ # Must reference @state to avoid infinite recursion
206
+ def timed_out(*args)
207
+ return false unless RESERVED_STATES.include?(@state)
208
+ @state = :ready
209
+ @stats_hash[:'timeouts'] += 1
210
+ connection = self.reserved_by
211
+ reset_reserve_state
212
+ self.beanstalk.register_job_timeout(connection, self)
213
+ return true
214
+ end
215
+
216
+
217
+ def timed_out?
218
+ return state == :timed_out
219
+ end
220
+
221
+
222
+ def touch(connection)
223
+ return false unless reserved_by_connection?(connection)
224
+ @state = :reserved
225
+ @timeout_at = Time.now.to_f + self.ttr
226
+ return true
227
+ end
228
+
229
+ end
@@ -0,0 +1,39 @@
1
+ class GemeraldBeanstalk::Jobs < ThreadSafe::Array
2
+ attr_reader :total_jobs
3
+
4
+ def counts_by_state
5
+ job_stats = {
6
+ 'current-jobs-urgent' => 0,
7
+ 'current-jobs-ready' => 0,
8
+ 'current-jobs-reserved' => 0,
9
+ 'current-jobs-delayed' => 0,
10
+ 'current-jobs-buried' => 0,
11
+ }
12
+ self.compact.each do |job|
13
+ state = job.state
14
+
15
+ job_stats["current-jobs-#{state}"] += 1
16
+ job_stats['current-jobs-urgent'] += 1 if state == :ready && job.priority < 1024
17
+ end
18
+ return job_stats
19
+ end
20
+
21
+
22
+ def enqueue(job)
23
+ @total_jobs += 1
24
+ push(job)
25
+ return self
26
+ end
27
+
28
+
29
+ def initialize(*)
30
+ @total_jobs = 0
31
+ super
32
+ end
33
+
34
+
35
+ def next_id
36
+ return @total_jobs + 1
37
+ end
38
+
39
+ end
@@ -0,0 +1,54 @@
1
+ require 'eventmachine'
2
+
3
+ module GemeraldBeanstalk::Server
4
+
5
+ def self.start(bind_address = nil, port = nil)
6
+ bind_address ||= '0.0.0.0'
7
+ port ||= 11300
8
+ full_address = "#{bind_address}:#{port}"
9
+ beanstalk = GemeraldBeanstalk::Beanstalk.new(full_address)
10
+ thread = Thread.new do
11
+ EventMachine.run do
12
+ EventMachine.start_server(bind_address, port, GemeraldBeanstalk::Server, beanstalk)
13
+ EventMachine.add_periodic_timer(0.01, beanstalk.method(:update_state))
14
+ end
15
+ end
16
+ $PROGRAM_NAME = "gemerald_beanstalk:#{full_address}"
17
+ return [thread, beanstalk]
18
+ end
19
+
20
+
21
+ def beanstalk
22
+ return @beanstalk
23
+ end
24
+
25
+
26
+ def initialize(beanstalk)
27
+ @beanstalk = beanstalk
28
+ @partial_message = ''
29
+ super
30
+ end
31
+
32
+
33
+ def post_init
34
+ @connection = beanstalk.connect(self)
35
+ end
36
+
37
+
38
+ def receive_data(data)
39
+ if data[-2, 2] == "\r\n"
40
+ message = @partial_message + data
41
+ @partial_message = ''
42
+ EventMachine.defer(proc { @connection.execute(message) })
43
+ else
44
+ @partial_message += data
45
+ end
46
+ end
47
+
48
+
49
+ def unbind
50
+ beanstalk.disconnect(@connection)
51
+ @connection.close_connection
52
+ end
53
+
54
+ end