officer 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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.