From 8307c1212f48c83faf1f331f0bcafb439dbeb89c Mon Sep 17 00:00:00 2001 From: Jakob de Maeyer Date: Mon, 10 Aug 2015 23:43:06 +0200 Subject: [PATCH] Add ExecutionEngine.close() method --- scrapy/core/engine.py | 15 +++++++++++++++ scrapy/crawler.py | 5 ++++- tests/test_crawl.py | 21 +++++++++++++++++++++ tests/test_engine.py | 24 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/scrapy/core/engine.py b/scrapy/core/engine.py index 992327bfe..eb2779b12 100644 --- a/scrapy/core/engine.py +++ b/scrapy/core/engine.py @@ -84,6 +84,21 @@ class ExecutionEngine(object): dfd = self._close_all_spiders() return dfd.addBoth(lambda _: self._finish_stopping_engine()) + def close(self): + """Close the execution engine gracefully. + + If it has already been started, stop it. In all cases, close all spiders + and the downloader. + """ + if self.running: + # Will also close spiders and downloader + return self.stop() + elif self.open_spiders: + # Will also close downloader + return self._close_all_spiders() + else: + return defer.succeed(self.downloader.close()) + def pause(self): """Pause the execution engine""" self.paused = True diff --git a/scrapy/crawler.py b/scrapy/crawler.py index c7e3bb528..a1937f2bd 100644 --- a/scrapy/crawler.py +++ b/scrapy/crawler.py @@ -73,8 +73,11 @@ class Crawler(object): yield self.engine.open_spider(self.spider, start_requests) yield defer.maybeDeferred(self.engine.start) except Exception: + exc = defer.fail() self.crawling = False - raise + if self.engine is not None: + yield self.engine.close() + yield exc def _create_spider(self, *args, **kwargs): return self.spidercls.from_crawler(self, *args, **kwargs) diff --git a/tests/test_crawl.py b/tests/test_crawl.py index 6d21acab0..b2105dcfa 100644 --- a/tests/test_crawl.py +++ b/tests/test_crawl.py @@ -226,3 +226,24 @@ with multiples lines s = dict(est[0]) self.assertEqual(s['engine.spider.name'], crawler.spider.name) self.assertEqual(s['len(engine.scraper.slot.active)'], 1) + + @defer.inlineCallbacks + def test_graceful_crawl_error_handling(self): + """ + Test whether errors happening anywhere in Crawler.crawl() are properly + reported (and not somehow swallowed) after a graceful engine shutdown. + The errors should not come from within Scrapy's core but from within + spiders/middlewares/etc., e.g. raised in Spider.start_requests(), + SpiderMiddleware.process_start_requests(), etc. + """ + + class TestError(Exception): + pass + + class FaultySpider(SimpleSpider): + def start_requests(self): + raise TestError + + crawler = get_crawler(FaultySpider) + yield self.assertFailure(crawler.crawl(), TestError) + self.assertFalse(crawler.crawling) diff --git a/tests/test_engine.py b/tests/test_engine.py index e14957eae..dad921a60 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -19,6 +19,7 @@ from twisted.web import server, static, util from twisted.trial import unittest from scrapy import signals +from scrapy.core.engine import ExecutionEngine from scrapy.utils.test import get_crawler from pydispatch import dispatcher from tests import tests_datadir @@ -234,6 +235,29 @@ class EngineTest(unittest.TestCase): self.assertEqual({'spider': self.run.spider, 'reason': 'finished'}, self.run.signals_catched[signals.spider_closed]) + @defer.inlineCallbacks + def test_close_downloader(self): + e = ExecutionEngine(get_crawler(TestSpider), lambda: None) + yield e.close() + + @defer.inlineCallbacks + def test_close_spiders_downloader(self): + e = ExecutionEngine(get_crawler(TestSpider), lambda: None) + yield e.open_spider(TestSpider(), []) + self.assertEqual(len(e.open_spiders), 1) + yield e.close() + self.assertEqual(len(e.open_spiders), 0) + + @defer.inlineCallbacks + def test_close_engine_spiders_downloader(self): + e = ExecutionEngine(get_crawler(TestSpider), lambda: None) + yield e.open_spider(TestSpider(), []) + e.start() + self.assertTrue(e.running) + yield e.close() + self.assertFalse(e.running) + self.assertEqual(len(e.open_spiders), 0) + if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == 'runserver':