This looks reasonable to me. I've tested ovsdbapp functional tests, neutron master functional tests and a tempest API test run, and some older stable/2024.1 neutron functional tests and see no failure differences. Importing pre-eventlet removal neutron code and running poller._get_poll() returns in _SelectSelect and in newer neutron I verified it return select.poll.
Acked-by: Terry Wilson <[email protected]> Tested-by: Terry Wilson <[email protected]> On Thu, Sep 17, 2026 at 9:38 AM 이규영 <[email protected]> wrote: > > From: Kyuyeong Lee <[email protected]> > Date: Thu, 17 Sep 2026 20:00:33 +0900 > Subject: [PATCH v3] python: poller: Use poll(2) instead of select(2). > > Poller has always allocated SelectPoll, that is _SelectSelect, which > emulates poll(2) on top of select.select. The comment above it offers to > swap in select.poll by hand when eventlet and gevent are not in use, but > the name it mentions, _SelectPoll, does not exist anywhere in the tree, so > uncommenting that line has no effect. > > Emulating poll(2) with the real select(2) is not merely slower. select(2) > cannot represent a file descriptor numbered FD_SETSIZE (1024) or higher, > and Python raises > > ValueError: filedescriptor out of range in select() > > instead of corrupting the fd_set. A long lived process that holds more > than a thousand file descriptors therefore breaks as soon as a descriptor > the poller watches lands above the limit, for example when a connection is > re-established late in the process lifetime. Poller.block() does not catch > ValueError, so it reaches the caller, and a caller that retries its loop > spins on a full CPU from then on. > > This has been masked for eventlet users, which is most of them: eventlet > replaces select.select with eventlet.green.select, which multiplexes > through the hub and has no FD_SETSIZE limit. As OpenStack removes its > eventlet dependency the emulation starts calling the real select(2) and the > limit applies. It was hit in production in a neutron-server whose API > workers each held around 1200 file descriptors: the OVSDB connection thread > wedged permanently at 100% CPU and every later transaction in that worker > failed until the process was restarted. > > Use poll(2), which has no FD_SETSIZE limit, and keep the emulation where > select.select is green. There it is not subject to the limit either, and > the real poll(2) would block the whole interpreter rather than yield to the > other greenlets, which is precisely what the emulation exists to avoid. > > get_system_poll() cannot be reused here. It deliberately returns the > original, blocking poll under eventlet, which is what the non-blocking > poll(0) probe in socket_util.check_connection_completion() wants, but not > what Poller.block() needs. > > Signed-off-by: Kyuyeong Lee <[email protected]> > --- > v2: No code change. Resent with an in-body From: line so that the author > matches the Signed-off-by; our mail gateway rewrites the display name > in the From header. > v3: No code change. v2 was mangled in transit, the tabs in > python/automake.mk were turned into spaces. Resent after checking > that they survive this time. > > python/automake.mk | 3 ++- > python/ovs/poller.py | 22 ++++++++++++++--- > python/ovs/tests/test_poller.py | 44 +++++++++++++++++++++++++++++++++ > 3 files changed, 64 insertions(+), 5 deletions(-) > create mode 100644 python/ovs/tests/test_poller.py > > diff --git a/python/automake.mk b/python/automake.mk > index c3e960c82..91ff76212 100644 > --- a/python/automake.mk > +++ b/python/automake.mk > @@ -49,7 +49,8 @@ ovs_pytests = \ > python/ovs/tests/test_kv.py \ > python/ovs/tests/test_list.py \ > python/ovs/tests/test_odp.py \ > - python/ovs/tests/test_ofp.py > + python/ovs/tests/test_ofp.py \ > + python/ovs/tests/test_poller.py > > ovs_flowviz = \ > python/ovs/flowviz/__init__.py \ > diff --git a/python/ovs/poller.py b/python/ovs/poller.py > index 12f40993f..0565e82a3 100644 > --- a/python/ovs/poller.py > +++ b/python/ovs/poller.py > @@ -110,9 +110,23 @@ class _SelectSelect(object): > > > SelectPoll = _SelectSelect > -# If eventlet/gevent isn't used, we can use select.poll by replacing > -# _SelectPoll with select.poll class > -# _SelectPoll = select.poll > + > + > +def _get_poll(): > + """Return a poll object suitable for the current environment. > + > + Under eventlet or gevent, select.select is replaced by a green > + implementation that multiplexes through the hub. _SelectSelect is > + therefore both cooperative and free of the FD_SETSIZE limit there, so > + keep using it. Everywhere else select.select is the real select(2), > + which cannot handle a file descriptor numbered FD_SETSIZE or higher, so > + use poll(2), which has no such limit. > + """ > + if _using_eventlet_green_select() or ( > + gevent_monkey and > + gevent_monkey.is_object_patched('select', 'select')): > + return _SelectSelect() > + return select.poll() > > > class Poller(object): > @@ -220,7 +234,7 @@ class Poller(object): > vlog.dbg("%s on fd %d" % (s, fd)) > > def __reset(self): > - self.poll = SelectPoll() > + self.poll = _get_poll() > self.timeout = -1 > > > diff --git a/python/ovs/tests/test_poller.py b/python/ovs/tests/test_poller.py > new file mode 100644 > index 000000000..9ff878413 > --- /dev/null > +++ b/python/ovs/tests/test_poller.py > @@ -0,0 +1,44 @@ > +import select > + > +from unittest import mock > + > +import ovs.poller > + > +# select.poll is a builtin function, not a type, so capture the type of the > +# object it returns in order to be able to assert on it. > +POLL_TYPE = type(select.poll()) > + > + > +def test_poller_uses_poll_by_default(): > + """Poller must use poll(2) when select.select is the real select(2). > + > + select(2) raises ValueError for a file descriptor numbered FD_SETSIZE > + (1024) or higher, poll(2) has no such limit. > + """ > + with mock.patch.object(ovs.poller, '_using_eventlet_green_select', > + return_value=False), \ > + mock.patch.object(ovs.poller, 'gevent_monkey', None): > + assert isinstance(ovs.poller.Poller().poll, POLL_TYPE) > + > + > +def test_poller_uses_select_under_eventlet(): > + """Poller must keep emulating poll with select.select under eventlet. > + > + The green select has no FD_SETSIZE limit either and, unlike the real > + poll(2), it yields to the hub instead of blocking the interpreter. > + """ > + with mock.patch.object(ovs.poller, '_using_eventlet_green_select', > + return_value=True): > + assert isinstance(ovs.poller.Poller().poll, ovs.poller._SelectSelect) > + > + > +def test_poller_uses_select_under_gevent(): > + """Same as under eventlet, for a gevent patched select.select.""" > + gevent_monkey = mock.Mock() > + gevent_monkey.is_object_patched.return_value = True > + with mock.patch.object(ovs.poller, '_using_eventlet_green_select', > + return_value=False), \ > + mock.patch.object(ovs.poller, 'gevent_monkey', gevent_monkey): > + assert isinstance(ovs.poller.Poller().poll, ovs.poller._SelectSelect) > + gevent_monkey.is_object_patched.assert_called_once_with('select', > + 'select') > -- > 2.43.0 > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
