Merge lp:~abentley/launchpad/ampoulejob-timeout into lp:launchpad
- ampoulejob-timeout
- Merge into devel
Proposed by
Aaron Bentley
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~abentley/launchpad/ampoulejob-timeout |
Merge into: | lp:launchpad |
Prerequisite: | lp:~abentley/launchpad/ampoulejob |
Diff against target: |
358 lines (+155/-40) 6 files modified
lib/lp/code/scripts/tests/test_update_preview_diffs.py (+5/-1) lib/lp/services/job/runner.py (+67/-31) lib/lp/services/job/tests/test_runner.py (+69/-2) lib/lp/translations/scripts/po_import.py (+4/-1) lib/lp/translations/scripts/tests/test_translations_import.py (+9/-0) versions.cfg (+1/-5) |
To merge this branch: | bzr merge lp:~abentley/launchpad/ampoulejob-timeout |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer (community) | Approve | ||
Review via email: mp+15986@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote : | # |
Revision history for this message
Paul Hummer (rockstar) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/scripts/tests/test_update_preview_diffs.py' | |||
2 | --- lib/lp/code/scripts/tests/test_update_preview_diffs.py 2010-01-06 01:34:20 +0000 | |||
3 | +++ lib/lp/code/scripts/tests/test_update_preview_diffs.py 2010-01-06 01:34:23 +0000 | |||
4 | @@ -76,7 +76,11 @@ | |||
5 | 76 | source_tree.bzrdir.root_transport.delete_tree('.bzr') | 76 | source_tree.bzrdir.root_transport.delete_tree('.bzr') |
6 | 77 | error_utility = errorlog.ErrorReportingUtility() | 77 | error_utility = errorlog.ErrorReportingUtility() |
7 | 78 | error_utility.configure('update_preview_diffs') | 78 | error_utility.configure('update_preview_diffs') |
9 | 79 | old_id = error_utility.getLastOopsReport().id | 79 | old_report = error_utility.getLastOopsReport() |
10 | 80 | if old_report is not None: | ||
11 | 81 | old_id = old_report.id | ||
12 | 82 | else: | ||
13 | 83 | old_id = None | ||
14 | 80 | retcode, stdout, stderr = run_script( | 84 | retcode, stdout, stderr = run_script( |
15 | 81 | 'cronscripts/update_preview_diffs.py', ['--twisted']) | 85 | 'cronscripts/update_preview_diffs.py', ['--twisted']) |
16 | 82 | self.assertEqual(0, retcode) | 86 | self.assertEqual(0, retcode) |
17 | 83 | 87 | ||
18 | === modified file 'lib/lp/services/job/runner.py' | |||
19 | --- lib/lp/services/job/runner.py 2010-01-06 01:34:20 +0000 | |||
20 | +++ lib/lp/services/job/runner.py 2010-01-06 01:34:23 +0000 | |||
21 | @@ -11,14 +11,16 @@ | |||
22 | 11 | __all__ = ['JobRunner'] | 11 | __all__ = ['JobRunner'] |
23 | 12 | 12 | ||
24 | 13 | 13 | ||
25 | 14 | import contextlib | ||
26 | 14 | import os | 15 | import os |
28 | 15 | from signal import getsignal, SIGCHLD, signal | 16 | from signal import getsignal, SIGCHLD, SIGHUP, signal |
29 | 16 | import sys | 17 | import sys |
30 | 17 | 18 | ||
31 | 18 | from ampoule import child, pool, main | 19 | from ampoule import child, pool, main |
34 | 19 | 20 | from twisted.internet import defer, error, reactor, stdio | |
33 | 20 | from twisted.internet import reactor, defer | ||
35 | 21 | from twisted.protocols import amp | 21 | from twisted.protocols import amp |
36 | 22 | from twisted.python import log, reflect | ||
37 | 23 | |||
38 | 22 | from zope.component import getUtility | 24 | from zope.component import getUtility |
39 | 23 | from zope.security.proxy import removeSecurityProxy | 25 | from zope.security.proxy import removeSecurityProxy |
40 | 24 | 26 | ||
41 | @@ -31,29 +33,9 @@ | |||
42 | 31 | from lp.services.scripts.base import LaunchpadCronScript | 33 | from lp.services.scripts.base import LaunchpadCronScript |
43 | 32 | from lp.services.job.interfaces.job import LeaseHeld, IRunnableJob, IJob | 34 | from lp.services.job.interfaces.job import LeaseHeld, IRunnableJob, IJob |
44 | 33 | from lp.services.mail.sendmail import MailController | 35 | from lp.services.mail.sendmail import MailController |
45 | 36 | from canonical.launchpad import scripts | ||
46 | 34 | from canonical.launchpad.webapp import errorlog | 37 | from canonical.launchpad.webapp import errorlog |
47 | 35 | 38 | ||
48 | 36 | BOOTSTRAP = """\ | ||
49 | 37 | import sys | ||
50 | 38 | |||
51 | 39 | def main(reactor, ampChildPath): | ||
52 | 40 | from twisted.application import reactors | ||
53 | 41 | reactors.installReactor(reactor) | ||
54 | 42 | |||
55 | 43 | from twisted.python import log | ||
56 | 44 | log.startLogging(sys.stderr) | ||
57 | 45 | |||
58 | 46 | from twisted.internet import reactor, stdio | ||
59 | 47 | from twisted.python import reflect | ||
60 | 48 | |||
61 | 49 | ampChild = reflect.namedAny(ampChildPath) | ||
62 | 50 | stdio.StandardIO(ampChild(), 3, 4) | ||
63 | 51 | from canonical.launchpad import scripts | ||
64 | 52 | scripts.execute_zcml_for_scripts(use_web_security=False) | ||
65 | 53 | reactor.run() | ||
66 | 54 | main(sys.argv[-2], sys.argv[-1]) | ||
67 | 55 | """ | ||
68 | 56 | |||
69 | 57 | 39 | ||
70 | 58 | class BaseRunnableJob: | 40 | class BaseRunnableJob: |
71 | 59 | """Base class for jobs to be run via JobRunner. | 41 | """Base class for jobs to be run via JobRunner. |
72 | @@ -125,6 +107,11 @@ | |||
73 | 125 | return | 107 | return |
74 | 126 | ctrl.send() | 108 | ctrl.send() |
75 | 127 | 109 | ||
76 | 110 | @staticmethod | ||
77 | 111 | @contextlib.contextmanager | ||
78 | 112 | def contextManager(): | ||
79 | 113 | yield | ||
80 | 114 | |||
81 | 128 | 115 | ||
82 | 129 | class BaseJobRunner(object): | 116 | class BaseJobRunner(object): |
83 | 130 | """Runner of Jobs.""" | 117 | """Runner of Jobs.""" |
84 | @@ -140,9 +127,6 @@ | |||
85 | 140 | def runJob(self, job): | 127 | def runJob(self, job): |
86 | 141 | """Attempt to run a job, updating its status as appropriate.""" | 128 | """Attempt to run a job, updating its status as appropriate.""" |
87 | 142 | job = IRunnableJob(job) | 129 | job = IRunnableJob(job) |
88 | 143 | job.acquireLease() | ||
89 | 144 | # Commit transaction to clear the row lock. | ||
90 | 145 | transaction.commit() | ||
91 | 146 | try: | 130 | try: |
92 | 147 | job.start() | 131 | job.start() |
93 | 148 | transaction.commit() | 132 | transaction.commit() |
94 | @@ -173,8 +157,6 @@ | |||
95 | 173 | dict(job.getOopsVars())): | 157 | dict(job.getOopsVars())): |
96 | 174 | try: | 158 | try: |
97 | 175 | self.runJob(job) | 159 | self.runJob(job) |
98 | 176 | except LeaseHeld: | ||
99 | 177 | self.incomplete_jobs.append(job) | ||
100 | 178 | except job.user_error_types, e: | 160 | except job.user_error_types, e: |
101 | 179 | job.notifyUserError(e) | 161 | job.notifyUserError(e) |
102 | 180 | except Exception: | 162 | except Exception: |
103 | @@ -222,6 +204,14 @@ | |||
104 | 222 | def runAll(self): | 204 | def runAll(self): |
105 | 223 | """Run all the Jobs for this JobRunner.""" | 205 | """Run all the Jobs for this JobRunner.""" |
106 | 224 | for job in self.jobs: | 206 | for job in self.jobs: |
107 | 207 | job = IRunnableJob(job) | ||
108 | 208 | try: | ||
109 | 209 | job.acquireLease() | ||
110 | 210 | except LeaseHeld: | ||
111 | 211 | self.incomplete_jobs.append(job) | ||
112 | 212 | continue | ||
113 | 213 | # Commit transaction to clear the row lock. | ||
114 | 214 | transaction.commit() | ||
115 | 225 | oops = self.runJobHandleError(job) | 215 | oops = self.runJobHandleError(job) |
116 | 226 | if oops is not None: | 216 | if oops is not None: |
117 | 227 | self._logOopsId(oops.id) | 217 | self._logOopsId(oops.id) |
118 | @@ -263,6 +253,16 @@ | |||
119 | 263 | return {'success': len(runner.completed_jobs), 'oops_id': oops_id} | 253 | return {'success': len(runner.completed_jobs), 'oops_id': oops_id} |
120 | 264 | 254 | ||
121 | 265 | 255 | ||
122 | 256 | class HUPProcessPool(pool.ProcessPool): | ||
123 | 257 | """A ProcessPool that kills with HUP.""" | ||
124 | 258 | |||
125 | 259 | def _handleTimeout(self, child): | ||
126 | 260 | try: | ||
127 | 261 | child.transport.signalProcess(SIGHUP) | ||
128 | 262 | except error.ProcessExitedAlready: | ||
129 | 263 | pass | ||
130 | 264 | |||
131 | 265 | |||
132 | 266 | class TwistedJobRunner(BaseJobRunner): | 266 | class TwistedJobRunner(BaseJobRunner): |
133 | 267 | """Run Jobs via twisted.""" | 267 | """Run Jobs via twisted.""" |
134 | 268 | 268 | ||
135 | @@ -274,15 +274,26 @@ | |||
136 | 274 | 'LPCONFIG': os.environ['LPCONFIG']}) | 274 | 'LPCONFIG': os.environ['LPCONFIG']}) |
137 | 275 | super(TwistedJobRunner, self).__init__(logger, error_utility) | 275 | super(TwistedJobRunner, self).__init__(logger, error_utility) |
138 | 276 | self.job_source = job_source | 276 | self.job_source = job_source |
140 | 277 | self.pool = pool.ProcessPool(job_amp, starter=starter, min=0) | 277 | self.pool = HUPProcessPool(job_amp, starter=starter, min=0) |
141 | 278 | 278 | ||
142 | 279 | def runJobInSubprocess(self, job): | 279 | def runJobInSubprocess(self, job): |
143 | 280 | """Run the job_class with the specified id in the process pool. | 280 | """Run the job_class with the specified id in the process pool. |
144 | 281 | 281 | ||
145 | 282 | :return: a Deferred that fires when the job has completed. | 282 | :return: a Deferred that fires when the job has completed. |
146 | 283 | """ | 283 | """ |
147 | 284 | job = IRunnableJob(job) | ||
148 | 285 | try: | ||
149 | 286 | job.acquireLease() | ||
150 | 287 | except LeaseHeld: | ||
151 | 288 | self.incomplete_jobs.append(job) | ||
152 | 289 | return | ||
153 | 284 | job_id = job.id | 290 | job_id = job.id |
155 | 285 | deferred = self.pool.doWork(RunJobCommand, job_id=job_id) | 291 | timeout = job.getTimeout() |
156 | 292 | # work around ampoule bug | ||
157 | 293 | if timeout == 0: | ||
158 | 294 | timeout = 0.0000000000001 | ||
159 | 295 | deferred = self.pool.doWork( | ||
160 | 296 | RunJobCommand, job_id = job_id, _timeout=timeout) | ||
161 | 286 | def update(response): | 297 | def update(response): |
162 | 287 | if response['success']: | 298 | if response['success']: |
163 | 288 | self.completed_jobs.append(job) | 299 | self.completed_jobs.append(job) |
164 | @@ -360,3 +371,28 @@ | |||
165 | 360 | self.logger.info( | 371 | self.logger.info( |
166 | 361 | '%d %s jobs did not complete.', | 372 | '%d %s jobs did not complete.', |
167 | 362 | len(runner.incomplete_jobs), self.source_interface.__name__) | 373 | len(runner.incomplete_jobs), self.source_interface.__name__) |
168 | 374 | |||
169 | 375 | |||
170 | 376 | class TimeoutError(Exception): | ||
171 | 377 | |||
172 | 378 | def __init__(self): | ||
173 | 379 | Exception.__init__(self, "Job ran too long.") | ||
174 | 380 | |||
175 | 381 | |||
176 | 382 | BOOTSTRAP = """\ | ||
177 | 383 | import sys | ||
178 | 384 | from twisted.application import reactors | ||
179 | 385 | reactors.installReactor(sys.argv[-2]) | ||
180 | 386 | from lp.services.job.runner import bootstrap | ||
181 | 387 | bootstrap(sys.argv[-1]) | ||
182 | 388 | """ | ||
183 | 389 | |||
184 | 390 | def bootstrap(ampChildPath): | ||
185 | 391 | def handler(signum, frame): | ||
186 | 392 | raise TimeoutError | ||
187 | 393 | signal(SIGHUP, handler) | ||
188 | 394 | log.startLogging(sys.stderr) | ||
189 | 395 | ampChild = reflect.namedAny(ampChildPath) | ||
190 | 396 | stdio.StandardIO(ampChild(), 3, 4) | ||
191 | 397 | scripts.execute_zcml_for_scripts(use_web_security=False) | ||
192 | 398 | reactor.run() | ||
193 | 363 | 399 | ||
194 | === modified file 'lib/lp/services/job/tests/test_runner.py' | |||
195 | --- lib/lp/services/job/tests/test_runner.py 2009-11-27 14:25:44 +0000 | |||
196 | +++ lib/lp/services/job/tests/test_runner.py 2010-01-06 01:34:23 +0000 | |||
197 | @@ -6,8 +6,8 @@ | |||
198 | 6 | 6 | ||
199 | 7 | from __future__ import with_statement | 7 | from __future__ import with_statement |
200 | 8 | 8 | ||
201 | 9 | import contextlib | ||
202 | 10 | import sys | 9 | import sys |
203 | 10 | from time import sleep | ||
204 | 11 | from unittest import TestLoader | 11 | from unittest import TestLoader |
205 | 12 | 12 | ||
206 | 13 | import transaction | 13 | import transaction |
207 | @@ -17,7 +17,9 @@ | |||
208 | 17 | from zope.interface import implements | 17 | from zope.interface import implements |
209 | 18 | 18 | ||
210 | 19 | from lp.testing.mail_helpers import pop_notifications | 19 | from lp.testing.mail_helpers import pop_notifications |
212 | 20 | from lp.services.job.runner import JobRunner, BaseRunnableJob | 20 | from lp.services.job.runner import ( |
213 | 21 | JobRunner, BaseRunnableJob, JobRunnerProcess, TwistedJobRunner | ||
214 | 22 | ) | ||
215 | 21 | from lp.services.job.interfaces.job import JobStatus, IRunnableJob | 23 | from lp.services.job.interfaces.job import JobStatus, IRunnableJob |
216 | 22 | from lp.services.job.model.job import Job | 24 | from lp.services.job.model.job import Job |
217 | 23 | from lp.testing import TestCaseWithFactory | 25 | from lp.testing import TestCaseWithFactory |
218 | @@ -248,5 +250,70 @@ | |||
219 | 248 | self.assertEqual(JobStatus.FAILED, job.job.status) | 250 | self.assertEqual(JobStatus.FAILED, job.job.status) |
220 | 249 | 251 | ||
221 | 250 | 252 | ||
222 | 253 | class StuckJob(BaseRunnableJob): | ||
223 | 254 | """Simulation of a job that stalls.""" | ||
224 | 255 | implements(IRunnableJob) | ||
225 | 256 | |||
226 | 257 | done = False | ||
227 | 258 | |||
228 | 259 | @classmethod | ||
229 | 260 | def iterReady(cls): | ||
230 | 261 | if not cls.done: | ||
231 | 262 | yield StuckJob() | ||
232 | 263 | cls.done = True | ||
233 | 264 | |||
234 | 265 | @staticmethod | ||
235 | 266 | def get(id): | ||
236 | 267 | return StuckJob() | ||
237 | 268 | |||
238 | 269 | def __init__(self): | ||
239 | 270 | self.id = 1 | ||
240 | 271 | self.job = Job() | ||
241 | 272 | |||
242 | 273 | def acquireLease(self): | ||
243 | 274 | # Must be enough time for the setup to complete and runJobHandleError | ||
244 | 275 | # to be called. 7 was the minimum that worked on my computer. | ||
245 | 276 | # -- abentley | ||
246 | 277 | return self.job.acquireLease(10) | ||
247 | 278 | |||
248 | 279 | def run(self): | ||
249 | 280 | sleep(30) | ||
250 | 281 | |||
251 | 282 | |||
252 | 283 | class StuckJobProcess(JobRunnerProcess): | ||
253 | 284 | |||
254 | 285 | job_class = StuckJob | ||
255 | 286 | |||
256 | 287 | |||
257 | 288 | StuckJob.amp = StuckJobProcess | ||
258 | 289 | |||
259 | 290 | |||
260 | 291 | class ListLogger: | ||
261 | 292 | |||
262 | 293 | def __init__(self): | ||
263 | 294 | self.entries = [] | ||
264 | 295 | |||
265 | 296 | def info(self, input): | ||
266 | 297 | self.entries.append(input) | ||
267 | 298 | |||
268 | 299 | |||
269 | 300 | class TestTwistedJobRunner(TestCaseWithFactory): | ||
270 | 301 | |||
271 | 302 | layer = LaunchpadZopelessLayer | ||
272 | 303 | |||
273 | 304 | def test_timeout(self): | ||
274 | 305 | """When a job exceeds its lease, an exception is raised.""" | ||
275 | 306 | logger = ListLogger() | ||
276 | 307 | runner = TwistedJobRunner.runFromSource(StuckJob, logger) | ||
277 | 308 | self.assertEqual([], runner.completed_jobs) | ||
278 | 309 | self.assertEqual(1, len(runner.incomplete_jobs)) | ||
279 | 310 | oops = errorlog.globalErrorUtility.getLastOopsReport() | ||
280 | 311 | expected = [ | ||
281 | 312 | 'Running through Twisted.', 'Job resulted in OOPS: %s' % oops.id] | ||
282 | 313 | self.assertEqual(expected, logger.entries) | ||
283 | 314 | self.assertEqual('TimeoutError', oops.type) | ||
284 | 315 | self.assertIn('Job ran too long.', oops.value) | ||
285 | 316 | |||
286 | 317 | |||
287 | 251 | def test_suite(): | 318 | def test_suite(): |
288 | 252 | return TestLoader().loadTestsFromName(__name__) | 319 | return TestLoader().loadTestsFromName(__name__) |
289 | 253 | 320 | ||
290 | === modified file 'lib/lp/translations/scripts/po_import.py' | |||
291 | --- lib/lp/translations/scripts/po_import.py 2009-11-17 09:50:33 +0000 | |||
292 | +++ lib/lp/translations/scripts/po_import.py 2010-01-06 01:34:23 +0000 | |||
293 | @@ -135,11 +135,14 @@ | |||
294 | 135 | text = MailWrapper().format(mail_body) | 135 | text = MailWrapper().format(mail_body) |
295 | 136 | simple_sendmail(from_email, to_email, mail_subject, text) | 136 | simple_sendmail(from_email, to_email, mail_subject, text) |
296 | 137 | 137 | ||
297 | 138 | def run(self, *args, **kwargs): | ||
298 | 139 | errorlog.globalErrorUtility.configure('poimport') | ||
299 | 140 | LaunchpadCronScript.run(self, *args, **kwargs) | ||
300 | 141 | |||
301 | 138 | def main(self): | 142 | def main(self): |
302 | 139 | """Import entries from the queue.""" | 143 | """Import entries from the queue.""" |
303 | 140 | self.logger.debug("Starting the import process.") | 144 | self.logger.debug("Starting the import process.") |
304 | 141 | 145 | ||
305 | 142 | errorlog.globalErrorUtility.configure('poimport') | ||
306 | 143 | self.deadline = datetime.now(UTC) + self.time_to_run | 146 | self.deadline = datetime.now(UTC) + self.time_to_run |
307 | 144 | translation_import_queue = getUtility(ITranslationImportQueue) | 147 | translation_import_queue = getUtility(ITranslationImportQueue) |
308 | 145 | 148 | ||
309 | 146 | 149 | ||
310 | === modified file 'lib/lp/translations/scripts/tests/test_translations_import.py' | |||
311 | --- lib/lp/translations/scripts/tests/test_translations_import.py 2009-10-20 05:17:01 +0000 | |||
312 | +++ lib/lp/translations/scripts/tests/test_translations_import.py 2010-01-06 01:34:23 +0000 | |||
313 | @@ -7,6 +7,7 @@ | |||
314 | 7 | from unittest import TestLoader | 7 | from unittest import TestLoader |
315 | 8 | 8 | ||
316 | 9 | from lp.testing import TestCaseWithFactory | 9 | from lp.testing import TestCaseWithFactory |
317 | 10 | from canonical.launchpad.webapp import errorlog | ||
318 | 10 | from canonical.testing.layers import LaunchpadScriptLayer | 11 | from canonical.testing.layers import LaunchpadScriptLayer |
319 | 11 | 12 | ||
320 | 12 | from lp.translations.interfaces.translationimportqueue import ( | 13 | from lp.translations.interfaces.translationimportqueue import ( |
321 | @@ -158,6 +159,14 @@ | |||
322 | 158 | self.assertEqual(RosettaImportStatus.FAILED, entry.status) | 159 | self.assertEqual(RosettaImportStatus.FAILED, entry.status) |
323 | 159 | self.assertEqual(message, entry.error_output) | 160 | self.assertEqual(message, entry.error_output) |
324 | 160 | 161 | ||
325 | 162 | def test_main_leaves_oops_handling_alone(self): | ||
326 | 163 | """Ensure that script.main is not altering oops reporting.""" | ||
327 | 164 | self.script.main() | ||
328 | 165 | default_reporting = errorlog.ErrorReportingUtility() | ||
329 | 166 | default_reporting.configure('error_reports') | ||
330 | 167 | self.assertEqual(default_reporting.oops_prefix, | ||
331 | 168 | errorlog.globalErrorUtility.oops_prefix) | ||
332 | 169 | |||
333 | 161 | 170 | ||
334 | 162 | def test_suite(): | 171 | def test_suite(): |
335 | 163 | return TestLoader().loadTestsFromName(__name__) | 172 | return TestLoader().loadTestsFromName(__name__) |
336 | 164 | 173 | ||
337 | === modified file 'versions.cfg' | |||
338 | --- versions.cfg 2010-01-06 01:34:20 +0000 | |||
339 | +++ versions.cfg 2010-01-06 01:34:23 +0000 | |||
340 | @@ -3,17 +3,13 @@ | |||
341 | 3 | 3 | ||
342 | 4 | [versions] | 4 | [versions] |
343 | 5 | # Alphabetical, case-insensitive, please! :-) | 5 | # Alphabetical, case-insensitive, please! :-) |
344 | 6 | <<<<<<< TREE | ||
345 | 7 | bzr = 2.1b4 | ||
346 | 8 | ======= | ||
347 | 9 | 6 | ||
348 | 10 | # from -r 3:lp:~launchpad/ampoule/launchpad-tweaked | 7 | # from -r 3:lp:~launchpad/ampoule/launchpad-tweaked |
349 | 11 | # To reproduce: | 8 | # To reproduce: |
350 | 12 | # bzr export ampoule-0.1.0-lp-1.tar.gz lp:~launchpad/ampoule/launchpad-tweaked\ | 9 | # bzr export ampoule-0.1.0-lp-1.tar.gz lp:~launchpad/ampoule/launchpad-tweaked\ |
351 | 13 | # -r 3 | 10 | # -r 3 |
352 | 14 | ampoule = 0.1.0-lp-1 | 11 | ampoule = 0.1.0-lp-1 |
355 | 15 | bzr = 2.1b3 | 12 | bzr = 2.1b4 |
354 | 16 | >>>>>>> MERGE-SOURCE | ||
356 | 17 | chameleon.core = 1.0b35 | 13 | chameleon.core = 1.0b35 |
357 | 18 | chameleon.zpt = 1.0b17 | 14 | chameleon.zpt = 1.0b17 |
358 | 19 | ClientForm = 0.2.10 | 15 | ClientForm = 0.2.10 |
= Summary =
Enforce timeouts for jobs running under Twisted.
== Proposed fix ==
Use Ampoule's timeout handling.
== Pre-implementation notes ==
This was discussed a bit with mwhudson.
== Implementation details ==
Use Job.getTimeout to determine the job's timeout and run it with that timeout.
Ideally, timeouts should cause the subprocess to oops, so subclass ProcessPool
as HUPProcessPool, and override _handleTimeout to use HUP instead. Install a
signal handler for HUP in the child process that raises TimeoutError.
The lease must be acquired before the job is run in order to determine the r.runJobInSubpr ocess.
timeout, so move acquireLease to JobRunner.runAll and
TwistedJobRunne
Extract most of the bootstrap code to lp.services. job.runner. bootstrap( )
== Tests == preview_ diffs -t test_runner
bin/test -t update_
== Demo and Q/A ==
None, really. We don't have any jobs that exceed timeouts at present.