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.
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