launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03413
[Merge] lp:~jtv/launchpad/db-bug-768342 into lp:launchpad/db-devel
Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/db-bug-768342 into lp:launchpad/db-devel with lp:~jtv/launchpad/db-bug-735621 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #768342 in Launchpad itself: "New generate-contents should log "live" apt-ftparchive output."
https://bugs.launchpad.net/launchpad/+bug/768342
For more details, see:
https://code.launchpad.net/~jtv/launchpad/db-bug-768342/+merge/58685
= Summary =
The generate-contents script runs apt-ftparchive, which may take a long time. It's not very nice to save up all its log output and dump it just once at the end: we want live output.
== Proposed fix ==
Luckily this is all neatly isolated in a function called "execute." I replaced its subprocess.Popen implementation with one based on CommandSpawner (and its helpful friends for logging and result tracking).
== Pre-implementation notes ==
This was Julian's one gripe from testing the branch for bug 735621, currently landing, which is a requisit for this one. We couldn't think of any meaningful tests for it, since I just re-implemented an existing, tested function.
== Implementation details ==
CommandSpawner is really meant for running multiple commands in parallel, but there's nothing against using it for just one.
== Tests ==
{{{
./bin/test -vvc lp.archivepublisher.tests.test_generate_contents
}}}
== Demo and Q/A ==
Run the generate-contents script on dogfood (or some other sizable archive) and watch its output. It won't be exactly per-line granularity because of buffering, but there should be some output from apt-ftparchive before it completes.
= Launchpad lint =
This lint was previously present and is, as far as I can see, inevitable:
Checking for conflicts and issues in changed files.
Linting changed files:
lib/canonical/config/schema-lazr.conf
lib/lp/archivepublisher/tests/test_generate_contents_files.py
lib/lp/archivepublisher/scripts/generate_contents_files.py
lib/canonical/launchpad/ftests/script.py
cronscripts/publishing/gen-contents/apt_conf_dist.template
cronscripts/publishing/gen-contents/Contents.top
scripts/generate-contents-files.py
cronscripts/publishing/gen-contents/apt_conf_header.template
lib/lp/services/command_spawner.py
./lib/canonical/config/schema-lazr.conf
536: Line exceeds 78 characters.
619: Line exceeds 78 characters.
995: Line exceeds 78 characters.
1084: Line exceeds 78 characters.
./cronscripts/publishing/gen-contents/apt_conf_dist.template
4: Line exceeds 78 characters.
5: Line exceeds 78 characters.
./scripts/generate-contents-files.py
8: '_pythonpath' imported but unused
--
https://code.launchpad.net/~jtv/launchpad/db-bug-768342/+merge/58685
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/db-bug-768342 into lp:launchpad/db-devel.
=== modified file 'lib/lp/archivepublisher/scripts/generate_contents_files.py'
--- lib/lp/archivepublisher/scripts/generate_contents_files.py 2011-04-21 14:28:35 +0000
+++ lib/lp/archivepublisher/scripts/generate_contents_files.py 2011-04-21 14:28:37 +0000
@@ -13,9 +13,13 @@
from zope.component import getUtility
from canonical.config import config
-from canonical.launchpad.ftests.script import run_command
from lp.archivepublisher.config import getPubConfig
from lp.registry.interfaces.distribution import IDistributionSet
+from lp.services.command_spawner import (
+ CommandSpawner,
+ OutputLineHandler,
+ ReturnCodeReceiver,
+ )
from lp.services.scripts.base import (
LaunchpadScript,
LaunchpadScriptFailure,
@@ -63,18 +67,31 @@
"""Execute a shell command.
:param logger: Output from the command will be logged here.
- :param command_line: Command to execute, as a list of tokens.
+ :param command: Command to execute, as a string.
+ :param args: Optional list of arguments for `command`.
:raises LaunchpadScriptFailure: If the command returns failure.
"""
- if args is None:
- description = command
- else:
- description = command + ' ' + ' '.join(args)
+ command_line = [command]
+ if args is not None:
+ command_line += args
+ description = ' '.join(command_line)
+
logger.debug("Execute: %s", description)
- retval, stdout, stderr = run_command(command, args)
- logger.debug(stdout)
- logger.warn(stderr)
- if retval != 0:
+ # Some of these commands can take a long time. Use CommandSpawner
+ # and friends to provide "live" log output. Simpler ways of running
+ # commands tend to save it all up and then dump it at the end, or
+ # have trouble logging it as neat lines.
+ stderr_logger = OutputLineHandler(logger.warn)
+ stdout_logger = OutputLineHandler(logger.debug)
+ receiver = ReturnCodeReceiver()
+ spawner = CommandSpawner()
+ spawner.start(
+ command_line, completion_handler=receiver,
+ stderr_handler=stderr_logger, stdout_handler=stdout_logger)
+ spawner.complete()
+ stdout_logger.finalize()
+ stderr_logger.finalize()
+ if receiver.returncode != 0:
raise LaunchpadScriptFailure(
"Failure while running command: %s" % description)
=== modified file 'lib/lp/services/command_spawner.py'
--- lib/lp/services/command_spawner.py 2011-04-06 03:43:28 +0000
+++ lib/lp/services/command_spawner.py 2011-04-21 14:28:37 +0000
@@ -99,9 +99,9 @@
:param stderr_handler: Callback to handle error output received from
the sub-process. Must take the output as its sole argument. May
be called any number of times as output comes in.
- :param failure_handler: Callback to be invoked, exactly once, when the
- sub-process exits. Must take `command`'s numeric return code as
- its sole argument.
+ :param completion_handler: Callback to be invoked, exactly once, when
+ the sub-process exits. Must take `command`'s numeric return code
+ as its sole argument.
"""
process = self._spawn(command)
handlers = {
@@ -172,9 +172,9 @@
try:
output = pipe_file.read()
except IOError, e:
+ # "Resource temporarily unavailable"--not an error really,
+ # just means there's nothing to read.
if e.errno != errno.EAGAIN:
- # "Resource temporarily unavailable"--not an error
- # really, just means there's nothing to read.
raise
else:
if len(output) > 0:
Follow ups