Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
illicitonion committed Oct 19, 2018
1 parent 4884683 commit aaf8692
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 3 deletions.
14 changes: 14 additions & 0 deletions pants-plugins/src/python/internal_backend/rules_for_testing/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_library(
name = 'plugin',
sources = ['__init__.py', 'register.py'],
dependencies = [
'src/python/pants/base:exceptions',
'src/python/pants/engine:addressable',
'src/python/pants/engine:console',
'src/python/pants/engine:rules',
'src/python/pants/engine:selectors',
]
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

from pants.engine.addressable import BuildFileAddresses
from pants.engine.console import Console
from pants.engine.rules import console_rule
from pants.engine.selectors import Select
from pants.rules.core.exceptions import GracefulTerminationException


@console_rule('list-and-die-for-testing', [Select(Console), Select(BuildFileAddresses)])
def fast_list_and_die_for_testing(console, addresses):
"""A fast variant of `./pants list` with a reduced feature set."""
for address in addresses.dependencies:
console.print_stdout(address.spec)
raise GracefulTerminationException(exit_code=42)


def rules():
return (fast_list_and_die_for_testing,)
1 change: 1 addition & 0 deletions pants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pythonpath: [

backend_packages: +[
"pants.backend.docgen",
"internal_backend.rules_for_testing",
"internal_backend.repositories",
"internal_backend.sitegen",
"internal_backend.utilities",
Expand Down
1 change: 1 addition & 0 deletions src/python/pants/bin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ python_library(
'src/python/pants/base:build_environment',
'src/python/pants/base:build_file',
'src/python/pants/base:cmd_line_spec_parser',
'src/python/pants/base:exceptions',
'src/python/pants/base:exception_sink',
'src/python/pants/base:exiter',
'src/python/pants/base:project_tree',
Expand Down
5 changes: 4 additions & 1 deletion src/python/pants/bin/daemon_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from pants.java.nailgun_io import NailgunStreamStdinReader, NailgunStreamWriter
from pants.java.nailgun_protocol import ChunkType, NailgunProtocol
from pants.pantsd.process_manager import ProcessManager
from pants.rules.core.exceptions import GracefulTerminationException
from pants.util.contextutil import hermetic_environment_as, stdio_as
from pants.util.socket import teardown_socket

Expand Down Expand Up @@ -230,10 +231,12 @@ def nailgunned_stdio(cls, sock, env, handle_stdin=True):
def _raise_deferred_exc(self):
"""Raises deferred exceptions from the daemon's synchronous path in the post-fork client."""
if self._deferred_exception:
exc_type, exc_value, exc_traceback = self._deferred_exception
if exc_type == GracefulTerminationException:
self._exiter.exit(exc_value.exit_code)
try:
# Expect `_deferred_exception` to be a 3-item tuple of the values returned by sys.exc_info().
# This permits use the 3-arg form of the `raise` statement to preserve the original traceback.
exc_type, exc_value, exc_traceback = self._deferred_exception
raise_with_traceback(exc_type(exc_value), exc_traceback)
except ValueError:
# If `_deferred_exception` isn't a 3-item tuple, treat it like a bare exception.
Expand Down
6 changes: 5 additions & 1 deletion src/python/pants/bin/local_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pants.init.target_roots_calculator import TargetRootsCalculator
from pants.option.options_bootstrapper import OptionsBootstrapper
from pants.reporting.reporting import Reporting
from pants.rules.core.exceptions import GracefulTerminationException
from pants.util.contextutil import maybe_profiled


Expand Down Expand Up @@ -190,8 +191,11 @@ def _maybe_run_v2(self):
self._options.goals_and_possible_v2_goals,
self._target_roots
)
except GracefulTerminationException as e:
logger.debug('Encountered graceful termination exception {}; exiting'.format(e))
return e.exit_code
except Exception as e:
logger.warn('Encountered unhandled exception {!r} during rule execution!'
logger.warn('Encountered unhandled exception {} during rule execution!'
.format(e))
return 1
else:
Expand Down
7 changes: 7 additions & 0 deletions src/python/pants/engine/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pants.engine.nodes import Return, State, Throw
from pants.engine.rules import RuleIndex, SingletonRule, TaskRule
from pants.engine.selectors import Select, constraint_for
from pants.rules.core.exceptions import GracefulTerminationException
from pants.util.contextutil import temporary_file_path
from pants.util.dirutil import check_no_overlapping_paths
from pants.util.objects import Collection, datatype
Expand Down Expand Up @@ -536,6 +537,12 @@ def products_request(self, products, subjects):
throw_root_states = tuple(state for root, state in result.root_products if type(state) is Throw)
if throw_root_states:
unique_exceptions = tuple({t.exc for t in throw_root_states})

# TODO: consider adding a new top-level function adjacent to products_request used for running console tasks,
# so that this code doesn't need to exist in this form.
if len(unique_exceptions) == 1 and isinstance(unique_exceptions[0], GracefulTerminationException):
raise unique_exceptions[0]

exception_noun = pluralize(len(unique_exceptions), 'Exception')

if self._scheduler.include_trace_on_error:
Expand Down
30 changes: 30 additions & 0 deletions src/python/pants/rules/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding=utf-8
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals


class GracefulTerminationException(Exception):
"""Indicates that a console_rule should eagerly terminate the run.
No error trace will be printed if this is raised; the runner will simply exit with the passed
exit code.
Nothing except a console_rule should ever raise this.
"""

def __init__(self, message='', exit_code=1):
"""
:param int exit_code: an optional exit code (default=1)
"""
super(GracefulTerminationException, self).__init__(message)

if exit_code == 0:
raise ValueError("Cannot create GracefulTerminationException with exit code of 0")

self._exit_code = exit_code

@property
def exit_code(self):
return self._exit_code
15 changes: 14 additions & 1 deletion tests/python/pants_test/engine/test_scheduler_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os

from pants.util.contextutil import temporary_dir
from pants_test.pants_run_integration_test import PantsRunIntegrationTest
from pants_test.pants_run_integration_test import PantsRunIntegrationTest, ensure_daemon


class SchedulerIntegrationTest(PantsRunIntegrationTest):
Expand All @@ -24,3 +24,16 @@ def test_visualize_to(self):
]
self.assert_success(self.run_pants(args))
self.assertTrue(len(os.listdir(destdir)) > 0)

@ensure_daemon
def test_graceful_termination(self):
args = [
'--no-v1',
'--v2',
'list-and-die-for-testing',
'examples/src/scala/org/pantsbuild/example/hello/welcome',
]
pants_result = self.run_pants(args)
self.assert_failure(pants_result)
self.assertEqual(pants_result.stdout_data, 'examples/src/scala/org/pantsbuild/example/hello/welcome:welcome\n')
self.assertEqual(pants_result.returncode, 42)
5 changes: 5 additions & 0 deletions tests/python/pants_test/pants_run_integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ def wrapper(self, *args, **kwargs):
with environment_as(**env):
try:
f(self, *args, **kwargs)
except Exception:
print('Test failed with enable-pantsd={}:'.format(enable_daemon))
if enable_daemon == 'false':
print('Skipping run with enable-pantsd=true because it already failed with enable-pantsd=false.')
raise
finally:
if enable_daemon:
self.assert_success(self.run_pants(['kill-pantsd']))
Expand Down

0 comments on commit aaf8692

Please sign in to comment.