Commit e21932e8 authored by Philip Withnall's avatar Philip Withnall

documentation: Document which yielding methods are safe to call concurrently

This is a follow-up commit to ce55fa2b.
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.
parent bf25b9b6
......@@ -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;
......
......@@ -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 ();
......
......@@ -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)
{
......
......@@ -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)
......
......@@ -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,
......
......@@ -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
......
......@@ -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);
......
......@@ -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 ();
......
......@@ -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.
*