imip-agent

Changeset

1040:8f2b373a311b
2016-02-08 Paul Boddie raw files shortlog changelog graph 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.
imiptools/handlers/resource.py (file) imiptools/handlers/scheduling/__init__.py (file) imiptools/handlers/scheduling/access.py (file) imiptools/handlers/scheduling/freebusy.py (file) imiptools/handlers/scheduling/manifest.py (file) imiptools/handlers/scheduling/quota.py (file) tools/update_scheduling_modules.py (file)
     1.1 --- a/imiptools/handlers/resource.py	Mon Feb 08 00:14:53 2016 +0100
     1.2 +++ b/imiptools/handlers/resource.py	Mon Feb 08 00:47:00 2016 +0100
     1.3 @@ -23,7 +23,9 @@
     1.4  from imiptools.handlers import Handler
     1.5  from imiptools.handlers.common import CommonFreebusy, CommonEvent
     1.6  from imiptools.handlers.scheduling import apply_scheduling_functions, \
     1.7 -                                          confirm_scheduling, retract_scheduling
     1.8 +                                          confirm_scheduling, \
     1.9 +                                          finish_scheduling, \
    1.10 +                                          retract_scheduling
    1.11  
    1.12  class ResourceHandler(CommonEvent, Handler):
    1.13  
    1.14 @@ -87,49 +89,56 @@
    1.15          "Attempt to schedule the current object for the current user."
    1.16  
    1.17          attendee_attr = uri_dict(self.obj.get_value_map("ATTENDEE"))[self.user]
    1.18 +
    1.19 +        # Attempt to schedule the event.
    1.20 +
    1.21          scheduled = self.schedule()
    1.22  
    1.23 -        # Update the participation of the resource in the object.
    1.24 -        # Update free/busy information.
    1.25 +        try:
    1.26 +            # Update the participation of the resource in the object.
    1.27 +            # Update free/busy information.
    1.28  
    1.29 -        if scheduled in ("ACCEPTED", "DECLINED"):
    1.30 -            method = "REPLY"
    1.31 -            attendee_attr = self.update_participation(scheduled)
    1.32 +            if scheduled in ("ACCEPTED", "DECLINED"):
    1.33 +                method = "REPLY"
    1.34 +                attendee_attr = self.update_participation(scheduled)
    1.35  
    1.36 -            self.update_event_in_freebusy(for_organiser=False)
    1.37 -            self.remove_event_from_freebusy_offers()
    1.38 +                self.update_event_in_freebusy(for_organiser=False)
    1.39 +                self.remove_event_from_freebusy_offers()
    1.40  
    1.41 -            # Set the complete event or an additional occurrence.
    1.42 +                # Set the complete event or an additional occurrence.
    1.43  
    1.44 -            event = self.obj.to_node()
    1.45 -            self.store.set_event(self.user, self.uid, self.recurrenceid, event)
    1.46 +                event = self.obj.to_node()
    1.47 +                self.store.set_event(self.user, self.uid, self.recurrenceid, event)
    1.48  
    1.49 -            # Remove additional recurrences if handling a complete event.
    1.50 -            # Also remove any previous cancellations involving this event.
    1.51 +                # Remove additional recurrences if handling a complete event.
    1.52 +                # Also remove any previous cancellations involving this event.
    1.53  
    1.54 -            if not self.recurrenceid:
    1.55 -                self.store.remove_recurrences(self.user, self.uid)
    1.56 -                self.store.remove_cancellations(self.user, self.uid)
    1.57 -            else:
    1.58 -                self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
    1.59 +                if not self.recurrenceid:
    1.60 +                    self.store.remove_recurrences(self.user, self.uid)
    1.61 +                    self.store.remove_cancellations(self.user, self.uid)
    1.62 +                else:
    1.63 +                    self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
    1.64  
    1.65 -            # Confirm any scheduling.
    1.66 +                if scheduled == "ACCEPTED":
    1.67 +                    self.confirm_scheduling()
    1.68  
    1.69 -            if scheduled == "ACCEPTED":
    1.70 -                self.confirm_scheduling()
    1.71 +            # For countered proposals, record the offer in the resource's
    1.72 +            # free/busy collection.
    1.73  
    1.74 -        # For countered proposals, record the offer in the resource's
    1.75 -        # free/busy collection.
    1.76 +            elif scheduled == "COUNTER":
    1.77 +                method = "COUNTER"
    1.78 +                self.update_event_in_freebusy_offers()
    1.79 +
    1.80 +            # For inappropriate periods, reply declining participation.
    1.81  
    1.82 -        elif scheduled == "COUNTER":
    1.83 -            method = "COUNTER"
    1.84 -            self.update_event_in_freebusy_offers()
    1.85 +            else:
    1.86 +                method = "REPLY"
    1.87 +                attendee_attr = self.update_participation("DECLINED")
    1.88  
    1.89 -        # For inappropriate periods, reply declining participation.
    1.90 +        # Confirm any scheduling.
    1.91  
    1.92 -        else:
    1.93 -            method = "REPLY"
    1.94 -            attendee_attr = self.update_participation("DECLINED")
    1.95 +        finally:
    1.96 +            self.finish_scheduling()
    1.97  
    1.98          # Make a version of the object with just this attendee, update the
    1.99          # DTSTAMP in the response, and return the object for sending.
   1.100 @@ -192,6 +201,15 @@
   1.101          if functions:
   1.102              confirm_scheduling(functions.split("\n"), self)
   1.103  
   1.104 +    def finish_scheduling(self):
   1.105 +
   1.106 +        "Finish the scheduling, unlocking resources where appropriate."
   1.107 +
   1.108 +        functions = self.get_preferences().get("scheduling_function",
   1.109 +            "schedule_in_freebusy").split("\n")
   1.110 +
   1.111 +        finish_scheduling(functions, self)
   1.112 +
   1.113      def retract_scheduling(self):
   1.114  
   1.115          "Retract this event from scheduling records."
     2.1 --- a/imiptools/handlers/scheduling/__init__.py	Mon Feb 08 00:14:53 2016 +0100
     2.2 +++ b/imiptools/handlers/scheduling/__init__.py	Mon Feb 08 00:47:00 2016 +0100
     2.3 @@ -21,8 +21,10 @@
     2.4  
     2.5  from imiptools.text import parse_line
     2.6  from imiptools.handlers.scheduling.manifest import confirmation_functions, \
     2.7 +                                                   locking_functions, \
     2.8                                                     retraction_functions, \
     2.9 -                                                   scheduling_functions
    2.10 +                                                   scheduling_functions, \
    2.11 +                                                   unlocking_functions
    2.12  
    2.13  # Function application/invocation.
    2.14  
    2.15 @@ -34,12 +36,25 @@
    2.16      """
    2.17  
    2.18      # Obtain the actual scheduling functions with arguments.
    2.19 +    # Also obtain functions to lock resources.
    2.20  
    2.21 -    functions = get_function_calls(functions, scheduling_functions)
    2.22 +    schedulers = get_function_calls(functions, scheduling_functions)
    2.23 +    locks = get_function_calls(functions, locking_functions)
    2.24 +
    2.25 +    # First, lock the resources to be used.
    2.26 +
    2.27 +    for fn, args in locks:
    2.28 +
    2.29 +        # Not all scheduling functions require compound locking.
    2.30 +
    2.31 +        if fn:
    2.32 +            fn(handler, args)
    2.33 +
    2.34 +    # Then, invoke the scheduling functions.
    2.35  
    2.36      response = "ACCEPTED"
    2.37  
    2.38 -    for fn, args in functions:
    2.39 +    for fn, args in schedulers:
    2.40  
    2.41          # NOTE: Should signal an error for incorrectly configured resources.
    2.42  
    2.43 @@ -77,6 +92,26 @@
    2.44      functions = get_function_calls(functions, confirmation_functions)
    2.45      apply_functions(functions, handler)
    2.46  
    2.47 +def finish_scheduling(functions, handler):
    2.48 +
    2.49 +    """
    2.50 +    Finish scheduling using the given scheduling 'functions' for the current
    2.51 +    object of the given 'handler'.
    2.52 +    """
    2.53 +
    2.54 +    # Obtain functions to unlock resources.
    2.55 +
    2.56 +    locks = get_function_calls(functions, unlocking_functions)
    2.57 +
    2.58 +    # Unlock the resources that were used.
    2.59 +
    2.60 +    for fn, args in locks:
    2.61 +
    2.62 +        # Not all scheduling functions require compound locking.
    2.63 +
    2.64 +        if fn:
    2.65 +            fn(handler, args)
    2.66 +
    2.67  def retract_scheduling(functions, handler):
    2.68  
    2.69      """
     3.1 --- a/imiptools/handlers/scheduling/access.py	Mon Feb 08 00:14:53 2016 +0100
     3.2 +++ b/imiptools/handlers/scheduling/access.py	Mon Feb 08 00:47:00 2016 +0100
     3.3 @@ -132,6 +132,11 @@
     3.4      "same_domain_only" : same_domain_only,
     3.5      }
     3.6  
     3.7 +# Registries of locking and unlocking functions.
     3.8 +
     3.9 +locking_functions = {}
    3.10 +unlocking_functions = {}
    3.11 +
    3.12  # Registries of listener functions.
    3.13  
    3.14  confirmation_functions = {}
     4.1 --- a/imiptools/handlers/scheduling/freebusy.py	Mon Feb 08 00:14:53 2016 +0100
     4.2 +++ b/imiptools/handlers/scheduling/freebusy.py	Mon Feb 08 00:47:00 2016 +0100
     4.3 @@ -217,6 +217,11 @@
     4.4      "schedule_next_available_in_freebusy" : schedule_next_available_in_freebusy,
     4.5      }
     4.6  
     4.7 +# Registries of locking and unlocking functions.
     4.8 +
     4.9 +locking_functions = {}
    4.10 +unlocking_functions = {}
    4.11 +
    4.12  # Registries of listener functions.
    4.13  
    4.14  confirmation_functions = {}
     5.1 --- a/imiptools/handlers/scheduling/manifest.py	Mon Feb 08 00:14:53 2016 +0100
     5.2 +++ b/imiptools/handlers/scheduling/manifest.py	Mon Feb 08 00:47:00 2016 +0100
     5.3 @@ -1,31 +1,45 @@
     5.4  confirmation_functions = {}
     5.5 +locking_functions = {}
     5.6  retraction_functions = {}
     5.7  scheduling_functions = {}
     5.8 +unlocking_functions = {}
     5.9  
    5.10  from imiptools.handlers.scheduling.quota import (
    5.11      confirmation_functions as c,
    5.12 +    locking_functions as l,
    5.13      retraction_functions as r,
    5.14 -    scheduling_functions as s)
    5.15 +    scheduling_functions as s,
    5.16 +    unlocking_functions as u)
    5.17  
    5.18  confirmation_functions.update(c)
    5.19 +locking_functions.update(l)
    5.20  retraction_functions.update(r)
    5.21  scheduling_functions.update(s)
    5.22 +unlocking_functions.update(u)
    5.23  
    5.24  from imiptools.handlers.scheduling.freebusy import (
    5.25      confirmation_functions as c,
    5.26 +    locking_functions as l,
    5.27      retraction_functions as r,
    5.28 -    scheduling_functions as s)
    5.29 +    scheduling_functions as s,
    5.30 +    unlocking_functions as u)
    5.31  
    5.32  confirmation_functions.update(c)
    5.33 +locking_functions.update(l)
    5.34  retraction_functions.update(r)
    5.35  scheduling_functions.update(s)
    5.36 +unlocking_functions.update(u)
    5.37  
    5.38  from imiptools.handlers.scheduling.access import (
    5.39      confirmation_functions as c,
    5.40 +    locking_functions as l,
    5.41      retraction_functions as r,
    5.42 -    scheduling_functions as s)
    5.43 +    scheduling_functions as s,
    5.44 +    unlocking_functions as u)
    5.45  
    5.46  confirmation_functions.update(c)
    5.47 +locking_functions.update(l)
    5.48  retraction_functions.update(r)
    5.49  scheduling_functions.update(s)
    5.50 +unlocking_functions.update(u)
    5.51  
     6.1 --- a/imiptools/handlers/scheduling/quota.py	Mon Feb 08 00:14:53 2016 +0100
     6.2 +++ b/imiptools/handlers/scheduling/quota.py	Mon Feb 08 00:47:00 2016 +0100
     6.3 @@ -76,15 +76,10 @@
     6.4      # Obtain the journal entries and limits.
     6.5  
     6.6      journal = handler.get_journal()
     6.7 -    journal.acquire_lock(quota)
     6.8 +    entries = journal.get_entries(quota, group)
     6.9  
    6.10 -    try:
    6.11 -        entries = journal.get_entries(quota, group)
    6.12 -        if _add_to_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)):
    6.13 -            journal.set_entries(quota, group, entries)
    6.14 -
    6.15 -    finally:
    6.16 -        journal.release_lock(quota)
    6.17 +    if _add_to_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)):
    6.18 +        journal.set_entries(quota, group, entries)
    6.19  
    6.20  def remove_from_quota(handler, args):
    6.21  
    6.22 @@ -100,15 +95,10 @@
    6.23      # Obtain the journal entries and limits.
    6.24  
    6.25      journal = handler.get_journal()
    6.26 -    journal.acquire_lock(quota)
    6.27 +    entries = journal.get_entries(quota, group)
    6.28  
    6.29 -    try:
    6.30 -        entries = journal.get_entries(quota, group)
    6.31 -        if _remove_from_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)):
    6.32 -            journal.set_entries(quota, group, entries)
    6.33 -
    6.34 -    finally:
    6.35 -        journal.release_lock(quota)
    6.36 +    if _remove_from_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)):
    6.37 +        journal.set_entries(quota, group, entries)
    6.38  
    6.39  def _get_quota_and_group(handler, args):
    6.40  
    6.41 @@ -254,15 +244,9 @@
    6.42      quota, organiser = _get_quota_and_identity(handler, args)
    6.43  
    6.44      journal = handler.get_journal()
    6.45 -    journal.acquire_lock(quota)
    6.46 -
    6.47 -    try:
    6.48 -        freebusy = journal.get_freebusy(quota, organiser)
    6.49 -        handler.update_freebusy(freebusy, organiser, True)
    6.50 -        journal.set_freebusy(quota, organiser, freebusy)
    6.51 -
    6.52 -    finally:
    6.53 -        journal.release_lock(quota)
    6.54 +    freebusy = journal.get_freebusy(quota, organiser)
    6.55 +    handler.update_freebusy(freebusy, organiser, True)
    6.56 +    journal.set_freebusy(quota, organiser, freebusy)
    6.57  
    6.58  def remove_from_quota_freebusy(handler, args):
    6.59  
    6.60 @@ -274,15 +258,9 @@
    6.61      quota, organiser = _get_quota_and_identity(handler, args)
    6.62  
    6.63      journal = handler.get_journal()
    6.64 -    journal.acquire_lock(quota)
    6.65 -
    6.66 -    try:
    6.67 -        freebusy = journal.get_freebusy(quota, organiser)
    6.68 -        handler.remove_from_freebusy(freebusy)
    6.69 -        journal.set_freebusy(quota, organiser, freebusy)
    6.70 -
    6.71 -    finally:
    6.72 -        journal.release_lock(quota)
    6.73 +    freebusy = journal.get_freebusy(quota, organiser)
    6.74 +    handler.remove_from_freebusy(freebusy)
    6.75 +    journal.set_freebusy(quota, organiser, freebusy)
    6.76  
    6.77  def _get_quota_and_identity(handler, args):
    6.78  
    6.79 @@ -300,6 +278,26 @@
    6.80  
    6.81      return quota, organiser
    6.82  
    6.83 +# Locking and unlocking.
    6.84 +
    6.85 +def lock_journal(handler, args):
    6.86 +
    6.87 +    "Using the 'handler' and 'args', lock the journal for the quota."
    6.88 +
    6.89 +    handler.get_journal().acquire_lock(_get_quota(handler, args))
    6.90 +
    6.91 +def unlock_journal(handler, args):
    6.92 +
    6.93 +    "Using the 'handler' and 'args', unlock the journal for the quota."
    6.94 +
    6.95 +    handler.get_journal().release_lock(_get_quota(handler, args))
    6.96 +
    6.97 +def _get_quota(handler, args):
    6.98 +
    6.99 +    "Return the quota using the 'handler' and 'args'."
   6.100 +
   6.101 +    return args and args[0] or handler.user
   6.102 +
   6.103  # Registry of scheduling functions.
   6.104  
   6.105  scheduling_functions = {
   6.106 @@ -307,6 +305,18 @@
   6.107      "schedule_across_quota" : schedule_across_quota,
   6.108      }
   6.109  
   6.110 +# Registries of locking and unlocking functions.
   6.111 +
   6.112 +locking_functions = {
   6.113 +    "check_quota" : lock_journal,
   6.114 +    "schedule_across_quota" : lock_journal,
   6.115 +    }
   6.116 +
   6.117 +unlocking_functions = {
   6.118 +    "check_quota" : unlock_journal,
   6.119 +    "schedule_across_quota" : unlock_journal,
   6.120 +    }
   6.121 +
   6.122  # Registries of listener functions.
   6.123  
   6.124  confirmation_functions = {
     7.1 --- a/tools/update_scheduling_modules.py	Mon Feb 08 00:14:53 2016 +0100
     7.2 +++ b/tools/update_scheduling_modules.py	Mon Feb 08 00:47:00 2016 +0100
     7.3 @@ -46,8 +46,10 @@
     7.4      try:
     7.5          print >>f, """\
     7.6  confirmation_functions = {}
     7.7 +locking_functions = {}
     7.8  retraction_functions = {}
     7.9  scheduling_functions = {}
    7.10 +unlocking_functions = {}
    7.11  """
    7.12  
    7.13          for filename in filenames:
    7.14 @@ -56,12 +58,16 @@
    7.15              print >>f, """\
    7.16  from imiptools.handlers.scheduling.%s import (
    7.17      confirmation_functions as c,
    7.18 +    locking_functions as l,
    7.19      retraction_functions as r,
    7.20 -    scheduling_functions as s)
    7.21 +    scheduling_functions as s,
    7.22 +    unlocking_functions as u)
    7.23  
    7.24  confirmation_functions.update(c)
    7.25 +locking_functions.update(l)
    7.26  retraction_functions.update(r)
    7.27  scheduling_functions.update(s)
    7.28 +unlocking_functions.update(u)
    7.29  """ % module
    7.30  
    7.31      finally: