sadie 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d35237ce7365435d796e102a1e8d8e2455d9363
4
- data.tar.gz: 03e110b3da7e3ab6d6ad0445e3a7c6ce357fdb55
3
+ metadata.gz: c2491a41d00ecae062b1fc3b7c4d0ae719b6cfeb
4
+ data.tar.gz: e041a331e9cbaaa65927628590191188d913d906
5
5
  SHA512:
6
- metadata.gz: 6f148d1da7eb8101b19d167ffb72fa7b1b0918093254fe62eaea3c915322b8658646150bbd89239a6ad31e29d73569977dbd9a4d5a05716f4eb2b351a343e6a6
7
- data.tar.gz: 8e849601b5ea45613827de33250b67f31cc980aed5d306f6b051bc9438416b9f5a48f0f8f57d6795c53a48f7e11604308a8ddcbc55b8dd39fd2e0d0fc6d69b02
6
+ metadata.gz: b884cc2dc571abb642daaf670dc264114d668b9799f108b32c1494e600f37f367fdb682872a48f999592ebc2a273f7cadefa789009dcfabc54e25eab7e42b919
7
+ data.tar.gz: 760fac48dbf4a93de8b9dc6d076c40e5219a1b5e68faa82ec59e05913adc5de888fdde220231b80397d9a33d0ed88234d51afdd46e6302bd0060201f4bf601fe
data/CHANGELOG CHANGED
@@ -26,4 +26,5 @@
26
26
  [0.0.51] eacher bugfix. now correctly handles specific keys.
27
27
  [0.0.52] code cleanup
28
28
  [0.1.01] ** ground-up rewrite, ignore everything before this line if you're not a historian **
29
- [0.1.8] new version now at capability pairing with old version.
29
+ [0.1.8] new version now at capability pairing with old version.
30
+ [0.1.9] added metadata for keys, abstracted expiry and refresh data structs into timestamp queues, abstracted various mutexes in session into lock manager object in preparation for distributed behavior
data/README CHANGED
@@ -1,12 +1,12 @@
1
1
  ==About Sadie
2
2
 
3
- Sadie is a general-purpose data server written in Ruby for other Rubyists.
3
+ Sadie is a general-purpose data server written in Ruby for other Rubyists. It is designed to be fast, but also resource efficient, storing data in memory or, optionally and on a key-by-key basis, in disk-based storage.
4
4
 
5
5
  ==Purpose
6
6
  Imagine you work in the IT department of a cell phone company and your boss asks you to design a system that will:
7
- * build a status page that shows a map with the operational status of all the cell towers and, for each one, the distance to the nearest field technician
8
- * generate a shapefile and a spreadsheet of the same data that will be available and up-to-date on the company's intranet, 24/7
9
- * when there's an outtage, send an SMS alert to the nearest field tech to the tower with the problem
7
+ * build a status page that shows a map with the operational status of all the cell towers and, for each one, the distance to the nearest field technician
8
+ * generate a shapefile and a spreadsheet of the same data that will be available and up-to-date on the company's intranet, 24/7
9
+ * when there's an outtage, send an SMS alert to the nearest field tech to the tower with the problem
10
10
 
11
11
  This is all inter-related data and, for a developer, it's not all that difficult to start thinking about how to approach these problems.
12
12
 
@@ -23,6 +23,8 @@ For data that changes often, it's possible to expire data after a set amount of
23
23
 
24
24
  Primers can also reference other primers so it's easy to hide complexity.
25
25
 
26
+ For large, less frequently accessed data, it is possible to specfiy that the value be stored to disk instead of memory.
27
+
26
28
  ==How to use it
27
29
  Create a directory for sadie to operate in, say /var/sadie.
28
30
 
@@ -36,8 +38,8 @@ In the primers directory, create files that end in .rb that look like:
36
38
  prime ["test.expires.nsecs"] do
37
39
 
38
40
  expire :never
39
- refresh 300 #generate this again every 5 minutes
40
-
41
+ refresh 300 # generate this again every 5 minutes
42
+ store_in :memory # optional, :memory is the default. :file is also supported
41
43
  after "test.expires.nsecs" do |key, val|
42
44
  cool_notify 'ward@thecleavers.com', val
43
45
  end
@@ -75,8 +77,8 @@ Do it! The github url for Sadie is at: [https://github.com/FredAtLandMetrics/sa
75
77
 
76
78
  ==Future versions
77
79
  Future version of Sadie will:
78
- * index on key string patterns
79
- * make use of a redis storage mechanism to become scalable and distributed
80
+ * index on key string patterns
81
+ * make use of a redis storage mechanism to become scalable and distributed
80
82
 
81
83
  ==Where to get it
82
84
  Sadie can be downloaded via its rubygems page[https://rubygems.org/gems/sadie] or from github[https://github.com/FredAtLandMetrics/sadie].
data/Rakefile CHANGED
@@ -41,6 +41,16 @@ namespace :spec do
41
41
  system "rspec spec/primer.rb"
42
42
  end
43
43
 
44
+ desc "test timestamp queue"
45
+ task :timestamp_queue do
46
+ system "rspec spec/timestamp_queue.rb"
47
+ end
48
+
49
+ desc "test lock_manager"
50
+ task :lock_manager do
51
+ system "rspec spec/lock_manager.rb"
52
+ end
53
+
44
54
  desc "test storage manager"
45
55
  task :storage_manager do
46
56
  system "rspec spec/storage_manager.rb"
data/TODO CHANGED
@@ -1,2 +1,25 @@
1
- 1) rewrite everything
2
- 2) re-document everything
1
+ ==Preparing for distributed-ness
2
+
3
+ The main idea behind distributed Sadie is a bunch of Sadie servers that share an nfs volume and which make use of a Redis cluster. So I need to be careful that no single sadie server need be "special".
4
+
5
+ As it stands, the session has these things to consider:
6
+
7
+ * mutexes
8
+ * expiry data structure
9
+ * refresh data structure
10
+ * registered keys
11
+
12
+ For mutexes, I'm thinking a locking abstraction will do. Something like:
13
+
14
+ lock_id = acquire_lock( params )
15
+ release_lock( lock_id )
16
+
17
+ Once abstracted, this should be relatively straightforward to extend as needed for distributed-ness.
18
+
19
+ [ NOTE: lock abstraction is now done ]
20
+
21
+ The expiry and refresh data structures are currently implemented as red-black trees, but it would be ideal if there were a way to share this information amongst the sadie instances. I'm thinking it might make sense to only a single sadie instance to be expiring at a time (and then only for a set amount of time). Further, I'm thinking refresh could be synchronized such that only a single sadie instance can be finding a refreshable key at a time, but, once it's selected one, it releases its lock so that another instance can begin a search for a another refreshable.
22
+
23
+ [ NOTE: the expiry and refresh data structures are now maintained by a 'timestamp_queue' class that will make it easier to swap in redis-based functionality ]
24
+
25
+ The registered keys data structure can actually be maintained separately in each instance in as much as the primers should really be the same across the instances.
@@ -0,0 +1,52 @@
1
+ require 'thread'
2
+
3
+ class LockManager
4
+
5
+ def initialize
6
+ @locks = {}
7
+ end
8
+
9
+ def create( params )
10
+ systype = params[:systype].to_s
11
+ locktype = params[:locktype].to_s
12
+ lock_id = "#{systype}:#{locktype}"
13
+ @locks[lock_id] = Mutex.new unless @locks.has_key?( lock_id )
14
+ lock_id
15
+ end
16
+
17
+ def acquire( lock_id )
18
+ if @locks[lock_id].try_lock
19
+ lock_id
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def release( lock_id )
26
+ if @locks.has_key?( lock_id )
27
+ @locks[lock_id].unlock
28
+ end
29
+ end
30
+
31
+ def critical_section_insist( lock_id )
32
+ if block_given?
33
+ if @locks.has_key?( lock_id )
34
+ @locks[lock_id].synchronize do
35
+ yield
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def critical_section_try( lock_id )
42
+ if block_given?
43
+ if @locks.has_key?( lock_id )
44
+ if acquire( lock_id )
45
+ yield
46
+ release( lock_id )
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -1,3 +1,3 @@
1
1
  class Sadie
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.9"
3
3
  end
@@ -3,26 +3,33 @@ require 'storage/memory'
3
3
  require 'storage/file'
4
4
  require 'primer'
5
5
  require 'thread'
6
- require 'rbtree'
6
+ require 'lock_manager'
7
+ require 'timestamp_queue'
7
8
 
8
9
  class SadieSession
9
10
  attr_accessor :primers_dirpath
10
11
 
11
12
  def initialize( params )
12
13
 
13
- @expiry_mutex = Mutex.new
14
- @expire_schedule = MultiRBTree.new
15
- @expiry_thread = Thread.new do
16
- _expiry_loop
17
- end
14
+ # init lock manager
15
+ @lockmgr = LockManager.new
18
16
 
19
- @refresh_mutex = Mutex.new
20
- @refresh_schedule = MultiRBTree.new
21
- @refresh_thread = Thread.new do
22
- _refresh_loop
23
- end
17
+ # init expiry and refresh threads
18
+ @expiry_lock = @lockmgr.create( :systype => :session,
19
+ :locktype => :expiry )
20
+ @refresh_lock = @lockmgr.create( :systype => :session,
21
+ :locktype => :refresh )
22
+
23
+ @expiry_queue,@refresh_queue = TimestampQueue.new,TimestampQueue.new
24
+
25
+ _initialize_expiry_thread
26
+ _initialize_refresh_thread
24
27
 
28
+
29
+ # init registered key hash
25
30
  @registered_key = {}
31
+
32
+ # init session operating parameters
26
33
  @default_storage_mechanism = :memory
27
34
  @file_storage_mechanism_dirpath = nil
28
35
  unless params.nil?
@@ -30,7 +37,6 @@ class SadieSession
30
37
 
31
38
  if params.has_key?( :primers_dirpath )
32
39
  self.primers_dirpath = params[:primers_dirpath]
33
- puts "initializing session with primer dirpath: #{self.primers_dirpath}"
34
40
  _register_primers
35
41
  end
36
42
 
@@ -45,18 +51,30 @@ class SadieSession
45
51
  end
46
52
  end
47
53
 
48
- @storage_manager_thread_mutex = Mutex.new
54
+ # init storage manager
55
+ @storagemgr_lock = @lockmgr.create( :systype => :session,
56
+ :locktype => :expiry )
49
57
  @storage_manager = SadieStorageManager.new
50
- @storage_manager_thread_mutex.synchronize do
58
+ @lockmgr.critical_section_insist( @storagemgr_lock ) do
51
59
  @storage_manager.register_storage_mechanism :memory, SadieStorageMechanismMemory.new
52
60
  @storage_manager.register_storage_mechanism :file, SadieStorageMechanismFile.new(:key_storage_dirpath => @file_storage_mechanism_dirpath)
53
61
  end
62
+
54
63
  end
55
64
 
56
- def has_key?( key )
57
- @storage_manager_thread_mutex.synchronize do
58
- @storage_manager.has_key?( key )
65
+ def has_key?( key, params )
66
+ ret = @storage_manager.has_key?( key )
67
+ include_primers = true
68
+
69
+ if ( params.is_a?( Hash ) ) && ( params.has_key?( :include_primers ) )
70
+ include_primers = params[:include_primers]
71
+ end
72
+
73
+ if ( ! ret ) && ( include_primers )
74
+ ret = primer_registered?( key )
59
75
  end
76
+
77
+ ret
60
78
  end
61
79
 
62
80
  def primer_registered?( key )
@@ -64,23 +82,33 @@ class SadieSession
64
82
  end
65
83
 
66
84
  def unset( key )
67
- @storage_manager_thread_mutex.synchronize do
85
+ @lockmgr.critical_section_insist( @storagemgr_lock ) do
68
86
  @storage_manager.unset( key )
69
87
  end
70
88
  end
71
89
 
90
+ def has_metadata?( key )
91
+ @storage_manager.has_metadata?( key )
92
+ end
93
+
94
+ def metadata( key )
95
+ @storage_manager.metadata( key )
96
+ end
97
+
72
98
  def set( keys, value, params=nil )
73
- expires, mechanism = :never, @default_storage_mechanism
99
+ expires, mechanism, metadata = :never, @default_storage_mechanism, nil
74
100
  unless params.nil?
75
101
  if params.is_a? Hash
76
102
  expires = params[:expire] if params.has_key?( :expire )
77
103
  mechanism = params[:mechanism] if params.has_key?( :mechanism )
104
+ metadata = params[:metadata] if params.has_key?( :metadata )
78
105
  end
79
106
  end
80
- @storage_manager_thread_mutex.synchronize do
107
+ @lockmgr.critical_section_insist( @storagemgr_lock ) do
81
108
  @storage_manager.set( :keys => Array( keys ),
82
109
  :value => value,
83
- :mechanism => mechanism )
110
+ :mechanism => mechanism,
111
+ :metadata => metadata )
84
112
  end
85
113
  _manage_expiry( keys, expires ) unless expires == :never || expires == :on_get
86
114
  end
@@ -106,13 +134,25 @@ class SadieSession
106
134
 
107
135
  private
108
136
 
137
+ def _initialize_refresh_thread
138
+ @refresh_thread = Thread.new do
139
+ _refresh_loop
140
+ end
141
+ end
142
+
143
+ def _initialize_expiry_thread
144
+ @expiry_thread = Thread.new do
145
+ _expiry_loop
146
+ end
147
+ end
148
+
109
149
  def _manage_expiry( keys, expires_seconds )
110
150
  if ! expires_seconds.is_a?( Symbol ) && expires_seconds.to_i > 0
111
151
  expires = expires_seconds.to_i + _current_time
112
152
  unless Array(keys).empty?
113
153
  Array(keys).each do |key|
114
- @expiry_mutex.synchronize do
115
- @expire_schedule[expires] = key
154
+ @lockmgr.critical_section_insist( @expiry_lock ) do
155
+ @expiry_queue.insert( key, :timestamp => expires )
116
156
  end
117
157
  end
118
158
  end
@@ -124,8 +164,8 @@ class SadieSession
124
164
  refreshes = refresh_seconds.to_i + _current_time
125
165
  unless Array(keys).empty?
126
166
  Array(keys).each do |key|
127
- @refresh_mutex.synchronize do
128
- @refresh_schedule[refreshes] = key
167
+ @lockmgr.critical_section_insist( @refresh_lock ) do
168
+ @refresh_queue.insert( key, :timestamp => refreshes )
129
169
  end
130
170
  end
131
171
  end
@@ -159,18 +199,27 @@ class SadieSession
159
199
 
160
200
  def _expiry_pass
161
201
  time_now_in_seconds = _current_time
162
- @expiry_mutex.synchronize do
163
- loop do
164
- break if @expire_schedule.empty?
165
- ts,key = @expire_schedule.shift
166
- if ts < time_now_in_seconds
202
+
203
+ loop do
204
+ break if @expiry_queue.empty?
205
+
206
+ ts,key = nil
207
+
208
+ keys_to_unset = nil
209
+ @lockmgr.critical_section_insist( @expiry_lock ) do
210
+
211
+ keys_to_unset = @expiry_queue.find( :all, :before => time_now_in_seconds )
212
+
213
+ end
214
+
215
+ unless keys_to_unset.nil?
216
+ keys_to_unset.each do |key|
167
217
  unset key
168
- else
169
- @expire_schedule[ts] = key
170
- break
171
218
  end
172
219
  end
173
- end
220
+
221
+ end
222
+
174
223
  end
175
224
 
176
225
  def _refresh_loop
@@ -182,18 +231,18 @@ class SadieSession
182
231
 
183
232
  def _refresh_pass
184
233
  time_now_in_seconds = _current_time
185
- loop do
186
- break if @refresh_schedule.empty?
187
- ts,key = @refresh_schedule.shift
188
- if ts < time_now_in_seconds
234
+ unless @refresh_queue.empty?
235
+ keys = nil
236
+ @lockmgr.critical_section_insist( @refresh_lock ) do
237
+ keys = @refresh_queue.find(:all, :before => time_now_in_seconds)
238
+ end
239
+
240
+ unless keys.nil?
241
+ keys.each do | key |
189
242
  _refresh key
190
- else
191
- @refresh_mutex.synchronize do
192
- @refresh_schedule[ts] = key
193
- end
194
- break
195
243
  end
196
244
  end
245
+ end
197
246
  end
198
247
 
199
248
  def _refresh( key )
@@ -41,6 +41,14 @@ class SadieStorageManager
41
41
  end
42
42
  end
43
43
 
44
+ def has_metadata?( key )
45
+ @mechanisms[where_key?( key )].has_metadata?( key ) if has_key?( key )
46
+ end
47
+
48
+ def metadata( key )
49
+ @mechanisms[where_key?( key )].metadata( key ) if has_key?( key )
50
+ end
51
+
44
52
  def set( params )
45
53
  unless params.nil?
46
54
 
@@ -52,9 +60,16 @@ class SadieStorageManager
52
60
 
53
61
  if params.has_key?( :keys ) && params[:keys].is_a?( Array ) &&
54
62
  params.has_key?( :value )
55
-
63
+ has_metadata = false
64
+ if params.has_key?(:metadata) && params[:metadata].is_a?( Hash )
65
+ has_metadata = true
66
+ end
56
67
  params[:keys].each do |key|
57
- @mechanisms[params[:mechanism]].set( key, params[:value] )
68
+ if has_metadata
69
+ @mechanisms[params[:mechanism]].set( key, params[:value], :metadata => params[:metadata] )
70
+ else
71
+ @mechanisms[params[:mechanism]].set( key, params[:value] )
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -1,4 +1,6 @@
1
1
  require 'sadie_storage_mechanism'
2
+ # require 'marshal'
3
+
2
4
  class SadieStorageMechanismFile < SadieStorageMechanism
3
5
  attr_accessor :key_storage_dirpath
4
6
 
@@ -13,9 +15,24 @@ class SadieStorageMechanismFile < SadieStorageMechanism
13
15
  end
14
16
  end
15
17
 
16
- def set( key, value )
18
+ def set( key, value, params=nil )
17
19
  _validate_keystorage_directory
18
20
  File.open(_keyvalue_filepath(key), 'wb') { |file| file.write(value) }
21
+ unless params.nil?
22
+ if params.has_key? :metadata
23
+ _write_metadata_file( key, params[:metadata] )
24
+ end
25
+ end
26
+ end
27
+
28
+ def metadata( key )
29
+ contents = File.open( _metadata_filepath( key ), 'rb') { |f| f.read }
30
+ # puts "contents: #{contents}"
31
+ Marshal.load( contents ) if has_metadata?( key )
32
+ end
33
+
34
+ def has_metadata?( key )
35
+ File.exists? _metadata_filepath( key )
19
36
  end
20
37
 
21
38
  def get( key )
@@ -36,15 +53,31 @@ class SadieStorageMechanismFile < SadieStorageMechanism
36
53
 
37
54
  private
38
55
 
56
+ def _metadata_filepath( key )
57
+ _ensure_metadata_dirpath_exists
58
+ File.join( self.key_storage_dirpath, '.meta', key )
59
+ end
60
+
61
+ def _write_metadata_file( key, metadata_hash )
62
+ raise 'metadata must be a hash' unless ( ! metadata_hash.nil? ) && ( metadata_hash.is_a?( Hash ) )
63
+ _ensure_metadata_dirpath_exists
64
+ File.open( _metadata_filepath( key ), 'wb' ) { |file| file.write( Marshal.dump( metadata_hash ) ) }
65
+ end
66
+
39
67
  def _validate_keystorage_directory
40
68
  raise "Key storage directory (#{self.key_storage_dirpath}) does not exist" unless _keystorage_directory_exists?
41
69
  end
42
70
 
43
71
  def _keyvalue_filepath(key)
44
- File.join(self.key_storage_dirpath,key)
72
+ File.join( self.key_storage_dirpath, key )
45
73
  end
46
74
 
47
75
  def _keystorage_directory_exists?
48
76
  Dir.exists?( self.key_storage_dirpath )
49
77
  end
78
+
79
+ def _ensure_metadata_dirpath_exists
80
+ dirpath = File.join( self.key_storage_dirpath, '.meta' )
81
+ Dir.mkdir( dirpath ) unless Dir.exists?( dirpath )
82
+ end
50
83
  end
@@ -3,10 +3,16 @@ class SadieStorageMechanismMemory < SadieStorageMechanism
3
3
 
4
4
  def initialize
5
5
  @storage_hash = {}
6
+ @metadata = {}
6
7
  end
7
8
 
8
- def set( key, value )
9
+ def set( key, value, params=nil )
9
10
  @storage_hash[key] = value
11
+ unless params.nil?
12
+ if params.has_key? :metadata
13
+ _write_metadata( key, params[:metadata] )
14
+ end
15
+ end
10
16
  end
11
17
 
12
18
  def get( key )
@@ -21,4 +27,19 @@ class SadieStorageMechanismMemory < SadieStorageMechanism
21
27
  @storage_hash.has_key?( key )
22
28
  end
23
29
 
30
+ def metadata( key )
31
+ @metadata[key] if has_metadata?( key )
32
+ end
33
+
34
+ def has_metadata?( key )
35
+ @metadata.has_key?( key )
36
+ end
37
+
38
+ private
39
+
40
+ def _write_metadata( key, metadata_hash )
41
+ raise 'metadata must be a hash' unless ( ! metadata_hash.nil? ) && ( metadata_hash.is_a?( Hash ) )
42
+ @metadata[key] = metadata_hash
43
+ end
44
+
24
45
  end
@@ -0,0 +1,103 @@
1
+ require 'rbtree'
2
+ class TimestampQueue
3
+
4
+ def initialize
5
+ @queue = MultiRBTree.new
6
+ end
7
+
8
+ def insert( key, params=nil )
9
+ ts = nil
10
+ if ( params.is_a?( Hash ) ) &&
11
+ ( params.has_key?( :timestamp ) )
12
+ ts = params[:timestamp]
13
+ else
14
+ ts = _current_time
15
+ end
16
+ @queue[ts] = key
17
+ end
18
+
19
+ def find( which, params=nil )
20
+ if which == :first
21
+ _find_first( params )
22
+ elsif which == :all
23
+ _find_all( params )
24
+ end
25
+ end
26
+
27
+ def empty?
28
+ @queue.empty?
29
+ end
30
+
31
+ private
32
+
33
+ def _get_functions( params )
34
+ testfunc,getfunc = nil,nil,nil
35
+ if params.is_a? Hash
36
+ if params.has_key? :before
37
+ thresh = params[:before]
38
+ testfunc = Proc.new { |x| (x < thresh) }
39
+ getfunc = Proc.new { |q| q.shift }
40
+ end
41
+ end
42
+ [testfunc,getfunc]
43
+ end
44
+
45
+ def _find_first( params )
46
+
47
+ ret = nil
48
+ testfunc,getfunc = _get_functions(params)
49
+ unless testfunc.nil?
50
+ ts,key = getfunc.call(@queue)
51
+ # puts "testing: #{ts}, #{key}"
52
+ if testfunc.call(ts)
53
+ ret = _package_rec(ts,key,params)
54
+ else
55
+ @queue[ts] = key
56
+ end
57
+ end
58
+
59
+ ret
60
+ end
61
+
62
+ def _find_all( params )
63
+ ret = nil
64
+ testfunc,getfunc = _get_functions(params)
65
+ unless testfunc.nil?
66
+
67
+ retarray = []
68
+ loop do
69
+ break if @queue.empty?
70
+ ts,key = getfunc.call(@queue)
71
+ if testfunc.call(ts)
72
+ retarray.push _package_rec(ts,key,params)
73
+ else
74
+ @queue[ts] = key
75
+ break
76
+ end
77
+ end
78
+ ret = retarray unless retarray.empty?
79
+ end
80
+ ret
81
+ end
82
+
83
+
84
+ def _package_rec(timestamp,key,params)
85
+ if params.nil? || ! params.is_a?( Hash )
86
+ key
87
+ elsif params.has_key?( :as )
88
+ if params[:as] == :hash
89
+ { :timestamp => timestamp,
90
+ :key => key }
91
+ else
92
+ key
93
+ end
94
+ else
95
+ key
96
+ end
97
+ end
98
+
99
+ def _current_time
100
+ Time.now.to_i
101
+ end
102
+
103
+ end
@@ -0,0 +1,147 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'lock_manager'
3
+ require 'thread'
4
+
5
+ describe LockManager do
6
+
7
+ before :each do
8
+ @lockmgr = LockManager.new
9
+ end
10
+
11
+ it "should properly protect critical sections with acquire and release" do
12
+ @total = 0
13
+ @max = 0
14
+ t1 = Thread.new do
15
+ @total += 1
16
+ @max = @total if @total > @max
17
+ sleep rand(3).to_i
18
+ @total -= 1
19
+ end
20
+ t2 = Thread.new do
21
+ @total += 1
22
+ @max = @total if @total > @max
23
+ sleep rand(3).to_i
24
+ @total -= 1
25
+ end
26
+ t3 = Thread.new do
27
+ @total += 1
28
+ @max = @total if @total > @max
29
+ sleep rand(3).to_i
30
+ @total -= 1
31
+ end
32
+ sleep 5
33
+ t1.join
34
+ t2.join
35
+ t3.join
36
+ ( @max > 1 ).should be_true
37
+
38
+ lockid = @lockmgr.create( :systype => 'test', :locktype => 'test' )
39
+ @total = 0
40
+ @max = 0
41
+ t1 = Thread.new do
42
+ 3.times do
43
+ unless @lockmgr.acquire( lockid ).nil?
44
+ @total += 1
45
+ @max = @total if @total > @max
46
+ sleep rand(3).to_i
47
+ @total -= 1
48
+ @lockmgr.release( lockid )
49
+ end
50
+ end
51
+ end
52
+ t2 = Thread.new do
53
+ 3.times do
54
+ unless @lockmgr.acquire( lockid ).nil?
55
+ @total += 1
56
+ @max = @total if @total > @max
57
+ sleep rand(3).to_i
58
+ @total -= 1
59
+ @lockmgr.release( lockid )
60
+ end
61
+ end
62
+ end
63
+ t3 = Thread.new do
64
+ 3.times do
65
+ unless @lockmgr.acquire( lockid ).nil?
66
+ @total += 1
67
+ @max = @total if @total > @max
68
+ sleep rand(3).to_i
69
+ @total -= 1
70
+ @lockmgr.release( lockid )
71
+ end
72
+ end
73
+ end
74
+ sleep 5
75
+ t1.join
76
+ t2.join
77
+ t3.join
78
+ ( @max > 1 ).should be_false
79
+ end
80
+
81
+ it "should properly protect critical sections with critical section insist" do
82
+ @total = 0
83
+ @max = 0
84
+ t1 = Thread.new do
85
+ @total += 1
86
+ @max = @total if @total > @max
87
+ sleep rand(3).to_i
88
+ @total -= 1
89
+ end
90
+ t2 = Thread.new do
91
+ @total += 1
92
+ @max = @total if @total > @max
93
+ sleep rand(3).to_i
94
+ @total -= 1
95
+ end
96
+ t3 = Thread.new do
97
+ @total += 1
98
+ @max = @total if @total > @max
99
+ sleep rand(3).to_i
100
+ @total -= 1
101
+ end
102
+ sleep 5
103
+ t1.join
104
+ t2.join
105
+ t3.join
106
+ ( @max > 1 ).should be_true
107
+
108
+ lockid = @lockmgr.create( :systype => 'test', :locktype => 'test' )
109
+ @total = 0
110
+ @max = 0
111
+ t1 = Thread.new do
112
+ 3.times do
113
+ @lockmgr.critical_section_insist( lockid ) do
114
+ @total += 1
115
+ @max = @total if @total > @max
116
+ sleep rand(3).to_i
117
+ @total -= 1
118
+ end
119
+ end
120
+ end
121
+ t2 = Thread.new do
122
+ 3.times do
123
+ @lockmgr.critical_section_insist( lockid ) do
124
+ @total += 1
125
+ @max = @total if @total > @max
126
+ sleep rand(3).to_i
127
+ @total -= 1
128
+ end
129
+ end
130
+ end
131
+ t3 = Thread.new do
132
+ 3.times do
133
+ @lockmgr.critical_section_insist( lockid ) do
134
+ @total += 1
135
+ @max = @total if @total > @max
136
+ sleep rand(3).to_i
137
+ @total -= 1
138
+ end
139
+ end
140
+ end
141
+ sleep 5
142
+ t1.join
143
+ t2.join
144
+ t3.join
145
+ ( @max > 1 ).should be_false
146
+ end
147
+ end
@@ -1,4 +1,5 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'timestamp_queue'
2
3
  require 'sadie_session'
3
4
  require 'pp'
4
5
  describe SadieSession do
@@ -38,7 +39,7 @@ describe SadieSession do
38
39
  end
39
40
 
40
41
  it "should not execute primer assign directives" do
41
- @session.has_key?("minimal.primer").should be_false
42
+ @session.has_key?("minimal.primer", :include_primers => false).should be_false
42
43
  end
43
44
 
44
45
  it "should be possible to _get_ registered keys" do
@@ -51,16 +52,23 @@ describe SadieSession do
51
52
 
52
53
  it "should be possible to expire on get" do
53
54
  @session.get("test.expires.onget").should == "testval"
54
- @session.has_key?("test.expires.onget").should be_false
55
+ @session.has_key?("test.expires.onget", :include_primers => false).should be_false
55
56
  end
56
57
 
57
58
  it "should put keys in the expire schedule" do
58
- def @session.in_expire_schedule?( key )
59
- ( ! @expire_schedule.values.index(key).nil? )
59
+ def @session.get_expiry_queue
60
+ @expiry_queue
60
61
  end
62
+ q = @session.get_expiry_queue
63
+ def q.in_expire_schedule?( key )
64
+ ! @queue.values.index(key).nil?
65
+ end
66
+ # def @session.in_expire_schedule?( key )
67
+ # ( ! @expire_schedule.values.index(key).nil? )
68
+ # end
61
69
 
62
70
  @session.get("test.expires.nsecs").should == "testval"
63
- @session.in_expire_schedule?("test.expires.nsecs").should be true
71
+ q.in_expire_schedule?("test.expires.nsecs").should be true
64
72
  end
65
73
 
66
74
  it "should expire keys using _expire_pass" do
@@ -71,9 +79,9 @@ describe SadieSession do
71
79
  @session.stub(:_current_time).and_return(2,5,8,11,14)
72
80
  @session.stub(:_expiry_loop).and_return(false)
73
81
  @session.get("test.expires.nsecs").should == "testval"
74
- @session.has_key?("test.expires.nsecs").should be_true
82
+ @session.has_key?("test.expires.nsecs", :include_primers => false).should be_true
75
83
  @session.run_expiry_pass
76
- @session.has_key?("test.expires.nsecs").should be_false
84
+ @session.has_key?("test.expires.nsecs", :include_primers => false).should be_false
77
85
  end
78
86
 
79
87
  it "should refresh keys" do
@@ -124,6 +132,34 @@ describe SadieSession do
124
132
  @session.detect_storage_mechanism('key2').should == :file
125
133
  end
126
134
 
135
+ it "should be possible for primer files to choose the storage mechanism" do
136
+ def @session.detect_storage_mechanism(key)
137
+ @storage_manager.where_key?( key )
138
+ end
139
+ val_mem = @session.get("minimal.primer")
140
+ val_file = @session.get("minimal.primer.file")
141
+ val_mem.should == "testval"
142
+ val_file.should == "testval_file"
143
+ @session.detect_storage_mechanism("minimal.primer").should == :memory
144
+ @session.detect_storage_mechanism("minimal.primer.file").should == :file
145
+ end
146
+
147
+ it "should be able to set and retrieve metadata" do
148
+ test_metadata_hash = { :type => :string,
149
+ :importance => :huge }
150
+ test_metadata_hash_right = { :type => :string,
151
+ :importance => :huge }
152
+ test_metadata_hash_wrong = { :type => :string,
153
+ :importance => :mild }
154
+ @session.set( "test.key1", "test.value1", :metadata => test_metadata_hash )
155
+ @session.set( "test.key2", "test.value2" )
156
+ @session.has_metadata?( "test.key1" ).should be_true
157
+ @session.has_metadata?( "test.key2" ).should be_false
158
+ ( @session.metadata( "test.key1" ) == test_metadata_hash_right ).should be_true
159
+ ( @session.metadata( "test.key1" ) == test_metadata_hash_wrong ).should be_false
160
+
161
+ end
162
+
127
163
  # --- SLOW!
128
164
  if ENV.has_key?('SADIE_SESSION_TEST_TIMERS') && ENV['SADIE_SESSION_TEST_TIMERS'].to_i == 1
129
165
 
@@ -4,55 +4,65 @@ require 'storage_mechanisms/memory'
4
4
 
5
5
  describe SadieStorageManager do
6
6
 
7
+ before :each do
8
+ @storage = SadieStorageManager.new
9
+ @mech = SadieStorageMechanismMemory.new
10
+ @storage.register_storage_mechanism :memory, @mech
11
+ end
12
+
7
13
  it "should be able to get and set using the memory storage mechanism" do
8
-
9
- storage = SadieStorageManager.new
10
- mech = SadieStorageMechanismMemory.new
11
- storage.register_storage_mechanism :memory, mech
12
- storage.set( :mechanism => :memory,
13
- :keys => [ "simple.test" ],
14
- :value => "test.value" )
15
- mech.get( "simple.test" ).should == "test.value"
14
+ @storage.set( :mechanism => :memory,
15
+ :keys => [ "simple.test" ],
16
+ :value => "test.value" )
17
+ @mech.get( "simple.test" ).should == "test.value"
16
18
  end
17
19
 
18
20
  it "should report a mechanism is registered after it has been registered" do
19
- storage = SadieStorageManager.new
20
- mech = SadieStorageMechanismMemory.new
21
- storage.register_storage_mechanism :memory, mech
22
- storage.mechanism_is_registered?( :memory ).should be_true
21
+ @storage.mechanism_is_registered?( :memory ).should be_true
23
22
  end
24
23
 
25
24
  it "should have a functional has_key? method" do
26
- storage = SadieStorageManager.new
27
- mech = SadieStorageMechanismMemory.new
28
- storage.register_storage_mechanism :memory, mech
29
- storage.has_key?( "test.key" ).should be_false
30
- storage.set( :keys => ["test.key"],
31
- :value => "test.value",
32
- :mechanism => :memory )
33
- storage.has_key?( "test.key" ).should be_true
25
+ @storage.has_key?( "test.key" ).should be_false
26
+ @storage.set( :keys => ["test.key"],
27
+ :value => "test.value",
28
+ :mechanism => :memory )
29
+ @storage.has_key?( "test.key" ).should be_true
34
30
  end
35
31
 
36
32
  it "should have a functional get method" do
37
- storage = SadieStorageManager.new
38
- mech = SadieStorageMechanismMemory.new
39
- storage.register_storage_mechanism :memory, mech
40
- storage.set( :keys => ["test.key"],
41
- :value => "test.value",
42
- :mechanism => :memory )
43
- storage.get( "test.key" ).should == "test.value"
33
+ @storage.set( :keys => ["test.key"],
34
+ :value => "test.value",
35
+ :mechanism => :memory )
36
+ @storage.get( "test.key" ).should == "test.value"
44
37
  end
45
38
 
46
39
  it "should have a functional unset method" do
47
- storage = SadieStorageManager.new
48
- mech = SadieStorageMechanismMemory.new
49
- storage.register_storage_mechanism :memory, mech
50
- storage.set( :keys => ["test.key"],
51
- :value => "test.value",
52
- :mechanism => :memory )
53
- storage.has_key?( "test.key" ).should be_true
54
- storage.unset "test.key"
55
- storage.has_key?( "test.key" ).should be_false
40
+ @storage.set( :keys => ["test.key"],
41
+ :value => "test.value",
42
+ :mechanism => :memory )
43
+ @storage.has_key?( "test.key" ).should be_true
44
+ @storage.unset "test.key"
45
+ @storage.has_key?( "test.key" ).should be_false
56
46
  end
57
47
 
48
+ it "should be able to set metadata for a key" do
49
+ @storage.set( :keys => ["test.key1"],
50
+ :value => "test.value1",
51
+ :mechanism => :memory )
52
+ test_metadata_hash = { :type => :string,
53
+ :importance => :huge }
54
+ test_metadata_hash_right = { :type => :string,
55
+ :importance => :huge }
56
+ test_metadata_hash_wrong = { :type => :string,
57
+ :importance => :mild }
58
+ @storage.set( :keys => ["test.key2"],
59
+ :value => "test.value2",
60
+ :mechanism => :memory,
61
+ :metadata => test_metadata_hash )
62
+ @storage.has_metadata?( "test.key1" ).should be_false
63
+ @storage.has_metadata?( "test.key2" ).should be_true
64
+ ( @storage.metadata( "test.key2" ) == test_metadata_hash_right ).should be_true
65
+ ( @storage.metadata( "test.key2" ) == test_metadata_hash_wrong ).should be_false
66
+ end
67
+
58
68
  end
@@ -28,4 +28,41 @@ describe SadieStorageMechanismFile do
28
28
  @mech.has_key?( "somekey.test" ).should be_false
29
29
  end
30
30
 
31
+ it "should create a file when set is called" do
32
+ @mech.set 'somekey.test','some_value'
33
+ File.exists?( File.join( '/tmp/stormech-file-test','somekey.test' ) ).should be_true
34
+ end
35
+
36
+ it "should delete a file when unset is called" do
37
+ @mech.set 'somekey.test','some_value'
38
+ File.exists?( File.join( '/tmp/stormech-file-test','somekey.test' ) ).should be_true
39
+ @mech.unset 'somekey.test'
40
+ File.exists?( File.join( '/tmp/stormech-file-test','somekey.test' ) ).should be_false
41
+ end
42
+
43
+ it "should write metadata to a file" do
44
+ @mech.set 'somekey.test','some_value', :metadata => { :type => :string }
45
+ File.exists?( File.join( '/tmp/stormech-file-test','somekey.test' ) ).should be_true
46
+ File.exists?( File.join( '/tmp/stormech-file-test/.meta','somekey.test' ) ).should be_true
47
+ end
48
+
49
+ it "should return the same metadata that was given to it" do
50
+ metadata_to_give = {
51
+ :type => :string,
52
+ :awesomeness_level => :excrutiatingly
53
+ }
54
+ metadata_to_test = {
55
+ :type => :string,
56
+ :awesomeness_level => :excrutiatingly
57
+ }
58
+ wrong_metadata_to_test = {
59
+ :type => :integer,
60
+ :awesomeness_level => :excrutiatingly
61
+ }
62
+ @mech.set 'somekey.test','some_value', :metadata => metadata_to_give
63
+ fetched_meta = @mech.metadata( 'somekey.test' )
64
+ ( fetched_meta == metadata_to_test ).should be_true
65
+ ( fetched_meta == wrong_metadata_to_test ).should be_false
66
+ end
67
+
31
68
  end
@@ -1,28 +1,46 @@
1
1
  require 'storage/memory'
2
2
  describe SadieStorageMechanismMemory do
3
3
 
4
+ before :each do
5
+ @mech = SadieStorageMechanismMemory.new
6
+ end
4
7
  it "should successfully return a set value" do
5
-
6
- mech = SadieStorageMechanismMemory.new
7
- mech.set 'somekey.test','some_value'
8
- mech.get( 'somekey.test' ).should == 'some_value'
8
+ @mech.set 'somekey.test','some_value'
9
+ @mech.get( 'somekey.test' ).should == 'some_value'
9
10
 
10
11
  end
11
12
 
12
13
  it "should have a functional has_key? method" do
13
- mech = SadieStorageMechanismMemory.new
14
- mech.has_key?( "somekey.test" ).should be_false
15
- mech.set 'somekey.test','some_value'
16
- mech.has_key?( "somekey.test" ).should be_true
14
+ @mech.has_key?( "somekey.test" ).should be_false
15
+ @mech.set 'somekey.test','some_value'
16
+ @mech.has_key?( "somekey.test" ).should be_true
17
17
  end
18
18
 
19
19
  it "should have a functional unset method" do
20
- mech = SadieStorageMechanismMemory.new
21
- mech.set 'somekey.test','some_value'
22
- mech.has_key?( "somekey.test" ).should be_true
23
- mech.unset 'somekey.test'
24
- mech.has_key?( "somekey.test" ).should be_false
20
+ @mech.set 'somekey.test','some_value'
21
+ @mech.has_key?( "somekey.test" ).should be_true
22
+ @mech.unset 'somekey.test'
23
+ @mech.has_key?( "somekey.test" ).should be_false
25
24
 
26
25
  end
27
26
 
27
+ it "should return the same metadata that was given to it" do
28
+ metadata_to_give = {
29
+ :type => :string,
30
+ :awesomeness_level => :excrutiatingly
31
+ }
32
+ metadata_to_test = {
33
+ :type => :string,
34
+ :awesomeness_level => :excrutiatingly
35
+ }
36
+ wrong_metadata_to_test = {
37
+ :type => :integer,
38
+ :awesomeness_level => :excrutiatingly
39
+ }
40
+ @mech.set 'somekey.test','some_value', :metadata => metadata_to_give
41
+ fetched_meta = @mech.metadata( 'somekey.test' )
42
+ ( fetched_meta == metadata_to_test ).should be_true
43
+ ( fetched_meta == wrong_metadata_to_test ).should be_false
44
+ end
45
+
28
46
  end
@@ -0,0 +1,39 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'timestamp_queue'
3
+
4
+ describe TimestampQueue do
5
+
6
+ before :each do
7
+ @tsq = TimestampQueue.new
8
+ end
9
+
10
+ it "should store a key using the current time if one is not provided" do
11
+ @tsq.stub(:_current_time).and_return(99)
12
+ @tsq.insert( 'testkey' )
13
+ rec = @tsq.find :first, :before => 100, :as => :hash
14
+ rec[:key].should == 'testkey'
15
+ rec[:timestamp].should == 99
16
+ end
17
+
18
+ it "should return a key if the as parameter is not provided" do
19
+ @tsq.stub(:_current_time).and_return(99)
20
+ @tsq.insert( 'testkey' )
21
+ key = @tsq.find :first, :before => 100
22
+ key.should == 'testkey'
23
+ end
24
+
25
+ it "should return an array of records using :all" do
26
+ @tsq.stub(:_current_time).and_return(99,100,101,104)
27
+ @tsq.insert( 'testkey1' )
28
+ @tsq.insert( 'testkey2' )
29
+ @tsq.insert( 'testkey3' )
30
+ @tsq.insert( 'testkey4' )
31
+ keys = @tsq.find :all, :before => 102
32
+ keys.is_a?( Array ).should be_true
33
+ keys.empty?.should be_false
34
+ keys.length.should == 3
35
+ keys[0].should == 'testkey1'
36
+ keys[1].should == 'testkey2'
37
+ keys[2].should == 'testkey3'
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ prime ["minimal.primer.file"] do
2
+ store_in :file
3
+ assign do
4
+ set "testval_file"
5
+ end
6
+ end
@@ -1,7 +1,7 @@
1
1
  prime ["test.refresh"] do
2
2
  refresh 1
3
3
  assign do
4
- if session.has_key?( "test.refresh" )
4
+ if session.has_key?( "test.refresh", :include_primers => false )
5
5
  set session.get("test.refresh").gsub(/^r/,"rr")
6
6
  else
7
7
  set "refresh"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sadie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred McDavid
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-07 00:00:00.000000000 Z
11
+ date: 2014-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -96,6 +96,7 @@ files:
96
96
  - doc/planning/brainstorm.txt
97
97
  - doc/planning/todo.examples.txt
98
98
  - lib/.gitignore
99
+ - lib/lock_manager.rb
99
100
  - lib/primer.rb
100
101
  - lib/sadie/version.rb
101
102
  - lib/sadie_server.rb
@@ -104,6 +105,7 @@ files:
104
105
  - lib/sadie_storage_mechanism.rb
105
106
  - lib/storage/file.rb
106
107
  - lib/storage/memory.rb
108
+ - lib/timestamp_queue.rb
107
109
  - rdoc/classes/Sadie.html
108
110
  - rdoc/created.rid
109
111
  - rdoc/files/README.html
@@ -116,6 +118,7 @@ files:
116
118
  - rdoc/index.html
117
119
  - rdoc/rdoc-style.css
118
120
  - sadie.gemspec
121
+ - spec/lock_manager.rb
119
122
  - spec/primer.rb
120
123
  - spec/sadie_server.rb
121
124
  - spec/sadie_server_lib.rb
@@ -123,10 +126,12 @@ files:
123
126
  - spec/storage_manager.rb
124
127
  - spec/storage_mechanisms/file.rb
125
128
  - spec/storage_mechanisms/memory.rb
129
+ - spec/timestamp_queue.rb
126
130
  - test/v2/another_test_installation/config/sadie.yml
127
131
  - test/v2/another_test_installation/primers/minimal.rb
128
132
  - test/v2/test_installation/config/sadie.yml
129
133
  - test/v2/test_installation/primers/minimal.rb
134
+ - test/v2/test_installation/primers/minimal_filestormech.rb
130
135
  - test/v2/test_installation/primers/onelevel/twolevel/test_subdir.rb
131
136
  - test/v2/test_installation/primers/test_after_each.rb
132
137
  - test/v2/test_installation/primers/test_after_key.rb