# HG changeset patch # User Paul Boddie # Date 1454888820 -3600 # Node ID 8f2b373a311b9692e4c78facda9b764521508f55 # Parent a12150034cbd9b2d4f160c9e3f332ab2b738f849 Introduced compound locking so that information can be in a consistent state for scheduling functions and confirmation functions within the same transaction, unchanged by concurrent transactions. diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/resource.py --- a/imiptools/handlers/resource.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/resource.py Mon Feb 08 00:47:00 2016 +0100 @@ -23,7 +23,9 @@ from imiptools.handlers import Handler from imiptools.handlers.common import CommonFreebusy, CommonEvent from imiptools.handlers.scheduling import apply_scheduling_functions, \ - confirm_scheduling, retract_scheduling + confirm_scheduling, \ + finish_scheduling, \ + retract_scheduling class ResourceHandler(CommonEvent, Handler): @@ -87,49 +89,56 @@ "Attempt to schedule the current object for the current user." attendee_attr = uri_dict(self.obj.get_value_map("ATTENDEE"))[self.user] + + # Attempt to schedule the event. + scheduled = self.schedule() - # Update the participation of the resource in the object. - # Update free/busy information. + try: + # Update the participation of the resource in the object. + # Update free/busy information. - if scheduled in ("ACCEPTED", "DECLINED"): - method = "REPLY" - attendee_attr = self.update_participation(scheduled) + if scheduled in ("ACCEPTED", "DECLINED"): + method = "REPLY" + attendee_attr = self.update_participation(scheduled) - self.update_event_in_freebusy(for_organiser=False) - self.remove_event_from_freebusy_offers() + self.update_event_in_freebusy(for_organiser=False) + self.remove_event_from_freebusy_offers() - # Set the complete event or an additional occurrence. + # Set the complete event or an additional occurrence. - event = self.obj.to_node() - self.store.set_event(self.user, self.uid, self.recurrenceid, event) + event = self.obj.to_node() + self.store.set_event(self.user, self.uid, self.recurrenceid, event) - # Remove additional recurrences if handling a complete event. - # Also remove any previous cancellations involving this event. + # Remove additional recurrences if handling a complete event. + # Also remove any previous cancellations involving this event. - if not self.recurrenceid: - self.store.remove_recurrences(self.user, self.uid) - self.store.remove_cancellations(self.user, self.uid) - else: - self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) + if not self.recurrenceid: + self.store.remove_recurrences(self.user, self.uid) + self.store.remove_cancellations(self.user, self.uid) + else: + self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) - # Confirm any scheduling. + if scheduled == "ACCEPTED": + self.confirm_scheduling() - if scheduled == "ACCEPTED": - self.confirm_scheduling() + # For countered proposals, record the offer in the resource's + # free/busy collection. - # For countered proposals, record the offer in the resource's - # free/busy collection. + elif scheduled == "COUNTER": + method = "COUNTER" + self.update_event_in_freebusy_offers() + + # For inappropriate periods, reply declining participation. - elif scheduled == "COUNTER": - method = "COUNTER" - self.update_event_in_freebusy_offers() + else: + method = "REPLY" + attendee_attr = self.update_participation("DECLINED") - # For inappropriate periods, reply declining participation. + # Confirm any scheduling. - else: - method = "REPLY" - attendee_attr = self.update_participation("DECLINED") + finally: + self.finish_scheduling() # Make a version of the object with just this attendee, update the # DTSTAMP in the response, and return the object for sending. @@ -192,6 +201,15 @@ if functions: confirm_scheduling(functions.split("\n"), self) + def finish_scheduling(self): + + "Finish the scheduling, unlocking resources where appropriate." + + functions = self.get_preferences().get("scheduling_function", + "schedule_in_freebusy").split("\n") + + finish_scheduling(functions, self) + def retract_scheduling(self): "Retract this event from scheduling records." diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/scheduling/__init__.py --- a/imiptools/handlers/scheduling/__init__.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/scheduling/__init__.py Mon Feb 08 00:47:00 2016 +0100 @@ -21,8 +21,10 @@ from imiptools.text import parse_line from imiptools.handlers.scheduling.manifest import confirmation_functions, \ + locking_functions, \ retraction_functions, \ - scheduling_functions + scheduling_functions, \ + unlocking_functions # Function application/invocation. @@ -34,12 +36,25 @@ """ # Obtain the actual scheduling functions with arguments. + # Also obtain functions to lock resources. - functions = get_function_calls(functions, scheduling_functions) + schedulers = get_function_calls(functions, scheduling_functions) + locks = get_function_calls(functions, locking_functions) + + # First, lock the resources to be used. + + for fn, args in locks: + + # Not all scheduling functions require compound locking. + + if fn: + fn(handler, args) + + # Then, invoke the scheduling functions. response = "ACCEPTED" - for fn, args in functions: + for fn, args in schedulers: # NOTE: Should signal an error for incorrectly configured resources. @@ -77,6 +92,26 @@ functions = get_function_calls(functions, confirmation_functions) apply_functions(functions, handler) +def finish_scheduling(functions, handler): + + """ + Finish scheduling using the given scheduling 'functions' for the current + object of the given 'handler'. + """ + + # Obtain functions to unlock resources. + + locks = get_function_calls(functions, unlocking_functions) + + # Unlock the resources that were used. + + for fn, args in locks: + + # Not all scheduling functions require compound locking. + + if fn: + fn(handler, args) + def retract_scheduling(functions, handler): """ diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/scheduling/access.py --- a/imiptools/handlers/scheduling/access.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/scheduling/access.py Mon Feb 08 00:47:00 2016 +0100 @@ -132,6 +132,11 @@ "same_domain_only" : same_domain_only, } +# Registries of locking and unlocking functions. + +locking_functions = {} +unlocking_functions = {} + # Registries of listener functions. confirmation_functions = {} diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/scheduling/freebusy.py --- a/imiptools/handlers/scheduling/freebusy.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/scheduling/freebusy.py Mon Feb 08 00:47:00 2016 +0100 @@ -217,6 +217,11 @@ "schedule_next_available_in_freebusy" : schedule_next_available_in_freebusy, } +# Registries of locking and unlocking functions. + +locking_functions = {} +unlocking_functions = {} + # Registries of listener functions. confirmation_functions = {} diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/scheduling/manifest.py --- a/imiptools/handlers/scheduling/manifest.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/scheduling/manifest.py Mon Feb 08 00:47:00 2016 +0100 @@ -1,31 +1,45 @@ confirmation_functions = {} +locking_functions = {} retraction_functions = {} scheduling_functions = {} +unlocking_functions = {} from imiptools.handlers.scheduling.quota import ( confirmation_functions as c, + locking_functions as l, retraction_functions as r, - scheduling_functions as s) + scheduling_functions as s, + unlocking_functions as u) confirmation_functions.update(c) +locking_functions.update(l) retraction_functions.update(r) scheduling_functions.update(s) +unlocking_functions.update(u) from imiptools.handlers.scheduling.freebusy import ( confirmation_functions as c, + locking_functions as l, retraction_functions as r, - scheduling_functions as s) + scheduling_functions as s, + unlocking_functions as u) confirmation_functions.update(c) +locking_functions.update(l) retraction_functions.update(r) scheduling_functions.update(s) +unlocking_functions.update(u) from imiptools.handlers.scheduling.access import ( confirmation_functions as c, + locking_functions as l, retraction_functions as r, - scheduling_functions as s) + scheduling_functions as s, + unlocking_functions as u) confirmation_functions.update(c) +locking_functions.update(l) retraction_functions.update(r) scheduling_functions.update(s) +unlocking_functions.update(u) diff -r a12150034cbd -r 8f2b373a311b imiptools/handlers/scheduling/quota.py --- a/imiptools/handlers/scheduling/quota.py Mon Feb 08 00:14:53 2016 +0100 +++ b/imiptools/handlers/scheduling/quota.py Mon Feb 08 00:47:00 2016 +0100 @@ -76,15 +76,10 @@ # Obtain the journal entries and limits. journal = handler.get_journal() - journal.acquire_lock(quota) + entries = journal.get_entries(quota, group) - try: - entries = journal.get_entries(quota, group) - if _add_to_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): - journal.set_entries(quota, group, entries) - - finally: - journal.release_lock(quota) + if _add_to_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): + journal.set_entries(quota, group, entries) def remove_from_quota(handler, args): @@ -100,15 +95,10 @@ # Obtain the journal entries and limits. journal = handler.get_journal() - journal.acquire_lock(quota) + entries = journal.get_entries(quota, group) - try: - entries = journal.get_entries(quota, group) - if _remove_from_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): - journal.set_entries(quota, group, entries) - - finally: - journal.release_lock(quota) + if _remove_from_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): + journal.set_entries(quota, group, entries) def _get_quota_and_group(handler, args): @@ -254,15 +244,9 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - journal.acquire_lock(quota) - - try: - freebusy = journal.get_freebusy(quota, organiser) - handler.update_freebusy(freebusy, organiser, True) - journal.set_freebusy(quota, organiser, freebusy) - - finally: - journal.release_lock(quota) + freebusy = journal.get_freebusy(quota, organiser) + handler.update_freebusy(freebusy, organiser, True) + journal.set_freebusy(quota, organiser, freebusy) def remove_from_quota_freebusy(handler, args): @@ -274,15 +258,9 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - journal.acquire_lock(quota) - - try: - freebusy = journal.get_freebusy(quota, organiser) - handler.remove_from_freebusy(freebusy) - journal.set_freebusy(quota, organiser, freebusy) - - finally: - journal.release_lock(quota) + freebusy = journal.get_freebusy(quota, organiser) + handler.remove_from_freebusy(freebusy) + journal.set_freebusy(quota, organiser, freebusy) def _get_quota_and_identity(handler, args): @@ -300,6 +278,26 @@ return quota, organiser +# Locking and unlocking. + +def lock_journal(handler, args): + + "Using the 'handler' and 'args', lock the journal for the quota." + + handler.get_journal().acquire_lock(_get_quota(handler, args)) + +def unlock_journal(handler, args): + + "Using the 'handler' and 'args', unlock the journal for the quota." + + handler.get_journal().release_lock(_get_quota(handler, args)) + +def _get_quota(handler, args): + + "Return the quota using the 'handler' and 'args'." + + return args and args[0] or handler.user + # Registry of scheduling functions. scheduling_functions = { @@ -307,6 +305,18 @@ "schedule_across_quota" : schedule_across_quota, } +# Registries of locking and unlocking functions. + +locking_functions = { + "check_quota" : lock_journal, + "schedule_across_quota" : lock_journal, + } + +unlocking_functions = { + "check_quota" : unlock_journal, + "schedule_across_quota" : unlock_journal, + } + # Registries of listener functions. confirmation_functions = { diff -r a12150034cbd -r 8f2b373a311b tools/update_scheduling_modules.py --- a/tools/update_scheduling_modules.py Mon Feb 08 00:14:53 2016 +0100 +++ b/tools/update_scheduling_modules.py Mon Feb 08 00:47:00 2016 +0100 @@ -46,8 +46,10 @@ try: print >>f, """\ confirmation_functions = {} +locking_functions = {} retraction_functions = {} scheduling_functions = {} +unlocking_functions = {} """ for filename in filenames: @@ -56,12 +58,16 @@ print >>f, """\ from imiptools.handlers.scheduling.%s import ( confirmation_functions as c, + locking_functions as l, retraction_functions as r, - scheduling_functions as s) + scheduling_functions as s, + unlocking_functions as u) confirmation_functions.update(c) +locking_functions.update(l) retraction_functions.update(r) scheduling_functions.update(s) +unlocking_functions.update(u) """ % module finally: