Merge lp:~abentley/launchpad/ampoule-0.1.1 into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/ampoule-0.1.1
Merge into: lp:launchpad
Diff against target: 239 lines (+36/-69)
5 files modified
lib/lp/bugs/windmill/tests/test_bug_inline_subscriber.py (+2/-1)
lib/lp/code/model/branchmergeproposaljob.py (+1/-9)
lib/lp/services/job/runner.py (+30/-45)
lib/lp/services/job/tests/test_runner.py (+1/-9)
versions.cfg (+2/-5)
To merge this branch: bzr merge lp:~abentley/launchpad/ampoule-0.1.1
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+18481@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

= Summary =
Update to Ampoule 0.2.0.

== Proposed fix ==
This branch updates ampoule and also adds a version for pyOpenSSL, which is a
new Ampoule dependency. This new version of ampoule has many changes that I
suggested, to push as much code upstream as I thought was architecturally
sound.

== Pre-implementation notes ==
None

== Implementation details ==
Instead of having a separate AMPChild subclass for every JobSource, a new
ampoule feature allows us to parameterize the JobRunnerProcess. We supply a
full path to the JobSource. In JobRunnerProcess, we import this and use it. I
used 'exec' for this, because the import module looked crazy complicated.

Instead of supplying our own BOOTSTRAP code in order to provide a bit more
startup code, a new ampoule feature lets us simply implement the context
manager protocol in JobRunnerProcess.

Instead of subclassing pool.ProcessPool to use SIGHUP, ampoule now lets us
parameterize the timout_signal.

Instead of converting our leases, which are datetimes, into timeout durations,
a new ampoule feature lets us supply them as deadlines.

job_class was renamed to job_source where this was a better description of its
usage.

== Tests ==
bin/test -vt test_runner -t update_preview_diffs

== Demo and Q/A ==
No visible change.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/services/job/tests/test_runner.py
  lib/lp/services/job/runner.py
  versions.cfg
  lib/lp/code/model/branchmergeproposaljob.py

== Pyflakes notices ==

lib/lp/services/job/runner.py
    240: undefined name 'job_source'

^^^ This is because of the exec.

== Pylint notices ==

lib/lp/services/job/runner.py
    35: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    239: [W0122, JobRunnerProcess.__init__] Use of the exec statement
    240: [E0602, JobRunnerProcess.__init__] Undefined variable 'job_source'

^^^ This is because of the exec.

lib/lp/code/model/branchmergeproposaljob.py
    22: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    23: [F0401] Unable to import 'lazr.enum' (No module named enum)

Revision history for this message
Aaron Bentley (abentley) wrote :

After submitting this, I remembered about __import__, so I've replaced exec with __import__.

Revision history for this message
Gavin Panella (allenap) wrote :

Hi Aaron,

I can't pretend to fully understand all of this, but the changes look
good. I have one tiny comment.

Gavin.

> === modified file 'lib/lp/services/job/runner.py'
> --- lib/lp/services/job/runner.py 2010-01-16 10:20:46 +0000
> +++ lib/lp/services/job/runner.py 2010-02-03 16:02:29 +0000
> @@ -15,6 +15,7 @@
> ]
>
>
> +from calendar import timegm
> import contextlib
> import logging
> import os
> @@ -22,9 +23,8 @@
> import sys
>
> from ampoule import child, pool, main
> -from twisted.internet import defer, error, reactor, stdio
> +from twisted.internet import defer, reactor
> from twisted.protocols import amp
> -from twisted.python import log, reflect
>
> from zope.component import getUtility
> from zope.security.proxy import removeSecurityProxy
> @@ -231,9 +231,25 @@
> class JobRunnerProcess(child.AMPChild):
> """Base class for processes that run jobs."""
>
> - def __init__(self):
> + def __init__(self, job_source_name):
> child.AMPChild.__init__(self)
> - self.context_manager = self.job_class.contextManager()
> + segments = job_source_name.split('.')
> + module = '.'.join(segments[:-1])
> + name = segments[-1]

Could you simplify this to:

  module, name = job_source_name.rsplit('.', 1)

?

Ah, I guess it wouldn't work if there are no "."s in job_source_name.

review: Approve
Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Gavin Panella wrote:
>> - def __init__(self):
>> + def __init__(self, job_source_name):
>> child.AMPChild.__init__(self)
>> - self.context_manager = self.job_class.contextManager()
>> + segments = job_source_name.split('.')
>> + module = '.'.join(segments[:-1])
>> + name = segments[-1]
>
> Could you simplify this to:
>
> module, name = job_source_name.rsplit('.', 1)

Done. Thanks for the tip.

> Ah, I guess it wouldn't work if there are no "."s in job_source_name.

If there are no dots, the import will fail anyhow. I can't imagine any
time when it would be sensible to supply a job_source_name with no dots
in it, though.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAktppBUACgkQ0F+nu1YWqI2AyACeM7qOYIfkj+04WACDGO5Mt+QA
QUQAni+O5BLOSNU31EOpbUauFrVgX3JF
=ieiV
-----END PGP SIGNATURE-----

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/windmill/tests/test_bug_inline_subscriber.py'
2--- lib/lp/bugs/windmill/tests/test_bug_inline_subscriber.py 2010-02-01 18:37:00 +0000
3+++ lib/lp/bugs/windmill/tests/test_bug_inline_subscriber.py 2010-02-03 22:24:19 +0000
4@@ -18,7 +18,8 @@
5 layer = BugsWindmillLayer
6 suite_name = 'Inline bug page subscribers test'
7
8- def test_inline_subscriber(self):
9+ def DISABLED_test_inline_subscriber(self):
10+ # This test fails intermittently. See bug #516781.
11 """Test inline subscribing on bugs pages.
12
13 This test makes sure that subscribing and unsubscribing
14
15=== modified file 'lib/lp/code/model/branchmergeproposaljob.py'
16--- lib/lp/code/model/branchmergeproposaljob.py 2010-01-11 20:07:17 +0000
17+++ lib/lp/code/model/branchmergeproposaljob.py 2010-02-03 22:24:19 +0000
18@@ -51,7 +51,7 @@
19 from lp.codehosting.vfs import get_multi_server, get_scanner_server
20 from lp.services.job.model.job import Job
21 from lp.services.job.interfaces.job import IRunnableJob
22-from lp.services.job.runner import BaseRunnableJob, JobRunnerProcess
23+from lp.services.job.runner import BaseRunnableJob
24
25
26 class BranchMergeProposalJobType(DBEnumeratedType):
27@@ -283,14 +283,6 @@
28 self.branch_merge_proposal.preview_diff = preview
29
30
31-class UpdatePreviewDiffProcess(JobRunnerProcess):
32- """A process that runs UpdatePreviewDiffJobs"""
33- job_class = UpdatePreviewDiffJob
34-
35-
36-UpdatePreviewDiffJob.amp = UpdatePreviewDiffProcess
37-
38-
39 class CreateMergeProposalJob(BaseRunnableJob):
40 """See `ICreateMergeProposalJob` and `ICreateMergeProposalJobSource`."""
41
42
43=== modified file 'lib/lp/services/job/runner.py'
44--- lib/lp/services/job/runner.py 2010-02-03 11:17:12 +0000
45+++ lib/lp/services/job/runner.py 2010-02-03 22:24:18 +0000
46@@ -15,6 +15,7 @@
47 ]
48
49
50+from calendar import timegm
51 import contextlib
52 import logging
53 import os
54@@ -22,9 +23,8 @@
55 import sys
56
57 from ampoule import child, pool, main
58-from twisted.internet import defer, error, reactor, stdio
59+from twisted.internet import defer, reactor
60 from twisted.protocols import amp
61-from twisted.python import log, reflect
62
63 from zope.component import getUtility
64 from zope.security.proxy import removeSecurityProxy
65@@ -242,9 +242,23 @@
66 class JobRunnerProcess(child.AMPChild):
67 """Base class for processes that run jobs."""
68
69- def __init__(self):
70+ def __init__(self, job_source_name):
71 child.AMPChild.__init__(self)
72- self.context_manager = self.job_class.contextManager()
73+ module, name = job_source_name.rsplit('.', 1)
74+ source_module = __import__(module, fromlist=[name])
75+ self.job_source = getattr(source_module, name)
76+ self.context_manager = self.job_source.contextManager()
77+
78+ @staticmethod
79+ def __enter__():
80+ def handler(signum, frame):
81+ raise TimeoutError
82+ scripts.execute_zcml_for_scripts(use_web_security=False)
83+ signal(SIGHUP, handler)
84+
85+ @staticmethod
86+ def __exit__(exc_type, exc_val, exc_tb):
87+ pass
88
89 def makeConnection(self, transport):
90 """The Job context is entered on connect."""
91@@ -258,9 +272,9 @@
92
93 @RunJobCommand.responder
94 def runJobCommand(self, job_id):
95- """Run a job of this job_class according to its job id."""
96+ """Run a job from this job_source according to its job id."""
97 runner = BaseJobRunner()
98- job = self.job_class.get(job_id)
99+ job = self.job_source.get(job_id)
100 oops = runner.runJobHandleError(job)
101 if oops is None:
102 oops_id = ''
103@@ -269,28 +283,22 @@
104 return {'success': len(runner.completed_jobs), 'oops_id': oops_id}
105
106
107-class HUPProcessPool(pool.ProcessPool):
108- """A ProcessPool that kills with HUP."""
109-
110- def _handleTimeout(self, child):
111- try:
112- child.transport.signalProcess(SIGHUP)
113- except error.ProcessExitedAlready:
114- pass
115-
116-
117 class TwistedJobRunner(BaseJobRunner):
118 """Run Jobs via twisted."""
119
120- def __init__(self, job_source, job_amp, logger=None, error_utility=None):
121+ def __init__(self, job_source, logger=None, error_utility=None):
122 starter = main.ProcessStarter(
123- bootstrap=BOOTSTRAP, packages=('twisted', 'ampoule'),
124+ packages=('twisted', 'ampoule'),
125 env={'PYTHONPATH': os.environ['PYTHONPATH'],
126 'PATH': os.environ['PATH'],
127 'LPCONFIG': os.environ['LPCONFIG']})
128 super(TwistedJobRunner, self).__init__(logger, error_utility)
129 self.job_source = job_source
130- self.pool = HUPProcessPool(job_amp, starter=starter, min=0)
131+ import_name = '%s.%s' % (
132+ removeSecurityProxy(job_source).__module__, job_source.__name__)
133+ self.pool = pool.ProcessPool(
134+ JobRunnerProcess, ampChildArgs=[import_name], starter=starter,
135+ min=0, timeout_signal=SIGHUP)
136
137 def runJobInSubprocess(self, job):
138 """Run the job_class with the specified id in the process pool.
139@@ -304,12 +312,9 @@
140 self.incomplete_jobs.append(job)
141 return
142 job_id = job.id
143- timeout = job.getTimeout()
144- # work around ampoule bug
145- if timeout == 0:
146- timeout = 0.0000000000001
147+ deadline = timegm(job.lease_expires.timetuple())
148 deferred = self.pool.doWork(
149- RunJobCommand, job_id = job_id, _timeout=timeout)
150+ RunJobCommand, job_id = job_id, _deadline=deadline)
151 def update(response):
152 if response['success']:
153 self.completed_jobs.append(job)
154@@ -362,8 +367,7 @@
155 def runFromSource(cls, job_source, logger, error_utility=None):
156 """Run all ready jobs provided by the specified source."""
157 logger.info("Running through Twisted.")
158- runner = cls(job_source, removeSecurityProxy(job_source).amp, logger,
159- error_utility)
160+ runner = cls(job_source, logger, error_utility)
161 reactor.callWhenRunning(runner.runAll)
162 handler = getsignal(SIGCHLD)
163 try:
164@@ -396,22 +400,3 @@
165
166 def __init__(self):
167 Exception.__init__(self, "Job ran too long.")
168-
169-
170-BOOTSTRAP = """\
171-import sys
172-from twisted.application import reactors
173-reactors.installReactor(sys.argv[-2])
174-from lp.services.job.runner import bootstrap
175-bootstrap(sys.argv[-1])
176-"""
177-
178-def bootstrap(ampChildPath):
179- def handler(signum, frame):
180- raise TimeoutError
181- signal(SIGHUP, handler)
182- log.startLogging(sys.stderr)
183- ampChild = reflect.namedAny(ampChildPath)
184- stdio.StandardIO(ampChild(), 3, 4)
185- scripts.execute_zcml_for_scripts(use_web_security=False)
186- reactor.run()
187
188=== modified file 'lib/lp/services/job/tests/test_runner.py'
189--- lib/lp/services/job/tests/test_runner.py 2010-01-16 10:20:46 +0000
190+++ lib/lp/services/job/tests/test_runner.py 2010-02-03 22:24:18 +0000
191@@ -18,7 +18,7 @@
192
193 from lp.testing.mail_helpers import pop_notifications
194 from lp.services.job.runner import (
195- JobRunner, BaseRunnableJob, JobRunnerProcess, TwistedJobRunner
196+ JobRunner, BaseRunnableJob, TwistedJobRunner
197 )
198 from lp.services.job.interfaces.job import JobStatus, IRunnableJob
199 from lp.services.job.model.job import Job
200@@ -283,14 +283,6 @@
201 sleep(30)
202
203
204-class StuckJobProcess(JobRunnerProcess):
205-
206- job_class = StuckJob
207-
208-
209-StuckJob.amp = StuckJobProcess
210-
211-
212 class ListLogger:
213
214 def __init__(self):
215
216=== modified file 'versions.cfg'
217--- versions.cfg 2010-02-03 10:57:21 +0000
218+++ versions.cfg 2010-02-03 22:24:18 +0000
219@@ -4,11 +4,7 @@
220 [versions]
221 # Alphabetical, case-insensitive, please! :-)
222
223-# from -r 3:lp:~launchpad/ampoule/launchpad-tweaked
224-# To reproduce:
225-# bzr export ampoule-0.1.0-lp-1.tar.gz lp:~launchpad/ampoule/launchpad-tweaked\
226-# -r 3
227-ampoule = 0.1.0-lp-1
228+ampoule = 0.2.0
229 # Non-released bzr version from bzr+ssh://bazaar.launchpad.net/~mwhudson/bzr/2.1.0b4-lp2
230 bzr = 2.1b4-lp2
231 chameleon.core = 1.0b35
232@@ -50,6 +46,7 @@
233 PasteDeploy = 1.3.3
234 pyasn1 = 0.0.9a
235 pycrypto = 2.0.1
236+pyOpenSSL = 0.10
237 python-memcached = 1.45
238 python-openid = 2.2.1
239 pytz = 2009l