From 316a53c697c8300d5807455d6206c7751cc75144 Mon Sep 17 00:00:00 2001 From: songzhendong12315 Date: Sat, 3 Oct 2026 18:23:01 +0800 Subject: [PATCH 1/3] fix: await aiohttp server handler on collector-address skip Skip must call the original RequestHandler._handle_request, not return the function object. Otherwise aiohttp unpacks a function as (resp, reset) and the inbound request fails. --- skywalking/plugins/sw_aiohttp.py | 2 +- tests/unit/test_http_reporter.py | 87 +++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/skywalking/plugins/sw_aiohttp.py b/skywalking/plugins/sw_aiohttp.py index 65bf4231..b7f25c41 100644 --- a/skywalking/plugins/sw_aiohttp.py +++ b/skywalking/plugins/sw_aiohttp.py @@ -85,7 +85,7 @@ async def _sw_handle_request(self, request: BaseRequest, start_time: float, *arg if config.agent_protocol == 'http' and config.agent_collector_backend_services.rstrip('/') \ .endswith(f'{request.url.host}:{request.url.port}'): - return _handle_request + return await _handle_request(self, request, start_time, *args, **kwargs) carrier = Carrier() method = request.method diff --git a/tests/unit/test_http_reporter.py b/tests/unit/test_http_reporter.py index 7c5f13fc..bffb9f3b 100644 --- a/tests/unit/test_http_reporter.py +++ b/tests/unit/test_http_reporter.py @@ -19,7 +19,7 @@ import threading import unittest from http.server import BaseHTTPRequestHandler, HTTPServer -from unittest.mock import patch +from unittest.mock import AsyncMock, MagicMock, patch from skywalking import config @@ -150,5 +150,90 @@ async def run(): get_context.assert_not_called() +class TestAiohttpServerCollectorSkip(unittest.TestCase): + """RequestHandler skip must await the original handler, not return the function.""" + + def setUp(self): + self._saved = ( + config.agent_collector_backend_services, + config.agent_protocol, + ) + config.agent_protocol = 'http' + config.agent_collector_backend_services = '127.0.0.1:12800' + + def tearDown(self): + ( + config.agent_collector_backend_services, + config.agent_protocol, + ) = self._saved + + def test_collector_host_skip_awaits_original_handler(self): + from aiohttp import ClientSession + from aiohttp.web_protocol import RequestHandler + + from skywalking.plugins import sw_aiohttp + + self.addCleanup(setattr, ClientSession, '_request', ClientSession._request) + self.addCleanup(setattr, RequestHandler, '_handle_request', RequestHandler._handle_request) + + stub = AsyncMock(return_value=('resp', False)) + RequestHandler._handle_request = stub + sw_aiohttp.install() + + handler = MagicMock() + request = MagicMock() + request.url.host = '127.0.0.1' + request.url.port = 12800 + + async def run(): + with patch.object(sw_aiohttp, 'get_context') as get_context: + result = await RequestHandler._handle_request(handler, request, 1.5, extra=True) + get_context.assert_not_called() + return result + + result = asyncio.run(run()) + self.assertEqual(('resp', False), result) + stub.assert_awaited_once_with(handler, request, 1.5, extra=True) + + def test_non_collector_host_still_creates_entry_span(self): + from aiohttp import ClientSession + from aiohttp.web_protocol import RequestHandler + + from skywalking.plugins import sw_aiohttp + + self.addCleanup(setattr, ClientSession, '_request', ClientSession._request) + self.addCleanup(setattr, RequestHandler, '_handle_request', RequestHandler._handle_request) + + resp = MagicMock() + resp.status = 200 + stub = AsyncMock(return_value=(resp, False)) + RequestHandler._handle_request = stub + sw_aiohttp.install() + + handler = MagicMock() + request = MagicMock() + request.url.host = '127.0.0.1' + request.url.port = 9999 + request.method = 'GET' + request.path = '/users' + request.headers = {} + request._transport_peername = ('10.0.0.1', 1234) + request.scheme = 'http' + request.host = '127.0.0.1:9999' + + span = MagicMock() + context = MagicMock() + context.new_entry_span.return_value = span + + async def run(): + with patch.object(sw_aiohttp, 'get_context', return_value=context): + return await RequestHandler._handle_request(handler, request, 0.0) + + result = asyncio.run(run()) + self.assertEqual((resp, False), result) + context.new_entry_span.assert_called_once() + stub.assert_awaited_once() + + if __name__ == '__main__': unittest.main() From 9c082d0a19e086ca4bc2d493e499e534baa19d6f Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Thu, 8 Oct 2026 09:11:41 +0800 Subject: [PATCH 2/3] fix: replace blocked paths-filter action with git change detection --- .github/workflows/CI.yaml | 59 ++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 5bcd070c..f34ee02e 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -80,30 +80,49 @@ jobs: outputs: agent: ${{ steps.filter.outputs.agent }} steps: - - uses: actions/checkout@v4 # required for push event + - uses: actions/checkout@v4 with: + fetch-depth: 0 persist-credentials: false - name: Check for file changes - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter - with: - # The following filters indicate a category along with - # the files that should not be ignored by CI when modified. - filters: | - agent: - - '.github/**/*.yaml' - - '**/*.py' - - '**/Dockerfile*' - - '**/Makefile' - - 'protocol/**' - - 'tests/**' - - '**/*.bat' - - '**/*.sh' - - '**/*.ps1' - - '**/pyproject.toml' - - '**/poetry.lock' - - '**/*.cfg' - list-files: json # logs matched files + shell: python3 {0} + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + EVENT_NAME: ${{ github.event_name }} + run: | + import fnmatch + import json + import os + import subprocess + + base = os.environ['BASE_SHA'] + head = os.environ['HEAD_SHA'] + if os.environ['EVENT_NAME'] == 'pull_request': + revisions = [f'{base}...{head}'] + else: + if not base.strip('0'): + base = subprocess.check_output( + ['git', 'hash-object', '-t', 'tree', '--stdin'], input=b'' + ).decode().strip() + revisions = [base, head] + paths = subprocess.check_output( + ['git', 'diff', '--name-only', '--no-renames', '-z', *revisions, '--'] + ).decode().split('\0') + patterns = ( + '*.py', 'Dockerfile*', 'Makefile', '*.bat', '*.sh', '*.ps1', + 'pyproject.toml', 'poetry.lock', '*.cfg', + ) + matched = [path for path in paths if path and ( + path.startswith(('protocol/', 'tests/')) + or (path.startswith('.github/') and path.endswith('.yaml')) + or any(fnmatch.fnmatchcase(path.rsplit('/', 1)[-1], pattern) for pattern in patterns) + )] + print(json.dumps(matched)) + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write(f"agent={str(bool(matched)).lower()}\n") + # Only run the Plugin and E2E jobs if there are essential changes as stated above prep-plugin-and-unit-tests: From a85e0e72fe253b2ced56a6ac493ec5fe57a2f14a Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Thu, 8 Oct 2026 10:03:38 +0800 Subject: [PATCH 3/3] fix: use Apache-approved paths-filter v4.0.3 --- .github/workflows/CI.yaml | 59 +++++++++++++-------------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index f34ee02e..bfc10e6a 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -80,49 +80,30 @@ jobs: outputs: agent: ${{ steps.filter.outputs.agent }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 # required for push event with: - fetch-depth: 0 persist-credentials: false - name: Check for file changes + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter - shell: python3 {0} - env: - BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - EVENT_NAME: ${{ github.event_name }} - run: | - import fnmatch - import json - import os - import subprocess - - base = os.environ['BASE_SHA'] - head = os.environ['HEAD_SHA'] - if os.environ['EVENT_NAME'] == 'pull_request': - revisions = [f'{base}...{head}'] - else: - if not base.strip('0'): - base = subprocess.check_output( - ['git', 'hash-object', '-t', 'tree', '--stdin'], input=b'' - ).decode().strip() - revisions = [base, head] - paths = subprocess.check_output( - ['git', 'diff', '--name-only', '--no-renames', '-z', *revisions, '--'] - ).decode().split('\0') - patterns = ( - '*.py', 'Dockerfile*', 'Makefile', '*.bat', '*.sh', '*.ps1', - 'pyproject.toml', 'poetry.lock', '*.cfg', - ) - matched = [path for path in paths if path and ( - path.startswith(('protocol/', 'tests/')) - or (path.startswith('.github/') and path.endswith('.yaml')) - or any(fnmatch.fnmatchcase(path.rsplit('/', 1)[-1], pattern) for pattern in patterns) - )] - print(json.dumps(matched)) - with open(os.environ['GITHUB_OUTPUT'], 'a') as output: - output.write(f"agent={str(bool(matched)).lower()}\n") - + with: + # The following filters indicate a category along with + # the files that should not be ignored by CI when modified. + filters: | + agent: + - '.github/**/*.yaml' + - '**/*.py' + - '**/Dockerfile*' + - '**/Makefile' + - 'protocol/**' + - 'tests/**' + - '**/*.bat' + - '**/*.sh' + - '**/*.ps1' + - '**/pyproject.toml' + - '**/poetry.lock' + - '**/*.cfg' + list-files: json # logs matched files # Only run the Plugin and E2E jobs if there are essential changes as stated above prep-plugin-and-unit-tests: