sadie 0.1.8 → 0.1.9

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.
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