thread_so_safe 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -18,5 +18,7 @@ coverage
18
18
  rdoc
19
19
  pkg
20
20
  *.gem
21
+ tmp/*
22
+ .document
21
23
 
22
24
  ## PROJECT::SPECIFIC
data/README.rdoc CHANGED
@@ -1,36 +1,139 @@
1
1
  = thread_so_safe
2
2
 
3
- thread_so_safe is a very simple gem to help keep multi-threaded environments synced.
3
+ +thread_so_safe+ is a very simple gem to help keep data accessed in multiple threads synced.
4
4
 
5
- == Examples
6
5
 
7
- ThreadSoSafe.safeguard('My.App Users')
6
+ == What is this thing?
7
+
8
+ Well the description pretty much says it all. +thread_so_safe+ is a gem to help keep data accessed in multiple threads synced.
9
+
10
+
11
+ == Why do I care?
12
+
13
+ That's a great question! Your data is important. If your data changes during the lifecycle of your application, your application may not run properly. Oh no! That sounds terrible! Luckily we can make our code smart enough that we don't have to worry about this.
14
+
15
+
16
+ == Are you reinventing the wheel?
17
+
18
+ Absolutely not! +thread_so_safe+ is not a wheel, but I might be reinventing a thread-safety mechanism. Some approaches to thread-safety lock the data from every other thread until the current thread is finished with the data. This has the potential to slow down your app quite a bit if you have many threads. Other approaches update the data found in each thread. This works when you know what you're looking for. +thread_so_safe+ gives you the means of checking, "has my data changed or is it still safe?" and, "I've changed my data, better send an update to any other threads that might be using this data."
19
+
20
+
21
+ == That sounds fancy, but how does it really work?
22
+
23
+ When you call +register_token+ it creates a file in +/tmp/thread_so_safe+. This file contains the +Time.now.to_i+ value at the time of creation. The time value is also stored in +ThreadSoSafe+'s memory. The +in_sync?+ method checks value stored in the +ThreadSoSafe+ object against the value in the file. If they match the thread is safe, but if they don't match +in_sync?+ will return false to indicating the thread isn't safe or synced anymore. The +update!+ and +reset!+ methods update the time value in the file.
24
+
25
+ At first I tried using only the timestamp of the file, but file timestamps only record hours, minutes and seconds. A lot can happen to a data source in one second. It's because of this that I decided to store the +Time.now.to_i+ value. +Time.now+ returns a time value with much greater detail than jus seconds, so it looked like a good idea.
26
+
27
+
28
+ == Example time
29
+
30
+ # It's time to register your application or data with ThreadSoSafe!
31
+ # Just pass in a unique string to the register_token method to generate
32
+ # a new thread-safety token.
33
+ ThreadSoSafe.register_token('My.App Users')
34
+
35
+ # Great! Now lets capture our data
8
36
  users = User.find(:all)
9
-
37
+
38
+ # Lets make a change to our data set
39
+ users.last.destroy
40
+
41
+ # Our data has changed so let's send an update to ThreadSoSafe so that
42
+ # any other piece of code using ThreadSoSafe.register_token('My.App Users')
43
+ # will know the data has changed.
44
+ ThreadSoSafe.update!
45
+
10
46
  # ...
11
-
12
- unless ThreadSoSafe.safe?
13
- users = User.find(:all)
14
- ThreadSoSafe.update!
47
+
48
+ # Well we've changed the data, but as anyone else in the meanwhile?
49
+ # We can use the in_sync? method to check.
50
+ if ThreadSoSafe.in_sync?
51
+ puts "The data is great! Keep going!"
52
+ # ...
15
53
  end
16
-
54
+
17
55
  ###
18
-
56
+
19
57
  # if you're working with multiple data sets that need to be in sync
20
58
  # you can pass the name (in the first example's case, My.App Users)
21
- # into the safe? and update!
22
-
23
- ThreadSoSafe.safeguard('Users')
59
+ # into the in_sync? and update!
60
+
61
+ ThreadSoSafe.register_token('Users')
24
62
  users = User.find(:all)
25
-
26
- ThreadSoSafe.safeguard('Roles')
63
+
64
+ ThreadSoSafe.register_token('Roles')
27
65
  roles = Role.find(:all)
28
-
66
+
29
67
  # ...
30
-
31
- unless ThreadSoSafe.safe?('Roles')
68
+
69
+ # Notice we're calling in_sync? but with a string now
70
+ unless ThreadSoSafe.in_sync?('Roles')
32
71
  users = User.find(:all)
72
+
73
+ # Also notice we're calling update!, but with a string as well.
33
74
  ThreadSoSafe.update!('Roles)
34
- end
75
+ end
76
+
77
+ ###
78
+
79
+ # We can notify other threads that data has changed with update!,
80
+ # but that updates our thread token as well. I've hit a case where
81
+ # I want to notify other threads that my data has changed AND force
82
+ # a reload of data in my current thread. This is why I made a reset!
83
+ # method. Calling reset! updates the token that all threads reference,
84
+ # but does not update the token stored in the gem. This results in
85
+ # in_sync? returning false and your code can pull the appropriate data
86
+ # and call update! afterwards to re-sync the threads.
87
+
88
+ class Settings < ActiveRecord::Base
89
+ class << self
90
+ def [](key)
91
+ load_collection
92
+ @settings_collection[key]
93
+ end
94
+
95
+ def []=(key, value)
96
+ item = find_or_create_by(:key => key)
97
+ item.value = value
98
+ item.save!
99
+
100
+ ThreadSoSafe.reset!
101
+ # We've reset the token. Next time Settings[:key] is called the
102
+ # load_collection method will rebuild the @settings_collection
103
+ end
104
+
105
+ private
106
+ def load_collection
107
+ if @settings_collection.nil?
108
+ # Our @settings_collection variable doesn't exist so register your token
109
+ ThreadSoSafe.register_token('MyAppSettings')
110
+ elsif ThreadSoSafe.in_sync?
111
+ # If our token is already registered and we're in-sync just return and
112
+ # don't bother running the rest of the code
113
+ return
114
+ end
115
+
116
+ # We've just registered our token or we're not in-sync anymore. I guess
117
+ # we had better reload our data
118
+
119
+ @settings_collection = {}
120
+
121
+ all.each { |s| @settings_collection[s.key] = s.value }
122
+
123
+ # We reloaded our data so lets inform the thread that we're re-synced
124
+ ThreadSoSafe.update!
125
+ end
126
+ end
127
+ end
128
+
129
+ == So... I have to use this in my app?
130
+
131
+ Yes. +thread_so_safe+ is just the mechanism used to keep track and inform whether or not data has changed. If have App1 using +thread_so_safe+, but App2 doesn't -- *and* manipulates the same data, your data wont be in sync. You'll need to use +thread_so_safe+ with both App1 and App2.
132
+
133
+
134
+ == Questions/Comments
135
+
136
+ Feel free to send me a message on Github or on Twitter (@daneharrigan). Thanks!
137
+
35
138
 
36
139
  Copyright (c) 2010 Dane Harrigan. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,18 +1,19 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
  require 'spec/rake/spectask'
4
+ require 'sdoc'
4
5
 
5
6
  begin
6
7
  require 'jeweler'
7
8
  Jeweler::Tasks.new do |gem|
8
9
  gem.name = "thread_so_safe"
9
- gem.version = "0.1.1"
10
+ gem.version = "0.2"
10
11
  gem.summary = %Q{thread_so_safe is a very simple gem to help keep multi-threaded environments synced.}
11
12
  gem.description = gem.summary
12
13
  gem.email = "dane.harrigan@gmail.com"
13
14
  gem.homepage = "http://github.com/daneharrigan/thread_so_safe"
14
15
  gem.authors = ["Dane Harrigan"]
15
- gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ gem.add_development_dependency "rspec", ">= 1.3.0"
16
17
  end
17
18
  Jeweler::GemcutterTasks.new
18
19
  rescue LoadError
@@ -43,4 +44,4 @@ Rake::RDocTask.new do |rdoc|
43
44
  rdoc.title = "thread_so_safe #{version}"
44
45
  rdoc.rdoc_files.include('README*')
45
46
  rdoc.rdoc_files.include('lib/**/*.rb')
46
- end
47
+ end
@@ -3,32 +3,114 @@ require 'fileutils'
3
3
 
4
4
  class ThreadSoSafe
5
5
  @@threads = {}
6
- @@current_thread = nil
6
+ @@current_token = nil
7
7
 
8
8
  class << self
9
- def safeguard(name)
10
- @@current_thread = name
11
- name = file_name(name)
12
- path = full_path(name)
13
-
14
- FileUtils.touch(path) unless File.exists? path
15
- @@threads[name] = File.mtime(path)
9
+ # register_token - This method creates and/or resumes existing ThreadSoSafe
10
+ # sessions. register_token accepts one parameter as a string. Multiple tokens can
11
+ # be registered ThreadSoSafe but only one at a time. The last registered_token is
12
+ # stored in @@current_token. Storing the last token allows for later methods to
13
+ # process without needing the token passed.
14
+ #
15
+ # ==== Example
16
+ # ThreadSoSafe.register_token('My.Application')
17
+ # ThreadSoSafe.register_token('My.Application Users')
18
+ def register_token(name)
19
+ @@current_token = name
20
+ token = file_name(name)
21
+ @@threads[token] = set_timestamp(token)
16
22
  return
17
23
  end
18
24
 
19
- def safe?(name=@@current_thread)
20
- @@current_thread = name
21
- name = file_name(name)
25
+ # in_sync? - This method returns a boolean value indicating whether or not the
26
+ # current thread is in sync with any other thread/application using the same
27
+ # token. in_sync? accepts a token, but isn't necessary if there is only one
28
+ # token registered in your application. If you have multiple tokens it's
29
+ # necessary to pass the token when checking if your thread is in-sync.
30
+ #
31
+ # ==== Example
32
+ # if ThreadSoSafe.in_sync?
33
+ # puts "Good thing I'm the only token registered!"
34
+ # end
35
+ #
36
+ # # with multiple tokens
37
+ # if ThreadSoSafe.in_sync?('My.Application')
38
+ # puts "We're still synced"
39
+ # end
40
+ #
41
+ # if ThreadSoSafe.in_sync?('My.Application Users')
42
+ # puts "Users are synced as well"
43
+ # end
44
+ def in_sync?(name=@@current_token)
45
+ token = file_name(name)
46
+
47
+ @@threads[token] == File.read(full_path(token))
48
+ end
22
49
 
23
- @@threads[name] == File.mtime("#{full_path name}")
50
+ # update! - This method updates the ThreadSoSafe session. Aftering being called,
51
+ # the in_sync? method will return true on the current thread, but false on any
52
+ # other thread until update! is called on it.
53
+ #
54
+ # ==== Example
55
+ # # thread 1
56
+ # ThreadSoSafe.register_token('My.Application Users')
57
+ # users = User.find(:all)
58
+ # users.last.destroy
59
+ # ThreadSoSafe.update!
60
+ #
61
+ # if ThreadSoSafe.in_sync?
62
+ # puts "Data is in sync"
63
+ # end
64
+ #
65
+ # ####
66
+ #
67
+ # # thread 2
68
+ # unless ThreadSoSafe.in_sync?
69
+ # puts "Data is out of sync"
70
+ # users = User.find(:all)
71
+ # ThreadSoSafe.update!
72
+ # end
73
+ def update!(name=@@current_token)
74
+ token = file_name(name)
75
+ file_content = File.read full_path(token)
76
+ if @@threads[token] == file_content
77
+ @@threads[token] = set_timestamp(token)
78
+ else
79
+ @@threads[token] = file_content
80
+ end
81
+ return
24
82
  end
25
83
 
26
- def update!(name=@@current_thread)
27
- name = file_name(name)
28
- file = full_path(name)
84
+ # Update token only and notify other threads
29
85
 
30
- FileUtils.touch(file) if @@threads[name] == File.mtime(file)
31
- @@threads[name] = File.mtime(file)
86
+ # reset! - This method updates the ThreadSoSafe session. Every thread, including
87
+ # the current, is notified of the change. This notification causes the in_sync?
88
+ # method returns false until update! is called.
89
+ #
90
+ # ==== Example
91
+ # # thread 1
92
+ # ThreadSoSafe.register_token('My.Application Users')
93
+ # users = User.find(:all)
94
+ # users.last.destroy
95
+ # ThreadSoSafe.reset!
96
+ #
97
+ # unless ThreadSoSafe.in_sync?
98
+ # puts "Data needs to be recaptured"
99
+ # users = User.find(:all)
100
+ # ThreadSoSafe.update!
101
+ # end
102
+ #
103
+ # ####
104
+ #
105
+ # # thread 2
106
+ # unless ThreadSoSafe.in_sync?
107
+ # puts "Data is out of sync"
108
+ # users = User.find(:all)
109
+ # ThreadSoSafe.update!
110
+ # end
111
+ def reset!(name=@@current_token)
112
+ set_timestamp file_name(name)
113
+ return
32
114
  end
33
115
 
34
116
  private
@@ -45,17 +127,22 @@ class ThreadSoSafe
45
127
  end
46
128
 
47
129
  def use_default_directory?
130
+ FileUtils.mkdir(default_directory) unless File.exists?(default_directory)
48
131
  File.writable?(default_directory)
49
132
  end
50
133
 
51
- #:nordoc:
52
134
  def default_directory
53
135
  '/tmp/thread_so_safe'
54
136
  end
55
137
 
56
- #:nordoc:
57
138
  def gem_directory
58
139
  File.expand_path(File.dirname(__FILE__)+'/../tmp')
59
140
  end
141
+
142
+ def set_timestamp(token)
143
+ timestamp = Time.now.to_f.to_s
144
+ File.open(full_path(token), 'w+') { |f| f.write timestamp }
145
+ timestamp
146
+ end
60
147
  end
61
148
  end
@@ -2,22 +2,33 @@ require "spec_helper"
2
2
 
3
3
  describe ThreadSoSafe do
4
4
  before(:all) do
5
- @app_name = 'My.Application'
5
+ @token = 'My.Application'
6
6
  @default_directory = '/tmp/thread_so_safe'
7
+
8
+ ThreadSoSafe.register_token(@token)
9
+ end
10
+
11
+ # :all works differently in rspec 1.3.0 and 2.0.
12
+ # :suite runs after everything is done in 2.0 like :all did in 1.3.0
13
+ # The Spec namespace has also been renamed to RSpec so I'm looking
14
+ # for Spec and if it doesn't exist then it must be RSpec 2
15
+ after(defined?(RSpec) ? :suite : :all) do
16
+ gem_directory = ThreadSoSafe.send(:gem_directory)
17
+
18
+ FileUtils.rm_rf(@default_directory) if File.exists?(@default_directory)
19
+ FileUtils.rm_rf("#{gem_directory}/*")
7
20
  end
8
21
 
9
- it "should be safe without passing the application name" do
10
- ThreadSoSafe.safeguard(@app_name)
11
- ThreadSoSafe.safe?.should == true
22
+ it "should be in-sync without passing the token" do
23
+ ThreadSoSafe.in_sync?.should == true
12
24
  end
13
25
 
14
- it "should be safe with the application name passed" do
15
- ThreadSoSafe.safeguard(@app_name)
16
- ThreadSoSafe.safe?(@app_name).should == true
26
+ it "should be in-sync with the token passed" do
27
+ ThreadSoSafe.in_sync?(@token).should == true
17
28
  end
18
29
 
19
- it "should encode the application name with md5" do
20
- ThreadSoSafe.send(:file_name,@app_name).should == Digest::MD5.hexdigest(@app_name)
30
+ it "should encode the token with md5" do
31
+ ThreadSoSafe.send(:file_name,@token).should == Digest::MD5.hexdigest(@token)
21
32
  end
22
33
 
23
34
  it "should return /tmp/thread_so_safe as the default directory" do
@@ -35,47 +46,82 @@ describe ThreadSoSafe do
35
46
  end
36
47
 
37
48
  it "should return the full path to the /tmp file" do
38
- file_name = ThreadSoSafe.send(:file_name, @app_name)
49
+ file_name = ThreadSoSafe.send(:file_name, @token)
39
50
  ThreadSoSafe.send(:full_path, file_name).should == "#{@default_directory}/#{file_name}"
40
51
  end
41
52
 
42
- context "when another thread updates the thread-safe token" do
53
+ context "when updating the thread-safe token" do
43
54
  before(:each) do
44
- ThreadSoSafe.safeguard(@app_name)
55
+ file_name = ThreadSoSafe.send(:file_name, @token)
56
+ @full_path = ThreadSoSafe.send(:full_path, file_name)
57
+ @file_content = File.read(@full_path)
58
+ end
59
+
60
+ it "should update the file content in on the /tmp file with the token passed" do
61
+ ThreadSoSafe.update!(@token)
62
+ File.read(@full_path).should_not == @file_content
63
+ end
64
+
65
+ it "should update the file content on the /tmp file without the token passed" do
66
+ ThreadSoSafe.update!
67
+ File.read(@full_path).should_not == @file_content
68
+ end
45
69
 
46
- file_name = ThreadSoSafe.send(:file_name, @app_name)
70
+ context "when the remote token is udpated by another thread" do
71
+ before(:each) do
72
+ File.open(@full_path, 'w') { |f| f.write(Time.now.to_i) }
73
+ end
74
+
75
+ it "should use the token stored in the /tmp file" do
76
+ ThreadSoSafe.update!(@token)
77
+ ThreadSoSafe.in_sync?.should == true
78
+ end
79
+ end
80
+ end
81
+
82
+ context "when another thread updates the thread-safe token" do
83
+ before(:all) do
84
+ file_name = ThreadSoSafe.send(:file_name, @token)
47
85
  full_path = ThreadSoSafe.send(:full_path, file_name)
48
86
 
49
- File.stub!(:mtime).and_return(Time.now+100)
87
+ File.open(full_path, 'w') { |f| f.write(Time.now.to_f) }
50
88
  end
51
89
 
52
- it "should not be safe without passing the application name" do
53
- ThreadSoSafe.safe?.should == false
90
+ it "should not be safe without passing the token" do
91
+ ThreadSoSafe.in_sync?.should == false
54
92
  end
55
93
 
56
- it "should be safe with the application name passed" do
57
- ThreadSoSafe.safe?(@app_name).should == false
94
+ it "should be safe with the token passed" do
95
+ ThreadSoSafe.in_sync?(@token).should == false
58
96
  end
59
97
  end
60
98
 
61
- context "when updating the thread-safe token" do
62
- before(:each) do
63
- ThreadSoSafe.safeguard(@app_name)
64
- file_name = ThreadSoSafe.send(:file_name, @app_name)
99
+ context "when resetting the thread-safe token" do
100
+ before(:all) do
101
+ file_name = ThreadSoSafe.send(:file_name, @token)
65
102
  @full_path = ThreadSoSafe.send(:full_path, file_name)
103
+ end
66
104
 
67
- @mtime = File.mtime @full_path
68
- File.stub!(:mtime).and_return(Time.now+100)
105
+ it "should not be in sync without the token passed" do
106
+ ThreadSoSafe.reset!
107
+ ThreadSoSafe.in_sync?.should == false
69
108
  end
70
109
 
71
- it "should update the mtime on the /tmp file with the application name passed" do
72
- ThreadSoSafe.update!(@app_name)
73
- File.mtime(@full_path).should_not == @mtime
110
+ it "should not be in sync with the token passed" do
111
+ ThreadSoSafe.reset!(@token)
112
+ ThreadSoSafe.in_sync?.should == false
74
113
  end
75
114
 
76
- it "should update the mtime on the /tmp file without the application name passed" do
77
- ThreadSoSafe.update!
78
- File.mtime(@full_path).should_not == @mtime
115
+ it "should update the file content on the /tmp file without the token passed" do
116
+ file_content = File.read(@full_path)
117
+ ThreadSoSafe.reset!
118
+ File.read(@full_path).should_not == file_content
119
+ end
120
+
121
+ it "should update the file content on the /tmp file with the token passed" do
122
+ file_content = File.read(@full_path)
123
+ ThreadSoSafe.reset!(@token)
124
+ File.read(@full_path).should_not == file_content
79
125
  end
80
126
  end
81
127
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{thread_so_safe}
8
- s.version = "0.1.1"
8
+ s.version = "0.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Dane Harrigan"]
12
- s.date = %q{2010-06-27}
12
+ s.date = %q{2010-07-04}
13
13
  s.description = %q{thread_so_safe is a very simple gem to help keep multi-threaded environments synced.}
14
14
  s.email = %q{dane.harrigan@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -17,8 +17,7 @@ Gem::Specification.new do |s|
17
17
  "README.rdoc"
18
18
  ]
19
19
  s.files = [
20
- ".document",
21
- ".gitignore",
20
+ ".gitignore",
22
21
  "LICENSE",
23
22
  "README.rdoc",
24
23
  "Rakefile",
@@ -30,7 +29,7 @@ Gem::Specification.new do |s|
30
29
  s.homepage = %q{http://github.com/daneharrigan/thread_so_safe}
31
30
  s.rdoc_options = ["--charset=UTF-8"]
32
31
  s.require_paths = ["lib"]
33
- s.rubygems_version = %q{1.3.6}
32
+ s.rubygems_version = %q{1.3.7}
34
33
  s.summary = %q{thread_so_safe is a very simple gem to help keep multi-threaded environments synced.}
35
34
  s.test_files = [
36
35
  "spec/spec_helper.rb",
@@ -41,13 +40,13 @@ Gem::Specification.new do |s|
41
40
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
42
41
  s.specification_version = 3
43
42
 
44
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
45
- s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
43
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
+ s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
46
45
  else
47
- s.add_dependency(%q<rspec>, [">= 1.2.9"])
46
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
48
47
  end
49
48
  else
50
- s.add_dependency(%q<rspec>, [">= 1.2.9"])
49
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
51
50
  end
52
51
  end
53
52
 
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_so_safe
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 15
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
8
+ - 2
9
+ version: "0.2"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Dane Harrigan
@@ -14,21 +14,23 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-06-27 00:00:00 -04:00
17
+ date: 2010-07-04 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
24
25
  requirements:
25
26
  - - ">="
26
27
  - !ruby/object:Gem::Version
28
+ hash: 27
27
29
  segments:
28
30
  - 1
29
- - 2
30
- - 9
31
- version: 1.2.9
31
+ - 3
32
+ - 0
33
+ version: 1.3.0
32
34
  type: :development
33
35
  version_requirements: *id001
34
36
  description: thread_so_safe is a very simple gem to help keep multi-threaded environments synced.
@@ -41,7 +43,6 @@ extra_rdoc_files:
41
43
  - LICENSE
42
44
  - README.rdoc
43
45
  files:
44
- - .document
45
46
  - .gitignore
46
47
  - LICENSE
47
48
  - README.rdoc
@@ -60,23 +61,27 @@ rdoc_options:
60
61
  require_paths:
61
62
  - lib
62
63
  required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
63
65
  requirements:
64
66
  - - ">="
65
67
  - !ruby/object:Gem::Version
68
+ hash: 3
66
69
  segments:
67
70
  - 0
68
71
  version: "0"
69
72
  required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
70
74
  requirements:
71
75
  - - ">="
72
76
  - !ruby/object:Gem::Version
77
+ hash: 3
73
78
  segments:
74
79
  - 0
75
80
  version: "0"
76
81
  requirements: []
77
82
 
78
83
  rubyforge_project:
79
- rubygems_version: 1.3.6
84
+ rubygems_version: 1.3.7
80
85
  signing_key:
81
86
  specification_version: 3
82
87
  summary: thread_so_safe is a very simple gem to help keep multi-threaded environments synced.
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE