1 | """ |
---|
2 | Manage status of long-running operations. |
---|
3 | |
---|
4 | Ported to Python 3. |
---|
5 | """ |
---|
6 | |
---|
7 | from zope.interface import Interface, implementer |
---|
8 | from allmydata.util import observer |
---|
9 | |
---|
10 | |
---|
11 | class IMonitor(Interface): |
---|
12 | """I manage status, progress, and cancellation for long-running operations. |
---|
13 | |
---|
14 | Whoever initiates the operation should create a Monitor instance and pass |
---|
15 | it into the code that implements the operation. That code should |
---|
16 | periodically check in with the Monitor, perhaps after each major unit of |
---|
17 | work has been completed, for two purposes. |
---|
18 | |
---|
19 | The first is to inform the Monitor about progress that has been made, so |
---|
20 | that external observers can be reassured that the operation is proceeding |
---|
21 | normally. If the operation has a well-known amount of work to perform, |
---|
22 | this notification should reflect that, so that an ETA or 'percentage |
---|
23 | complete' value can be derived. |
---|
24 | |
---|
25 | The second purpose is to check to see if the operation has been |
---|
26 | cancelled. The impatient observer who no longer wants the operation to |
---|
27 | continue will inform the Monitor; the next time the operation code checks |
---|
28 | in, it should notice that the operation has been cancelled, and wrap |
---|
29 | things up. The same monitor can be passed to multiple operations, all of |
---|
30 | which may check for cancellation: this pattern may be simpler than having |
---|
31 | the original caller keep track of subtasks and cancel them individually. |
---|
32 | """ |
---|
33 | |
---|
34 | # the following methods are provided for the operation code |
---|
35 | |
---|
36 | def is_cancelled(): |
---|
37 | """Returns True if the operation has been cancelled. If True, |
---|
38 | operation code should stop creating new work, and attempt to stop any |
---|
39 | work already in progress.""" |
---|
40 | |
---|
41 | def raise_if_cancelled(): |
---|
42 | """Raise OperationCancelledError if the operation has been cancelled. |
---|
43 | Operation code that has a robust error-handling path can simply call |
---|
44 | this periodically.""" |
---|
45 | |
---|
46 | def set_status(status): |
---|
47 | """Sets the Monitor's 'status' object to an arbitrary value. |
---|
48 | Different operations will store different sorts of status information |
---|
49 | here. Operation code should use get+modify+set sequences to update |
---|
50 | this.""" |
---|
51 | |
---|
52 | def get_status(): |
---|
53 | """Return the status object. If the operation failed, this will be a |
---|
54 | Failure instance.""" |
---|
55 | |
---|
56 | def finish(status): |
---|
57 | """Call this when the operation is done, successful or not. The |
---|
58 | Monitor's lifetime is influenced by the completion of the operation |
---|
59 | it is monitoring. The Monitor's 'status' value will be set with the |
---|
60 | 'status' argument, just as if it had been passed to set_status(). |
---|
61 | This value will be used to fire the Deferreds that are returned by |
---|
62 | when_done(). |
---|
63 | |
---|
64 | Operations that fire a Deferred when they finish should trigger this |
---|
65 | with d.addBoth(monitor.finish)""" |
---|
66 | |
---|
67 | # the following methods are provided for the initiator of the operation |
---|
68 | |
---|
69 | def is_finished(): |
---|
70 | """Return a boolean, True if the operation is done (whether |
---|
71 | successful or failed), False if it is still running.""" |
---|
72 | |
---|
73 | def when_done(): |
---|
74 | """Return a Deferred that fires when the operation is complete. It |
---|
75 | will fire with the operation status, the same value as returned by |
---|
76 | get_status().""" |
---|
77 | |
---|
78 | def cancel(): |
---|
79 | """Cancel the operation as soon as possible. is_cancelled() will |
---|
80 | start returning True after this is called.""" |
---|
81 | |
---|
82 | # get_status() is useful too, but it is operation-specific |
---|
83 | |
---|
84 | |
---|
85 | class OperationCancelledError(Exception): |
---|
86 | pass |
---|
87 | |
---|
88 | |
---|
89 | @implementer(IMonitor) |
---|
90 | class Monitor(object): |
---|
91 | |
---|
92 | def __init__(self): |
---|
93 | self.cancelled = False |
---|
94 | self.finished = False |
---|
95 | self.status = None |
---|
96 | self.observer = observer.OneShotObserverList() |
---|
97 | |
---|
98 | def is_cancelled(self): |
---|
99 | return self.cancelled |
---|
100 | |
---|
101 | def raise_if_cancelled(self): |
---|
102 | if self.cancelled: |
---|
103 | raise OperationCancelledError() |
---|
104 | |
---|
105 | def is_finished(self): |
---|
106 | return self.finished |
---|
107 | |
---|
108 | def when_done(self): |
---|
109 | return self.observer.when_fired() |
---|
110 | |
---|
111 | def cancel(self): |
---|
112 | self.cancelled = True |
---|
113 | |
---|
114 | def finish(self, status_or_failure): |
---|
115 | self.set_status(status_or_failure) |
---|
116 | self.finished = True |
---|
117 | self.observer.fire(status_or_failure) |
---|
118 | return status_or_failure |
---|
119 | |
---|
120 | def get_status(self): |
---|
121 | return self.status |
---|
122 | def set_status(self, status): |
---|
123 | self.status = status |
---|