morlock 0.0.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/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/README.markdown +51 -0
- data/Rakefile +7 -0
- data/lib/morlock/base.rb +21 -0
- data/lib/morlock/gem_client.rb +39 -0
- data/lib/morlock/rails.rb +17 -0
- data/lib/morlock/version.rb +3 -0
- data/lib/morlock.rb +2 -0
- data/morlock.gemspec +24 -0
- data/spec/morlock_spec.rb +124 -0
- data/spec/spec_helper.rb +8 -0
- metadata +82 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@morlock --create
|
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Morlock
|
2
|
+
|
3
|
+
Morlock turns your memcached server into a distributed, conflict-eating machine. Rails integration is dug in.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Creating a new Morlock instance
|
8
|
+
|
9
|
+
#### Ruby
|
10
|
+
|
11
|
+
require 'memcache-client'
|
12
|
+
require 'morlock'
|
13
|
+
|
14
|
+
mem_cache_client = MemCache.new("memcached.you.com")
|
15
|
+
morlock = Morlock.new(mem_cache_client)
|
16
|
+
|
17
|
+
If you prefer Dalli, use that instead:
|
18
|
+
|
19
|
+
require 'dalli'
|
20
|
+
dc = Dalli::Client.new('localhost:11211')
|
21
|
+
morlock = Morlock.new(dc)
|
22
|
+
|
23
|
+
#### Rails
|
24
|
+
|
25
|
+
If you're already using MemCacheStore in your Rails app, using Morlock is trivial. Morlock will automatically use the memcached server that is backing Rails.cache.
|
26
|
+
|
27
|
+
With Bundler:
|
28
|
+
|
29
|
+
gem 'morlock', :require => 'morlock/rails'
|
30
|
+
|
31
|
+
Or in any script after Rails has loaded:
|
32
|
+
|
33
|
+
require 'morlock/rails'
|
34
|
+
|
35
|
+
At this point, `Rails.morlock` should be defined and available. Use it instead of `morlock` in the examples below.
|
36
|
+
|
37
|
+
### Distributed Locking
|
38
|
+
|
39
|
+
Possible usages:
|
40
|
+
|
41
|
+
handle_failed_lock unless morlock.lock(key) do
|
42
|
+
# We have the lock
|
43
|
+
end
|
44
|
+
|
45
|
+
morlock.lock(key) { # We have the lock } || raise "Unable to lock!"
|
46
|
+
|
47
|
+
morlock.lock(key, :failure => failure_proc) do
|
48
|
+
# We have the lock
|
49
|
+
end
|
50
|
+
|
51
|
+
morlock.lock(key, :failure => failure_proc, :success => success_proc)
|
data/Rakefile
ADDED
data/lib/morlock/base.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'morlock/gem_client'
|
2
|
+
|
3
|
+
class Morlock
|
4
|
+
DEFAULT_EXPIRATION = 60
|
5
|
+
|
6
|
+
attr_accessor :client
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = Morlock::GemClient.wrap(client)
|
10
|
+
end
|
11
|
+
|
12
|
+
def lock(key, options = {})
|
13
|
+
lock_obtained = @client.add(key, options[:expiration] || DEFAULT_EXPIRATION)
|
14
|
+
yield if lock_obtained && block_given?
|
15
|
+
options[:success].call if lock_obtained && options[:success]
|
16
|
+
options[:failure].call if !lock_obtained && options[:failure]
|
17
|
+
lock_obtained
|
18
|
+
ensure
|
19
|
+
@client.delete(key) if lock_obtained
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Morlock
|
2
|
+
class UnknownGemClient < StandardError; end
|
3
|
+
|
4
|
+
class GemClient
|
5
|
+
GEM_CLIENTS = []
|
6
|
+
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.wrap(client)
|
12
|
+
GEM_CLIENTS.each do |gem, gem_client|
|
13
|
+
if (eval(gem) rescue false) && client.is_a?(eval(gem))
|
14
|
+
return gem_client.new(client)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
raise UnknownGemClient.new("You provided Morlock a memcached client of an unknown type: #{client.class}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(key)
|
22
|
+
@client.delete(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class DalliGemClient < GemClient
|
27
|
+
def add(key, expiration)
|
28
|
+
@client.add(key, 1, expiration)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
GemClient::GEM_CLIENTS << ["Dalli::Client", DalliGemClient]
|
32
|
+
|
33
|
+
class MemcacheGemClient < GemClient
|
34
|
+
def add(key, expiration)
|
35
|
+
@client.add(key, 1, expiration, true) !~ /NOT_STORED/
|
36
|
+
end
|
37
|
+
end
|
38
|
+
GemClient::GEM_CLIENTS << ["MemCache", MemcacheGemClient]
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Morlock
|
2
|
+
class MorlockRailtie < ::Rails::Railtie
|
3
|
+
config.after_initialize do
|
4
|
+
if defined?(ActiveSupport::Cache::MemCacheStore) && Rails.cache.is_a?(ActiveSupport::Cache::MemCacheStore) && Rails.cache.instance_variable_get(:@data)
|
5
|
+
Rails.module_eval do
|
6
|
+
class << self
|
7
|
+
def morlock
|
8
|
+
@@morlock ||= Morlock.new(Rails.cache.instance_variable_get(:@data))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
else
|
13
|
+
Rails.logger.warn "WARNING: Morlock detected that you are not using the Rails ActiveSupport::Cache::MemCacheStore. Rails.morlock will not be setup."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/morlock.rb
ADDED
data/morlock.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "morlock/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "morlock"
|
7
|
+
s.version = Morlock::VERSION
|
8
|
+
s.authors = ["Andrew Cantino & Chris Alvarado-Dryden"]
|
9
|
+
s.email = ["pair+andrew+chris@mavenlink.com"]
|
10
|
+
s.homepage = "https://github.com/mavenlink/morlock"
|
11
|
+
s.summary = %q{Distributed locking with memcached.}
|
12
|
+
s.description = %q{}
|
13
|
+
|
14
|
+
s.rubyforge_project = "morlock"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rr"
|
24
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Morlock do
|
4
|
+
describe "#lock" do
|
5
|
+
module Dalli
|
6
|
+
class Client; end
|
7
|
+
end
|
8
|
+
class MemCache; end
|
9
|
+
|
10
|
+
context "working with different memcached client gems" do
|
11
|
+
describe "with Dalli" do
|
12
|
+
before do
|
13
|
+
@mock_client = Dalli::Client.new
|
14
|
+
stub(@mock_client).delete(anything)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should issue a memcached add command on the given key" do
|
18
|
+
mock(@mock_client).add("some_key", 1, 60)
|
19
|
+
morlock = Morlock.new(@mock_client)
|
20
|
+
morlock.lock("some_key", :expiration => 60)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return true when the lock is acquired" do
|
24
|
+
mock(@mock_client).add("some_key", 1, 60) { true }
|
25
|
+
morlock = Morlock.new(@mock_client)
|
26
|
+
morlock.lock("some_key", :expiration => 60).should == true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return false when the lock is not acquired" do
|
30
|
+
mock(@mock_client).add("some_key", 1, 60) { false }
|
31
|
+
morlock = Morlock.new(@mock_client)
|
32
|
+
morlock.lock("some_key", :expiration => 60).should == false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "with MemCache" do
|
37
|
+
before do
|
38
|
+
@mock_client = MemCache.new
|
39
|
+
stub(@mock_client).delete(anything)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should issue a memcached add command on the given key" do
|
43
|
+
mock(@mock_client).add("some_key", 1, 60, true)
|
44
|
+
morlock = Morlock.new(@mock_client)
|
45
|
+
morlock.lock("some_key", :expiration => 60)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return true when the lock is acquired" do
|
49
|
+
mock(@mock_client).add("some_key", 1, 60, true) { "STORED\r\n" }
|
50
|
+
morlock = Morlock.new(@mock_client)
|
51
|
+
morlock.lock("some_key", :expiration => 60).should == true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return false when the lock is not acquired" do
|
55
|
+
mock(@mock_client).add("some_key", 1, 60, true) { "NOT_STORED\r\n" }
|
56
|
+
morlock = Morlock.new(@mock_client)
|
57
|
+
morlock.lock("some_key", :expiration => 60).should == false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "general behavior" do
|
63
|
+
before do
|
64
|
+
@mock_client = Dalli::Client.new
|
65
|
+
@morlock = Morlock.new(@mock_client)
|
66
|
+
end
|
67
|
+
|
68
|
+
def lock_will_succeed
|
69
|
+
key = nil
|
70
|
+
mock(@mock_client).add(anything, anything, anything) { |k| key = k; true }
|
71
|
+
mock(@mock_client).delete(anything) { |k| k.should == key }
|
72
|
+
end
|
73
|
+
|
74
|
+
def lock_will_fail
|
75
|
+
mock(@mock_client).add(anything, anything, anything) { |k| false }
|
76
|
+
do_not_allow(@mock_client).delete
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should yield on success" do
|
80
|
+
lock_will_succeed
|
81
|
+
yielded = false
|
82
|
+
@morlock.lock("some_key") do
|
83
|
+
yielded = true
|
84
|
+
end
|
85
|
+
yielded.should be_true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should not yield on failure" do
|
89
|
+
lock_will_fail
|
90
|
+
yielded = false
|
91
|
+
@morlock.lock("some_key") do
|
92
|
+
yielded = true
|
93
|
+
end
|
94
|
+
yielded.should be_false
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should accept :success and :failure procs and call :success on success" do
|
98
|
+
lock_will_succeed
|
99
|
+
failed, succeeded = nil, nil
|
100
|
+
@morlock.lock("some_key", :failure => lambda { failed = true }, :success => lambda { succeeded = true })
|
101
|
+
failed.should be_nil
|
102
|
+
succeeded.should be_true
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should accept :success and :failure procs and call :failure on failure" do
|
106
|
+
lock_will_fail
|
107
|
+
failed, succeeded = nil, nil
|
108
|
+
@morlock.lock("some_key", :failure => lambda { failed = true }, :success => lambda { succeeded = true })
|
109
|
+
failed.should be_true
|
110
|
+
succeeded.should be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return false on failure" do
|
114
|
+
lock_will_fail
|
115
|
+
@morlock.lock("some_key").should be_false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should return true on success" do
|
119
|
+
lock_will_succeed
|
120
|
+
@morlock.lock("some_key").should be_true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: morlock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Cantino & Chris Alvarado-Dryden
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70130999067400 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70130999067400
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rr
|
27
|
+
requirement: &70130999066920 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70130999066920
|
36
|
+
description: ''
|
37
|
+
email:
|
38
|
+
- pair+andrew+chris@mavenlink.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rvmrc
|
45
|
+
- Gemfile
|
46
|
+
- README.markdown
|
47
|
+
- Rakefile
|
48
|
+
- lib/morlock.rb
|
49
|
+
- lib/morlock/base.rb
|
50
|
+
- lib/morlock/gem_client.rb
|
51
|
+
- lib/morlock/rails.rb
|
52
|
+
- lib/morlock/version.rb
|
53
|
+
- morlock.gemspec
|
54
|
+
- spec/morlock_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: https://github.com/mavenlink/morlock
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: morlock
|
76
|
+
rubygems_version: 1.8.15
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Distributed locking with memcached.
|
80
|
+
test_files:
|
81
|
+
- spec/morlock_spec.rb
|
82
|
+
- spec/spec_helper.rb
|