officer 0.8.6 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTORS +2 -0
- data/README.markdown +45 -9
- data/VERSION +1 -1
- data/bin/officer +1 -48
- data/lib/officer/client.rb +17 -7
- data/lib/officer/connection.rb +7 -2
- data/lib/officer/runner.rb +101 -0
- data/lib/officer/server.rb +38 -2
- data/lib/officer.rb +2 -0
- data/officer.gemspec +10 -14
- data/spec/integration/officer_spec.rb +78 -29
- data/spec/spec_helper.rb +2 -1
- metadata +67 -100
data/CONTRIBUTORS
ADDED
data/README.markdown
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Officer is designed to help you coordinate distributed processes and avoid race conditions. Inspiration comes from [elock](http://github.com/dustin/elock).
|
4
4
|
|
5
|
+
Read more in my blog post: [http://remesch.com/officer-the-ruby-lock-server-and-client](http://remesch.com/officer-the-ruby-lock-server-and-client)
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
gem install officer
|
@@ -9,23 +11,37 @@ Officer is designed to help you coordinate distributed processes and avoid race
|
|
9
11
|
## Usage
|
10
12
|
|
11
13
|
Officer uses the 'daemons' gem to simplify creating long lived background processes.
|
12
|
-
Here are some simple examples in case you aren't familiar with it.
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
Help information:
|
16
|
+
officer --help
|
17
|
+
|
18
|
+
Usage: officer [-hofplsd]
|
19
|
+
-h, --host=HOST The hostname or IP to bind to (default: 0.0.0.0)
|
20
|
+
-o, --socket-type=OPTION TCP or UNIX (default: TCP)
|
21
|
+
-f, --socket-file=FILE Full path and name to the UNIX socket file (only used if --socket-type=UNIX, default: /tmp/officer.sock)
|
22
|
+
-p, --port=PORT The port to listen on (default: 11500)
|
23
|
+
-l, --log-level Set the log level to debug, info, or error (default: error)
|
24
|
+
-s, --stats Log stats every 5 seconds (default: off, required log level: info)
|
25
|
+
-d, --pid-dir Set directory where pid file will be saved (default: operating system's run directory)
|
26
|
+
--help
|
16
27
|
|
17
|
-
Officer's help information:
|
18
|
-
sudo officer run -- --help
|
19
28
|
|
20
29
|
Run Officer in the foreground with full logging and statistics:
|
21
|
-
|
30
|
+
|
31
|
+
officer run -- -l debug -s -d /tmp
|
22
32
|
|
23
33
|
Run Officer in the background (production mode) and listen on a specific IP and port:
|
24
|
-
|
34
|
+
|
35
|
+
officer start -- -h 127.0.0.1 -p 9999 -d /tmp
|
36
|
+
|
37
|
+
### Other notes:
|
25
38
|
|
26
39
|
- The server listens on 0.0.0.0:11500 by default.
|
27
40
|
- All debugging and error output goes to stdout for now.
|
28
|
-
-
|
41
|
+
- By default, a pid file is created in /var/run and stdout is written to /var/log/officer.output. This will require root permissions which is normally a bad idea. You can avoid this by picking a different directory (example: officer start -- -d /tmp).
|
42
|
+
- I personally run Officer in production using Ruby Enterprise Edition (REE) which is based on Ruby 1.8.7.
|
43
|
+
- RVM and JRuby users should check the [Known Issues](https://github.com/chadrem/officer/wiki/Known-Issues) wiki page.
|
44
|
+
- UNIX domain sockets are supported (example: officer start -- -o UNIX -p /tmp)
|
29
45
|
|
30
46
|
## Ruby Client
|
31
47
|
|
@@ -40,6 +56,8 @@ Options:
|
|
40
56
|
|
41
57
|
- :host => Hostname or IP address of the server to bind to (default: 0.0.0.0).
|
42
58
|
- :port => TCP Port to listen on (default: 11500).
|
59
|
+
- :socket_type => TCP or UNIX (default: TCP).
|
60
|
+
- :socket_file => Full path to the server's UNIX domain socket file (default: /tmp/officer.sock). This option is only used when the socket type is UNIX.
|
43
61
|
|
44
62
|
|
45
63
|
### Lock
|
@@ -81,6 +99,13 @@ Options:
|
|
81
99
|
- Useful if you use Officer with Phusion Passenger and smart spawning. See [Passenger's documentation](http://www.modrails.com/documentation/Users%20guide%20Apache.html#_smart_spawning_gotcha_1_unintential_file_descriptor_sharing) for more information.
|
82
100
|
|
83
101
|
|
102
|
+
### Disconnect
|
103
|
+
|
104
|
+
client.disconnect
|
105
|
+
|
106
|
+
- Close the connection to the server.
|
107
|
+
|
108
|
+
|
84
109
|
### Show locks
|
85
110
|
|
86
111
|
client.locks
|
@@ -100,6 +125,17 @@ Options:
|
|
100
125
|
client.my_locks
|
101
126
|
|
102
127
|
|
128
|
+
## Contributing to Officer
|
129
|
+
|
130
|
+
1. Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
131
|
+
2. Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
132
|
+
3. Fork the project.
|
133
|
+
4. Start a feature/bugfix branch.
|
134
|
+
5. Commit and push until you are happy with your contribution.
|
135
|
+
6. Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
136
|
+
7. Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
137
|
+
|
138
|
+
|
103
139
|
## Copyright
|
104
140
|
|
105
|
-
Copyright (c) 2010 Chad Remesch. See LICENSE for details.
|
141
|
+
Copyright (c) 2010 - 2012 Chad Remesch. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/bin/officer
CHANGED
@@ -3,51 +3,4 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'officer'
|
5
5
|
|
6
|
-
|
7
|
-
:dir_mode => :system,
|
8
|
-
:multiple => false,
|
9
|
-
:monitor => true,
|
10
|
-
:log_output => true
|
11
|
-
}
|
12
|
-
|
13
|
-
def parse_command_line
|
14
|
-
if ARGV.include? '--'
|
15
|
-
ARGV.slice! 0..ARGV.index('--')
|
16
|
-
end
|
17
|
-
|
18
|
-
Choice.options do
|
19
|
-
option :host do
|
20
|
-
short '-h'
|
21
|
-
long '--host=HOST'
|
22
|
-
desc 'The hostname or IP to bind to (default: 0.0.0.0)'
|
23
|
-
end
|
24
|
-
|
25
|
-
option :port do
|
26
|
-
short '-p'
|
27
|
-
long '--port=PORT'
|
28
|
-
desc 'The port to listen on (default: 11500)'
|
29
|
-
cast Integer
|
30
|
-
end
|
31
|
-
|
32
|
-
option :log_level do
|
33
|
-
short '-l'
|
34
|
-
long '--log-level'
|
35
|
-
desc 'Set the log level to debug, info, or error (default: error)'
|
36
|
-
end
|
37
|
-
|
38
|
-
option :stats do
|
39
|
-
short '-s'
|
40
|
-
long '--stats'
|
41
|
-
desc 'Log stats every 5 seconds (default: off, required log level: info)'
|
42
|
-
end
|
43
|
-
|
44
|
-
option :help do
|
45
|
-
long '--help'
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
Daemons.run_proc('officer', daemon_options) do
|
51
|
-
parse_command_line
|
52
|
-
Officer::Server.new(Choice.choices).run
|
53
|
-
end
|
6
|
+
Officer::Runner.new.run
|
data/lib/officer/client.rb
CHANGED
@@ -15,6 +15,8 @@ module Officer
|
|
15
15
|
|
16
16
|
class Client
|
17
17
|
def initialize options={}
|
18
|
+
@socket_type = options[:socket_type] || 'TCP'
|
19
|
+
@socket_file = options[:socket_file] || '/tmp/officer.sock'
|
18
20
|
@host = options[:host] || 'localhost'
|
19
21
|
@port = options[:port] || 11500
|
20
22
|
@namespace = options[:namespace]
|
@@ -27,6 +29,11 @@ module Officer
|
|
27
29
|
connect
|
28
30
|
end
|
29
31
|
|
32
|
+
def disconnect
|
33
|
+
@socket.close if @socket
|
34
|
+
@socket = nil
|
35
|
+
end
|
36
|
+
|
30
37
|
def lock name, options={}
|
31
38
|
result = execute :command => 'lock', :name => name_with_ns(name),
|
32
39
|
:timeout => options[:timeout], :queue_max => options[:queue_max]
|
@@ -42,7 +49,7 @@ module Officer
|
|
42
49
|
response = lock name, options
|
43
50
|
result = response['result']
|
44
51
|
queue = (response['queue'] || []).join ','
|
45
|
-
|
52
|
+
|
46
53
|
raise LockTimeoutError.new("queue=#{queue}") if result == 'timed_out'
|
47
54
|
raise LockQueuedMaxError.new("queue=#{queue}") if result == 'queue_maxed'
|
48
55
|
raise LockError unless %w(acquired already_acquired).include?(result)
|
@@ -80,13 +87,16 @@ module Officer
|
|
80
87
|
def connect
|
81
88
|
raise AlreadyConnectedError if @socket
|
82
89
|
|
83
|
-
|
84
|
-
|
85
|
-
|
90
|
+
case @socket_type
|
91
|
+
when 'TCP'
|
92
|
+
@socket = TCPSocket.new @host, @port.to_i
|
93
|
+
when 'UNIX'
|
94
|
+
@socket = UNIXSocket.new @socket_file
|
95
|
+
else
|
96
|
+
raise "Invalid socket type: #{@socket_type}"
|
97
|
+
end
|
86
98
|
|
87
|
-
|
88
|
-
@socket.close if @socket
|
89
|
-
@socket = nil
|
99
|
+
@socket.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
|
90
100
|
end
|
91
101
|
|
92
102
|
def execute command
|
data/lib/officer/connection.rb
CHANGED
@@ -92,7 +92,12 @@ module Officer
|
|
92
92
|
include LockStoreCallbacks
|
93
93
|
|
94
94
|
def to_host_s
|
95
|
-
|
95
|
+
begin
|
96
|
+
@to_host_s ||= non_cached_to_host_s
|
97
|
+
rescue ArgumentError
|
98
|
+
# we assume unix socket, so no ip/port info
|
99
|
+
@to_host_s ||= 'UNIX socket client'
|
100
|
+
end
|
96
101
|
end
|
97
102
|
|
98
103
|
private
|
@@ -118,4 +123,4 @@ module Officer
|
|
118
123
|
end
|
119
124
|
|
120
125
|
end
|
121
|
-
end
|
126
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Officer
|
2
|
+
|
3
|
+
class Runner
|
4
|
+
def run
|
5
|
+
hack_argv
|
6
|
+
set_choices
|
7
|
+
unhack_argv
|
8
|
+
set_daemon_options
|
9
|
+
run_daemon
|
10
|
+
end
|
11
|
+
|
12
|
+
# HACK: Both the Choice and Daemons gems will parse ARGV so I
|
13
|
+
# modify ARGV for Choice and then restore it for Daemons.
|
14
|
+
def hack_argv
|
15
|
+
if ARGV.include? '--'
|
16
|
+
@saved_args = ARGV.slice! 0..ARGV.index('--')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def unhack_argv
|
21
|
+
ARGV.unshift(*@saved_args) if @saved_args
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_choices
|
25
|
+
|
26
|
+
Choice.options do
|
27
|
+
option :host do
|
28
|
+
short '-h'
|
29
|
+
long '--host=HOST'
|
30
|
+
desc 'The hostname or IP to bind to (default: 0.0.0.0)'
|
31
|
+
end
|
32
|
+
|
33
|
+
option :socket_type do
|
34
|
+
short '-o'
|
35
|
+
long '--socket-type=OPTION'
|
36
|
+
desc 'TCP or UNIX (default: TCP)'
|
37
|
+
default 'TCP'
|
38
|
+
validate /^(TCP|UNIX)$/
|
39
|
+
end
|
40
|
+
|
41
|
+
option :socket_file do
|
42
|
+
short '-f'
|
43
|
+
long '--socket-file=FILE'
|
44
|
+
desc "Full path and name to the UNIX domain socket file (only used with '-o UNIX', default: /tmp/officer.sock)"
|
45
|
+
end
|
46
|
+
|
47
|
+
option :port do
|
48
|
+
short '-p'
|
49
|
+
long '--port=PORT'
|
50
|
+
desc 'The port to listen on (default: 11500)'
|
51
|
+
cast Integer
|
52
|
+
end
|
53
|
+
|
54
|
+
option :log_level do
|
55
|
+
short '-l'
|
56
|
+
long '--log-level'
|
57
|
+
desc 'Set the log level to debug, info, or error (default: error)'
|
58
|
+
end
|
59
|
+
|
60
|
+
option :stats do
|
61
|
+
short '-s'
|
62
|
+
long '--stats'
|
63
|
+
desc 'Log stats every 5 seconds (default: off, required log level: info)'
|
64
|
+
end
|
65
|
+
|
66
|
+
option :pid_dir do
|
67
|
+
short '-d'
|
68
|
+
long '--pid-dir'
|
69
|
+
desc "Set directory where pid file will be saved (default: operating system's run directory)"
|
70
|
+
end
|
71
|
+
|
72
|
+
option :help do
|
73
|
+
long '--help'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
@choices = Choice.choices
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_daemon_options
|
81
|
+
@daemon_options = {
|
82
|
+
:dir_mode => :system,
|
83
|
+
:multiple => false,
|
84
|
+
:monitor => true,
|
85
|
+
:log_output => true
|
86
|
+
}
|
87
|
+
|
88
|
+
if @choices[:pid_dir]
|
89
|
+
@daemon_options[:dir_mode] = :normal
|
90
|
+
@daemon_options[:dir] = @choices[:pid_dir]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def run_daemon
|
95
|
+
Daemons.run_proc('officer', @daemon_options) do
|
96
|
+
Officer::Server.new(@choices).run
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/lib/officer/server.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
module Officer
|
2
2
|
|
3
3
|
class Server
|
4
|
+
class ShutdownConnection < EventMachine::Connection
|
5
|
+
def unbind
|
6
|
+
EM::stop_event_loop
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
def initialize params={}
|
11
|
+
@semaphore = Mutex.new
|
12
|
+
set_running(false)
|
13
|
+
|
5
14
|
@params = params
|
6
15
|
|
16
|
+
params[:socket_type] ||= 'TCP'
|
7
17
|
params[:port] ||= 11500
|
8
18
|
params[:host] ||= '0.0.0.0'
|
19
|
+
params[:socket_file] ||= '/tmp/officer.sock'
|
9
20
|
params[:stats] ||= false
|
10
21
|
params[:log_level] ||= 'error'
|
11
22
|
|
@@ -15,14 +26,39 @@ module Officer
|
|
15
26
|
def run
|
16
27
|
EM.error_handler {|e| Officer::Log.error e}
|
17
28
|
|
29
|
+
EM.kqueue = true if EM.kqueue?
|
30
|
+
EM.epoll = true if EM.epoll?
|
31
|
+
|
18
32
|
EM::run do
|
19
33
|
if @params[:stats]
|
20
34
|
EM::PeriodicTimer.new(5) {Officer::LockStore.instance.log_state}
|
21
35
|
end
|
22
36
|
|
23
|
-
|
37
|
+
if @enable_shutdown_port
|
38
|
+
EM::start_server '127.0.0.1', 11501, ShutdownConnection
|
39
|
+
end
|
40
|
+
|
41
|
+
if @params[:socket_type] == 'TCP'
|
42
|
+
EM::start_server @params[:host], @params[:port], Connection::Connection
|
43
|
+
else
|
44
|
+
EM::start_unix_domain_server @params[:socket_file], Connection::Connection
|
45
|
+
end
|
46
|
+
|
47
|
+
set_running(true)
|
24
48
|
end
|
49
|
+
|
50
|
+
set_running(false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def running?
|
54
|
+
@semaphore.synchronize {@running}
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def set_running value
|
60
|
+
@semaphore.synchronize {@running = value}
|
25
61
|
end
|
26
62
|
end
|
27
63
|
|
28
|
-
end
|
64
|
+
end
|
data/lib/officer.rb
CHANGED
@@ -3,6 +3,7 @@ require 'singleton'
|
|
3
3
|
require 'set'
|
4
4
|
require 'logger'
|
5
5
|
require 'delegate'
|
6
|
+
require 'thread'
|
6
7
|
|
7
8
|
# Gems.
|
8
9
|
require 'rubygems'
|
@@ -17,5 +18,6 @@ require 'officer/log'
|
|
17
18
|
require 'officer/commands'
|
18
19
|
require 'officer/connection'
|
19
20
|
require 'officer/lock_store'
|
21
|
+
require 'officer/runner'
|
20
22
|
require 'officer/server'
|
21
23
|
require 'officer/client'
|
data/officer.gemspec
CHANGED
@@ -4,15 +4,14 @@
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
7
|
+
s.name = "officer"
|
8
|
+
s.version = "0.9.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Chad Remesch"]
|
12
|
-
s.date =
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
s.email = %q{chad@remesch.com}
|
12
|
+
s.date = "2012-03-16"
|
13
|
+
s.description = "Officer is designed to help you coordinate distributed processes and avoid race conditions."
|
14
|
+
s.email = "chad@remesch.com"
|
16
15
|
s.executables = ["officer"]
|
17
16
|
s.extra_rdoc_files = [
|
18
17
|
"LICENSE",
|
@@ -22,6 +21,7 @@ Gem::Specification.new do |s|
|
|
22
21
|
".autotest",
|
23
22
|
".document",
|
24
23
|
".rspec",
|
24
|
+
"CONTRIBUTORS",
|
25
25
|
"LICENSE",
|
26
26
|
"README.markdown",
|
27
27
|
"Rakefile",
|
@@ -33,23 +33,19 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/officer/connection.rb",
|
34
34
|
"lib/officer/lock_store.rb",
|
35
35
|
"lib/officer/log.rb",
|
36
|
+
"lib/officer/runner.rb",
|
36
37
|
"lib/officer/server.rb",
|
37
38
|
"officer.gemspec",
|
38
39
|
"spec/integration/officer_spec.rb",
|
39
40
|
"spec/spec_helper.rb"
|
40
41
|
]
|
41
|
-
s.homepage =
|
42
|
+
s.homepage = "http://github.com/chadrem/officer"
|
42
43
|
s.licenses = ["MIT"]
|
43
44
|
s.require_paths = ["lib"]
|
44
|
-
s.rubygems_version =
|
45
|
-
s.summary =
|
46
|
-
s.test_files = [
|
47
|
-
"spec/integration/officer_spec.rb",
|
48
|
-
"spec/spec_helper.rb"
|
49
|
-
]
|
45
|
+
s.rubygems_version = "1.8.17"
|
46
|
+
s.summary = "Ruby lock server and client built on EventMachine."
|
50
47
|
|
51
48
|
if s.respond_to? :specification_version then
|
52
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
53
49
|
s.specification_version = 3
|
54
50
|
|
55
51
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
@@ -1,15 +1,17 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
require "benchmark"
|
3
2
|
|
4
3
|
describe Officer do
|
5
4
|
before do
|
6
|
-
@
|
7
|
-
|
8
|
-
|
5
|
+
@server = Officer::Server.new :stats => true # :log_level => "debug"
|
6
|
+
@server.instance_variable_set("@enable_shutdown_port", true)
|
7
|
+
@server_thread = Thread.new {@server.run}
|
8
|
+
while !@server.running?; end
|
9
9
|
end
|
10
10
|
|
11
11
|
after do
|
12
|
-
|
12
|
+
shutdown_socket = TCPSocket.new("127.0.0.1", 11501)
|
13
|
+
shutdown_socket.close
|
14
|
+
while @server.running?; end
|
13
15
|
end
|
14
16
|
|
15
17
|
describe "COMMAND: with_lock" do
|
@@ -42,7 +44,11 @@ describe Officer do
|
|
42
44
|
it "should allow a client to reset all of its locks (release them all)" do
|
43
45
|
@client.lock("testlock1")
|
44
46
|
@client.lock("testlock2")
|
45
|
-
@client.my_locks
|
47
|
+
actual = @client.my_locks
|
48
|
+
expected = {"value"=>["testlock1", "testlock2"], "result"=>"my_locks"}
|
49
|
+
actual.class.should eq(Hash)
|
50
|
+
actual["value"].sort.should eq(expected["value"].sort)
|
51
|
+
actual["result"].should eq(expected["result"])
|
46
52
|
@client.reset
|
47
53
|
@client.my_locks.should eq({"value"=>[], "result"=>"my_locks"})
|
48
54
|
end
|
@@ -90,8 +96,8 @@ describe Officer do
|
|
90
96
|
it "should allow a client to see all the connections to a server" do
|
91
97
|
connections = @client2.connections
|
92
98
|
|
93
|
-
connections["value"]["127.0.0.1:#{@client1_src_port}"].should eq(["client1_testlock1", "client1_testlock2"])
|
94
|
-
connections["value"]["127.0.0.1:#{@client2_src_port}"].should eq(["client2_testlock1", "client2_testlock2"])
|
99
|
+
connections["value"]["127.0.0.1:#{@client1_src_port}"].sort.should eq(["client1_testlock1", "client1_testlock2"].sort)
|
100
|
+
connections["value"]["127.0.0.1:#{@client2_src_port}"].sort.should eq(["client2_testlock1", "client2_testlock2"].sort)
|
95
101
|
connections["value"].keys.length.should eq(2)
|
96
102
|
connections["result"].should eq("connections")
|
97
103
|
end
|
@@ -163,6 +169,15 @@ describe Officer do
|
|
163
169
|
@client.unlock("testlock")
|
164
170
|
@client.my_locks.should eq({"value"=>[], "result"=>"my_locks"})
|
165
171
|
end
|
172
|
+
|
173
|
+
it "should inform the client they already have a lock if they previously locked it" do
|
174
|
+
@client.lock("testlock")
|
175
|
+
@client.lock("testlock").should eq({"result" => "already_acquired", "name" => "testlock"})
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should inform the client they don't have a lock if they try to unlock a lock that they don't have" do
|
179
|
+
@client.unlock("testlock").should eq({"result" => "release_failed", "name" => "testlock"})
|
180
|
+
end
|
166
181
|
end
|
167
182
|
|
168
183
|
describe "locking options" do
|
@@ -190,6 +205,12 @@ describe Officer do
|
|
190
205
|
)
|
191
206
|
end
|
192
207
|
|
208
|
+
it "should allow a client to set an instant timeout when obtaining a lock (block syntax)" do
|
209
|
+
lambda {
|
210
|
+
@client2.with_lock("testlock", :timeout => 0){}
|
211
|
+
}.should raise_error(Officer::LockTimeoutError, "queue=127.0.0.1:#{@client1_src_port}")
|
212
|
+
end
|
213
|
+
|
193
214
|
it "should allow a client to set a positive integer timeout when obtaining a lock" do
|
194
215
|
time = Benchmark.realtime do
|
195
216
|
@client2.lock("testlock", :timeout => 1).should eq(
|
@@ -203,43 +224,47 @@ describe Officer do
|
|
203
224
|
|
204
225
|
describe "OPTION: queue_max" do
|
205
226
|
before do
|
206
|
-
end
|
207
|
-
|
208
|
-
after do
|
209
|
-
@thread1.terminate
|
210
|
-
@thread2.terminate
|
211
|
-
end
|
212
|
-
|
213
|
-
it "should allow a client to abort when obtaining a lock if too many other clients are waiting for the same lock" do
|
214
227
|
@client1 = Officer::Client.new
|
215
228
|
@client1.lock("testlock")
|
216
229
|
|
217
230
|
@thread1 = Thread.new {
|
218
231
|
@client2 = Officer::Client.new
|
219
232
|
@client2.lock("testlock")
|
220
|
-
raise "This should never execute since the lock request should block"
|
221
233
|
}
|
222
234
|
|
223
235
|
@thread2 = Thread.new {
|
224
236
|
@client3 = Officer::Client.new
|
225
237
|
@client3.lock("testlock")
|
226
|
-
raise "This should never execute since the lock request should block"
|
227
238
|
}
|
228
239
|
|
229
|
-
|
240
|
+
@client4 = Officer::Client.new
|
241
|
+
while @client4.locks["value"]["testlock"].count != 3; end
|
230
242
|
|
231
|
-
@
|
232
|
-
@
|
243
|
+
@client1_src_port = @client1.instance_variable_get('@socket').addr[1]
|
244
|
+
@client2_src_port = @client2.instance_variable_get('@socket').addr[1]
|
245
|
+
@client3_src_port = @client3.instance_variable_get('@socket').addr[1]
|
246
|
+
end
|
233
247
|
|
234
|
-
|
235
|
-
|
236
|
-
client3_src_port = @client3.instance_variable_get('@socket').addr[1]
|
248
|
+
after do
|
249
|
+
end
|
237
250
|
|
238
|
-
|
239
|
-
@client4.lock("testlock", :queue_max => 3)
|
240
|
-
|
241
|
-
|
242
|
-
|
251
|
+
it "should allow a client to abort lock acquisition if the wait queue is too long" do
|
252
|
+
actual = @client4.lock("testlock", :queue_max => 3)
|
253
|
+
expected = {
|
254
|
+
"result" => "queue_maxed",
|
255
|
+
"name" => "testlock",
|
256
|
+
"queue" => ["127.0.0.1:#{@client1_src_port}", "127.0.0.1:#{@client2_src_port}", "127.0.0.1:#{@client3_src_port}"]
|
257
|
+
}
|
258
|
+
actual.class.should eq(Hash)
|
259
|
+
actual["result"].should eq(expected["result"])
|
260
|
+
actual["name"].should eq(expected["name"])
|
261
|
+
actual["queue"].sort.should eq(expected["queue"].sort)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should allow a client to abort lock acquisition if the wait queue is too long (block syntax)" do
|
265
|
+
lambda {
|
266
|
+
@client4.with_lock("testlock", :queue_max => 3) {}
|
267
|
+
}.should raise_error(Officer::LockQueuedMaxError)
|
243
268
|
end
|
244
269
|
end
|
245
270
|
|
@@ -260,5 +285,29 @@ describe Officer do
|
|
260
285
|
end
|
261
286
|
end
|
262
287
|
end
|
288
|
+
|
289
|
+
describe "EXPERIMENTAL: server support for non-blocking clients attempting to release a queued lock request" do
|
290
|
+
before do
|
291
|
+
@client = Officer::Client.new
|
292
|
+
|
293
|
+
@socket = TCPSocket.new "127.0.0.1", 11500
|
294
|
+
@socket = TCPSocket.new "127.0.0.1", 11500
|
295
|
+
end
|
296
|
+
|
297
|
+
after do
|
298
|
+
@client.send("disconnect")
|
299
|
+
@client = nil
|
300
|
+
|
301
|
+
@socket.close
|
302
|
+
@socket = nil
|
303
|
+
end
|
304
|
+
|
305
|
+
it "should inform the client that their request has been de-queued" do
|
306
|
+
@client.lock("testlock")
|
307
|
+
@socket.write("{\"command\":\"lock\",\"name\":\"testlock\"}\n")
|
308
|
+
@socket.write("{\"command\":\"unlock\",\"name\":\"testlock\"}\n")
|
309
|
+
JSON.parse(@socket.gets("\n").chomp).should eq({"result" => "released", "name" => "testlock"})
|
310
|
+
end
|
311
|
+
end
|
263
312
|
end
|
264
313
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
require 'rspec'
|
4
|
+
require "benchmark"
|
5
|
+
require "json"
|
4
6
|
require 'officer'
|
5
7
|
|
6
8
|
# Requires supporting files with custom matchers and macros, etc,
|
@@ -8,5 +10,4 @@ require 'officer'
|
|
8
10
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
11
|
|
10
12
|
RSpec.configure do |config|
|
11
|
-
|
12
13
|
end
|
metadata
CHANGED
@@ -1,108 +1,85 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: officer
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 8
|
9
|
-
- 6
|
10
|
-
version: 0.8.6
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Chad Remesch
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-03-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: rspec
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &70245430020780 !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 31
|
30
|
-
segments:
|
31
|
-
- 2
|
32
|
-
- 4
|
33
|
-
- 0
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
34
21
|
version: 2.4.0
|
35
22
|
type: :development
|
36
|
-
version_requirements: *id001
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: eventmachine
|
39
23
|
prerelease: false
|
40
|
-
|
24
|
+
version_requirements: *70245430020780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: eventmachine
|
27
|
+
requirement: &70245430020300 !ruby/object:Gem::Requirement
|
41
28
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
segments:
|
47
|
-
- 0
|
48
|
-
version: "0"
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
49
33
|
type: :runtime
|
50
|
-
version_requirements: *id002
|
51
|
-
- !ruby/object:Gem::Dependency
|
52
|
-
name: json
|
53
34
|
prerelease: false
|
54
|
-
|
35
|
+
version_requirements: *70245430020300
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &70245430019820 !ruby/object:Gem::Requirement
|
55
39
|
none: false
|
56
|
-
requirements:
|
57
|
-
- -
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
|
60
|
-
segments:
|
61
|
-
- 0
|
62
|
-
version: "0"
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
63
44
|
type: :runtime
|
64
|
-
version_requirements: *id003
|
65
|
-
- !ruby/object:Gem::Dependency
|
66
|
-
name: daemons
|
67
45
|
prerelease: false
|
68
|
-
|
46
|
+
version_requirements: *70245430019820
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: daemons
|
49
|
+
requirement: &70245430019320 !ruby/object:Gem::Requirement
|
69
50
|
none: false
|
70
|
-
requirements:
|
71
|
-
- -
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
|
74
|
-
segments:
|
75
|
-
- 0
|
76
|
-
version: "0"
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
77
55
|
type: :runtime
|
78
|
-
version_requirements: *id004
|
79
|
-
- !ruby/object:Gem::Dependency
|
80
|
-
name: choice
|
81
56
|
prerelease: false
|
82
|
-
|
57
|
+
version_requirements: *70245430019320
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: choice
|
60
|
+
requirement: &70245430018760 !ruby/object:Gem::Requirement
|
83
61
|
none: false
|
84
|
-
requirements:
|
85
|
-
- -
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
|
88
|
-
segments:
|
89
|
-
- 0
|
90
|
-
version: "0"
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
91
66
|
type: :runtime
|
92
|
-
|
93
|
-
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70245430018760
|
69
|
+
description: Officer is designed to help you coordinate distributed processes and
|
70
|
+
avoid race conditions.
|
94
71
|
email: chad@remesch.com
|
95
|
-
executables:
|
72
|
+
executables:
|
96
73
|
- officer
|
97
74
|
extensions: []
|
98
|
-
|
99
|
-
extra_rdoc_files:
|
75
|
+
extra_rdoc_files:
|
100
76
|
- LICENSE
|
101
77
|
- README.markdown
|
102
|
-
files:
|
78
|
+
files:
|
103
79
|
- .autotest
|
104
80
|
- .document
|
105
81
|
- .rspec
|
82
|
+
- CONTRIBUTORS
|
106
83
|
- LICENSE
|
107
84
|
- README.markdown
|
108
85
|
- Rakefile
|
@@ -114,44 +91,34 @@ files:
|
|
114
91
|
- lib/officer/connection.rb
|
115
92
|
- lib/officer/lock_store.rb
|
116
93
|
- lib/officer/log.rb
|
94
|
+
- lib/officer/runner.rb
|
117
95
|
- lib/officer/server.rb
|
118
96
|
- officer.gemspec
|
119
97
|
- spec/integration/officer_spec.rb
|
120
98
|
- spec/spec_helper.rb
|
121
|
-
has_rdoc: true
|
122
99
|
homepage: http://github.com/chadrem/officer
|
123
|
-
licenses:
|
100
|
+
licenses:
|
124
101
|
- MIT
|
125
102
|
post_install_message:
|
126
103
|
rdoc_options: []
|
127
|
-
|
128
|
-
require_paths:
|
104
|
+
require_paths:
|
129
105
|
- lib
|
130
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
107
|
none: false
|
132
|
-
requirements:
|
133
|
-
- -
|
134
|
-
- !ruby/object:Gem::Version
|
135
|
-
|
136
|
-
|
137
|
-
- 0
|
138
|
-
version: "0"
|
139
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
113
|
none: false
|
141
|
-
requirements:
|
142
|
-
- -
|
143
|
-
- !ruby/object:Gem::Version
|
144
|
-
|
145
|
-
segments:
|
146
|
-
- 0
|
147
|
-
version: "0"
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
148
118
|
requirements: []
|
149
|
-
|
150
119
|
rubyforge_project:
|
151
|
-
rubygems_version: 1.
|
120
|
+
rubygems_version: 1.8.17
|
152
121
|
signing_key:
|
153
122
|
specification_version: 3
|
154
123
|
summary: Ruby lock server and client built on EventMachine.
|
155
|
-
test_files:
|
156
|
-
- spec/integration/officer_spec.rb
|
157
|
-
- spec/spec_helper.rb
|
124
|
+
test_files: []
|