
Instead of keeping all greenthreads active even if they are not being used only keep the greenthreads active if there exists active work to complete. This saves resources (each greenthread takes up memory) and allows a pool to shrink and grow in a more dynamic fashion. Fixes bug 1339406 Change-Id: Idc8ab8447045915a0ffbaf21fa5c4bdb7a9e3593
132 lines
4.0 KiB
Python
132 lines
4.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (C) 2013 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import collections
|
|
import functools
|
|
|
|
import testtools
|
|
|
|
from taskflow import test
|
|
from taskflow.utils import eventlet_utils as eu
|
|
|
|
|
|
@testtools.skipIf(not eu.EVENTLET_AVAILABLE, 'eventlet is not available')
|
|
class GreenExecutorTest(test.TestCase):
|
|
def make_funcs(self, called, amount):
|
|
|
|
def store_call(name):
|
|
called[name] += 1
|
|
|
|
for i in range(0, amount):
|
|
yield functools.partial(store_call, name=i)
|
|
|
|
def test_func_calls(self):
|
|
called = collections.defaultdict(int)
|
|
|
|
with eu.GreenExecutor(2) as e:
|
|
for f in self.make_funcs(called, 2):
|
|
e.submit(f)
|
|
|
|
self.assertEqual(1, called[0])
|
|
self.assertEqual(1, called[1])
|
|
|
|
def test_no_construction(self):
|
|
self.assertRaises(ValueError, eu.GreenExecutor, 0)
|
|
self.assertRaises(ValueError, eu.GreenExecutor, -1)
|
|
self.assertRaises(ValueError, eu.GreenExecutor, "-1")
|
|
|
|
def test_result_callback(self):
|
|
called = collections.defaultdict(int)
|
|
|
|
def callback(future):
|
|
called[future] += 1
|
|
|
|
funcs = list(self.make_funcs(called, 1))
|
|
with eu.GreenExecutor(2) as e:
|
|
for func in funcs:
|
|
f = e.submit(func)
|
|
f.add_done_callback(callback)
|
|
|
|
self.assertEqual(2, len(called))
|
|
|
|
def test_exception_transfer(self):
|
|
|
|
def blowup():
|
|
raise IOError("Broke!")
|
|
|
|
with eu.GreenExecutor(2) as e:
|
|
f = e.submit(blowup)
|
|
|
|
self.assertRaises(IOError, f.result)
|
|
|
|
def test_result_transfer(self):
|
|
|
|
def return_given(given):
|
|
return given
|
|
|
|
create_am = 50
|
|
with eu.GreenExecutor(2) as e:
|
|
fs = []
|
|
for i in range(0, create_am):
|
|
fs.append(e.submit(functools.partial(return_given, i)))
|
|
|
|
self.assertEqual(create_am, len(fs))
|
|
for i in range(0, create_am):
|
|
result = fs[i].result()
|
|
self.assertEqual(i, result)
|
|
|
|
def test_called_restricted_size(self):
|
|
called = collections.defaultdict(int)
|
|
|
|
with eu.GreenExecutor(1) as e:
|
|
for f in self.make_funcs(called, 100):
|
|
e.submit(f)
|
|
self.assertEqual(99, e.amount_delayed)
|
|
|
|
self.assertFalse(e.alive)
|
|
self.assertEqual(100, len(called))
|
|
self.assertGreaterEqual(1, e.workers_created)
|
|
self.assertEqual(0, e.amount_delayed)
|
|
|
|
def test_shutdown_twice(self):
|
|
e = eu.GreenExecutor(1)
|
|
self.assertTrue(e.alive)
|
|
e.shutdown()
|
|
self.assertFalse(e.alive)
|
|
e.shutdown()
|
|
self.assertFalse(e.alive)
|
|
|
|
def test_func_cancellation(self):
|
|
called = collections.defaultdict(int)
|
|
|
|
fs = []
|
|
with eu.GreenExecutor(2) as e:
|
|
for func in self.make_funcs(called, 2):
|
|
fs.append(e.submit(func))
|
|
# Greenthreads don't start executing until we wait for them
|
|
# to, since nothing here does IO, this will work out correctly.
|
|
#
|
|
# If something here did a blocking call, then eventlet could swap
|
|
# one of the executors threads in, but nothing in this test does.
|
|
for f in fs:
|
|
self.assertFalse(f.running())
|
|
f.cancel()
|
|
|
|
self.assertEqual(0, len(called))
|
|
for f in fs:
|
|
self.assertTrue(f.cancelled())
|
|
self.assertTrue(f.done())
|