skynet 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/History.txt +99 -0
  2. data/Manifest.txt +10 -9
  3. data/README.txt +74 -7
  4. data/app_generators/skynet_install/skynet_install_generator.rb +26 -22
  5. data/app_generators/skynet_install/templates/migration.rb +11 -5
  6. data/app_generators/skynet_install/templates/skynet +25 -12
  7. data/app_generators/skynet_install/templates/skynet_schema.sql +56 -0
  8. data/bin/skynet +26 -2
  9. data/bin/skynet_install +24 -0
  10. data/bin/skynet_tuplespace_server +13 -0
  11. data/config/hoe.rb +1 -0
  12. data/lib/skynet.rb +3 -0
  13. data/lib/skynet/mapreduce_helper.rb +74 -0
  14. data/lib/skynet/message_queue_adapters/mysql.rb +225 -172
  15. data/lib/skynet/message_queue_adapters/tuple_space.rb +31 -16
  16. data/lib/skynet/skynet_active_record_extensions.rb +78 -46
  17. data/lib/skynet/skynet_config.rb +162 -23
  18. data/lib/skynet/skynet_console.rb +23 -10
  19. data/lib/skynet/skynet_console_helper.rb +61 -58
  20. data/lib/skynet/skynet_job.rb +741 -493
  21. data/lib/skynet/skynet_launcher.rb +5 -1
  22. data/lib/skynet/skynet_manager.rb +106 -49
  23. data/lib/skynet/skynet_message.rb +169 -174
  24. data/lib/skynet/skynet_message_queue.rb +29 -16
  25. data/lib/skynet/skynet_partitioners.rb +92 -0
  26. data/lib/skynet/skynet_ruby_extensions.rb +3 -4
  27. data/lib/skynet/skynet_task.rb +61 -19
  28. data/lib/skynet/skynet_tuplespace_server.rb +0 -2
  29. data/lib/skynet/skynet_worker.rb +73 -51
  30. data/lib/skynet/version.rb +1 -1
  31. data/test/test_active_record_extensions.rb +138 -0
  32. data/test/test_helper.rb +6 -0
  33. data/test/{mysql_message_queue_adaptor_test.rb → test_mysql_message_queue_adapter.rb} +94 -30
  34. data/test/test_skynet.rb +11 -11
  35. data/test/test_skynet_install_generator.rb +0 -4
  36. data/test/test_skynet_job.rb +717 -0
  37. data/test/test_skynet_manager.rb +142 -0
  38. data/test/test_skynet_message.rb +229 -0
  39. data/test/test_skynet_task.rb +24 -0
  40. data/test/{tuplespace_message_queue_test.rb → test_tuplespace_message_queue.rb} +25 -30
  41. data/website/index.html +56 -16
  42. data/website/index.txt +55 -25
  43. data/website/template.rhtml +1 -1
  44. metadata +29 -13
  45. data/app_generators/skynet_install/templates/skynet_console +0 -16
  46. data/bin/skynet_console +0 -9
  47. data/sometest.rb +0 -23
  48. data/test/all_models_test.rb +0 -139
  49. data/test/skynet_manager_test.rb +0 -107
  50. data/test/skynet_message_test.rb +0 -42
  51. 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] = 4
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[:QUEUE_DATABASE] and not @@db_set
41
- begin
42
- SkynetMessageQueue.establish_connection Skynet::CONFIG[:QUEUE_DATABASE]
43
- SkynetWorkerQueue.establish_connection Skynet::CONFIG[:QUEUE_DATABASE]
44
- rescue ActiveRecord::AdapterNotSpecified => e
45
- warn "#{Skynet::CONFIG[:QUEUE_DATABASE]} not defined as a database adaptor #{e.message}"
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.keys.each do |field|
69
- value = template[fields[field]]
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.values.each do |field|
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
- def take_next_task(curver,timeout=0,payload_type=nil)
104
- timeout = Skynet::CONFIG[:NEXT_TASK_TIMEOUT] if timeout < 1
105
- debug "TASK NEXT TASK!!!!!!! timeout: #{timeout}"
106
- message = nil
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
- # debug "start #{Time.now} timeout #{start + timeout}"
111
- message_row = take(Skynet::Message.next_task_template(curver,payload_type),start,timeout)
112
- next unless message_row
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
- begin
115
- message = Skynet::Message.new(message_row.attributes)
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
- update_message(result_message,timeout)
200
+ update_message_with_result(result_message,timeout)
139
201
  end
140
202
 
141
- def update_message(message,timeout=nil)
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
- rows = update(%{
145
- update skynet_message_queues
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
- raw_payload = '#{message.raw_payload}',
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
- update_message(message.error_message(error),timeout)
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
- if hostname
261
- SkynetWorkerQueue.connection.execute("delete from skynet_worker_queues where hostname = '#{hostname}'")
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
- SkynetWorkerQueue.destroy_all
340
+ false
264
341
  end
265
342
  end
266
343
 
267
344
  def set_worker_version(ver=nil)
268
345
  ver ||= 1
269
- # SkynetWorkerQueue.transaction do
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
- # ver = SkynetWorkerQueue.connection.select_value("select min(version) from skynet_message_queues where tasktype = 'task'")
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
- set_worker_version(1)
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.destroy_all
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.delete("delete from skynet_message_queues where expire_time BETWEEN 1 AND '#{Time.now.to_f}'")
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 => 0,
306
- :taken_tasks => 0,
307
- :untaken_tasks => 0,
308
- :taken_master_tasks => 0,
309
- :taken_task_tasks => 0,
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
- :processed => 0,
313
- :number_of_workers => 0,
314
- :active_workers => 0,
315
- :idle_workers => 0,
316
- :hosts => 0,
317
- :masters => 0,
318
- :taskworkers => 0,
319
- :time => Time.now.to_f
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 skynet_message_queues
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
- else
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
- rows = SkynetMessageQueue.connection.update(sql)
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.1
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 take(template,start=Time.now,timeout=1,sleep_time=nil)
442
- conditions = template_to_conditions(template)
443
- sleep_time ||= timeout
444
- transaction_id = get_unique_id(1)
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
- if Time.now.to_f > start.to_f + timeout
504
- debug "MISSTIMEOUT PTYPE #{payload_type} #{temperature(payload_type)}"
505
- raise Skynet::RequestExpiredError.new
506
- else
507
- sleepy = rand(sleep_time * 0.5 )
508
- debug "EMPTY QUEUE #{temperature(payload_type)} SLEEPING: #{sleep_time} / #{sleepy}"
509
- sleep sleepy
510
- return false
511
- end
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
- ## try SQRT *2
545
- ## try POW 0.6 or .75
546
- def set_temperature(payload_type,conditions)
547
- temp_q_conditions = "type = '#{payload_type}' AND updated_on < '#{(Time.now - 5).strftime('%Y-%m-%d %H:%M:%S')}'"
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
- temperature = SkynetWorkerQueue.connection.select_value(%{select (
552
- CASE WHEN (@t:=FLOOR(
553
- POW(@c:=(SELECT count(*) FROM skynet_message_queues WHERE #{conditions}
554
- ),0.6))) < 1 THEN 1 ELSE @t END) from skynet_queue_temperature WHERE #{temp_q_conditions}
555
- })
556
- if temperature
557
- update("UPDATE skynet_queue_temperature SET temperature = #{temperature} WHERE #{temp_q_conditions}")
558
- @@temperature[payload_type.to_sym] = temperature.to_f
559
- else
560
- sleepy = rand Skynet::CONFIG[:MYSQL_TEMPERATURE_CHANGE_SLEEP]
561
- sleep sleepy
562
- @@temperature[payload_type.to_sym] = SkynetWorkerQueue.connection.select_value("select temperature from skynet_queue_temperature WHERE type = '#{payload_type}'").to_f
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 skynet_message_queues WHERE #{conditions})) < 1 THEN 1 ELSE @t END")
565
- # tasks = SkynetMessageQueue.connection.select_value("select count(*) from skynet_message_queues WHERE #{conditions}").to_i
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