Web · Wiki · Activities · Blog · Lists · Chat · Meeting · Bugs · Git · Translate · Archive · People · Donate

Commit eea772d7e5fed34fd06b34d3604d6b404f57daa3

fsemulation: guard all data store accesses with lock

Despite having internal locking, python-dbus / libdbus apparently
isn't thread-safe. All current data store implementations are
single-threaded anyway, so just guard all data store accesses (which
happen via D-Bus) with a (single) lock to prevent multiple D-Bus calls
being invoked in parallel.
  • Diff rendering mode:
  • inline
  • side by side

fsemulation.py

14# along with this program. If not, see <http://www.gnu.org/licenses/>.14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15import collections15import collections
16import errno16import errno
17import functools
17import logging18import logging
18import os19import os
19import stat20import stat
21import threading
20import time22import time
2123
22import dbus24import dbus
40"""Metadata properties used for determining the file name of an entry"""40"""Metadata properties used for determining the file name of an entry"""
4141
4242
43def synchronised(func):
44 @functools.wraps(func)
45 def wrapper(self, *args, **kwargs):
46 with self._lock:
47 return func(self, *args, **kwargs)
48 return wrapper
49
50
43class _LRU(collections.MutableMapping):51class _LRU(collections.MutableMapping):
44 """Simple, but reasonably fast Least Recently Used (LRU) cache"""52 """Simple, but reasonably fast Least Recently Used (LRU) cache"""
4553
93class DataStore(object):93class DataStore(object):
94 def __init__(self):94 def __init__(self):
95 self.supports_versions = False95 self.supports_versions = False
96 self._lock = threading.RLock()
96 self._data_store_version = 097 self._data_store_version = 0
97 bus = dbus.SessionBus()98 bus = dbus.SessionBus()
98 try:99 try:
124 logging.info('0.84+ data store without version support found')124 logging.info('0.84+ data store without version support found')
125 self._data_store_version = 84125 self._data_store_version = 84
126126
127 @synchronised
127 def list_object_ids(self, query=None):128 def list_object_ids(self, query=None):
128 """Retrieve the object_ids of all (matching) data store entries129 """Retrieve the object_ids of all (matching) data store entries
129130
151 for entry in self._data_store.find(query, ['uid'],151 for entry in self._data_store.find(query, ['uid'],
152 byte_arrays=True, timeout=DBUS_TIMEOUT_MAX)[0]]152 byte_arrays=True, timeout=DBUS_TIMEOUT_MAX)[0]]
153153
154 @synchronised
154 def list_metadata(self, query=None):155 def list_metadata(self, query=None):
155 """Retrieve object_id and selected metadata of matching entries156 """Retrieve object_id and selected metadata of matching entries
156157
186 for entry in self._data_store.find(query, properties,186 for entry in self._data_store.find(query, properties,
187 timeout=DBUS_TIMEOUT_MAX, byte_arrays=True)[0]]187 timeout=DBUS_TIMEOUT_MAX, byte_arrays=True)[0]]
188188
189 @synchronised
189 def list_versions(self, tree_id):190 def list_versions(self, tree_id):
190 """Retrieve all version_ids of the given data store entry"""191 """Retrieve all version_ids of the given data store entry"""
191 options = {'all_versions': True, 'order_by': ['-timestamp']}192 options = {'all_versions': True, 'order_by': ['-timestamp']}
194 for entry in self._data_store.find({'tree_id': tree_id},194 for entry in self._data_store.find({'tree_id': tree_id},
195 options, timeout=DBUS_TIMEOUT_MAX, byte_arrays=True)[0]]195 options, timeout=DBUS_TIMEOUT_MAX, byte_arrays=True)[0]]
196196
197 @synchronised
197 def list_tree_ids(self, query=None):198 def list_tree_ids(self, query=None):
198 """Retrieve the tree_ids of all (matching) data store entries"""199 """Retrieve the tree_ids of all (matching) data store entries"""
199 return [unicode(entry[0]) for entry in self.list_object_ids(query)]200 return [unicode(entry[0]) for entry in self.list_object_ids(query)]
200201
202 @synchronised
201 def list_property_values(self, name, query=None):203 def list_property_values(self, name, query=None):
202 """Return all unique values of the given property"""204 """Return all unique values of the given property"""
203 assert isinstance(name, unicode)205 assert isinstance(name, unicode)
220220
221 return dict.fromkeys([entry.get(name) for entry in entries]).keys()221 return dict.fromkeys([entry.get(name) for entry in entries]).keys()
222222
223 @synchronised
223 def check_object_id(self, object_id):224 def check_object_id(self, object_id):
224 """Return True if the given object_id identifies a data store entry"""225 """Return True if the given object_id identifies a data store entry"""
225 try:226 try:
232232
233 return True233 return True
234234
235 @synchronised
235 def check_tree_id(self, tree_id):236 def check_tree_id(self, tree_id):
236 """Return True if the given tree_id identifies a data store entry"""237 """Return True if the given tree_id identifies a data store entry"""
237 assert isinstance(tree_id, unicode)238 assert isinstance(tree_id, unicode)
241 byte_arrays=True)[0]241 byte_arrays=True)[0]
242 return bool(results)242 return bool(results)
243243
244 @synchronised
244 def check_property_contains(self, name, word):245 def check_property_contains(self, name, word):
245 """Return True if there is at least one entry containing word in the246 """Return True if there is at least one entry containing word in the
246 given property247 given property
263263
264 return bool(results)264 return bool(results)
265265
266 @synchronised
266 def get_properties(self, object_id, names=None):267 def get_properties(self, object_id, names=None):
267 """Read given properties for data store entry identified by object_id268 """Read given properties for data store entry identified by object_id
268269
299299
300 return self._convert_metadata(metadata)300 return self._convert_metadata(metadata)
301301
302 @synchronised
302 def list_properties(self, object_id):303 def list_properties(self, object_id):
303 """List the names of all properties for this entry304 """List the names of all properties for this entry
304305
307 """307 """
308 return self.get_properties(object_id).keys()308 return self.get_properties(object_id).keys()
309309
310 @synchronised
310 def create_property(self, object_id, name, value):311 def create_property(self, object_id, name, value):
311 """Set the given property, raising an error if it already exists"""312 """Set the given property, raising an error if it already exists"""
312 assert isinstance(name, unicode)313 assert isinstance(name, unicode)
318 metadata[name] = value318 metadata[name] = value
319 self._change_metadata(object_id, metadata)319 self._change_metadata(object_id, metadata)
320320
321 @synchronised
321 def replace_property(self, object_id, name, value):322 def replace_property(self, object_id, name, value):
322 """Modify the given, already existing property"""323 """Modify the given, already existing property"""
323 assert isinstance(name, unicode)324 assert isinstance(name, unicode)
331 metadata[name] = value331 metadata[name] = value
332 self._change_metadata(object_id, metadata)332 self._change_metadata(object_id, metadata)
333333
334 @synchronised
334 def set_properties(self, object_id, properties):335 def set_properties(self, object_id, properties):
335 """Write the given (sub)set of properties336 """Write the given (sub)set of properties
336337
346 metadata.update(properties)346 metadata.update(properties)
347 self._change_metadata(object_id, metadata)347 self._change_metadata(object_id, metadata)
348348
349 @synchronised
349 def remove_properties(self, object_id, names):350 def remove_properties(self, object_id, names):
350 """Remove the given (sub)set of properties351 """Remove the given (sub)set of properties
351352
363363
364 self._change_metadata(object_id, metadata)364 self._change_metadata(object_id, metadata)
365365
366 @synchronised
366 def remove_entry(self, object_id):367 def remove_entry(self, object_id):
367 """Remove a single (version of a) data store entry"""368 """Remove a single (version of a) data store entry"""
368 if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:369 if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:
378 assert isinstance(object_id, unicode)378 assert isinstance(object_id, unicode)
379 self._data_store.delete(object_id, timeout=DBUS_TIMEOUT_MAX)379 self._data_store.delete(object_id, timeout=DBUS_TIMEOUT_MAX)
380380
381 @synchronised
381 def create_new(self, properties):382 def create_new(self, properties):
382 """Create a new data store entry383 """Create a new data store entry
383384
396 else:396 else:
397 return self._data_store.create(properties, '', False)397 return self._data_store.create(properties, '', False)
398398
399 @synchronised
399 def get_data(self, object_id):400 def get_data(self, object_id):
400 """Return path to data for data store entry identified by object_id."""401 """Return path to data for data store entry identified by object_id."""
401 if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:402 if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:
410 assert isinstance(object_id, unicode)410 assert isinstance(object_id, unicode)
411 return self._data_store.get_filename(object_id, byte_arrays=True)411 return self._data_store.get_filename(object_id, byte_arrays=True)
412412
413 @synchronised
413 def get_size(self, object_id):414 def get_size(self, object_id):
414 # FIXME: make use of filesize property if available415 # FIXME: make use of filesize property if available
415 path = self.get_data(object_id)416 path = self.get_data(object_id)
421 os.remove(path)421 os.remove(path)
422 return size422 return size
423423
424 @synchronised
424 def write_data(self, object_id, path):425 def write_data(self, object_id, path):
425 """Update data for data store entry identified by object_id.426 """Update data for data store entry identified by object_id.
426427