Merge lp:~divmod-dev/divmod.org/start-scheduler-when-parent-service-starts-3000-2 into lp:divmod.org

Proposed by Jean-Paul Calderone
Status: Merged
Approved by: Tristan Seligmann
Approved revision: 2689
Merged at revision: 2695
Proposed branch: lp:~divmod-dev/divmod.org/start-scheduler-when-parent-service-starts-3000-2
Merge into: lp:divmod.org
Diff against target: 330 lines (+117/-27)
6 files modified
Axiom/axiom/scheduler.py (+27/-0)
Axiom/axiom/store.py (+9/-3)
Axiom/axiom/test/historic/stub_subscheduler1to2.py (+3/-1)
Axiom/axiom/test/historic/test_subscheduler1to2.py (+10/-8)
Axiom/axiom/test/test_scheduler.py (+25/-5)
Axiom/axiom/test/test_upgrading.py (+43/-10)
To merge this branch: bzr merge lp:~divmod-dev/divmod.org/start-scheduler-when-parent-service-starts-3000-2
Reviewer Review Type Date Requested Status
Tristan Seligmann Approve
Review via email: mp+87280@code.launchpad.net

Description of the change

This fixes the scheduling startup issue by ensuring the scheduler powerup is created and associated with the store service as soon as the store service is created.

To post a comment you must log in.
Revision history for this message
Tristan Seligmann (mithrandi) wrote :

This all basically looks good, and fixes an annoying long-standing bug, which is great. I just have one very minor thing to mention:

62 + if getattr(scheduler, 'setServiceParent', None) is not None:
63 + scheduler.setServiceParent(collection)

It seems a bit awkward to use getattr to (conditionally) retrieve the attribute and then retrieve it again via dot syntax. Perhaps this might be better:

setServiceParent = getattr(scheduler, 'setServiceParent', None)
if setServiceParent is not None:
    setServiceParent(collection)

or even:

getattr(scheduler, 'setServiceParent', lambda ign: None)(collection)

Please go ahead and merge regardless of whether you change this code or not.

review: Approve
2690. By Jean-Paul Calderone

Avoid an extra attribute lookup

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Axiom/axiom/scheduler.py'
2--- Axiom/axiom/scheduler.py 2009-07-07 20:35:43 +0000
3+++ Axiom/axiom/scheduler.py 2012-08-04 16:13:18 +0000
4@@ -1,5 +1,28 @@
5 # -*- test-case-name: axiom.test.test_scheduler -*-
6
7+"""
8+Timed event scheduling for Axiom databases.
9+
10+With this module, applications can schedule an L{Item} to have its C{run} method
11+called at a particular point in the future. This call will happen even if the
12+process which initially schedules it exits and the database is later re-opened
13+by another process (of course, if the scheduled time comes and goes while no
14+process is using the database, then the call will be delayed until some process
15+opens the database and starts its services).
16+
17+This module contains two implementations of the L{axiom.iaxiom.IScheduler}
18+interface, one for site stores and one for sub-stores. Items can only be
19+scheduled using an L{IScheduler} implementations from the store containing the
20+item. This means a typical way to schedule an item to be run is::
21+
22+ IScheduler(item.store).schedule(item, when)
23+
24+The scheduler service can also be retrieved from the site store's service
25+collection by name::
26+
27+ IServiceCollection(siteStore).getServiceNamed(SITE_SCHEDULER)
28+"""
29+
30 import warnings
31
32 from zope.interface import implements
33@@ -20,6 +43,9 @@
34
35 VERBOSE = False
36
37+SITE_SCHEDULER = u"Site Scheduler"
38+
39+
40 class TimedEventFailureLog(Item):
41 typeName = 'timed_event_failure_log'
42 schemaVersion = 1
43@@ -213,6 +239,7 @@
44
45 def __init__(self, store):
46 self.store = store
47+ self.setName(SITE_SCHEDULER)
48
49
50 def startService(self):
51
52=== modified file 'Axiom/axiom/store.py'
53--- Axiom/axiom/store.py 2009-07-07 20:35:43 +0000
54+++ Axiom/axiom/store.py 2012-08-04 16:13:18 +0000
55@@ -140,6 +140,13 @@
56 batcher = batch.BatchProcessingControllerService(st)
57 batcher.setServiceParent(collection)
58
59+ scheduler = iaxiom.IScheduler(st)
60+ # If it's an old database, we might get a SubScheduler instance. It has no
61+ # setServiceParent method.
62+ setServiceParent = getattr(scheduler, 'setServiceParent', None)
63+ if setServiceParent is not None:
64+ setServiceParent(collection)
65+
66 return collection
67
68
69@@ -989,7 +996,6 @@
70 sched = _SiteScheduler(empowered)
71 else:
72 sched = _UserScheduler(empowered)
73- sched.setServiceParent(IService(empowered))
74 empowered._schedulerService = sched
75 return empowered._schedulerService
76 return None
77@@ -1522,8 +1528,8 @@
78
79 if (key[0], key[1] + 1) in onDiskSchema:
80 raise RuntimeError(
81- "Greater versions of database %r objects in the DB than in memory" %
82- (actualType.typeName,))
83+ "Memory version of %r is %d; database has newer" % (
84+ actualType.typeName, key[1]))
85
86
87 # finally find old versions of the data and prepare to upgrade it.
88
89=== modified file 'Axiom/axiom/test/historic/stub_subscheduler1to2.py'
90--- Axiom/axiom/test/historic/stub_subscheduler1to2.py 2009-07-07 20:35:43 +0000
91+++ Axiom/axiom/test/historic/stub_subscheduler1to2.py 2012-08-04 16:13:18 +0000
92@@ -7,12 +7,14 @@
93
94 from axiom.test.historic.stubloader import saveStub
95
96+from axiom.substore import SubStore
97 from axiom.scheduler import SubScheduler
98 from axiom.dependency import installOn
99
100
101 def createDatabase(store):
102- installOn(SubScheduler(store=store), store)
103+ sub = SubStore.createNew(store, ["substore"]).open()
104+ installOn(SubScheduler(store=sub), sub)
105
106
107 if __name__ == '__main__':
108
109=== modified file 'Axiom/axiom/test/historic/subscheduler1to2.axiom.tbz2'
110Binary files Axiom/axiom/test/historic/subscheduler1to2.axiom.tbz2 2009-07-07 20:35:43 +0000 and Axiom/axiom/test/historic/subscheduler1to2.axiom.tbz2 2012-08-04 16:13:18 +0000 differ
111=== modified file 'Axiom/axiom/test/historic/test_subscheduler1to2.py'
112--- Axiom/axiom/test/historic/test_subscheduler1to2.py 2009-07-07 20:35:43 +0000
113+++ Axiom/axiom/test/historic/test_subscheduler1to2.py 2012-08-04 16:13:18 +0000
114@@ -5,6 +5,7 @@
115 """
116
117 from axiom.iaxiom import IScheduler
118+from axiom.substore import SubStore
119 from axiom.scheduler import SubScheduler, _UserScheduler
120 from axiom.test.historic.stubloader import StubbedTest
121
122@@ -16,11 +17,12 @@
123 and adapting the L{Store} to L{IScheduler} succeeds with a
124 L{_UserScheduler}.
125 """
126- scheduler = self.store.findUnique(SubScheduler)
127- self.assertEquals(list(self.store.interfacesFor(scheduler)), [])
128-
129- # Slothfully grant this test store the appearance of being a user
130- # store.
131- self.store.parent = self.store
132-
133- self.assertIsInstance(IScheduler(self.store), _UserScheduler)
134+ sub = self.store.findFirst(SubStore).open()
135+ upgraded = sub.whenFullyUpgraded()
136+ def subUpgraded(ignored):
137+ scheduler = sub.findUnique(SubScheduler)
138+ self.assertEquals(list(sub.interfacesFor(scheduler)), [])
139+
140+ self.assertIsInstance(IScheduler(sub), _UserScheduler)
141+ upgraded.addCallback(subUpgraded)
142+ return upgraded
143
144=== modified file 'Axiom/axiom/test/test_scheduler.py'
145--- Axiom/axiom/test/test_scheduler.py 2009-07-07 20:35:43 +0000
146+++ Axiom/axiom/test/test_scheduler.py 2012-08-04 16:13:18 +0000
147@@ -6,21 +6,20 @@
148 from twisted.trial import unittest
149 from twisted.trial.unittest import TestCase
150 from twisted.application.service import IService
151-from twisted.internet.defer import Deferred
152 from twisted.internet.task import Clock
153-from twisted.python import filepath, versions
154+from twisted.python import filepath
155
156 from epsilon.extime import Time
157
158 from axiom.scheduler import TimedEvent, _SubSchedulerParentHook, TimedEventFailureLog
159 from axiom.scheduler import Scheduler, SubScheduler
160+from axiom.scheduler import SITE_SCHEDULER
161 from axiom.store import Store
162 from axiom.item import Item
163 from axiom.substore import SubStore
164
165 from axiom.attributes import integer, text, inmemory, boolean, timestamp
166 from axiom.iaxiom import IScheduler
167-from axiom.dependency import installOn
168
169 class TestEvent(Item):
170
171@@ -169,7 +168,6 @@
172 """
173 Test the unscheduleFirst method of the scheduler.
174 """
175- d = Deferred()
176 sch = IScheduler(self.store)
177 t1 = TestEvent(testCase=self, name=u't1', store=self.store)
178 t2 = TestEvent(testCase=self, name=u't2', store=self.store, runAgain=None)
179@@ -252,6 +250,16 @@
180 super(TopStoreSchedTest, self).setUp()
181
182
183+ def test_namedService(self):
184+ """
185+ The site store IScheduler implementation can be retrieved as a named
186+ service from the store's IServiceCollection powerup.
187+ """
188+ self.assertIdentical(
189+ IService(self.store).getServiceNamed(SITE_SCHEDULER),
190+ IScheduler(self.store))
191+
192+
193 def testBasicScheduledError(self):
194 S = IScheduler(self.store)
195 S.schedule(NotActuallyRunnable(store=self.store), self.now())
196@@ -408,6 +416,17 @@
197 return service.stopService()
198
199
200+ def test_schedulerStartsWhenServiceStarts(self):
201+ """
202+ Test that IScheduler(store).startService() gets called whenever
203+ IService(store).startService() is called.
204+ """
205+ service = IService(self.store)
206+ service.startService()
207+ scheduler = service.getServiceNamed(SITE_SCHEDULER)
208+ self.assertTrue(scheduler.running)
209+
210+
211 def test_scheduleWhileStopped(self):
212 """
213 Test that a schedule call on a L{Scheduler} which has not been started
214@@ -789,7 +808,8 @@
215 storeID = self.oldScheduler.storeID
216 del self.oldScheduler
217 gc.collect()
218- scheduler = self.store.getItemByID(storeID)
219+ # Just load the scheduler, we don't need to use it for anything.
220+ self.store.getItemByID(storeID)
221 warnings = self.flushWarnings([self.test_deprecated])
222 self.assertEquals(len(warnings), 1)
223 self.assertEquals(warnings[0]['category'], PendingDeprecationWarning)
224
225=== modified file 'Axiom/axiom/test/test_upgrading.py'
226--- Axiom/axiom/test/test_upgrading.py 2009-01-02 14:21:43 +0000
227+++ Axiom/axiom/test/test_upgrading.py 2012-08-04 16:13:18 +0000
228@@ -12,7 +12,7 @@
229 from twisted.trial import unittest
230 from twisted.python import filepath
231 from twisted.application.service import IService
232-from twisted.internet.defer import maybeDeferred
233+from twisted.internet.defer import maybeDeferred, succeed
234 from twisted.python.reflect import namedModule
235 from twisted.python import log
236
237@@ -116,15 +116,31 @@
238 self.currentStore = store.Store(self.dbdir, debug=dbg)
239 return self.currentStore
240
241+
242 def closeStore(self):
243- self.currentStore.close()
244- self.currentStore = None
245+ """
246+ Close C{self.currentStore} and discard the reference. If there is a
247+ store service running, stop it first.
248+ """
249+ service = IService(self.currentStore)
250+ if service.running:
251+ result = service.stopService()
252+ else:
253+ result = succeed(None)
254+ def close(ignored):
255+ self.currentStore.close()
256+ self.currentStore = None
257+ result.addCallback(close)
258+ return result
259+
260
261 def startStoreService(self):
262 svc = IService(self.currentStore)
263 svc.getServiceNamed("Batch Processing Controller").disownServiceParent()
264 svc.startService()
265
266+
267+
268 def _logMessagesFrom(f):
269 L = []
270 log.addObserver(L.append)
271@@ -308,6 +324,8 @@
272 player = s.getItemByID(playerID, autoUpgrade=False)
273 sword = s.getItemByID(swordID, autoUpgrade=False)
274 self._testPlayerAndSwordState(player, sword)
275+ # Stop that service we started.
276+ return IService(s).stopService()
277
278 return s.whenFullyUpgraded().addCallback(afterUpgrade)
279
280@@ -367,8 +385,6 @@
281 s = self.openStore()
282 self.startStoreService()
283 def afterFirstUpgrade(result):
284- self.closeStore()
285-
286 choose(morenewapp)
287 s = self.openStore()
288 self.startStoreService()
289@@ -379,7 +395,10 @@
290 sword = store.getItemByID(swordID, autoUpgrade=False)
291 self._testPlayerAndSwordState(player, sword)
292
293- return s.whenFullyUpgraded().addCallback(afterFirstUpgrade)
294+ d = s.whenFullyUpgraded()
295+ d.addCallback(lambda ignored: self.closeStore())
296+ d.addCallback(afterFirstUpgrade)
297+ return d
298
299
300
301@@ -399,11 +418,25 @@
302 self.currentSubStore = ss.open()
303 return self.currentSubStore
304
305+
306 def closeStore(self):
307- self.currentSubStore.close()
308- self.currentTopStore.close()
309- self.currentSubStore = None
310- self.currentTopStore = None
311+ """
312+ Close C{self.currentTopStore} and C{self.currentSubStore}. If there is
313+ a store service running in C{self.currentTopStore}, stop it first.
314+ """
315+ service = IService(self.currentTopStore)
316+ if service.running:
317+ result = service.stopService()
318+ else:
319+ result = succeed(None)
320+ def stopped(ignored):
321+ self.currentSubStore.close()
322+ self.currentTopStore.close()
323+ self.currentSubStore = None
324+ self.currentTopStore = None
325+ result.addCallback(stopped)
326+ return result
327+
328
329 def startStoreService(self):
330 svc = IService(self.currentTopStore)

Subscribers

People subscribed via source and target branches

to all changes: