blob: 614ed7e2573dbb83bbd5bd3466fa684b37aeb8c2 [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
"""
PROTON-1800 BlockingConnection descriptor leak
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import contextlib
import socket
import uuid
import gc
import os
import threading
import subprocess
import warnings
from collections import namedtuple
import cproton
import proton
import proton.reactor
from proton import Message
from proton.utils import SyncRequestResponse, BlockingConnection
from proton.handlers import IncomingMessageHandler
from test_unittest import unittest
def get_fd_set():
# type: () -> set[str]
return set(os.listdir('/proc/self/fd/'))
@contextlib.contextmanager
def no_fd_leaks(test):
# type: (unittest.TestCase) -> None
with warnings.catch_warnings(record=True) as ws:
before = get_fd_set()
yield
delta = get_fd_set().difference(before)
if len(delta) != 0:
subprocess.check_call("ls -lF /proc/{0}/fd/".format(os.getpid()), shell=True)
test.fail("Found {0} new fd(s) after the test".format(delta))
if len(ws) > 0:
test.fail([w.message for w in ws])
class Broker(proton.handlers.MessagingHandler):
def __init__(self, acceptor_url):
# type: (str) -> None
super(Broker, self).__init__()
self.acceptor_url = acceptor_url
self.sender = None
self.acceptor = None
self._acceptor_opened_event = threading.Event()
def get_acceptor_sockname(self):
# type: () -> (str, int)
self._acceptor_opened_event.wait()
if hasattr(self.acceptor, '_selectable'): # proton 0.30.0+
sockname = self.acceptor._selectable._delegate.getsockname()
else: # works in proton 0.27.0
selectable = cproton.pn_cast_pn_selectable(self.acceptor._impl)
fd = cproton.pn_selectable_get_fd(selectable)
s = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
sockname = s.getsockname()
return sockname[:2]
def on_start(self, event):
self.acceptor = event.container.listen(self.acceptor_url)
self._acceptor_opened_event.set()
def on_link_opening(self, event):
if event.link.is_sender:
assert event.link.remote_source.dynamic
address = str(uuid.uuid4())
event.link.source.address = address
self.sender = event.link
elif event.link.remote_target.address:
event.link.target.address = event.link.remote_target.address
def on_message(self, event):
message = event.message
assert self.sender.source.address == message.reply_to
reply = proton.Message(body=message.body.upper(), correlation_id=message.correlation_id)
self.sender.send(reply)
@contextlib.contextmanager
def test_broker():
broker = Broker('localhost:0')
container = proton.reactor.Container(broker)
threading.Thread(target=container.run).start()
try:
yield broker
finally:
container.stop()
PROC_SELF_FD_EXISTS = os.path.exists("/proc/self/fd"), "Skipped: Directory /proc/self/fd does not exist"
def skipOnFailure(reason="AssertionError ignored."):
"""Decorator for test methods that swallows AssertionErrors.
Passing tests are reported unchanged, tests failing with AssertionError are
reported as skipped, and tests failing with any other kind of error are
permitted to fail.
Use this to temporarily suppress flaky or environment-sensitive tests without
turning them into dead code that is not being run at all.
If a test is reliably failing, use unittest.expectedFailure instead."""
def skip_on_assertion(old_test):
def new_test(*args, **kwargs):
try:
old_test(*args, **kwargs)
except AssertionError as e:
raise unittest.SkipTest("Test failure '{0}' ignored: {1}".format(
str(e), reason))
return new_test
return skip_on_assertion
class Proton1800Test(unittest.TestCase):
@unittest.skipUnless(*PROC_SELF_FD_EXISTS)
@skipOnFailure(reason="PROTON-1800: sut is leaking one fd on Ubuntu Xenial docker image")
def test_sync_request_response_blocking_connection_no_fd_leaks(self):
with test_broker() as tb:
sockname = tb.get_acceptor_sockname()
url = "{0}:{1}".format(*sockname)
opts = namedtuple('Opts', ['address', 'timeout'])(address=url, timeout=3)
with no_fd_leaks(self):
client = SyncRequestResponse(
BlockingConnection(url, opts.timeout, allowed_mechs="ANONYMOUS"), "somequeue")
try:
request = "One Two Three Four"
response = client.call(Message(body=request))
self.assertEqual(response.body, "ONE TWO THREE FOUR")
finally:
client.connection.close()
gc.collect()