archipelago 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/hashish.rb ADDED
@@ -0,0 +1,199 @@
1
+ # Archipelago - a distributed computing toolkit for ruby
2
+ # Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'current'
19
+ require 'bdb'
20
+
21
+ module Archipelago
22
+
23
+ #
24
+ # The module containing the persistence default provider.
25
+ #
26
+ module Hashish
27
+
28
+ #
29
+ # In essence a Berkeley Database backed Hash.
30
+ #
31
+ # Will cache all values having been written or read
32
+ # in a normal Hash cache for fast access.
33
+ #
34
+ # Will save the last update timestamp for all keys
35
+ # in a separate Hash cache AND a separate Berkeley Database.
36
+ #
37
+ class BerkeleyHashish
38
+ include Archipelago::Current::Synchronized
39
+ #
40
+ # Initialize an instance with the +name+ and BDB::Env +env+.
41
+ #
42
+ def initialize(name, env)
43
+ super()
44
+ @content_db = env.open_db(BDB::HASH, name, "content", BDB::CREATE)
45
+ @content = {}
46
+ @timestamps_db = env.open_db(BDB::HASH, name, "timestamps", BDB::CREATE | BDB::NOMMAP)
47
+ @timestamps = {}
48
+ @lock = Archipelago::Current::Lock.new
49
+ end
50
+ #
51
+ # Returns a deep ( Marshal.load(Marshal.dump(o)) ) clone
52
+ # of the object represented by +key+.
53
+ #
54
+ def get_deep_clone(key)
55
+ return Marshal.load(@content_db[Marshal.dump(key)])
56
+ end
57
+ #
58
+ # Simply get the value for the +key+.
59
+ #
60
+ def [](key)
61
+ @lock.synchronize_on(key) do
62
+
63
+ value = @content[key]
64
+ return value if value
65
+
66
+ return get_from_db(key)
67
+
68
+ end
69
+ end
70
+ #
71
+ # Insert +value+ under +key+.
72
+ #
73
+ def []=(key, value)
74
+ @lock.synchronize_on(key) do
75
+
76
+ @content[key] = value
77
+
78
+ write_to_db(key, Marshal.dump(key), Marshal.dump(value))
79
+
80
+ return value
81
+
82
+ end
83
+ end
84
+ #
85
+ # Stores whatever is under +key+ if it is not the same as
86
+ # whats in the persistent db.
87
+ #
88
+ def store_if_changed(key)
89
+ @lock.synchronize_on(key) do
90
+
91
+ serialized_key = Marshal.dump(key)
92
+ serialized_value = Marshal.dump(@content[key])
93
+
94
+ write_to_db(key, serialized_key, serialized_value) if @content_db[serialized_key] != serialized_value
95
+
96
+ end
97
+ end
98
+ #
99
+ # Returns the last time the value under +key+ was changed.
100
+ #
101
+ def timestamp(key)
102
+ @lock.synchronize_on(key) do
103
+
104
+ timestamp = @timestamps[key]
105
+ return timestamp if timestamp
106
+
107
+ serialized_key = Marshal.dump(key)
108
+ serialized_timestamp = @timestamps_db[serialized_key]
109
+ return nil unless serialized_timestamp
110
+
111
+ timestamp = Marshal.load(serialized_timestamp)
112
+ @timestamps[key] = timestamp
113
+ return timestamp
114
+
115
+ end
116
+ end
117
+ #
118
+ # Delete +key+ and its value and timestamp.
119
+ #
120
+ def delete(key)
121
+ @lock.synchronize_on(key) do
122
+
123
+ serialized_key = Marshal.dump(key)
124
+
125
+ @content.delete(key)
126
+ @content_db[serialized_key] = nil
127
+ @timestamps.delete(key)
128
+ @timestamps_db[serialized_key] = nil
129
+
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ #
136
+ # Write +key+, serialized as +serialized_key+ and
137
+ # +serialized_value+ to the db.
138
+ #
139
+ def write_to_db(key, serialized_key, serialized_value)
140
+ now = Time.now
141
+
142
+ @content_db[serialized_key] = serialized_value
143
+ @timestamps_db[serialized_key] = Marshal.dump(now)
144
+ @timestamps[key] = now
145
+ end
146
+
147
+ #
148
+ # Read +key+ from db and if it is found
149
+ # put it in the cache Hash.
150
+ #
151
+ def get_from_db(key)
152
+ serialized_key = Marshal.dump(key)
153
+ serialized_value = @content_db[serialized_key]
154
+ return nil unless serialized_value
155
+
156
+ value = Marshal.load(serialized_value)
157
+ @content[key] = value
158
+ return value
159
+ end
160
+ end
161
+
162
+ #
163
+ # A simple persistence provider backed by Berkeley db.
164
+ #
165
+ class BerkeleyHashishProvider
166
+ #
167
+ # Initialize an instance with the given
168
+ # +env_path+ to its database dir.
169
+ #
170
+ def initialize(env_path)
171
+ env_path.mkpath
172
+ @env = BDB::Env.open(env_path, BDB::CREATE | BDB::INIT_MPOOL)
173
+ end
174
+ #
175
+ # Returns a cleverly cached (but slightly inefficient)
176
+ # hash-like instance (see Archipelago::Hashish::BerkeleyHashish)
177
+ # using +name+.
178
+ #
179
+ def get_cached_hashish(name)
180
+ BerkeleyHashish.new(name, @env)
181
+ end
182
+ #
183
+ # Returns a normal hash-like instance using +name+.
184
+ #
185
+ def get_hashish(name)
186
+ @env.open_db(BDB::HASH, name, nil, BDB::CREATE | BDB::NOMMAP)
187
+ end
188
+ #
189
+ # Removes the persistent files of this instance.
190
+ #
191
+ def unlink
192
+ p = Pathname.new(@env.home)
193
+ p.rmtree if p.exist?
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ end
data/lib/pirate.rb ADDED
@@ -0,0 +1,205 @@
1
+ # Archipelago - a distributed computing toolkit for ruby
2
+ # Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'disco'
19
+ require 'tranny'
20
+ require 'treasure'
21
+ require 'pp'
22
+ require 'drb'
23
+ require 'digest/sha1'
24
+
25
+ module Archipelago
26
+
27
+ #
28
+ # The client library that knows about
29
+ # all remote databases and handles reads
30
+ # and write to them.
31
+ #
32
+ module Pirate
33
+
34
+ #
35
+ # Raised when you try to begin a transaction but have no managers
36
+ # available.
37
+ #
38
+ class NoTransactionManagerAvailableException < RuntimeError
39
+ def initialize(pirate)
40
+ super("#{pirate} can not find any transaction manager for you")
41
+ end
42
+ end
43
+
44
+ #
45
+ # Raised when you try to do stuff without any remote database
46
+ # available.
47
+ #
48
+ class NoRemoteDatabaseAvailableException < RuntimeError
49
+ def initialize(pirate)
50
+ super("#{pirate} can not find any remote database for you")
51
+ end
52
+ end
53
+
54
+ INITIAL_SERVICE_UPDATE_INTERVAL = 1
55
+ MAXIMUM_SERVICE_UPDATE_INTERVAL = 60
56
+ CHEST_DESCRIPTION = {
57
+ :class => 'Archipelago::Treasure::Chest'
58
+ }
59
+ TRANNY_DESCRIPTION = {
60
+ :class => 'Archipelago::Tranny::Manager'
61
+ }
62
+
63
+ #
64
+ # The class that actually keeps track of the Archipelago::Treasure:Chests and the
65
+ # Archipelago::Treasure:Dubloons in them.
66
+ #
67
+ class Captain
68
+ attr_reader :chests, :treasure_map, :trannies, :transaction
69
+ #
70
+ # Initialize an instance using an Archipelago::Disco::Jockey with <i>:jockey_options</i>
71
+ # if given, that looks for new services <i>:initial_service_update_interval</i> or INITIAL_SERVICE_UPDATE_INTERVAL,
72
+ # when it starts and never slower than every <i>:maximum_service_update_interval</i> or MAXIMUM_SERVICE_UPDATE_INTERVAL.
73
+ #
74
+ # Will look for Archipelago::Treasure::Chests matching <i>:chest_description</i> or CHEST_DESCRIPTION and
75
+ # Archipelago::Tranny::Managers matching <i>:tranny_description</i> or TRANNY_DESCRIPTION.
76
+ #
77
+ def initialize(options = {})
78
+ @treasure_map = Archipelago::Disco::Jockey.new(options[:jockey_options] || {})
79
+
80
+ @chest_description = CHEST_DESCRIPTION.merge(options[:chest_description] || {})
81
+ @tranny_description = TRANNY_DESCRIPTION.merge(options[:tranny_description] || {})
82
+ @jockey_options = options[:jockey_options] || {}
83
+
84
+ start_service_updater(options[:initial_service_update_interval] || INITIAL_SERVICE_UPDATE_INTERVAL,
85
+ options[:maximum_service_update_interval] || MAXIMUM_SERVICE_UPDATE_INTERVAL)
86
+
87
+ @yar_counter = 0
88
+
89
+ @transaction = nil
90
+ end
91
+
92
+ #
93
+ # Get a value from the distributed database network using a +key+,
94
+ # optionally within a +transaction+.
95
+ #
96
+ def [](key, transaction = nil)
97
+ responsible_chest(key)[:service][key, transaction || @transaction]
98
+ end
99
+
100
+ #
101
+ # Write a value to the distributed database network,
102
+ # optionally inside a transaction.
103
+ #
104
+ # Usage:
105
+ # * p["my key", transaction] = value
106
+ # * p["my key"] = value
107
+ #
108
+ def []=(key, p1, p2 = nil)
109
+ if @transaction && p2.nil?
110
+ p2 = p1
111
+ p1 = @transaction
112
+ end
113
+ responsible_chest(key)[:service][key, p1] = p2
114
+ end
115
+
116
+ #
117
+ # Delete a value from the distributed database network,
118
+ # optionally inside a transaction.
119
+ #
120
+ def delete(key, transaction = nil)
121
+ responsible_chest(key)[:service].delete(key, transaction || @transaction)
122
+ end
123
+
124
+ #
125
+ # Return a clone of this instance bound to a newly created transaction.
126
+ #
127
+ def begin
128
+ raise NoTransactionManagerAvailableException.new(self) if @trannies.empty?
129
+
130
+ rval = self.clone
131
+ rval.instance_eval do
132
+ @transaction = @trannies.values.first[:service].begin
133
+ end
134
+
135
+ return rval
136
+ end
137
+
138
+ #
139
+ # Commit the transaction we are a member of and forget about it.
140
+ #
141
+ def commit!
142
+ @transaction.commit!
143
+ @transaction = nil
144
+ end
145
+
146
+ #
147
+ # Abort the transaction we are a member of and forget about it.
148
+ #
149
+ def abort!
150
+ @transaction.abort!
151
+ @transaction = nil
152
+ end
153
+
154
+ #
155
+ # Yarrr!
156
+ #
157
+ def yar!
158
+ @yar_counter += 1
159
+ 'yar!'
160
+ end
161
+
162
+ private
163
+
164
+ #
165
+ # Get the chest responsible for +key+.
166
+ #
167
+ def responsible_chest(key)
168
+ raise NoRemoteDatabaseAvailableException.new(self) if @chests.empty?
169
+
170
+ key_id = Digest::SHA1.new(Marshal.dump(key)).to_s
171
+ sorted_chest_ids = @chests.keys.sort
172
+ sorted_chest_ids.each do |id|
173
+ return @chests[id] if id > key_id
174
+ end
175
+ return @chests[sorted_chest_ids.first]
176
+ end
177
+
178
+ #
179
+ # Start a thread looking up existing chests between every
180
+ # +initial+ and +maximum+ seconds.
181
+ #
182
+ def start_service_updater(initial, maximum)
183
+ @chests = @treasure_map.lookup(Archipelago::Disco::Query.new(@chest_description), 0)
184
+ @trannies = @treasure_map.lookup(Archipelago::Disco::Query.new(@tranny_description), 0)
185
+ Thread.start do
186
+ standoff = initial
187
+ loop do
188
+ begin
189
+ sleep(standoff)
190
+ standoff *= 2
191
+ standoff = maximum if standoff > maximum
192
+ @chests = @treasure_map.lookup(Archipelago::Disco::Query.new(@chest_description), 0)
193
+ @trannies = @treasure_map.lookup(Archipelago::Disco::Query.new(@tranny_description), 0)
194
+ rescue Exception => e
195
+ puts e
196
+ pp e.backtrace
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ end
204
+
205
+ end