officer 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown ADDED
@@ -0,0 +1,50 @@
1
+ # Officer - Distributed Lock Server and Client
2
+
3
+ This project is a work in progress and shouldn't be considered production ready at this time.
4
+ It is implemented using Ruby and Eventmachine. Inspiration comes from [elock](http://github.com/dustin/elock).
5
+
6
+ ## Installation
7
+
8
+ install gemcutter
9
+ gem install officer
10
+
11
+ ## Usage
12
+
13
+ Start the server using the 'officer' command in a shell.
14
+ It will listen on all interfaces on port 11500.
15
+ All debugging output goes to stdout for now.
16
+
17
+ ## Ruby Client
18
+
19
+ require 'rubygems'
20
+ require 'officer'
21
+
22
+ ### Create a client object (:host and :port default to localhost:11500)
23
+
24
+ client = Officer::Client.new :host => 'localhost', :port => 11500
25
+
26
+ ### Lock
27
+
28
+ client.lock 'some_lock_name'
29
+
30
+ ### Unlock
31
+
32
+ client.unlock 'some_lock_name
33
+
34
+ ### Wrap a block of code in a lock/unlock (with optional 5 second timeout)
35
+
36
+ client.with_lock('some_lock_name', :timeout => 5) do
37
+ puts 'hello world'
38
+ end
39
+
40
+ ### Release all locks for this connection
41
+
42
+ client.reset
43
+
44
+ ### Reconnect (all locks will be released)
45
+
46
+ client.reconnect
47
+
48
+ ## Copyright
49
+
50
+ Copyright (c) 2010 Chad Remesch. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.1
@@ -11,6 +11,7 @@ module Officer
11
11
  class AlreadyConnectedError < GenericError; end
12
12
  class NotConnectedError < GenericError; end
13
13
  class LockError < GenericError; end
14
+ class LockTimeoutError < LockError; end
14
15
  class UnlockError < GenericError; end
15
16
 
16
17
  class Client
@@ -28,17 +29,20 @@ module Officer
28
29
  connect
29
30
  end
30
31
 
31
- def lock name
32
- execute({:command => 'lock', :name => name}.to_json)
32
+ def lock name, options={}
33
+ execute({:command => 'lock', :name => name, :timeout => options[:timeout]}.to_json)
33
34
  end
34
35
 
35
36
  def unlock name
36
37
  execute({:command => 'unlock', :name => name}.to_json)
37
38
  end
38
39
 
39
- def with_lock name
40
- response = lock name
41
- raise LockError unless response['result'] == 'acquired'
40
+ def with_lock name, options={}
41
+ response = lock name, options
42
+ result = response['result']
43
+
44
+ raise LockTimeoutError if result == 'timed_out'
45
+ raise LockError if result != 'acquired'
42
46
 
43
47
  begin
44
48
  yield
@@ -48,6 +52,10 @@ module Officer
48
52
  end
49
53
  end
50
54
 
55
+ def reset
56
+ execute({:command => 'reset'}.to_json)
57
+ end
58
+
51
59
  private
52
60
  def connect
53
61
  raise AlreadyConnectedError if @socket
@@ -37,13 +37,20 @@ module Officer
37
37
  end
38
38
 
39
39
  private
40
- def valid?
41
- raise 'Must override.'
40
+ def setup # Override if necessary.
41
+ end
42
+
43
+ def valid? # Override if necessary.
44
+ true
42
45
  end
43
46
 
44
47
  def require_string arg
45
48
  arg.class == String && arg.length > 0
46
49
  end
50
+
51
+ def optional_positive_integer arg
52
+ arg.nil? || (arg.is_a?(Fixnum) && arg > 0)
53
+ end
47
54
  end
48
55
 
49
56
  class Lock < Base
@@ -51,16 +58,18 @@ module Officer
51
58
 
52
59
  def execute
53
60
  L.debug 'executing lock command.'
54
- Officer::LockStore.instance.acquire @name, @connection
61
+ Officer::LockStore.instance.acquire @name, @connection, :timeout => @timeout
55
62
  end
56
63
 
57
64
  private
58
65
  def setup
59
66
  @name = @request['name']
67
+ @timeout = @request['timeout']
60
68
  end
61
69
 
62
70
  def valid?
63
71
  require_string @request['name']
72
+ optional_positive_integer @request['timeout']
64
73
  end
65
74
  end
66
75
 
@@ -81,5 +90,14 @@ module Officer
81
90
  require_string @request['name']
82
91
  end
83
92
  end
93
+
94
+ class Reset < Base
95
+ register
96
+
97
+ def execute
98
+ L.debug 'executing reset command.'
99
+ Officer::LockStore.instance.reset @connection
100
+ end
101
+ end
84
102
  end
85
103
  end
@@ -4,7 +4,9 @@ module Officer
4
4
  module EmCallbacks
5
5
  def post_init
6
6
  L.debug "Client connected."
7
+
7
8
  @connected = true
9
+ @timers = {} # name => Timer
8
10
  end
9
11
 
10
12
  def receive_line line
@@ -24,31 +26,64 @@ module Officer
24
26
  @connected = false
25
27
 
26
28
  L.debug "client disconnected."
27
- Officer::LockStore.instance.unbind self
29
+
30
+ Officer::LockStore.instance.reset self
28
31
  end
29
32
  end
30
33
 
31
34
  module LockCallbacks
32
35
  def acquired name
33
36
  L.debug "acquired lock: #{name}"
37
+
38
+ @timers.delete(name).cancel if @timers[name]
39
+
34
40
  send_line({:result => 'acquired', :name => name}.to_json)
35
41
  end
36
42
 
37
43
  def released name
38
44
  L.debug "released lock: #{name}"
45
+
39
46
  send_line({:result => 'released', :name => name}.to_json)
40
47
  end
41
48
 
42
49
  def release_failed name
43
50
  L.debug "release lock failed: #{name}"
51
+
44
52
  send_line({:result => 'release_failed', :name => name}.to_json)
45
53
  end
54
+
55
+ def reset_succeeded
56
+ L.debug "reset"
57
+
58
+ @timers.values.each {|timer| timer.cancel}
59
+ send_line({:result => 'reset_succeeded'}.to_json)
60
+ end
61
+
62
+ def queued name, options={}
63
+ L.debug "queued. options=#{options.inspect}"
64
+
65
+ timeout = options[:timeout]
66
+
67
+ return if timeout.nil? || @timers[name]
68
+
69
+ @timers[name] = EM::Timer.new(timeout) do
70
+ Officer::LockStore.instance.timeout name, self
71
+ end
72
+ end
73
+
74
+ def timed_out name
75
+ L.debug "Timed out connection=#{object_id}, name=#{name}"
76
+
77
+ @timers.delete name
78
+ send_line({:result => 'timed_out', :name => name}.to_json)
79
+ end
46
80
  end
47
81
 
48
82
  class Connection < EventMachine::Protocols::LineAndTextProtocol
49
83
  include EmCallbacks
50
84
  include LockCallbacks
51
85
 
86
+ private
52
87
  attr_reader :connected
53
88
 
54
89
  def send_line line
@@ -15,7 +15,7 @@ module Officer
15
15
 
16
16
  def initialize
17
17
  @locks = {} # name => Lock
18
- @connections = {} # Connection => [name, ...]
18
+ @connections = {} # Connection => Set(name, ...)
19
19
  end
20
20
 
21
21
  def log_state
@@ -32,43 +32,45 @@ module Officer
32
32
 
33
33
  puts "Connections:"
34
34
  @connections.each do |connection, names|
35
- puts "#{connection.object_id}: names=[#{names.join(', ')}]"
35
+ puts "#{connection.object_id}: names=[#{names.to_a.join(', ')}]"
36
36
  end
37
37
  puts
38
38
 
39
39
  L.debug '-----'
40
40
  end
41
41
 
42
- def acquire name, connection
42
+ def acquire name, connection, options={}
43
43
  lock = @locks[name] ||= Lock.new(name)
44
44
 
45
- # Queue the lock request unless this connection already has the lock.
46
- unless lock.queue.include? connection
47
- lock.queue << connection
48
- (@connections[connection] ||= []) << name
49
- end
45
+ lock.queue << connection unless lock.queue.include? connection
46
+
47
+ (@connections[connection] ||= Set.new) << name
50
48
 
51
- # Tell the client to block unless it has acquired the lock.
52
49
  if lock.queue.first == connection
53
50
  connection.acquired name
51
+ else
52
+ connection.queued name, options
54
53
  end
55
54
  end
56
55
 
57
- def release name, connection
56
+ def release name, connection, options={}
57
+ options.reverse_merge! :callback => true
58
+
58
59
  lock = @locks[name]
59
60
  names = @connections[connection]
60
61
 
61
62
  # Client should only be able to release a lock that
62
63
  # exists and that it has previously queued.
63
64
  if lock.nil? || !names.include?(name)
64
- connection.release_failed(name) and return
65
+ connection.release_failed(name) if options[:callback]
66
+ return
65
67
  end
66
68
 
67
69
  # If connecton has the lock, release it and let the next
68
70
  # connection know that it has acquired the lock.
69
71
  if lock.queue.first == connection
70
72
  lock.queue.shift
71
- connection.released name
73
+ connection.released name if options[:callback]
72
74
 
73
75
  if next_connection = lock.queue.first
74
76
  next_connection.acquired name
@@ -76,24 +78,35 @@ module Officer
76
78
  @locks.delete name
77
79
  end
78
80
 
79
- # If the connection is queued, but doesn't have the lock,
80
- # release it and leave the other connections alone.
81
+ # If the connection is queued and doesn't have the lock,
82
+ # dequeue it and leave the other connections alone.
81
83
  else
82
84
  lock.queue.delete connection
83
85
  connection.released name
84
86
  end
85
87
 
86
- @connections[connection].delete name
88
+ names.delete name
87
89
  end
88
90
 
89
- def unbind connection
90
- names = @connections[connection] || []
91
+ def reset connection
92
+ names = @connections[connection] || Set.new
91
93
 
92
94
  names.each do |name|
93
- release name, connection
95
+ release name, connection, :callback => false
94
96
  end
95
97
 
96
98
  @connections.delete connection
99
+ connection.reset_succeeded
100
+ end
101
+
102
+ def timeout name, connection
103
+ lock = @locks[name]
104
+ names = @connections[connection]
105
+
106
+ lock.queue.delete connection
107
+ names.delete name
108
+
109
+ connection.timed_out name
97
110
  end
98
111
  end
99
112
 
data/officer.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{officer}
8
- s.version = "0.1.1"
8
+ s.version = "0.2.1"
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"]
@@ -16,13 +16,13 @@ Gem::Specification.new do |s|
16
16
  s.executables = ["officer"]
17
17
  s.extra_rdoc_files = [
18
18
  "LICENSE",
19
- "README.rdoc"
19
+ "README.markdown"
20
20
  ]
21
21
  s.files = [
22
22
  ".document",
23
23
  ".gitignore",
24
24
  "LICENSE",
25
- "README.rdoc",
25
+ "README.markdown",
26
26
  "Rakefile",
27
27
  "VERSION",
28
28
  "bin/officer",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: officer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chad Remesch
@@ -50,12 +50,12 @@ extensions: []
50
50
 
51
51
  extra_rdoc_files:
52
52
  - LICENSE
53
- - README.rdoc
53
+ - README.markdown
54
54
  files:
55
55
  - .document
56
56
  - .gitignore
57
57
  - LICENSE
58
- - README.rdoc
58
+ - README.markdown
59
59
  - Rakefile
60
60
  - VERSION
61
61
  - bin/officer
data/README.rdoc DELETED
@@ -1,8 +0,0 @@
1
- = Officer - Distributed Lock Server and Client
2
-
3
- This project is a work in progress and shouldn't be considered production ready at this time.
4
- It is implemented using Ruby and Eventmachine. Inspiration comes from elock.
5
-
6
- == Copyright
7
-
8
- Copyright (c) 2010 Chad Remesch. See LICENSE for details.