system_lock 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/MIT-LICENSE +19 -0
  2. data/README +53 -0
  3. data/lib/system_lock.rb +115 -0
  4. metadata +48 -0
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Rodney Carvalho
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,53 @@
1
+ System Lock
2
+ ===========
3
+
4
+ System Lock is a system-wide critical section for ruby code. This works across multiple mongrel/thin instances
5
+ across multiple servers. This also works with recursive calls to the lock by using reference counting. The
6
+ information required for the lock is stored in memcached.
7
+
8
+ Requirements
9
+ ============
10
+
11
+ Memcached Server
12
+ Dalli Gem
13
+
14
+ What you need to know
15
+ ====================
16
+
17
+ 1) Set this after you initialize memcached. CACHE is the instance of
18
+ DALLI e.g. "CACHE = Dalli::Client.new(SERVER_LIST, {:username = "XXXXXX", :password => "XXXXXX"})"
19
+
20
+ Internaut::SystemLock.memcached_instance = CACHE
21
+
22
+ If you are in Rails you can use the following in your environment.rb:
23
+
24
+ Internaut::SystemLock.memcached_instance = Rails.cache
25
+
26
+ 2) Setup your lock around the code you want to protect. The unique identifier should be something that identifies
27
+ not only the code block, but in which cases you want it to be protected. For example, in this case I am using
28
+ something that looks like a route or url. I want to protect this code block for all users trying to access
29
+ project id 152.
30
+
31
+ Internaut::SystemLock.critical_section('/projects/152') do
32
+ # any code that you execute in here will be serialized as long
33
+ # as it has the same unique code block identifier
34
+ end
35
+
36
+ API
37
+ ===
38
+ Internaut::SystemLock.memcached_instance(MEMCACHED_INSTANCE)
39
+
40
+ Internaut::SystemLock.critical_section(unique_code_block_identifier, timeout=60)
41
+ # Timeout default 60 seconds. Set it higher than you need it to be.
42
+
43
+ Internaut::SystemLock.disable!
44
+ Internaut::SystemLock.enable! (enabled by default)
45
+ Internaut::SystemLock.enabled?
46
+
47
+
48
+ Information stored in Memcached
49
+ ===============================
50
+ unique_code_block_identifier => [ process_id + unique_server_id_string, reference_count ]
51
+
52
+
53
+ Copyright (c) 2009 Rodney Carvalho, released under the MIT license
@@ -0,0 +1,115 @@
1
+ # timeout must be longer than it takes to execute code block
2
+ module Internaut
3
+ class CacheTest
4
+ @@path = 'SystemLock::internaut_test_values'
5
+ def self.add(value)
6
+ if val = result
7
+ value = "#{val},#{value}"
8
+ end
9
+ SystemLock.memcached_instance.set @@path, value, 60.seconds.to_i
10
+ end
11
+
12
+ def self.result
13
+ SystemLock.memcached_instance.get(@@path)
14
+ end
15
+
16
+ def self.clear
17
+ SystemLock.memcached_instance.set @@path, nil
18
+ end
19
+ end
20
+
21
+ class SystemLock
22
+ @@enabled = true
23
+
24
+ def self.disable!
25
+ @@enabled = false
26
+ end
27
+
28
+ def self.enable!
29
+ @@enabled = true
30
+ end
31
+
32
+ def self.enabled?
33
+ @@enabled
34
+ end
35
+
36
+ def self.memcached_instance= cache
37
+ @@CACHE = cache
38
+ end
39
+
40
+ def self.memcached_instance
41
+ @@CACHE
42
+ end
43
+
44
+ def self.critical_section(unique_code_block_identifier, timeout=60)
45
+ # puts "entering cc #{Process.pid}"
46
+ yield and return unless @@enabled
47
+
48
+ begin
49
+ path = "SystemLock::#{unique_code_block_identifier}"
50
+ # puts "waiting for lock..."
51
+ # wait until the lock is released or until we timeout
52
+
53
+ # We use a combination of the process id and a unique app id in case the process id is the same on two different machines
54
+ unique_process_id = UNIQUE_APPLICATION_INSTANCE_ID + Process.pid.to_s
55
+ got_the_lock = false
56
+ while( !got_the_lock )
57
+ while( (cache = @@CACHE.get(path)) && (cache[0] != unique_process_id) rescue return ) do
58
+ # puts "locked waiting.... #{path}: #{Process.pid} : #{cache.inspect}"
59
+ sleep(0.1)
60
+ end
61
+
62
+ counter = cache && cache[1]
63
+ counter ||= 0
64
+ # set the lock
65
+ set_lock_to = [unique_process_id,counter]
66
+ # puts "attempting to grab lock: #{set_lock_to.inspect}"
67
+ @@CACHE.set( path, set_lock_to, timeout.seconds.to_i )
68
+ cache = @@CACHE.get(path)
69
+ if cache && (cache[0] == unique_process_id)
70
+ # we have to double check to make sure we actually got the lock
71
+ # sleep(0.1)
72
+ cache = @@CACHE.get(path)
73
+ if cache && (cache[0] == unique_process_id)
74
+ # puts "got the lock"
75
+ got_the_lock = true
76
+
77
+ # puts "grabbing lock: #{set_lock_to.inspect}"
78
+ # add reference - we use ref counting for recursive calls
79
+ set_lock_to = [unique_process_id,cache[1]+1]
80
+ @@CACHE.set( path, set_lock_to, timeout.seconds.to_i )
81
+ else
82
+ redo
83
+ end
84
+ else
85
+ # puts "redoing: #{RefCounter.get(path)}, #{unique_process_id}"
86
+ redo
87
+ end
88
+ end
89
+ # puts "acquired lock #{Process.pid}"
90
+ CacheTest.add "entering #{Process.pid}" if Rails.env.test?
91
+
92
+ # Do the action
93
+ yield
94
+
95
+ CacheTest.add "exiting #{Process.pid}" if Rails.env.test?
96
+ # puts "leaving lock #{Process.pid}"
97
+ # make sure we always unlock, even if there is an exception
98
+ ensure
99
+ cache = @@CACHE.get(path)
100
+ if cache
101
+ if cache[1] > 1
102
+
103
+ cache[1] -= 1
104
+ # puts "decrementing count to #{cache.inspect}"
105
+ @@CACHE.set( path, cache)
106
+ else
107
+ @@CACHE.set( path, nil)
108
+ # puts "decrementing count to nil - #{path}"
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: system_lock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - rcarvalho
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-16 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: System Lock is a system-weide Ruby Critical Section that uses Memcached
15
+ to lock
16
+ email: i@rodneyc.me
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/system_lock.rb
22
+ - MIT-LICENSE
23
+ - README
24
+ homepage: http://github.com/rcarvalho/system_lock
25
+ licenses: []
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: 1.3.6
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 1.8.6
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: Ruby system-wide lock using memcached
48
+ test_files: []