[folks] documentation: Document which yielding methods are safe to call concurrently



commit e21932e8d233f68e0b065882eb06b15089a2935a
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun Dec 30 00:48:38 2012 +0000

    documentation: Document which yielding methods are safe to call concurrently
    
    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<weak string, weak string> 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<Persona>? 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<Persona> 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<AbstractFieldDetails<string>> 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<string> 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<string> _multi_value_query (string query)
     {
       Gee.HashSet<string> ret = new Gee.HashSet<string> ();
@@ -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<string> 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<string, WebServiceFieldDetails> 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<EmailFieldDetails> 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<PhoneFieldDetails> 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<AbstractFieldDetails<string>> 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<UrlFieldDetails> 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<string, ImFieldDetails> 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<PostalAddressFieldDetails> 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<RoleFieldDetails> 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<NoteFieldDetails> 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<Object> 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<string> _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<string> _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<string>  _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<string> _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<string> _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 <urn> 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 <urn> 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<string, File>? _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<Persona> 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<T> : 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<T> : 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<T> : Object
         {
           try
             {
+              /* We assume that replace_contents_async() is atomic. */
               yield this._cache_file.replace_contents_async (
                   data, null, false,
                   FileCreateFlags.PRIVATE, cancellable, null);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]