hydroponic_bean 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -0
- data/lib/hydroponic_bean/commands.rb +12 -0
- data/lib/hydroponic_bean/commands/other.rb +13 -35
- data/lib/hydroponic_bean/commands/producer.rb +5 -2
- data/lib/hydroponic_bean/commands/tube.rb +1 -11
- data/lib/hydroponic_bean/commands/worker.rb +76 -0
- data/lib/hydroponic_bean/connection.rb +16 -0
- data/lib/hydroponic_bean/data.rb +99 -1
- data/lib/hydroponic_bean/job.rb +68 -8
- data/lib/hydroponic_bean/protocol.rb +3 -1
- data/lib/hydroponic_bean/tube.rb +48 -9
- data/lib/hydroponic_bean/version.rb +1 -1
- data/protocol.txt +715 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a0b2b9b746d52cb5ed0499c0ac2f8dc45213231
|
4
|
+
data.tar.gz: 4f23cda0c022146a7c5b99cb043363cc6a59ae8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e0d194be39a4af19c053f946e9eda3740e3e1dbf17791608920abbd2bf5e2eb178804436eea5ec3c7be9e2177afef5991a52e81256133b943eab42120f16aa1
|
7
|
+
data.tar.gz: de72c9b293986ced6d10fc67952e8d1b2af2ac50896bbea77079931cc8bd4f96e1e994e82842f9cf008fbe21890bc0a9b43ff2bcd1ed12dda8ec01386f240432
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.0
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -9,5 +9,17 @@ module HydroponicBean
|
|
9
9
|
include HydroponicBean::Commands::Worker
|
10
10
|
include HydroponicBean::Commands::Tube
|
11
11
|
include HydroponicBean::Commands::Other
|
12
|
+
|
13
|
+
# Find a job by id and yield it
|
14
|
+
#
|
15
|
+
# Outputs Protocol::NOT_FOUND if the block returns false
|
16
|
+
# or the job is not found
|
17
|
+
def for_job(id)
|
18
|
+
job = HydroponicBean.find_job(id)
|
19
|
+
if !job || !yield(job)
|
20
|
+
output(Protocol::NOT_FOUND)
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
12
24
|
end
|
13
25
|
end
|
@@ -2,12 +2,13 @@ module HydroponicBean
|
|
2
2
|
module Commands
|
3
3
|
module Other
|
4
4
|
def peek(stream, id = nil)
|
5
|
-
id
|
6
|
-
|
7
|
-
|
5
|
+
for_job(id) do |job|
|
6
|
+
peek_output(job)
|
7
|
+
end
|
8
8
|
end
|
9
9
|
|
10
10
|
def peek_ready(stream)
|
11
|
+
HydroponicBean.update_time!
|
11
12
|
peek_output current_tube.ready_jobs.first
|
12
13
|
end
|
13
14
|
|
@@ -16,46 +17,23 @@ module HydroponicBean
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def peek_delayed(stream)
|
20
|
+
HydroponicBean.update_time!
|
19
21
|
peek_output current_tube.delayed_jobs.first
|
20
22
|
end
|
21
23
|
|
22
24
|
def stats_job(stream, id)
|
23
|
-
id
|
24
|
-
|
25
|
-
|
26
|
-
output(
|
27
|
-
return false
|
25
|
+
for_job(id) do |job|
|
26
|
+
stats = job.serialize_stats.to_yaml
|
27
|
+
output("OK #{stats.length}\r\n")
|
28
|
+
output("#{stats}\r\n")
|
28
29
|
end
|
29
|
-
|
30
|
-
stats = job.serialize_stats.to_yaml
|
31
|
-
output("OK #{stats.length}\r\n")
|
32
|
-
output("#{stats}\r\n")
|
33
30
|
end
|
34
31
|
|
35
32
|
def kick_job(stream, id)
|
36
|
-
id
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
return false
|
41
|
-
end
|
42
|
-
job.kick
|
43
|
-
output("KICKED\r\n")
|
44
|
-
end
|
45
|
-
|
46
|
-
def watch(stream, tube_name)
|
47
|
-
watched_tube_names << tube_name
|
48
|
-
watched_tube_names.uniq!
|
49
|
-
output("WATCHING #{watched_tube_names.count}\r\n")
|
50
|
-
end
|
51
|
-
|
52
|
-
def ignore(stream, tube_name)
|
53
|
-
watched_tube_names.delete(tube_name)
|
54
|
-
if watched_tube_names.empty?
|
55
|
-
watched_tube_names << tube_name
|
56
|
-
output("NOT_IGNORED\r\n")
|
57
|
-
else
|
58
|
-
output("WATCHING #{watched_tube_names.count}\r\n")
|
33
|
+
for_job(id) do |job|
|
34
|
+
if job.kick
|
35
|
+
output("KICKED\r\n")
|
36
|
+
end
|
59
37
|
end
|
60
38
|
end
|
61
39
|
|
@@ -7,6 +7,9 @@ module HydroponicBean
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def put(stream, pri, delay, ttr, bytes)
|
10
|
+
# Mark this connection as a producer
|
11
|
+
producer!
|
12
|
+
|
10
13
|
bytes = bytes.to_i
|
11
14
|
data = stream.read(bytes)
|
12
15
|
|
@@ -20,8 +23,8 @@ module HydroponicBean
|
|
20
23
|
return false
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
output("INSERTED #{id}\r\n")
|
26
|
+
job = create_job(pri, delay, ttr, data)
|
27
|
+
output("INSERTED #{job.id}\r\n")
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -1,17 +1,6 @@
|
|
1
1
|
module HydroponicBean
|
2
2
|
module Commands
|
3
3
|
module Tube
|
4
|
-
def delete(stream, id)
|
5
|
-
job = HydroponicBean.jobs[id.to_i - 1]
|
6
|
-
if job
|
7
|
-
job.delete
|
8
|
-
output("DELETED\r\n")
|
9
|
-
else
|
10
|
-
output(Protocol::NOT_FOUND)
|
11
|
-
return false
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
4
|
def list_tubes(stream)
|
16
5
|
tubes = HydroponicBean.tubes.keys.to_yaml
|
17
6
|
output("OK #{tubes.length}\r\n")
|
@@ -48,6 +37,7 @@ module HydroponicBean
|
|
48
37
|
end
|
49
38
|
|
50
39
|
def kick(stream, bound)
|
40
|
+
HydroponicBean.update_time!
|
51
41
|
bound = bound.to_i
|
52
42
|
tube = current_tube
|
53
43
|
output("KICKED #{tube.kick(bound)}\r\n")
|
@@ -1,6 +1,82 @@
|
|
1
1
|
module HydroponicBean
|
2
2
|
module Commands
|
3
3
|
module Worker
|
4
|
+
def reserve(stream)
|
5
|
+
reserve_with_timeout(stream, -1)
|
6
|
+
end
|
7
|
+
|
8
|
+
def reserve_with_timeout(stream, seconds)
|
9
|
+
# Mark this connection as a worker
|
10
|
+
worker!
|
11
|
+
|
12
|
+
if deadline_soon?
|
13
|
+
output("DEADLINE_SOON\r\n")
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
|
17
|
+
seconds = seconds.to_i
|
18
|
+
|
19
|
+
if job = wait_for_job(seconds)
|
20
|
+
output("RESERVED #{job.id} #{job.data.length}\r\n")
|
21
|
+
output("#{job.data}\r\n")
|
22
|
+
else
|
23
|
+
output("TIMED_OUT\r\n")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def release(stream, id, pri, delay)
|
28
|
+
# We don't have a BURIED response here
|
29
|
+
for_job(id) do |job|
|
30
|
+
if job.release(self, pri, delay)
|
31
|
+
output("RELEASED\r\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def bury(stream, id, pri)
|
37
|
+
for_job(id) do |job|
|
38
|
+
if job.bury(self, pri)
|
39
|
+
output("BURIED\r\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete(stream, id)
|
45
|
+
for_job(id) do |job|
|
46
|
+
if job.delete(self)
|
47
|
+
output("DELETED\r\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def touch(stream, id)
|
53
|
+
for_job(id) do |job|
|
54
|
+
if job.touch(self)
|
55
|
+
output("TOUCHED\r\n")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def watch(stream, tube_name)
|
61
|
+
watched_tube_names << tube_name
|
62
|
+
watched_tube_names.uniq!
|
63
|
+
output_watching
|
64
|
+
end
|
65
|
+
|
66
|
+
def ignore(stream, tube_name)
|
67
|
+
watched_tube_names.delete(tube_name)
|
68
|
+
if watched_tube_names.empty?
|
69
|
+
watched_tube_names << tube_name
|
70
|
+
output("NOT_IGNORED\r\n")
|
71
|
+
else
|
72
|
+
output_watching
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def output_watching
|
78
|
+
output("WATCHING #{watched_tube_names.count}\r\n")
|
79
|
+
end
|
4
80
|
end
|
5
81
|
end
|
6
82
|
end
|
@@ -4,8 +4,23 @@ module HydroponicBean
|
|
4
4
|
class Connection
|
5
5
|
include HydroponicBean::Protocol
|
6
6
|
|
7
|
+
attr_accessor :waiting
|
8
|
+
alias_method :waiting?, :waiting
|
9
|
+
|
7
10
|
def initialize
|
8
11
|
@_read, @_write = IO.pipe
|
12
|
+
@worker, @producer = false
|
13
|
+
@waiting = false
|
14
|
+
HydroponicBean.add_connection(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def worker?; @worker; end
|
18
|
+
def worker!; @worker = true; end
|
19
|
+
def producer?; @producer; end
|
20
|
+
def producer!; @producer = true; end
|
21
|
+
|
22
|
+
def closed?
|
23
|
+
@_write.closed?
|
9
24
|
end
|
10
25
|
|
11
26
|
# Necessary interface used by beaneater
|
@@ -24,6 +39,7 @@ module HydroponicBean
|
|
24
39
|
def close
|
25
40
|
@_read.close
|
26
41
|
@_write.close
|
42
|
+
HydroponicBean.remove_connection(self)
|
27
43
|
end
|
28
44
|
|
29
45
|
protected
|
data/lib/hydroponic_bean/data.rb
CHANGED
@@ -1,15 +1,56 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
1
3
|
require 'hydroponic_bean/tube'
|
2
4
|
require 'hydroponic_bean/job'
|
3
5
|
|
4
6
|
module HydroponicBean
|
7
|
+
def self.clear
|
8
|
+
tubes.clear
|
9
|
+
jobs.clear
|
10
|
+
connections.clear
|
11
|
+
commands.clear
|
12
|
+
end
|
13
|
+
|
5
14
|
def self.tubes
|
6
15
|
@tubes ||= Hash.new{|h, k| h[k] = Tube.new(k)}
|
7
16
|
end
|
8
17
|
|
18
|
+
def self.find_job(id)
|
19
|
+
id = id.to_i
|
20
|
+
if id == 0
|
21
|
+
return nil
|
22
|
+
else
|
23
|
+
job = jobs[id - 1]
|
24
|
+
job&.update_time!
|
25
|
+
return job
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
9
29
|
def self.jobs
|
10
30
|
@jobs ||= []
|
11
31
|
end
|
12
32
|
|
33
|
+
def self.update_time!
|
34
|
+
jobs.each(&:update_time!)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.connections
|
38
|
+
@connections ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.add_connection(connection)
|
42
|
+
connections.push(connection)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.remove_connection(connection)
|
46
|
+
connections.delete(connection)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Keep track of commands for stats
|
50
|
+
def self.commands
|
51
|
+
@commands ||= Hash.new{|h, k| h[k] = 0}
|
52
|
+
end
|
53
|
+
|
13
54
|
module Data
|
14
55
|
def current_tube_name
|
15
56
|
@current_tube_name ||= 'default'
|
@@ -23,12 +64,69 @@ module HydroponicBean
|
|
23
64
|
@watched_tube_names ||= ['default']
|
24
65
|
end
|
25
66
|
|
67
|
+
def watched_tubes
|
68
|
+
watched_tube_names.map do |name|
|
69
|
+
HydroponicBean.tubes[name]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
26
73
|
def create_job(pri, delay, ttr, data)
|
27
74
|
job = Job.new(current_tube, pri, delay, ttr, data)
|
28
75
|
|
29
76
|
HydroponicBean.jobs.push(job)
|
30
77
|
|
31
|
-
return job
|
78
|
+
return job
|
79
|
+
end
|
80
|
+
|
81
|
+
def deadline_soon?
|
82
|
+
HydroponicBean.update_time!
|
83
|
+
watched_tubes.map(&:reserved_jobs).flatten.select do |job|
|
84
|
+
job.reserved_by == self
|
85
|
+
end.sort_by(&:ttr_left).first&.deadline_soon?
|
86
|
+
end
|
87
|
+
|
88
|
+
def reserve_job
|
89
|
+
HydroponicBean.update_time!
|
90
|
+
reservable_jobs.first&.reserve(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
def reservable_jobs
|
94
|
+
watched_tubes.reject(&:paused?).map(&:ready_jobs).flatten.sort_by(&:created_at).sort_by(&:pri)
|
95
|
+
end
|
96
|
+
|
97
|
+
def wait_for_job(timeout)
|
98
|
+
self.waiting = true
|
99
|
+
if timeout == 0
|
100
|
+
return reserve_job
|
101
|
+
else
|
102
|
+
timeout = [0, timeout].max
|
103
|
+
Timeout.timeout(timeout) do
|
104
|
+
while !(job = reserve_job)
|
105
|
+
sleep 0.49
|
106
|
+
end
|
107
|
+
return job
|
108
|
+
end
|
109
|
+
end
|
110
|
+
rescue Timeout::Error
|
111
|
+
return nil
|
112
|
+
ensure
|
113
|
+
self.waiting = false
|
114
|
+
end
|
115
|
+
|
116
|
+
def stats
|
117
|
+
{
|
118
|
+
'current-jobs-urgent' => HydroponicBean.jobs.select(&:urgent?).count,
|
119
|
+
'current-jobs-ready' => HydroponicBean.jobs.select(&:ready?).count,
|
120
|
+
'current-jobs-reserved' => HydroponicBean.jobs.select(&:reserved?).count,
|
121
|
+
'current-jobs-delayed' => HydroponicBean.jobs.select(&:delayed?).count,
|
122
|
+
'current-jobs-buried' => HydroponicBean.jobs.select(&:buried?).count,
|
123
|
+
'total-jobs' => HydroponicBean.jobs.count,
|
124
|
+
'current-tubes' => HydroponicBean.tubes.count,
|
125
|
+
'current-connections' => HydroponicBean.connections.count,
|
126
|
+
'current-producers' => HydroponicBean.connections.select(&:produced?).count,
|
127
|
+
'current-workers' => HydroponicBean.connections.select(&:workers?).count,
|
128
|
+
'current-waiting' => HydroponicBean.connections.select(&:waiting?).count,
|
129
|
+
}.merge(Hash[HydroponicBean.commands.map{|k, v| ["cmd-#{k}", v]}])
|
32
130
|
end
|
33
131
|
end
|
34
132
|
end
|
data/lib/hydroponic_bean/job.rb
CHANGED
@@ -5,9 +5,7 @@ module HydroponicBean
|
|
5
5
|
end
|
6
6
|
|
7
7
|
attr_accessor :id, :pri, :delay, :ttr, :data, :created_at,
|
8
|
-
:state, :tube, :stats
|
9
|
-
|
10
|
-
attr_reader :deleted
|
8
|
+
:reserved_at, :reserved_by, :state, :tube, :stats
|
11
9
|
|
12
10
|
def initialize(tube, pri, delay, ttr, data)
|
13
11
|
@id = self.class.next_id
|
@@ -15,6 +13,7 @@ module HydroponicBean
|
|
15
13
|
@tube = tube
|
16
14
|
@pri = pri.to_i
|
17
15
|
@delay = delay.to_i
|
16
|
+
@reserved_at = nil
|
18
17
|
@ttr = ttr.to_i
|
19
18
|
@state = @delay > 0 ? State.delayed : State.ready
|
20
19
|
@data = data
|
@@ -30,6 +29,15 @@ module HydroponicBean
|
|
30
29
|
@tube.push(self)
|
31
30
|
end
|
32
31
|
|
32
|
+
def update_time!
|
33
|
+
if (delayed? || reserved?) && time_left == 0
|
34
|
+
if reserved?
|
35
|
+
stats['timeouts'] += 1
|
36
|
+
end
|
37
|
+
@state = State.ready
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
33
41
|
def age
|
34
42
|
(Time.now.utc - created_at).to_i
|
35
43
|
end
|
@@ -44,13 +52,54 @@ module HydroponicBean
|
|
44
52
|
def delayed?; exists? && state == State.delayed; end
|
45
53
|
def buried?; exists? && state == State.buried; end
|
46
54
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
55
|
+
def reserved_by?(connection)
|
56
|
+
reserved? && reserved_by == connection
|
57
|
+
end
|
58
|
+
|
59
|
+
def reserve(connection)
|
60
|
+
if ready?
|
61
|
+
stats['reserves'] += 1
|
62
|
+
@state = State.reserved
|
63
|
+
@reserved_by = connection
|
64
|
+
@reserved_at = Time.now.utc
|
65
|
+
# For convenience and one-liners
|
66
|
+
return self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def release(connection, pri, delay)
|
71
|
+
if reserved_by?(connection)
|
72
|
+
stats['releases'] += 1
|
73
|
+
@pri = pri.to_i
|
74
|
+
@delay = delay.to_i
|
75
|
+
@reserved_at = nil
|
76
|
+
@reserved_by = nil
|
77
|
+
@state = @delay > 0 ? State.delayed : State.ready
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def bury(connection, pri)
|
82
|
+
if reserved_by?(connection)
|
83
|
+
stats['buries'] += 1
|
84
|
+
@pri = pri.to_i
|
85
|
+
@reserved_at = nil
|
86
|
+
@reserved_by = nil
|
87
|
+
@state = State.buried
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete(connection)
|
92
|
+
if exists? && (!reserved? || reserved_by?(connection))
|
93
|
+
@tube.job_deleted!
|
50
94
|
@deleted = true
|
51
95
|
end
|
52
96
|
end
|
53
|
-
|
97
|
+
|
98
|
+
def touch(connection)
|
99
|
+
if reserved_by?(connection)
|
100
|
+
@reserved_at = Time.now.utc
|
101
|
+
end
|
102
|
+
end
|
54
103
|
|
55
104
|
def kick
|
56
105
|
if buried? || delayed?
|
@@ -59,8 +108,16 @@ module HydroponicBean
|
|
59
108
|
end
|
60
109
|
end
|
61
110
|
|
111
|
+
def ttr_left
|
112
|
+
[ttr - (Time.now.utc - reserved_at).to_i, 0].max
|
113
|
+
end
|
114
|
+
|
62
115
|
def time_left
|
63
|
-
reserved? ?
|
116
|
+
reserved? ? ttr_left : [delay - age, 0].max
|
117
|
+
end
|
118
|
+
|
119
|
+
def deadline_soon?
|
120
|
+
ttr_left <= 1
|
64
121
|
end
|
65
122
|
|
66
123
|
def serialize_stats
|
@@ -77,6 +134,9 @@ module HydroponicBean
|
|
77
134
|
}.merge(stats)
|
78
135
|
end
|
79
136
|
|
137
|
+
def deleted?; @deleted; end
|
138
|
+
def exists?; !@deleted; end
|
139
|
+
|
80
140
|
module State
|
81
141
|
def self.ready; :ready; end
|
82
142
|
def self.reserved; :reserved; end
|