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 ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@morlock --create
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in morlock.gemspec
4
+ gemspec
5
+
6
+ #gem "memcache-client"
7
+ #gem 'dalli'
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
@@ -0,0 +1,7 @@
1
+ require "rubygems"
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new('spec')
7
+ task :default => :spec
@@ -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
@@ -0,0 +1,3 @@
1
+ class Morlock
2
+ VERSION = "0.0.1"
3
+ end
data/lib/morlock.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "morlock/version"
2
+ require 'morlock/base'
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
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'morlock'
4
+ require 'rr'
5
+
6
+ RSpec.configure do |config|
7
+ config.mock_with :rr
8
+ end
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