skynet 0.9.1 → 0.9.2
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/History.txt +99 -0
- data/Manifest.txt +10 -9
- data/README.txt +74 -7
- data/app_generators/skynet_install/skynet_install_generator.rb +26 -22
- data/app_generators/skynet_install/templates/migration.rb +11 -5
- data/app_generators/skynet_install/templates/skynet +25 -12
- data/app_generators/skynet_install/templates/skynet_schema.sql +56 -0
- data/bin/skynet +26 -2
- data/bin/skynet_install +24 -0
- data/bin/skynet_tuplespace_server +13 -0
- data/config/hoe.rb +1 -0
- data/lib/skynet.rb +3 -0
- data/lib/skynet/mapreduce_helper.rb +74 -0
- data/lib/skynet/message_queue_adapters/mysql.rb +225 -172
- data/lib/skynet/message_queue_adapters/tuple_space.rb +31 -16
- data/lib/skynet/skynet_active_record_extensions.rb +78 -46
- data/lib/skynet/skynet_config.rb +162 -23
- data/lib/skynet/skynet_console.rb +23 -10
- data/lib/skynet/skynet_console_helper.rb +61 -58
- data/lib/skynet/skynet_job.rb +741 -493
- data/lib/skynet/skynet_launcher.rb +5 -1
- data/lib/skynet/skynet_manager.rb +106 -49
- data/lib/skynet/skynet_message.rb +169 -174
- data/lib/skynet/skynet_message_queue.rb +29 -16
- data/lib/skynet/skynet_partitioners.rb +92 -0
- data/lib/skynet/skynet_ruby_extensions.rb +3 -4
- data/lib/skynet/skynet_task.rb +61 -19
- data/lib/skynet/skynet_tuplespace_server.rb +0 -2
- data/lib/skynet/skynet_worker.rb +73 -51
- data/lib/skynet/version.rb +1 -1
- data/test/test_active_record_extensions.rb +138 -0
- data/test/test_helper.rb +6 -0
- data/test/{mysql_message_queue_adaptor_test.rb → test_mysql_message_queue_adapter.rb} +94 -30
- data/test/test_skynet.rb +11 -11
- data/test/test_skynet_install_generator.rb +0 -4
- data/test/test_skynet_job.rb +717 -0
- data/test/test_skynet_manager.rb +142 -0
- data/test/test_skynet_message.rb +229 -0
- data/test/test_skynet_task.rb +24 -0
- data/test/{tuplespace_message_queue_test.rb → test_tuplespace_message_queue.rb} +25 -30
- data/website/index.html +56 -16
- data/website/index.txt +55 -25
- data/website/template.rhtml +1 -1
- metadata +29 -13
- data/app_generators/skynet_install/templates/skynet_console +0 -16
- data/bin/skynet_console +0 -9
- data/sometest.rb +0 -23
- data/test/all_models_test.rb +0 -139
- data/test/skynet_manager_test.rb +0 -107
- data/test/skynet_message_test.rb +0 -42
- data/tmtags +0 -1242
@@ -0,0 +1,56 @@
|
|
1
|
+
CREATE TABLE skynet_message_queues (
|
2
|
+
id int(11) NOT NULL auto_increment,
|
3
|
+
queue_id int(11) default '0',
|
4
|
+
tran_id bigint(20) unsigned default NULL,
|
5
|
+
created_on datetime default NULL,
|
6
|
+
updated_on datetime default NULL,
|
7
|
+
tasktype varchar(255) default NULL,
|
8
|
+
task_id bigint(20) unsigned default NULL,
|
9
|
+
job_id bigint(20) unsigned default NULL,
|
10
|
+
raw_payload text,
|
11
|
+
payload_type varchar(255) default NULL,
|
12
|
+
name varchar(255) default NULL,
|
13
|
+
expiry int(11) default NULL,
|
14
|
+
expire_time decimal(16,4) default NULL,
|
15
|
+
iteration int(11) default NULL,
|
16
|
+
version int(11) default NULL,
|
17
|
+
timeout decimal(16,4) default NULL,
|
18
|
+
retry int(11) default '0',
|
19
|
+
PRIMARY KEY (id),
|
20
|
+
UNIQUE KEY index_skynet_message_queues_on_tran_id (tran_id),
|
21
|
+
KEY index_skynet_message_queues_on_job_id (job_id),
|
22
|
+
KEY index_skynet_message_queues_on_task_id (task_id),
|
23
|
+
KEY index_skynet_mqueue_for_take (queue_id,tasktype,payload_type,expire_time)
|
24
|
+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
25
|
+
CREATE TABLE skynet_worker_queues (
|
26
|
+
id int(11) NOT NULL auto_increment,
|
27
|
+
queue_id int(11) default '0',
|
28
|
+
created_on datetime default NULL,
|
29
|
+
updated_on datetime default NULL,
|
30
|
+
tasktype varchar(255) default NULL,
|
31
|
+
tasksubtype varchar(255) default NULL,
|
32
|
+
worker_id bigint(20) unsigned default NULL,
|
33
|
+
hostname varchar(255) default NULL,
|
34
|
+
process_id int(11) default NULL,
|
35
|
+
job_id bigint(20) unsigned default NULL,
|
36
|
+
task_id bigint(20) unsigned default NULL,
|
37
|
+
iteration int(11) default NULL,
|
38
|
+
name varchar(255) default NULL,
|
39
|
+
map_or_reduce varchar(255) default NULL,
|
40
|
+
started_at decimal(16,4) default NULL,
|
41
|
+
version int(11) default NULL,
|
42
|
+
processed int(11) default NULL,
|
43
|
+
timeout decimal(16,4) default NULL,
|
44
|
+
PRIMARY KEY (id),
|
45
|
+
UNIQUE KEY index_skynet_worker_queues_on_worker_id (worker_id),
|
46
|
+
KEY index_skynet_worker_queues_on_hostname_and_process_id (hostname,process_id)
|
47
|
+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
48
|
+
CREATE TABLE skynet_queue_temperature (
|
49
|
+
id int(11) NOT NULL auto_increment,
|
50
|
+
queue_id int(11) default '0',
|
51
|
+
updated_on datetime default NULL,
|
52
|
+
count int(11) default '0',
|
53
|
+
temperature decimal(6,4) default NULL,
|
54
|
+
type varchar(255) default NULL,
|
55
|
+
PRIMARY KEY (id)
|
56
|
+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
data/bin/skynet
CHANGED
@@ -1,8 +1,33 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
# This is the main skynet starter script.
|
4
|
+
# It can be used to start the skynet_tuplespace_server as well as all skynet workers.
|
5
|
+
# It is important that this script has access to your code. If you want to run
|
6
|
+
# skynet within your code you'll want to read about bin/skynet_install[link:files/bin/skynet_install.html]
|
7
|
+
#
|
8
|
+
# Usage: skynet [options]
|
9
|
+
# -w, --workers WORKERS Number of workers to start. The default is 4 and is stored in Skynet::CONFIG[:NUMBER_OF_WORKERS]
|
10
|
+
# -i, --increment-worker-version Increment Worker Version
|
11
|
+
# -a, --add-workers WORKERS Number of workers to add.
|
12
|
+
# -k, --remove-workers WORKERS Number of workers to remove.
|
13
|
+
# -r, --required LIBRARY Require the specified libraries
|
14
|
+
# --restart-all-workers Restart All Workers
|
15
|
+
# --restart-workers Restart Workers
|
16
|
+
#
|
17
|
+
# If you have chosen to use the TupleSpace message queue adapter this script will see if there is an available TS first
|
18
|
+
# and start one if there is not. You can also start the bin/skynet_tuplespace_server[link:files/bin/skynet_tuplespace_server.html] manually.
|
19
|
+
#
|
20
|
+
# Running skynet starts a Skynet::Manager which in turn spawns the Skynet::Worker processes with the appropriate options.
|
21
|
+
# You only need to run skynet once per machine. If you want to add more workers, use the appropriate flags above to do so.
|
22
|
+
# Only one manager should be running per machine. The Skynet::Manager does not dole out tasks
|
23
|
+
#
|
24
|
+
# You should set all of your Skynet::CONFIG (or Skynet.configure()) options here (or in one of your environment files.)
|
25
|
+
# See Skynet::Config for more information on configuration options.
|
26
|
+
|
27
|
+
require 'rubygems'
|
3
28
|
require File.expand_path(File.dirname(__FILE__)) + '/../lib/skynet.rb'
|
4
29
|
|
5
|
-
Skynet::CONFIG[:WORKER_CHECK_DELAY]
|
30
|
+
Skynet::CONFIG[:WORKER_CHECK_DELAY] ||= 4
|
6
31
|
Skynet::CONFIG[:LAUNCHER_PATH] = File.expand_path(__FILE__)
|
7
32
|
|
8
33
|
begin
|
@@ -16,5 +41,4 @@ rescue Skynet::ConnectionError
|
|
16
41
|
end
|
17
42
|
end
|
18
43
|
|
19
|
-
|
20
44
|
Skynet.new
|
data/bin/skynet_install
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# skynet_install is used to install skynet binaries into your application.
|
4
|
+
# The value of using your own skynet binaries is that they can ensure your code
|
5
|
+
# is available to all skynet workers.
|
6
|
+
# This is also how you can run skynet from within rails. (using --rails)
|
7
|
+
#
|
8
|
+
# USAGE: skynet_install [--rails] directory (can be '.' for current)"
|
9
|
+
#
|
10
|
+
# Options:
|
11
|
+
# -v, --version Show the skynet_install version number and quit.
|
12
|
+
# --include-migration Include mysql migration if you want to use mysql as your message queue
|
13
|
+
# -r, --rails Install into rails app
|
14
|
+
# Default: false
|
15
|
+
# General Options:
|
16
|
+
# -h, --help Show this help message and quit.
|
17
|
+
# -p, --pretend Run but do not make any changes.
|
18
|
+
# -f, --force Overwrite files that already exist.
|
19
|
+
# -s, --skip Skip files that already exist.
|
20
|
+
# -q, --quiet Suppress normal output.
|
21
|
+
# -t, --backtrace Debugging: show backtrace on errors.
|
22
|
+
# -c, --svn Modify files with subversion. (Note: svn must be in path)
|
23
|
+
#
|
24
|
+
|
1
25
|
require 'rubygems'
|
2
26
|
require 'rubigen'
|
3
27
|
|
@@ -1,5 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
# SkynetTupleSpace server is one of the message queues you can use with Skynet. Make sure you set:
|
4
|
+
# Skynet::CONFIG[:MESSAGE_QUEUE_ADAPTER] = "Skynet::MessageQueueAdapter::TupleSpace"
|
5
|
+
#
|
6
|
+
# Usage: skynet_tuplespace_server (start|stop|run) [options]
|
7
|
+
# -t, --ontop TRUE Dont Daemonize
|
8
|
+
# -p, --port PORT Port to listen on. default 7647
|
9
|
+
# -o, --log LOGFILE Logfile to log to
|
10
|
+
# -l, --loglevel LOGLEVEL Log level defaults to DEBUG
|
11
|
+
# -d, --piddir PIDDIR Directory to put pidfile
|
12
|
+
# -u, --drburi Drb URI What DRbURI to use
|
13
|
+
|
3
14
|
require 'rubygems'
|
4
15
|
require 'daemons'
|
5
16
|
require 'pp'
|
@@ -36,6 +47,8 @@ OptionParser.new do |opt|
|
|
36
47
|
else
|
37
48
|
options[:drburi] = "druby://#{v}"
|
38
49
|
end
|
50
|
+
options[:drburi] =~ /druby:\/\/.+?:(\d*)/
|
51
|
+
options[:port] = $1.to_i
|
39
52
|
end
|
40
53
|
|
41
54
|
opt.parse!(ARGV)
|
data/config/hoe.rb
CHANGED
@@ -61,6 +61,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
|
61
61
|
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
62
62
|
p.extra_deps = [
|
63
63
|
['daemons',">= 1"],
|
64
|
+
['rubigen', ">=1.1.1"]
|
64
65
|
]
|
65
66
|
# An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
66
67
|
|
data/lib/skynet.rb
CHANGED
@@ -8,6 +8,7 @@ require 'drb'
|
|
8
8
|
require 'skynet_guid_generator'
|
9
9
|
require 'skynet_logger'
|
10
10
|
require 'skynet_config'
|
11
|
+
require 'timeout'
|
11
12
|
|
12
13
|
Skynet::CONFIG[:SKYNET_PATH] ||= File.expand_path(File.dirname(__FILE__) +"/..")
|
13
14
|
# Skynet::CONFIG[:LAUNCHER_PATH] ||= File.expand_path(ENV['_'])
|
@@ -17,6 +18,7 @@ require 'skynet_message'
|
|
17
18
|
require 'message_queue_adapters/message_queue_adapter'
|
18
19
|
require 'message_queue_adapters/tuple_space'
|
19
20
|
require "skynet_message_queue"
|
21
|
+
require 'skynet_partitioners'
|
20
22
|
require 'skynet_job'
|
21
23
|
require 'skynet_worker'
|
22
24
|
require 'skynet_task'
|
@@ -32,3 +34,4 @@ end
|
|
32
34
|
require 'mapreduce_test'
|
33
35
|
require 'skynet_launcher'
|
34
36
|
require 'skynet_console'
|
37
|
+
require 'mapreduce_helper'
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module MapreduceHelper
|
2
|
+
# You can include the MapreduceHelper into your class to give you standard self.map and self.reduce methods.
|
3
|
+
# You need only implement self.map_each and self.reduce_each methods which accept a single item (istead of an arrad)
|
4
|
+
#
|
5
|
+
# Example Usage:
|
6
|
+
# This example is a bit contrived.
|
7
|
+
#
|
8
|
+
# class MapReduceTest
|
9
|
+
# include MapreduceHelper
|
10
|
+
#
|
11
|
+
# def self.run
|
12
|
+
# job = Skynet::Job.new(
|
13
|
+
# :mappers => 2,
|
14
|
+
# :reducers => 1,
|
15
|
+
# :map_reduce_class => self,
|
16
|
+
# :map_data => ['http://www.geni.com'.'http://www.yahoo.com','http://www.cnet.com']
|
17
|
+
# )
|
18
|
+
# results = job.run
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def self.map_each(url)
|
22
|
+
# SomeUrlSlurper.gather_results(url) # returns an array of urls of sites that link to the given url
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# def self.reduce(linked_from_url)
|
26
|
+
# SomeUrlSluper.find_text("mysite", linked_from_url) # finds all the times "mysite" appears in the given url, which we know links to the url given in the map_data
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# MapReduceTest.run
|
31
|
+
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.extend MapreduceHelper
|
35
|
+
end
|
36
|
+
|
37
|
+
# Takes an array of map_data, iterates over that array calling self.map_each(item) for each
|
38
|
+
# item in that array. Catches exceptions in each iteration and continues processing.
|
39
|
+
def map(map_data_array)
|
40
|
+
raise Skynet::Job::BadMapOrReduceError.new("#{self.class} has no self.map_each method.") unless self.respond_to?(:map_each)
|
41
|
+
if map_data_array.is_a?(Array)
|
42
|
+
results = []
|
43
|
+
map_data_array.each do |data|
|
44
|
+
begin
|
45
|
+
results << map_each(data)
|
46
|
+
rescue Exception => e
|
47
|
+
error "ERROR IN #{self} [#{e.class} #{e.message}] #{e.backtrace.join("\n")}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
results
|
51
|
+
else
|
52
|
+
map_each(map_data_array)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Takes an array of post reduce_partitioned data, iterates over that array calling self.reduce_each(item) for each
|
57
|
+
# item in that array. Catches exceptions in each iteration and continues processing.
|
58
|
+
def reduce(reduce_partitioned_data_array)
|
59
|
+
raise Skynet::Job::BadMapOrReduceError.new("#{self.class} has no self.reduce_each method.") unless self.respond_to?(:reduce_each)
|
60
|
+
if reduce_partitioned_data_array.is_a?(Array)
|
61
|
+
results = []
|
62
|
+
reduce_partitioned_data_array.each do |data|
|
63
|
+
begin
|
64
|
+
results << reduce_each(data)
|
65
|
+
rescue Exception => e
|
66
|
+
error "ERROR IN #{self} [#{e.class} #{e.message}] #{e.backtrace.join("\n")}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
results
|
70
|
+
else
|
71
|
+
reduce_each(reduce_partitioned_data_array)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -28,7 +28,6 @@ class Skynet
|
|
28
28
|
SEARCH_FIELDS = [:tasktype, :task_id, :job_id, :payload_type, :expire_time, :iteration, :version] unless defined?(SEARCH_FIELDS)
|
29
29
|
|
30
30
|
Skynet::CONFIG[:MYSQL_MESSAGE_QUEUE_TEMP_CHECK_DELAY] ||= 30
|
31
|
-
|
32
31
|
|
33
32
|
@@db_set = false
|
34
33
|
|
@@ -36,21 +35,36 @@ class Skynet
|
|
36
35
|
:mysql
|
37
36
|
end
|
38
37
|
|
39
|
-
def initialize
|
40
|
-
if Skynet::CONFIG[:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
def initialize
|
39
|
+
if Skynet::CONFIG[:MYSQL_MESSAGE_QUEUE_TABLE]
|
40
|
+
SkynetMessageQueue.table_name = Skynet::CONFIG[:MYSQL_MESSAGE_QUEUE_TABLE]
|
41
|
+
end
|
42
|
+
if not @@db_set
|
43
|
+
if Skynet::CONFIG[:MYSQL_QUEUE_DATABASE]
|
44
|
+
begin
|
45
|
+
SkynetMessageQueue.establish_connection Skynet::CONFIG[:MYSQL_QUEUE_DATABASE]
|
46
|
+
SkynetWorkerQueue.establish_connection Skynet::CONFIG[:MYSQL_QUEUE_DATABASE]
|
47
|
+
rescue ActiveRecord::AdapterNotSpecified => e
|
48
|
+
error "#{Skynet::CONFIG[:MYSQL_QUEUE_DATABASE]} not defined as a database adaptor #{e.message}"
|
49
|
+
end
|
50
|
+
elsif not ActiveRecord::Base.connected?
|
51
|
+
db_options = {
|
52
|
+
:adapter => Skynet::CONFIG[:MYSQL_ADAPTER],
|
53
|
+
:host => Skynet::CONFIG[:MYSQL_HOST],
|
54
|
+
:username => Skynet::CONFIG[:MYSQL_USERNAME],
|
55
|
+
:password => Skynet::CONFIG[:MYSQL_PASSWORD],
|
56
|
+
:database => Skynet::CONFIG[:MYSQL_DATABASE]
|
57
|
+
}
|
58
|
+
ActiveRecord::Base.establish_connection(db_options)
|
46
59
|
end
|
47
60
|
end
|
48
61
|
@@db_set = true
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
def message_queue_table
|
66
|
+
Skynet::CONFIG[:MYSQL_MESSAGE_QUEUE_TABLE] || SkynetMessageQueue.table_name
|
49
67
|
end
|
50
|
-
|
51
|
-
# def initialize
|
52
|
-
# SkynetMessageQueue.connection.execute("set session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")
|
53
|
-
# end
|
54
68
|
|
55
69
|
def self.debug_class_desc
|
56
70
|
"MYSQLMQ"
|
@@ -61,12 +75,11 @@ class Skynet
|
|
61
75
|
end
|
62
76
|
|
63
77
|
def template_to_conditions(template,fields=Skynet::Message.fields)
|
64
|
-
fields = fields.invert
|
65
78
|
conditions = []
|
66
79
|
values = []
|
67
80
|
|
68
|
-
fields.
|
69
|
-
value = template[
|
81
|
+
fields.each_with_index do |field,ii|
|
82
|
+
value = template[ii]
|
70
83
|
next unless value
|
71
84
|
if value.is_a?(Range)
|
72
85
|
conditions << "#{field} BETWEEN #{value.first} AND #{value.last}"
|
@@ -81,8 +94,9 @@ class Skynet
|
|
81
94
|
end
|
82
95
|
|
83
96
|
def message_to_hash(message,timeout=nil,fields=Skynet::Message.fields)
|
97
|
+
timeout ||= message.expiry
|
84
98
|
hash = {}
|
85
|
-
fields.
|
99
|
+
fields.each do |field|
|
86
100
|
next if field == :drburi
|
87
101
|
# next unless message.send(field)
|
88
102
|
if message.send(field).is_a?(Symbol)
|
@@ -99,57 +113,110 @@ class Skynet
|
|
99
113
|
end
|
100
114
|
hash
|
101
115
|
end
|
116
|
+
|
117
|
+
def write_fallback_message(message_row, message)
|
118
|
+
tran_id = get_unique_id(1)
|
119
|
+
ftm = message.fallback_task_message
|
120
|
+
update_sql = %{
|
121
|
+
update #{message_queue_table}
|
122
|
+
SET iteration = #{ftm.iteration },
|
123
|
+
expire_time = #{ftm.expire_time},
|
124
|
+
updated_on = '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}',
|
125
|
+
tran_id = #{tran_id}
|
126
|
+
WHERE id = #{message_row.id} AND iteration = #{message.iteration}
|
127
|
+
AND tran_id #{(message_row.tran_id ? " =#{message_row.tran_id}" : ' IS NULL')}
|
128
|
+
}
|
129
|
+
rows = update(update_sql) || 0
|
130
|
+
message_row.tran_id = tran_id if rows == 1
|
131
|
+
rows
|
132
|
+
end
|
133
|
+
|
134
|
+
def take_next_task(curver,timeout=0.5,payload_type=nil,queue_id=0)
|
135
|
+
timeout = Skynet::CONFIG[:MYSQL_NEXT_TASK_TIMEOUT] if timeout < 1
|
136
|
+
debug "TASK NEXT TASK!!!!!!! timeout: #{timeout} queue_id:#{queue_id}"
|
102
137
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
start = Time.now
|
108
|
-
rows = nil
|
138
|
+
start = Time.now
|
139
|
+
template = Skynet::Message.next_task_template(curver, payload_type)
|
140
|
+
message = nil
|
141
|
+
|
109
142
|
loop do
|
110
|
-
|
111
|
-
|
112
|
-
|
143
|
+
rows = 0
|
144
|
+
message = nil
|
145
|
+
template = Skynet::Message.next_task_template(curver, payload_type)
|
146
|
+
begin
|
147
|
+
message_row = find_next_message(template, payload_type)
|
148
|
+
if message_row
|
149
|
+
message = Skynet::Message.new(message_row.attributes)
|
150
|
+
rows = write_fallback_message(message_row, message)
|
113
151
|
|
114
|
-
|
115
|
-
|
152
|
+
if rows < 1
|
153
|
+
old_temp = temperature(payload_type)
|
154
|
+
set_temperature(payload_type, template_to_conditions(template), queue_id)
|
155
|
+
debug "MISSCOLLISION PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
156
|
+
else
|
157
|
+
break
|
158
|
+
end
|
159
|
+
else # no messages on queue with this temp
|
160
|
+
old_temp = temperature(payload_type)
|
161
|
+
if old_temp > 1
|
162
|
+
set_temperature(payload_type, template_to_conditions(template), queue_id)
|
163
|
+
end
|
164
|
+
debug "MISS PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
165
|
+
end
|
116
166
|
rescue Skynet::Message::BadMessage => e
|
117
|
-
message = nil
|
118
167
|
message_row.destroy
|
119
168
|
next
|
169
|
+
rescue ActiveRecord::StatementInvalid => e
|
170
|
+
if e.message =~ /Deadlock/
|
171
|
+
old_temp = temperature(payload_type)
|
172
|
+
set_temperature(payload_type, template_to_conditions(template), queue_id)
|
173
|
+
debug "COLLISION PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
174
|
+
else
|
175
|
+
raise e
|
176
|
+
end
|
177
|
+
end
|
178
|
+
if Time.now.to_f > start.to_f + timeout
|
179
|
+
debug "MISSTIMEOUT PTYPE #{payload_type} #{temperature(payload_type)}"
|
180
|
+
raise Skynet::RequestExpiredError.new
|
181
|
+
else
|
182
|
+
sleepy = rand(timeout * 0.5 )
|
183
|
+
debug "EMPTY QUEUE #{temperature(payload_type)} SLEEPING: #{timeout} / #{sleepy}"
|
184
|
+
sleep sleepy
|
120
185
|
end
|
121
|
-
ftm = message.fallback_task_message
|
122
|
-
rows = update("update skynet_message_queues set iteration = #{ftm.iteration }, expire_time = #{ftm.expire_time} where iteration = #{message.iteration} and id = #{message_row.id}")
|
123
|
-
|
124
|
-
# message = nil if rows == 0
|
125
|
-
return message if message
|
126
186
|
end
|
127
|
-
end
|
128
187
|
|
129
|
-
|
188
|
+
return message
|
189
|
+
end
|
130
190
|
|
131
191
|
def write_message(message,timeout=nil)
|
192
|
+
timeout ||= message.expiry
|
132
193
|
SkynetMessageQueue.create(message_to_hash(message, timeout))
|
133
194
|
end
|
134
195
|
|
135
196
|
def write_result(message,result=[],timeout=nil)
|
197
|
+
timeout ||= message.expiry
|
136
198
|
result_message = message.result_message(result)
|
137
199
|
result_message.expire_time = nil
|
138
|
-
|
200
|
+
update_message_with_result(result_message,timeout)
|
139
201
|
end
|
140
202
|
|
141
|
-
def
|
203
|
+
def update_message_with_result(message,timeout=nil)
|
204
|
+
timeout ||= message.expiry
|
142
205
|
timeout_sql = (timeout ? ", timeout = #{timeout}, expire_time = #{Time.now.to_f + timeout}" : '')
|
143
|
-
rows = 0
|
144
|
-
|
145
|
-
|
206
|
+
rows = 0
|
207
|
+
raw_payload_sql = " raw_payload = "
|
208
|
+
raw_payload_sql << (message.raw_payload ? "'#{::Mysql.escape_string(message.raw_payload)}'" : 'NULL')
|
209
|
+
update_sql = %{
|
210
|
+
update #{message_queue_table}
|
146
211
|
set tasktype = "#{message.tasktype}",
|
147
|
-
|
212
|
+
#{raw_payload_sql},
|
148
213
|
payload_type = "#{message.payload_type}",
|
214
|
+
updated_on = "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}",
|
149
215
|
tran_id = NULL
|
150
216
|
#{timeout_sql}
|
151
217
|
where task_id = #{message.task_id}
|
152
|
-
}
|
218
|
+
}
|
219
|
+
rows = update(update_sql)
|
153
220
|
|
154
221
|
raise Skynet::RequestExpiredError.new() if rows == 0
|
155
222
|
end
|
@@ -173,6 +240,7 @@ class Skynet
|
|
173
240
|
# sleep_time ||= timeout
|
174
241
|
|
175
242
|
message_row = SkynetMessageQueue.find(:first,:conditions => conditions)
|
243
|
+
|
176
244
|
break if message_row
|
177
245
|
|
178
246
|
if Time.now.to_f > start.to_f + timeout
|
@@ -203,12 +271,11 @@ class Skynet
|
|
203
271
|
|
204
272
|
def write_error(message,error='',timeout=nil)
|
205
273
|
message.expire_time = nil
|
206
|
-
|
274
|
+
update_message_with_result(message.error_message(error),timeout)
|
207
275
|
end
|
208
276
|
|
209
|
-
def write_worker_status(task, timeout=nil)
|
277
|
+
def write_worker_status(task, timeout=nil)
|
210
278
|
message = Skynet::WorkerStatusMessage.new(task)
|
211
|
-
worker_fields = Skynet::WorkerStatusMessage.fields.reject {|k,f| f == :process_id or f == :hostname or f == :tasksubtype or f == :tasktype}
|
212
279
|
update_hash = message_to_hash(message, timeout, Skynet::WorkerStatusMessage.fields)
|
213
280
|
update_hash.each do |k,v|
|
214
281
|
if not v
|
@@ -226,7 +293,7 @@ class Skynet
|
|
226
293
|
rows = update(insert_sql)
|
227
294
|
rescue ActiveRecord::StatementInvalid => e
|
228
295
|
if e.message =~ /Duplicate/
|
229
|
-
error "DUPLICATE WORKER #{e.message}"
|
296
|
+
error "DUPLICATE WORKER #{e.message} #{e.backtrace.join("\n")}"
|
230
297
|
else
|
231
298
|
raise e
|
232
299
|
end
|
@@ -256,40 +323,53 @@ class Skynet
|
|
256
323
|
end
|
257
324
|
|
258
325
|
|
259
|
-
def clear_worker_status(hostname=nil)
|
260
|
-
|
261
|
-
|
326
|
+
def clear_worker_status(hostname=nil)
|
327
|
+
sql = "delete from skynet_worker_queues "
|
328
|
+
if hostname
|
329
|
+
sql << "where hostname = '#{hostname}'"
|
330
|
+
end
|
331
|
+
SkynetWorkerQueue.connection.execute(sql)
|
332
|
+
end
|
333
|
+
|
334
|
+
def version_active?(curver=nil, queue_id=0)
|
335
|
+
return true unless curver
|
336
|
+
message_row = find_next_message(Skynet::Message.next_task_template(curver, nil, queue_id), :any, 1)
|
337
|
+
if message_row or curver.to_i == get_worker_version.to_i
|
338
|
+
true
|
262
339
|
else
|
263
|
-
|
340
|
+
false
|
264
341
|
end
|
265
342
|
end
|
266
343
|
|
267
344
|
def set_worker_version(ver=nil)
|
268
345
|
ver ||= 1
|
269
|
-
#
|
270
|
-
# SkynetWorkerQueue.connection.execute("delete from skynet_worker_queues where tasktype = 'workerversion'")
|
271
|
-
SkynetWorkerQueue.connection.insert("replace skynet_worker_queues (worker_id, tasktype, version) values (0, 'workerversion',#{ver})")
|
272
|
-
# end
|
346
|
+
SkynetMessageQueue.connection.insert("replace #{message_queue_table} (tran_id, task_id, tasktype, version) values (0, 0, 'version',#{ver})")
|
273
347
|
ver
|
274
348
|
end
|
275
349
|
|
276
350
|
def get_worker_version
|
277
|
-
|
278
|
-
ver = SkynetWorkerQueue.connection.select_value("select version from skynet_worker_queues where tasktype = 'workerversion'")
|
351
|
+
ver = SkynetMessageQueue.connection.select_value("select version from #{message_queue_table} where tran_id = 0 and tasktype = 'version'")
|
279
352
|
if not ver
|
280
|
-
|
353
|
+
begin
|
354
|
+
SkynetMessageQueue.connection.insert("insert into #{message_queue_table} (tran_id, task_id, tasktype, version) values (0, 0, 'version', 1)")
|
355
|
+
rescue ActiveRecord::StatementInvalid => e
|
356
|
+
if e.message =~ /Duplicate/
|
357
|
+
return get_worker_version
|
358
|
+
else
|
359
|
+
raise e
|
360
|
+
end
|
361
|
+
end
|
281
362
|
ver = 1
|
282
363
|
end
|
283
364
|
ver.to_i
|
284
365
|
end
|
285
366
|
|
286
|
-
|
287
367
|
def clear_outstanding_tasks
|
288
|
-
SkynetMessageQueue.
|
368
|
+
SkynetMessageQueue.connection.execute("delete from #{message_queue_table} where tasktype = 'task'")
|
289
369
|
end
|
290
370
|
|
291
371
|
def delete_expired_messages
|
292
|
-
SkynetMessageQueue.connection.
|
372
|
+
SkynetMessageQueue.connection.execute("delete from #{message_queue_table} where (expire_time BETWEEN 1 AND '#{Time.now.to_f}') and iteration = -1")
|
293
373
|
end
|
294
374
|
|
295
375
|
# select hostname, iteration, count(id) as number_of_workers, count(iteration) as iteration, sum(processed) as processed, max(started_at) as most_recent_task_time from skynet_worker_queues where tasksubtype = 'worker' group by hostname, iteration;
|
@@ -301,27 +381,28 @@ class Skynet
|
|
301
381
|
|
302
382
|
def stats
|
303
383
|
stats = {
|
304
|
-
:servers
|
305
|
-
:results
|
306
|
-
:taken_tasks
|
307
|
-
:untaken_tasks
|
308
|
-
:taken_master_tasks
|
309
|
-
:taken_task_tasks
|
384
|
+
:servers => {},
|
385
|
+
:results => 0,
|
386
|
+
:taken_tasks => 0,
|
387
|
+
:untaken_tasks => 0,
|
388
|
+
:taken_master_tasks => 0,
|
389
|
+
:taken_task_tasks => 0,
|
310
390
|
:untaken_master_tasks => 0,
|
311
391
|
:untaken_task_tasks => 0,
|
312
|
-
:
|
313
|
-
:
|
314
|
-
:
|
315
|
-
:
|
316
|
-
:
|
317
|
-
:
|
318
|
-
:
|
319
|
-
:
|
392
|
+
:failed_tasks => 0,
|
393
|
+
:processed => 0,
|
394
|
+
:number_of_workers => 0,
|
395
|
+
:active_workers => 0,
|
396
|
+
:idle_workers => 0,
|
397
|
+
:hosts => 0,
|
398
|
+
:masters => 0,
|
399
|
+
:taskworkers => 0,
|
400
|
+
:time => Time.now.to_f
|
320
401
|
}
|
321
402
|
|
322
403
|
stat_rows = SkynetWorkerQueue.connection.select_all(%{
|
323
404
|
SELECT tasktype, payload_type, iteration, count(id) as number_of_tasks
|
324
|
-
FROM
|
405
|
+
FROM #{message_queue_table}
|
325
406
|
GROUP BY tasktype, payload_type, iteration
|
326
407
|
})
|
327
408
|
# pp stat_rows
|
@@ -338,9 +419,11 @@ class Skynet
|
|
338
419
|
if row["iteration"].to_i > 0
|
339
420
|
stats["taken_#{type_of_tasks}".to_sym] += row["number_of_tasks"].to_i
|
340
421
|
stats[:taken_tasks] += row["number_of_tasks"].to_i
|
341
|
-
|
422
|
+
elsif row["iteration"].to_i == 0
|
342
423
|
stats["untaken_#{type_of_tasks}".to_sym] += row["number_of_tasks"].to_i
|
343
424
|
stats[:untaken_tasks] += row["number_of_tasks"].to_i
|
425
|
+
else
|
426
|
+
stats[:failed_tasks] += row["number_of_tasks"].to_i
|
344
427
|
end
|
345
428
|
end
|
346
429
|
end
|
@@ -415,13 +498,15 @@ class Skynet
|
|
415
498
|
def update(sql)
|
416
499
|
rows = 0
|
417
500
|
3.times do
|
418
|
-
begin
|
419
|
-
|
501
|
+
begin
|
502
|
+
SkynetMessageQueue.transaction do
|
503
|
+
rows = SkynetMessageQueue.connection.update(sql)
|
504
|
+
end
|
420
505
|
return rows
|
421
506
|
rescue ActiveRecord::StatementInvalid => e
|
422
507
|
if e.message =~ /Deadlock/ or e.message =~ /Transaction/
|
423
508
|
error "#{self.class} update had collision #{e.message}"
|
424
|
-
sleep 0.
|
509
|
+
sleep 0.2
|
425
510
|
next
|
426
511
|
else
|
427
512
|
raise e
|
@@ -432,83 +517,24 @@ class Skynet
|
|
432
517
|
end
|
433
518
|
|
434
519
|
Skynet::CONFIG[:MYSQL_TEMPERATURE_CHANGE_SLEEP] ||= 40
|
435
|
-
|
436
|
-
@@temperature ||= {}
|
437
|
-
@@temperature[:task] ||= 1
|
438
|
-
@@temperature[:master] ||= 1
|
439
|
-
@@temperature[:any] ||= 1
|
440
520
|
|
441
|
-
def
|
442
|
-
conditions = template_to_conditions(template)
|
443
|
-
|
444
|
-
|
445
|
-
times_tried = 0
|
446
|
-
payload_type = template[Skynet::Message.fields.invert[:payload_type]]
|
447
|
-
payload_type ||= :any
|
448
|
-
payload_type = payload_type.to_sym
|
449
|
-
|
450
|
-
10.times do
|
451
|
-
begin
|
452
|
-
## TEPERATURE
|
453
|
-
temperature_sql = (temperature(payload_type) > 1 ? " AND id % #{temperature(payload_type).ceil} = #{rand(temperature(payload_type)).to_i} " : '')
|
454
|
-
|
455
|
-
### Mqke sure we get the old ones. If we order by on ever select its VERY expensive.
|
456
|
-
order_by = (payload_type != :master and rand(100) < 5) ? "ORDER BY payload_type desc, created_on desc" : ''
|
457
|
-
|
458
|
-
sql = <<-SQL
|
459
|
-
SELECT *
|
460
|
-
FROM skynet_message_queues
|
461
|
-
WHERE #{conditions} #{temperature_sql}
|
462
|
-
#{order_by}
|
463
|
-
LIMIT 1
|
464
|
-
SQL
|
465
|
-
|
466
|
-
message_row = SkynetMessageQueue.find_by_sql(sql).first
|
467
|
-
if message_row
|
468
|
-
update_conditions = "ID = #{message_row.id} and tran_id "
|
469
|
-
if message_row.tran_id
|
470
|
-
update_conditions << "= #{message_row.tran_id}"
|
471
|
-
else
|
472
|
-
update_conditions << 'IS NULL'
|
473
|
-
end
|
474
|
-
rows = SkynetMessageQueue.connection.update(
|
475
|
-
"UPDATE skynet_message_queues set tran_id = #{transaction_id} WHERE #{update_conditions}"
|
476
|
-
)
|
477
|
-
if rows < 1
|
478
|
-
old_temp = temperature(payload_type)
|
479
|
-
set_temperature(payload_type,conditions)
|
480
|
-
info "MISSCOLLISION PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
481
|
-
next
|
482
|
-
end
|
483
|
-
return message_row
|
484
|
-
else
|
485
|
-
old_temp = temperature(payload_type)
|
486
|
-
set_temperature(payload_type,conditions)
|
487
|
-
info "MISS PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
488
|
-
break if temperature(payload_type) == 1 and old_temp == 1
|
489
|
-
next
|
490
|
-
end
|
491
|
-
rescue ActiveRecord::StatementInvalid => e
|
492
|
-
if e.message =~ /Deadlock/
|
493
|
-
old_temp = temperature(payload_type)
|
494
|
-
set_temperature(payload_type,conditions)
|
495
|
-
info "COLLISION PTYPE #{payload_type} OLDTEMP: #{old_temp} NEWTEMP: #{temperature(payload_type)}"
|
496
|
-
next
|
497
|
-
else
|
498
|
-
raise e
|
499
|
-
end
|
500
|
-
end
|
501
|
-
end
|
521
|
+
def find_next_message(template, payload_type, temperature=nil)
|
522
|
+
conditions = template_to_conditions(template)
|
523
|
+
temperature ||= temperature(payload_type)
|
524
|
+
temperature_sql = (temperature > 1 ? " AND id % #{temperature.ceil} = #{rand(temperature).to_i} " : '')
|
502
525
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
526
|
+
### Mqke sure we get the old ones. If we order by on ever select its VERY expensive.
|
527
|
+
order_by = (payload_type != :master and rand(100) < 5) ? "ORDER BY payload_type desc, created_on desc" : ''
|
528
|
+
|
529
|
+
sql = <<-SQL
|
530
|
+
SELECT *
|
531
|
+
FROM #{message_queue_table}
|
532
|
+
WHERE #{conditions} #{temperature_sql}
|
533
|
+
#{order_by}
|
534
|
+
LIMIT 1
|
535
|
+
SQL
|
536
|
+
|
537
|
+
SkynetMessageQueue.find_by_sql(sql).first
|
512
538
|
end
|
513
539
|
|
514
540
|
# Skynet::CONFIG[:temperature_growth_rate] ||= 2
|
@@ -537,37 +563,64 @@ class Skynet
|
|
537
563
|
# values[set][:total_score] += score
|
538
564
|
# end
|
539
565
|
|
566
|
+
@@temperature ||= {}
|
567
|
+
@@temperature[:task] ||= 1
|
568
|
+
@@temperature[:master] ||= 1
|
569
|
+
@@temperature[:any] ||= 1
|
570
|
+
|
540
571
|
def temperature(payload_type)
|
572
|
+
payload_type ||= :any
|
573
|
+
payload_type = payload_type.to_sym
|
541
574
|
@@temperature[payload_type.to_sym]
|
542
575
|
end
|
576
|
+
|
577
|
+
Skynet::CONFIG[:MYSQL_QUEUE_TEMP_POW] ||= 0.6
|
543
578
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
579
|
+
def set_temperature(payload_type, conditions, queue_id=0)
|
580
|
+
payload_type ||= :any
|
581
|
+
payload_type = payload_type.to_sym
|
582
|
+
|
583
|
+
temp_q_conditions = "queue_id = #{queue_id} AND type = '#{payload_type}' AND updated_on < '#{(Time.now - 5).strftime('%Y-%m-%d %H:%M:%S')}'"
|
548
584
|
# "POW(#{(rand(40) + 40) * 0.01})"
|
549
585
|
# its almost like the temperature table needs to store the POW and adjust that to be adaptive. Like some % of the time it
|
550
586
|
# uses the one in the table, and some % it tries a new one and scores it.
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
587
|
+
begin
|
588
|
+
temperature = SkynetMessageQueue.connection.select_value(%{select (
|
589
|
+
CASE WHEN (@t:=FLOOR(
|
590
|
+
POW(@c:=(SELECT count(*) FROM #{message_queue_table} WHERE #{conditions}
|
591
|
+
),#{Skynet::CONFIG[:MYSQL_QUEUE_TEMP_POW]}))) < 1 THEN 1 ELSE @t END) from skynet_queue_temperature WHERE #{temp_q_conditions}
|
592
|
+
})
|
593
|
+
if temperature
|
594
|
+
rows = update("UPDATE skynet_queue_temperature SET temperature = #{temperature} WHERE #{temp_q_conditions}")
|
595
|
+
@@temperature[payload_type.to_sym] = temperature.to_f
|
596
|
+
else
|
597
|
+
sleepy = rand Skynet::CONFIG[:MYSQL_TEMPERATURE_CHANGE_SLEEP]
|
598
|
+
sleep sleepy
|
599
|
+
@@temperature[payload_type.to_sym] = get_temperature(payload_type, queue_id)
|
600
|
+
end
|
601
|
+
rescue ActiveRecord::StatementInvalid => e
|
602
|
+
if e.message =~ /away/
|
603
|
+
ActiveRecord::Base.connection.reconnect!
|
604
|
+
SkynetMessageQueue.connection.reconnect!
|
605
|
+
end
|
563
606
|
end
|
564
|
-
# update("UPDATE skynet_queue_temperature SET type = '#{payload_type}', temperature = CASE WHEN @t:=FLOOR(SQRT(select count(*) from
|
565
|
-
# tasks = SkynetMessageQueue.connection.select_value("select count(*) from
|
607
|
+
# update("UPDATE skynet_queue_temperature SET type = '#{payload_type}', temperature = CASE WHEN @t:=FLOOR(SQRT(select count(*) from #{message_queue_table} WHERE #{conditions})) < 1 THEN 1 ELSE @t END")
|
608
|
+
# tasks = SkynetMessageQueue.connection.select_value("select count(*) from #{message_queue_table} WHERE #{conditions}").to_i
|
566
609
|
# sleep 4 if payload_type == :tasks and tasks < 100
|
567
610
|
# @@temperature[payload_type.to_sym] = tasks ** 0.5
|
568
611
|
# @@temperature[payload_type.to_sym] *= multiplier
|
569
612
|
@@temperature[payload_type.to_sym] = 1 if @@temperature[payload_type.to_sym] < 1
|
570
613
|
end
|
614
|
+
|
615
|
+
def get_temperature(payload_type, queue_id=0)
|
616
|
+
payload_type ||= :any
|
617
|
+
payload_type = payload_type.to_sym
|
618
|
+
value = SkynetMessageQueue.connection.select_value("select temperature from skynet_queue_temperature WHERE type = '#{payload_type}'").to_f
|
619
|
+
if not value
|
620
|
+
SkynetMessageQueue.connection.execute("insert into skynet_queue_temperature (queue_id,type,temperature) values (#{queue_id},'#{payload_type}',#{@@temperature[payload_type.to_sym]})")
|
621
|
+
end
|
622
|
+
value
|
623
|
+
end
|
571
624
|
end
|
572
625
|
end
|
573
626
|
end
|