From e21932e8d233f68e0b065882eb06b15089a2935a Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 30 Dec 2012 00:48:38 +0000 Subject: [PATCH] documentation: Document which yielding methods are safe to call concurrently MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This is a follow-up commit to ce55fa2bf2f5f8cf95532da585d835bafeeb3347. I went through all methods in folks which yield to another async method, and tried to work out whether the caller was safe to run multiple times concurrently (e.g. begin a second asynchronous call to it between a previous async call beginning and finishing). I’ve marked all such methods as safe (or not safe) as appropriate. I haven’t made any attempt to make the unsafe methods safe, except in one case in backend-store.vala. --- backends/eds/lib/edsf-persona-store.vala | 4 +- backends/key-file/kf-persona-store.vala | 2 + backends/libsocialweb/lib/swf-persona-store.vala | 4 ++ backends/telepathy/lib/tpf-persona-store.vala | 13 ++++++ backends/tracker/lib/trf-persona-store.vala | 58 +++++++++++++++++++++++- folks/anti-linkable.vala | 6 +++ folks/avatar-cache.vala | 8 ++++ folks/backend-store.vala | 32 ++++++++++++- folks/individual-aggregator.vala | 21 +++++++++ folks/object-cache.vala | 5 ++ 10 files changed, 149 insertions(+), 4 deletions(-) diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala index ee21d06..b943085 100644 --- a/backends/eds/lib/edsf-persona-store.vala +++ b/backends/eds/lib/edsf-persona-store.vala @@ -1033,7 +1033,9 @@ public class Edsf.PersonaStore : Folks.PersonaStore SourceFunc? _open_address_book_callback = null; /* non-null iff yielded */ /* Guarantees that either the address book will be open once the method - * returns, or an error will be thrown. */ + * returns, or an error will be thrown. + * + * This method is not safe to run multiple times concurrently. */ private async void _open_address_book () throws GLib.Error { Error? err_out = null; diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala index c989ae3..cfe1061 100644 --- a/backends/key-file/kf-persona-store.vala +++ b/backends/key-file/kf-persona-store.vala @@ -436,6 +436,8 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore return this._key_file; } + /* This is safe to call multiple times concurrently (in the same thread). + * Previous calls will be cancelled when a new call begins. */ internal async void save_key_file () { var key_file_data = this._key_file.to_data (); diff --git a/backends/libsocialweb/lib/swf-persona-store.vala b/backends/libsocialweb/lib/swf-persona-store.vala index f23f414..40e8f85 100644 --- a/backends/libsocialweb/lib/swf-persona-store.vala +++ b/backends/libsocialweb/lib/swf-persona-store.vala @@ -222,6 +222,8 @@ public class Swf.PersonaStore : Folks.PersonaStore "Personas cannot be removed from this store."); } + /* This is safe to call multiple times concurrently (assuming libsocialweb + * itself is safe). */ private async string[]? _get_static_capabilities () throws GLib.Error { /* Take a reference to the PersonaStore while waiting for the async call @@ -267,6 +269,8 @@ public class Swf.PersonaStore : Folks.PersonaStore return caps; } + /* This is safe to call multiple times concurrently (assuming libsocialweb + * itself is safe). */ private async ClientContactView? _contacts_query_open_view (string query, HashTable parameters) { diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala index 1dfbfcf..0c6c5d1 100644 --- a/backends/telepathy/lib/tpf-persona-store.vala +++ b/backends/telepathy/lib/tpf-persona-store.vala @@ -619,6 +619,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore this._logger = null; } + /* This method is not safe to call multiple times concurrently. */ private async void _initialise_favourite_contacts () throws GLib.Error { if (this._logger == null) @@ -898,6 +899,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore /** * If our account is disconnected, we want to continue to export a static * view of personas from the cache. old_personas will be notified as removed. + * + * This method is safe to call multiple times concurrently. Previous calls + * will be cancelled by subsequent calls. */ private async void _load_cache (HashSet? old_personas) { @@ -967,6 +971,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore this.notify_property ("always-writeable-properties"); } + /* This method is safe to call multiple times concurrently. */ public override async void flush () { debug ("Flushing Tpf.PersonaStore %p (‘%s’).", this, this.id); @@ -990,6 +995,8 @@ public class Tpf.PersonaStore : Folks.PersonaStore /** * When we're about to disconnect, store the current set of personas to the * cache file so that we can access them once offline. + * + * This method is safe to call multiple times concurrently. */ private async void _store_cache (HashSet old_personas) { @@ -1283,6 +1290,8 @@ public class Tpf.PersonaStore : Folks.PersonaStore } } + /* This method is safe to call multiple times concurrently for the same (or + * different) contact ID, assuming Telepathy is safe. */ private async Persona _ensure_persona_for_id (string contact_id) throws GLib.Error { @@ -1295,6 +1304,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore * * See {@link Folks.PersonaStore.add_persona_from_details}. * + * This method is safe to call multiple times concurrently for the same (or + * different) contact IDs (assuming Telepathy is safe). + * * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if the ``contact`` key was * not provided in ``details`` * @throws Folks.PersonaStoreError.STORE_OFFLINE if the CM is offline @@ -1746,6 +1758,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore return templates; } + /* This method is safe to call multiple times concurrently. */ private async void _populate_counters () { if (this._log == null) diff --git a/backends/tracker/lib/trf-persona-store.vala b/backends/tracker/lib/trf-persona-store.vala index eec04e8..dd0a7ab 100644 --- a/backends/tracker/lib/trf-persona-store.vala +++ b/backends/tracker/lib/trf-persona-store.vala @@ -887,7 +887,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /** * Remove a {@link Persona} from the PersonaStore. * - * See {@link Folks.PersonaStore.remove_persona}. + * See {@link Folks.PersonaStore.remove_persona}. This method is not safe to + * call multiple times concurrently on the same persona. * * @throws Folks.PersonaStoreError currently unused */ @@ -907,6 +908,9 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (q.printf (urn, urn), "remove_persona"); } + /* This method is not safe to call multiple times concurrently, since one call + * could be part-way through removing attributes of the URN while a subsequent + * call is attempting to retrieve the URN. */ private async string _remove_attributes_from_persona (Folks.Persona persona, char remove_flag) { @@ -915,6 +919,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return urn; } + /* This method is safe to call multiple times concurrently. */ private async void _build_update_query_set ( Tracker.Sparql.Builder builder, Set> properties, @@ -982,6 +987,9 @@ public class Trf.PersonaStore : Folks.PersonaStore * check to if the deleted nco:Person * is the only one holding a link, if so we * remove the resource. + * + * This method is not safe to call multiple times concurrently, since the + * deletions will race. */ private async void _remove_attributes (string urn, char remove_flag) { @@ -1169,6 +1177,7 @@ public class Trf.PersonaStore : Folks.PersonaStore (Trf.OntologyDefs.NCO_FEMALE); } + /* This is safe to call multiple times concurrently. */ private async void _build_predicates_table () { if (PersonaStore._prefix_tracker_id != null) @@ -1405,6 +1414,8 @@ public class Trf.PersonaStore : Folks.PersonaStore return added_personas; } + /* This method is not safe to call multiple times concurrently on the same + * persona, since the queries and updates will race. */ private async void _do_update (Persona p, Event e, bool adding = true) { if (e.pred_id == @@ -1673,6 +1684,7 @@ public class Trf.PersonaStore : Folks.PersonaStore } } + /* This method is safe to call multiple times concurrently. */ private async string _get_property (int subject_tracker_id, string property, string subject_type = Trf.OntologyDefs.NCO_PERSON) @@ -1688,6 +1700,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return yield this._single_value_query (query); } + /* This method is safe to call multiple times concurrently. */ private async string _get_nao_property_by_person_id (int nco_person_id, string prop_name) { @@ -1704,6 +1717,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return yield this._single_value_query (query); } + /* This method is safe to call multiple times concurrently. */ private async string[] _get_nao_property_by_prop_id (int nao_prop_id) { const string query_t = "SELECT " + @@ -1722,6 +1736,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /* * This should be kept in sync with Trf.AfflInfoFields + * + * This method is safe to call multiple times concurrently. */ private async Trf.AfflInfo _get_affl_info ( string person_id, string affiliation_id) @@ -1842,6 +1858,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return affl_info; } + /* This method is safe to call multiple times concurrently. */ private async string? _insert_persona (string query, string persona_var) throws PersonaStoreError { @@ -1895,6 +1912,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return null; } + /* This method is safe to call multiple times concurrently. */ private async string _single_value_query (string query) { Gee.HashSet rows = yield this._multi_value_query (query); @@ -1905,6 +1923,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return ""; } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _multi_value_query (string query) { Gee.HashSet ret = new Gee.HashSet (); @@ -1933,6 +1952,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return ret; } + /* This method is safe to call multiple times concurrently. */ private async string _urn_from_tracker_id (string tracker_id) { const string query = "SELECT fn:concat('<', tracker:uri(%s), '>') " + @@ -1940,6 +1960,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return yield this._single_value_query (query.printf (tracker_id)); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_nickname (Trf.Persona persona, string nickname) { const string query_t = "DELETE { "+ @@ -1964,6 +1985,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "change_nickname"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_local_ids (Trf.Persona persona, Set local_ids) { @@ -1973,6 +1995,7 @@ public class Trf.PersonaStore : Folks.PersonaStore "_set_local_ids"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_web_service_addrs (Trf.Persona persona, MultiMap ws_obj) { @@ -1982,6 +2005,7 @@ public class Trf.PersonaStore : Folks.PersonaStore "_set_web_service_addrs"); } + /* This method is safe to call multiple times concurrently. */ private async void _set_tracker_property(Trf.Persona persona, string prop_name, string prop_value, string callers_name) { @@ -2008,6 +2032,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, callers_name); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_is_favourite (Folks.Persona persona, bool is_favourite) { @@ -2041,6 +2066,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "change_is_favourite"); } + /* This method may not be safe to call multiple times concurrently. */ internal async void _set_emails (Folks.Persona persona, Set emails) { @@ -2048,6 +2074,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.Attrib.EMAILS); } + /* This method may not be safe to call multiple times concurrently. */ internal async void _set_phones (Folks.Persona persona, Set phone_numbers) { @@ -2055,6 +2082,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.Attrib.PHONES); } + /* This method may not be safe to call multiple times concurrently. */ internal async void _set_unique_attrib_set (Folks.Persona persona, Set> properties, Trf.Attrib attrib) { @@ -2092,6 +2120,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (builder.result, query_name); } + /* This method is probably not safe to call multiple times concurrently. */ internal async void _set_urls (Folks.Persona persona, Set urls) { @@ -2099,6 +2128,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.Attrib.URLS); } + /* This method is probably not safe to call multiple times concurrently. */ internal async void _set_im_addresses (Folks.Persona persona, MultiMap im_addresses) { @@ -2117,6 +2147,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._set_attrib_set (persona, ims, Trf.Attrib.IM_ADDRESSES); } + /* This method is probably not safe to call multiple times concurrently. */ internal async void _set_postal_addresses (Folks.Persona persona, Set postal_addresses) { @@ -2124,6 +2155,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.Attrib.POSTAL_ADDRESSES); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_roles (Folks.Persona persona, Set roles) { @@ -2176,6 +2208,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (del_q + builder.result, "_set_roles"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_notes (Folks.Persona persona, Set notes) { @@ -2213,6 +2246,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (del_q + builder.result, "_set_notes"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_birthday (Folks.Persona persona, owned DateTime bday) { @@ -2240,6 +2274,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "_set_birthday"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_gender (Folks.Persona persona, owned Gender gender) { @@ -2281,6 +2316,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "_set_gender"); } + /* This method is not safe to call multiple times concurrently. */ internal async void _set_avatar (Folks.Persona persona, LoadableIcon? avatar) { @@ -2343,6 +2379,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "_set_avatar"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_structured_name (Folks.Persona persona, StructuredName? sname) { @@ -2386,6 +2423,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (query, "_set_structured_name"); } + /* This method is safe to call multiple times concurrently. */ internal async void _set_full_name (Folks.Persona persona, string full_name) { @@ -2413,6 +2451,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /* NOTE: * - first we nuke old attribs * - we create new affls with the new attribs + * + * This method is probably not safe to call multiple times concurrently. */ private async void _set_attrib_set (Folks.Persona persona, Set attribs, Trf.Attrib what) @@ -2547,6 +2587,7 @@ public class Trf.PersonaStore : Folks.PersonaStore yield this._tracker_update (builder.result, "set_attrib"); } + /* This method is safe to call multiple times concurrently. */ private async bool _tracker_update (string query, string caller) { bool ret = false; @@ -2577,12 +2618,14 @@ public class Trf.PersonaStore : Folks.PersonaStore return ret; } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _affiliations_from_persona (string urn) { return yield this._linked_resources (urn, Trf.OntologyDefs.NCO_PERSON, Trf.OntologyDefs.NCO_HAS_AFFILIATION); } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _phones_from_affiliation (string affl) { return yield this._linked_resources (affl, @@ -2590,6 +2633,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.OntologyDefs.NCO_HAS_PHONE); } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _postals_from_affiliation (string affl) { return yield this._linked_resources (affl, @@ -2597,6 +2641,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.OntologyDefs.NCO_HAS_POSTAL_ADDRESS); } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _imaddrs_from_affiliation (string affl) { return yield this._linked_resources (affl, @@ -2604,6 +2649,7 @@ public class Trf.PersonaStore : Folks.PersonaStore Trf.OntologyDefs.NCO_HAS_IMADDRESS); } + /* This method is safe to call multiple times concurrently. */ private async Gee.HashSet _emails_from_affiliation (string affl) { return yield this._linked_resources (affl, @@ -2614,6 +2660,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /** * Retrieve the list of linked resources of a given subject * + * This method is safe to call multiple times concurrently. + * * @param resource the urn of the resource in format * @return number of resources linking to this resource */ @@ -2640,6 +2688,9 @@ public class Trf.PersonaStore : Folks.PersonaStore * This means that _delete_resource shold be called before * removing the resources that hold a link to it (which also * makes sense from the signaling perspective). + * + * This method is not safe to call multiple times concurrently, as the + * resource count check races with deletion. */ private async bool _delete_resource (string resource_urn, bool check_count = true) @@ -2672,6 +2723,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /** * Retrieve the list of linked resources of a given subject * + * This method is safe to call multiple times concurrently. + * * @param urn the urn of the subject in format * @param subject_type i.e: nco:Person, nco:Affiliation, etc * @param linking_predicate i.e.: nco:hasAffiliation @@ -2691,6 +2744,7 @@ public class Trf.PersonaStore : Folks.PersonaStore return yield this._multi_value_query (query); } + /* This method is safe to call multiple times concurrently. */ private async string _urn_from_persona (Folks.Persona persona) { var id = ((Trf.Persona) persona).tracker_id; @@ -2700,6 +2754,8 @@ public class Trf.PersonaStore : Folks.PersonaStore /** * Helper method to figure out if a constrained property * already exists. + * + * This method is safe to call multiple times concurrently. */ private async string _urn_from_property (string class_name, string property_name, diff --git a/folks/anti-linkable.vala b/folks/anti-linkable.vala index 23d42c8..39b4e63 100644 --- a/folks/anti-linkable.vala +++ b/folks/anti-linkable.vala @@ -105,6 +105,9 @@ public interface Folks.AntiLinkable : Folks.Persona * Any attempt to anti-link a persona with itself is not an error, but is * ignored. * + * This method is safe to call multiple times concurrently (e.g. begin one + * asynchronous call, then begin another before the first has finished). + * * @param other_personas the personas to anti-link to this one * @throws PropertyError if setting the anti-links failed * @since 0.7.3 @@ -135,6 +138,9 @@ public interface Folks.AntiLinkable : Folks.Persona * The UIDs of all personas in ``other_personas`` will be removed from this * persona's anti-links set and the changes propagated to backends. * + * This method is safe to call multiple times concurrently (e.g. begin one + * asynchronous call, then begin another before the first has finished). + * * @param other_personas the personas to remove anti-links from this one * @throws PropertyError if setting the anti-links failed * @since 0.7.3 diff --git a/folks/avatar-cache.vala b/folks/avatar-cache.vala index 3536f0a..78ff67b 100644 --- a/folks/avatar-cache.vala +++ b/folks/avatar-cache.vala @@ -130,6 +130,10 @@ public class Folks.AvatarCache : Object * example, this ID could be the UID of a persona. The URI of the cached * avatar file will be returned. * + * This method may be called multiple times concurrently for the same avatar + * ID (e.g. an asynchronous call may be made, and a subsequent asynchronous + * call may begin before the first has finished). + * * @param id the globally unique ID for the avatar * @param avatar the avatar data to cache * @return a URI for the file storing the cached avatar @@ -155,6 +159,10 @@ public class Folks.AvatarCache : Object try { + /* In order for this to be concurrency-safe, we assume that + * replace_async() does an atomic substitution of the new file for + * the old when the stream is closed. (i.e. It's + * concurrency-safe). */ dest_avatar_stream = yield dest_avatar_file.replace_async (null, false, FileCreateFlags.PRIVATE); diff --git a/folks/backend-store.vala b/folks/backend-store.vala index a63563a..ae17d65 100644 --- a/folks/backend-store.vala +++ b/folks/backend-store.vala @@ -250,6 +250,10 @@ public class Folks.BackendStore : Object { * called for the first time. If it isn't called explicitly, * {@link BackendStore.load_backends} will call it. * + * This method is safe to call multiple times concurrently (e.g. an + * asynchronous call may begin between a subsequent asynchronous call + * beginning and finishing). + * * @since 0.3.0 */ public async void prepare () @@ -275,6 +279,8 @@ public class Folks.BackendStore : Object { * ``FOLKS_BACKEND_PATH`` environment variable, if it's set. If it's not set, * backends will be searched for in a path set at compilation time. * + * This method is not safe to call multiple times concurrently. + * * @throws GLib.Error currently unused */ public async void load_backends () throws GLib.Error @@ -379,6 +385,8 @@ public class Folks.BackendStore : Object { Internal.profiling_end ("loading backends in BackendStore"); } + /* This method is not safe to call multiple times concurrently, since there's + * a race in updating this._prepared_backends. */ private async void _backend_load_if_needed (Backend backend) { if (this._backend_is_enabled (backend.name)) @@ -402,6 +410,8 @@ public class Folks.BackendStore : Object { } } + /* This method is not safe to call multiple times concurrently, since there's + * a race in updating this._prepared_backends. */ private async bool _backend_unload_if_needed (Backend backend) { var unloaded = false; @@ -539,6 +549,10 @@ public class Folks.BackendStore : Object { * to load it when {@link BackendStore.load_backends} is called. This will * not load the backend if it's not currently loaded. * + * This method is safe to call multiple times concurrently (e.g. an + * asynchronous call may begin after a previous asynchronous call for the same + * backend name has begun and before it has finished). + * * @param name the name of the backend to enable * @since 0.3.2 */ @@ -555,6 +569,10 @@ public class Folks.BackendStore : Object { * client application is restarted. This will not remove the backend if it's * already loaded. * + * This method is safe to call multiple times concurrently (e.g. an + * asynchronous call may begin after a previous asynchronous call for the same + * backend name has begun and before it has finished). + * * @param name the name of the backend to disable * @since 0.3.2 */ @@ -564,6 +582,7 @@ public class Folks.BackendStore : Object { yield this._save_key_file (); } + /* This method is safe to call multiple times concurrently. */ private async HashMap? _get_modules_from_dir (File dir) { debug ("Searching for modules in folder '%s' ..", dir.get_path ()); @@ -697,6 +716,7 @@ public class Folks.BackendStore : Object { debug ("Loaded module source: '%s'", module.name ()); } + /* This method is safe to call multiple times concurrently. */ private async static void _get_file_info (File file, out bool is_file, out bool is_dir) @@ -734,6 +754,7 @@ public class Folks.BackendStore : Object { is_dir = (file_info.get_file_type () == FileType.DIRECTORY); } + /* This method is safe to call multiple times concurrently. */ private async void _load_disabled_backend_names () { File file; @@ -760,7 +781,7 @@ public class Folks.BackendStore : Object { this._config_file = file; /* Load the disabled backends file */ - this._backends_key_file = new GLib.KeyFile (); + var key_file = new GLib.KeyFile (); try { uint8[] contents; @@ -770,7 +791,7 @@ public class Folks.BackendStore : Object { if (contents_s.length > 0) { - this._backends_key_file.load_from_data (contents_s, + key_file.load_from_data (contents_s, contents_s.length, KeyFileFlags.KEEP_COMMENTS); } } @@ -783,8 +804,15 @@ public class Folks.BackendStore : Object { return; } } + finally + { + /* Update the key file in memory, whether the new one is empty or + * full. */ + this._backends_key_file = (owned) key_file; + } } + /* This method is safe to call multiple times concurrently. */ private async void _save_key_file () { var key_file_data = this._backends_key_file.to_data (); diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala index 18865f1..6202b3f 100644 --- a/folks/individual-aggregator.vala +++ b/folks/individual-aggregator.vala @@ -1817,6 +1817,9 @@ public class Folks.IndividualAggregator : Object * Completely remove the individual and all of its personas from their * backing stores. * + * This method is safe to call multiple times concurrently (for the same + * individual or different individuals). + * * @param individual the {@link Individual} to remove * @throws GLib.Error if removing the persona failed — this will be passed * through from {@link PersonaStore.remove_persona} @@ -1844,6 +1847,9 @@ public class Folks.IndividualAggregator : Object * * This will leave other personas in the same individual alone. * + * This method is safe to call multiple times concurrently (for the same + * persona or different personas). + * * @param persona the {@link Persona} to remove * @throws GLib.Error if removing the persona failed — this will be passed * through from {@link PersonaStore.remove_persona} @@ -1866,6 +1872,8 @@ public class Folks.IndividualAggregator : Object * before is signalled by {@link IndividualAggregator.individuals_changed} and * {@link Individual.removed}. * + * This method is safe to call multiple times concurrently. + * * @param personas the {@link Persona}s to be linked * @throws IndividualAggregatorError.NO_PRIMARY_STORE if no primary store has * been configured for the individual aggregator @@ -2039,6 +2047,10 @@ public class Folks.IndividualAggregator : Object * new {@link Individual}s will be signalled by * {@link IndividualAggregator.individuals_changed}. * + * This method is safe to call multiple times concurrently, although + * concurrent calls for the same individual may result in duplicate personas + * being created. + * * @param individual the {@link Individual} to unlink * @throws GLib.Error if removing the linking persona failed — this will be * passed through from {@link PersonaStore.remove_persona} @@ -2120,6 +2132,10 @@ public class Folks.IndividualAggregator : Object * {@link IndividualAggregatorError.PROPERTY_NOT_WRITEABLE} error will be * thrown. * + * This method is safe to call multiple times concurrently, although + * concurrent calls for the same individual may result in duplicate personas + * being created. + * * @param individual the individual for which ``property_name`` should be * writeable * @param property_name the name of the property which needs to be writeable @@ -2148,6 +2164,9 @@ public class Folks.IndividualAggregator : Object return p; } + /* This is safe to call multiple times concurrently, *but* if the set of + * personas doesn't change, multiple duplicate personas may be created in the + * writeable store. */ private async Persona _ensure_personas_property_writeable ( Set personas, string property_name) throws IndividualAggregatorError @@ -2249,6 +2268,8 @@ public class Folks.IndividualAggregator : Object * been called, and will call {@link IndividualAggregator.prepare} itself in * that case. * + * This method is safe to call multiple times concurrently. + * * @param id ID of the individual to look up * @return individual with ``id``, or ``null`` if no such individual was found * @throws GLib.Error from {@link IndividualAggregator.prepare} diff --git a/folks/object-cache.vala b/folks/object-cache.vala index b33a4fe..69a5881 100644 --- a/folks/object-cache.vala +++ b/folks/object-cache.vala @@ -180,6 +180,8 @@ public abstract class Folks.ObjectCache : Object * If any errors are encountered while loading the objects, warnings will be * logged as appropriate and ``null`` will be returned. * + * This method is safe to call multiple times concurrently. + * * @param cancellable A {@link GLib.Cancellable} for the operation, or * ``null``. * @return A set of objects from the cache, or ``null``. @@ -324,6 +326,8 @@ public abstract class Folks.ObjectCache : Object * cache will be left in a consistent state, but may be storing the old set * of objects or the new set. * + * This method is safe to call multiple times concurrently. + * * @param objects A set of objects to store. This may be empty, but may not * be ``null``. * @param cancellable A {@link GLib.Cancellable} for the operation, or @@ -374,6 +378,7 @@ public abstract class Folks.ObjectCache : Object { try { + /* We assume that replace_contents_async() is atomic. */ yield this._cache_file.replace_contents_async ( data, null, false, FileCreateFlags.PRIVATE, cancellable, null); -- 2.7.4