corosync-commander 0.0.1 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90dec889b8284d9eacfcf3ad9fc2e30156790428
4
- data.tar.gz: 8beb44d2adfb7f708b48e0f986f8e1e8c68300c5
3
+ metadata.gz: 0f9f90af1b3fac9c2164e54e664cae15be76ef65
4
+ data.tar.gz: ccddc4d241a87b80def6ce1ad6dae3073a520e5e
5
5
  SHA512:
6
- metadata.gz: a65aa251126d8f329c7f9d125b9c4d7508270a5fedcf49e2b147612073ceadaddae4afc13683a4355ef6dd0a86830861f346a70c5f5df78f78c3505cec9a5671
7
- data.tar.gz: 5ff4ac01de166776a97c30fa7a8d7f5338383bb927e89c2ea68964b06609a9969f1942f01ec044f2597473a67a0195154df7e618d783ffca5be34e70acaf4595
6
+ metadata.gz: 47de186e7ba2e4bfc304761b6e704f776ded20ab55d3428fdb5dbd5d5e886511fc80ee438003d357a269c5f66703f5c68142502507c38a6b0d55004ccccdf3c1
7
+ data.tar.gz: 47136a7dbb32771007e34f76a4ae86341b14d360b6cbfa7988e34c05533451961991c2564cce2bcf6da891190d3cbba17129c9f1017649066c472902223764ad
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.gem
2
+ .yardoc
data/Rakefile CHANGED
@@ -2,6 +2,79 @@
2
2
  desc 'Run tests'
3
3
  require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new(:test) do |t|
5
+ ENV['RUBY_THREAD_MACHINE_STACK_SIZE'] = '1572864'
5
6
  t.pattern = 'spec/**/*.rb'
6
7
  t.rspec_opts = '-c -f d --fail-fast'
7
8
  end
9
+
10
+
11
+ ########################################
12
+ @gemspec_file = Dir.glob('*.gemspec').first
13
+ def spec
14
+ require 'rubygems' unless defined? Gem::Specification
15
+ @spec ||= eval(File.read(@gemspec_file))
16
+ end
17
+
18
+ desc 'Bump version'
19
+ task 'version' do
20
+ file = 'lib/version.rb'
21
+ const = 'CorosyncCommander::GEM_VERSION'
22
+
23
+ current_version = %x{ruby -e "require './#{file}'; puts #{const}"}.chomp
24
+ current_version_commit = %x{git rev-parse --verify #{current_version} 2>/dev/null}.chomp
25
+ current_head_commit = %x{git rev-parse HEAD}.chomp
26
+ if current_version_commit != '' and current_version_commit != current_head_commit then
27
+ # there have been commits since the current version
28
+
29
+ next_version = current_version.split('.')
30
+ next_version[-1] = next_version.last.to_i + 1
31
+ next_version = next_version.join('.')
32
+ print "Next version? (#{next_version}): "
33
+ response = STDIN.gets.chomp
34
+ if response != '' then
35
+ raise StandardError, "Not a valid version" unless response.match(/^[0-9\.]$/)
36
+ next_version = response
37
+ end
38
+
39
+ const_name = const.sub(/^.+:/, '')
40
+ new_file_content = ''
41
+ File.open(file, 'r') do |file|
42
+ file.each_line do |line|
43
+ new_file_content += line.sub(/(#{const_name}\s*=\s*['"])#{current_version}(['"])/, "\\1#{next_version}\\2")
44
+ end
45
+ end
46
+ File.open(file, 'w') do |file|
47
+ file.write new_file_content
48
+ end
49
+ message = %x{git log #{current_version_commit}..HEAD --pretty=format:'* %s%n %an (%ai) - @%h%n'}.gsub(/'/, "'\\\\''")
50
+
51
+ sh "git commit -m 'Version: #{next_version}\n\n#{message}' #{file}"
52
+
53
+ @spec = nil
54
+ end
55
+ end
56
+
57
+ desc 'Build gem file'
58
+ task 'build' do
59
+ sh "gem build #{@gemspec_file}"
60
+ end
61
+
62
+ desc 'Publish gem file'
63
+ task 'publish' do
64
+ gem_file = "#{spec.name}-#{spec.version}.gem"
65
+ sh "git tag #{spec.version}"
66
+ sh "git push"
67
+ sh "gem push #{gem_file}"
68
+ end
69
+
70
+ desc 'Release a new version'
71
+ task 'release' do
72
+ raise StandardError, "Not on master branch" if %x{git rev-parse --abbrev-ref HEAD}.chomp != "master"
73
+ raise StandardError, "Uncommitted files" if %x{git status --porcelain}.chomp.size != 0
74
+
75
+ [:test, :version, :build, :publish].each do |task|
76
+ puts "# #{task}\n"
77
+ Rake::Task[task].execute
78
+ puts "\n"
79
+ end
80
+ end
@@ -95,6 +95,9 @@ class CorosyncCommander::Execution
95
95
  return if !@queue.is_a?(Queue) # we've called `clear`
96
96
 
97
97
  while @pending_members.nil?
98
+ if Thread.current.object_id == @cc.dispatch_thread.object_id then
99
+ raise StandardError, "Deadlock! Can not call wait for responses on callback thread."
100
+ end
98
101
  message = @queue.shift
99
102
 
100
103
  next if message.type == 'leave' # we havent received the echo, so we dont care yet
@@ -27,7 +27,7 @@ require File.expand_path('../corosync_commander/callback_list', __FILE__)
27
27
  # hostnames = []
28
28
  # begin
29
29
  # enum.each do |response, node|
30
- # hostname << response
30
+ # hostnames << response
31
31
  # end
32
32
  # rescue CorosyncCommander::RemoteException => e
33
33
  # puts "Caught remote exception: #{e}"
@@ -51,6 +51,9 @@ class CorosyncCommander
51
51
 
52
52
  attr_reader :execution_queues
53
53
 
54
+ # @!visibility private
55
+ attr_reader :dispatch_thread
56
+
54
57
  # Creates a new instance and connects to CPG.
55
58
  # If a group name is provided, it will join that group. Otherwise it will only connect. This is so that you can establish the command callbacks and avoid NotImplementedError exceptions
56
59
  # @param group_name [String] Name of the group to join
@@ -63,6 +66,9 @@ class CorosyncCommander
63
66
 
64
67
  @cpg_members = nil
65
68
 
69
+ @leader_pool = []
70
+ @leader_pool.extend(Sync_m)
71
+
66
72
  # we can either share the msgid counter across all threads, or have a msgid counter on each thread and send the thread ID with each message. I prefer the former
67
73
  @next_execution_id = 0
68
74
  @next_execution_id_mutex = Mutex.new
@@ -127,31 +133,41 @@ class CorosyncCommander
127
133
  def cpg_message(message, sender)
128
134
  message = CorosyncCommander::Execution::Message.from_cpg_message(message, sender)
129
135
 
130
- if sender == @cpg.member || message.recipients.include?(@cpg.member)
136
+ # This is the possible message classifications
137
+ # Command echo (potentially also a command to us)
138
+ # Response echo
139
+ # Command to us
140
+ # Response to us
141
+ # Command to someone else
142
+ # Response to someone else
143
+
144
+ if message.type == 'command' and sender == @cpg.member then
145
+ # It's a command echo
131
146
  execution_queue = nil
132
147
  @execution_queues.sync_synchronize(:SH) do
133
148
  execution_queue = @execution_queues[message.execution_id]
134
149
  end
135
150
  if !execution_queue.nil? then
136
151
  # someone is listening
137
- if sender == @cpg.member and message.type == 'command' then
138
- # the Execution object needs a list of the members at the time it's message was received
139
- message_echo = message.dup
140
- message_echo.type = 'echo'
141
- message_echo.content = @cpg_members
142
- execution_queue << message_echo
143
- else
144
- execution_queue << message
145
- end
152
+ message_echo = message.dup
153
+ message_echo.type = 'echo'
154
+ message_echo.content = @cpg_members
155
+ execution_queue << message_echo
156
+ end
157
+ elsif message.type != 'command' and message.recipients.include?(@cpg.member) then
158
+ # It's a response to us
159
+ execution_queue = nil
160
+ @execution_queues.sync_synchronize(:SH) do
161
+ execution_queue = @execution_queues[message.execution_id]
162
+ end
163
+ if !execution_queue.nil? then
164
+ # someone is listening
165
+ execution_queue << message
146
166
  end
147
167
  end
148
168
 
149
- if message.recipients.size > 0 and !message.recipients.include?(@cpg.member) then
150
- return
151
- end
152
-
153
- if message.type == 'command' then
154
- # we received a command from another node
169
+ if message.type == 'command' and (message.recipients.size == 0 or message.recipients.include?(@cpg.member)) then
170
+ # It's a command to us
155
171
  begin
156
172
  # see if we've got a registered callback
157
173
  command_callback = nil
@@ -163,7 +179,7 @@ class CorosyncCommander
163
179
  end
164
180
 
165
181
  command_args = message.content[1]
166
- reply_value = command_callback.call(*command_args)
182
+ reply_value = command_callback.call(message.sender, *command_args)
167
183
  message_reply = message.reply(reply_value)
168
184
  @cpg.send(message_reply)
169
185
  rescue => e
@@ -178,6 +194,18 @@ class CorosyncCommander
178
194
  def cpg_confchg(member_list, left_list, join_list)
179
195
  @cpg_members = member_list
180
196
 
197
+ if leader_position == -1 then # this will only happen on join
198
+ @leader_pool.sync_synchronize(:EX) do
199
+ @leader_pool.replace(member_list.to_a)
200
+ end
201
+ elsif left_list.size > 0 then
202
+ @leader_pool.sync_synchronize(:EX) do
203
+ @leader_pool.delete_if {|m| left_list.include?(m)}
204
+ end
205
+ end
206
+
207
+ @confchg_callback.call(member_list, left_list, join_list) if @confchg_callback
208
+
181
209
  # we look for any members leaving the cluster, and if so we notify all threads that are waiting for a response that they may have just lost a node
182
210
  return if left_list.size == 0
183
211
 
@@ -194,6 +222,10 @@ class CorosyncCommander
194
222
  end
195
223
  end
196
224
 
225
+ def on_confchg(&block)
226
+ @confchg_callback = block
227
+ end
228
+
197
229
  # @!attribute [r] commands
198
230
  # @return [CorosyncCommander::CallbackList] List of command callbacks
199
231
  def commands
@@ -230,4 +262,29 @@ class CorosyncCommander
230
262
  end
231
263
  end
232
264
  private :execution_queue_finalizer
265
+
266
+ # Gets the member's position in the leadership queue.
267
+ # The leadership position is simply how many nodes currently in the group were in the group before we joined.
268
+ # @return [Integer]
269
+ def leader_position
270
+ @leader_pool.synchronize(:SH) do
271
+ @leader_pool.size - 1
272
+ end
273
+ end
274
+
275
+ # Indicates whether we are the group leader.
276
+ # If we are the leader, it means that we are the oldest member of the group.
277
+ # This is slightly different than just calling `leader_position == 0` in that if it is -1 (meaning we havent received the CPG confchg callback yet), we wait for the CPG join to complete.
278
+ # @return [Boolean]
279
+ def leader?
280
+ position = nil
281
+ loop do
282
+ position = leader_position
283
+ break if position != -1
284
+ Thread.pass # let the dispatch thread run so we can get our join message
285
+ # This isn't ideal as if the dispatch thread doesn't immediatly complete the join, we start spinning.
286
+ # But the only other way is to use condition variables, which combined with Sync_m, would just be really messy and stupidly complex (and I don't want to go to a plain mutex and lose the ability to use shared locks).
287
+ end
288
+ position == 0
289
+ end
233
290
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class CorosyncCommander
2
- GEM_VERSION = '0.0.1'
2
+ GEM_VERSION = '0.0.2'
3
3
  end
@@ -2,22 +2,6 @@ require 'spec_helper'
2
2
  require 'timeout'
3
3
 
4
4
  describe CorosyncCommander do
5
- def fork_execute(wait, cc_recip, command, *args)
6
- recip = cc_recip.cpg.member
7
- pid = fork do
8
- cc = CorosyncCommander.new(cc_recip.cpg.group)
9
- exe = cc.execute([recip], command, *args)
10
- exe.wait
11
- exit!(0)
12
- end
13
- if wait then
14
- status = Process.wait2(pid)
15
- status.exitstatus
16
- else
17
- pid
18
- end
19
- end
20
-
21
5
  before(:all) do
22
6
  Timeout.timeout(1) do
23
7
  @cc = CorosyncCommander.new("CorosyncCommander RSPEC #{Random.rand(2 ** 32)}")
@@ -44,7 +28,7 @@ describe CorosyncCommander do
44
28
  end
45
29
 
46
30
  it 'registers a callback (block style)' do
47
- @cc.commands.register 'summation' do |arg1,arg2|
31
+ @cc.commands.register 'summation' do |sender,arg1,arg2|
48
32
  arg1 + arg2
49
33
  end
50
34
 
@@ -52,7 +36,7 @@ describe CorosyncCommander do
52
36
  end
53
37
 
54
38
  it 'registers a callback (assignment style)' do
55
- @cc.commands['summation'] = Proc.new do |arg1,arg2|
39
+ @cc.commands['summation'] = Proc.new do |sender,arg1,arg2|
56
40
  arg1 + arg2
57
41
  end
58
42
  expect(@cc.commands['summation']).to be_a(Proc)
@@ -95,7 +79,7 @@ describe CorosyncCommander do
95
79
  num1 = Random.rand(2 ** 32)
96
80
  num2 = Random.rand(2 ** 32)
97
81
 
98
- @cc.commands.register('summation2') do |number|
82
+ @cc.commands.register('summation2') do |sender,number|
99
83
  sum += number
100
84
  end
101
85
 
@@ -178,4 +162,21 @@ describe CorosyncCommander do
178
162
  expect(responses.find_all{|r| r == 'OK'}.size).to eq(2)
179
163
  end
180
164
  end
165
+
166
+ it 'checks for leadership (true)' do
167
+ expect(@cc.leader?).to eq(true)
168
+ end
169
+
170
+ it 'checks for leadership (false)' do
171
+ forkpid = fork do
172
+ cc = CorosyncCommander.new(@cc.cpg.group)
173
+ if !cc.leader? then
174
+ exit 0 # we aren't a leader, as expected
175
+ end
176
+ exit 1 # something went wrong
177
+ end
178
+
179
+ status = Process.wait2(forkpid)
180
+ expect(status[1]).to eq(0)
181
+ end
181
182
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: corosync-commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Hemmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-06 00:00:00.000000000 Z
11
+ date: 2013-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: corosync