diff --git a/.ci/oauth2/setup.sh b/.ci/oauth2/setup.sh
index 49af95fb16..8c11cee739 100755
--- a/.ci/oauth2/setup.sh
+++ b/.ci/oauth2/setup.sh
@@ -46,9 +46,9 @@ function start_rabbitmq
--network "$docker_network" \
--publish 5672:5672 \
--publish 15672:15672 \
- --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/enabled_plugins:/etc/rabbitmq/enabled_plugins" \
- --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/$mode/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \
- --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/$mode/signing-key/signing-key.pem:/etc/rabbitmq/signing-key.pem:ro" \
+ --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/enabled_plugins:/etc/rabbitmq/enabled_plugins" \
+ --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/$mode/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \
+ --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/$mode/signing-key/signing-key.pem:/etc/rabbitmq/signing-key.pem:ro" \
rabbitmq:3-management
}
@@ -90,7 +90,7 @@ function start_oauth_service
--publish 8080:8080 \
--env 'UAA_CONFIG_PATH=/uaa' \
--env 'JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom' \
- --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/uaa:/uaa" \
+ --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/uaa:/uaa" \
"cloudfoundry/uaa:$uaa_image_version"
else
readonly keycloak_docker_name="$docker_name_prefix-keycloak"
@@ -101,7 +101,7 @@ function start_oauth_service
--env 'KEYCLOAK_ADMIN=admin' \
--env 'KEYCLOAK_ADMIN_PASSWORD=admin' \
--env KC_HEALTH_ENABLED=true \
- --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/keycloak/import:/opt/keycloak/data/import" \
+ --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/keycloak/import:/opt/keycloak/data/import" \
"quay.io/keycloak/keycloak:$keycloak_image_version" start-dev --metrics-enabled=true --import-realm
fi
}
diff --git a/.ci/oauth2/test.sh b/.ci/oauth2/test.sh
index e99f5449fa..eda1f159b6 100755
--- a/.ci/oauth2/test.sh
+++ b/.ci/oauth2/test.sh
@@ -12,4 +12,4 @@ source "$script_dir/common.sh"
export OAUTH2_MODE="$mode"
-dotnet test --environment OAUTH2_MODE="$mode" "$GITHUB_WORKSPACE/projects/OAuth2Test/OAuth2Test.csproj" --logger "console;verbosity=detailed" --framework "net6.0"
+dotnet test --environment OAUTH2_MODE="$mode" "$GITHUB_WORKSPACE/projects/Test/OAuth2/OAuth2.csproj" --logger "console;verbosity=detailed"
diff --git a/.ci/ubuntu/rabbitmq.conf b/.ci/ubuntu/rabbitmq.conf
index ba2a758c03..d03fa510cd 100644
--- a/.ci/ubuntu/rabbitmq.conf
+++ b/.ci/ubuntu/rabbitmq.conf
@@ -1,6 +1,9 @@
log.console = false
+log.exchange = false
log.file = /var/log/rabbitmq/rabbitmq.log
-log.file.level = debug
+log.file.level = info
+# log.connection.level = warning
+# log.channel.level = warning
listeners.tcp.default = 5672
listeners.ssl.default = 5671
reverse_dns_lookups = false
diff --git a/.ci/windows/gha-run-tests.ps1 b/.ci/windows/gha-run-tests.ps1
deleted file mode 100644
index d75f977e57..0000000000
--- a/.ci/windows/gha-run-tests.ps1
+++ /dev/null
@@ -1,39 +0,0 @@
-$ProgressPreference = 'Continue'
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-
-$erlang_reg_path = 'HKLM:\SOFTWARE\Ericsson\Erlang'
-if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\')
-{
- $erlang_reg_path = 'HKLM:\SOFTWARE\WOW6432Node\Ericsson\Erlang'
-}
-$erlang_erts_version = Get-ChildItem -Path $erlang_reg_path -Name
-$erlang_home = (Get-ItemProperty -LiteralPath $erlang_reg_path\$erlang_erts_version).'(default)'
-
-Write-Host "[INFO] Setting ERLANG_HOME to '$erlang_home'..."
-$env:ERLANG_HOME = $erlang_home
-[Environment]::SetEnvironmentVariable('ERLANG_HOME', $erlang_home, 'Machine')
-
-$rabbitmq_base_path = (Get-ItemProperty -Name Install_Dir -Path 'HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.\RabbitMQ Server').Install_Dir
-$regPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ'
-if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\')
-{
- $regPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ'
-}
-$rabbitmq_version = (Get-ItemProperty $regPath "DisplayVersion").DisplayVersion
-$rabbitmqctl_path = Resolve-Path -LiteralPath (Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmqctl.bat')
-
-Write-Host "[INFO] Setting RABBITMQ_RABBITMQCTL_PATH to '$rabbitmqctl_path'..."
-$env:RABBITMQ_RABBITMQCTL_PATH = $rabbitmqctl_path
-[Environment]::SetEnvironmentVariable('RABBITMQ_RABBITMQCTL_PATH', $rabbitmqctl_path, 'Machine')
-
-New-Variable -Name ci_dir -Option Constant -Value (Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.ci')
-New-Variable -Name certs_dir -Option Constant -Value (Join-Path -Path $ci_dir -ChildPath 'certs')
-
-$csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'projects' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj')
-
-dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=$rabbitmqctl_path" `
- --environment 'RABBITMQ_LONG_RUNNING_TESTS=true' `
- --environment 'PASSWORD=grapefruit' `
- --environment "SSL_CERTS_DIR=$certs_dir" `
- $csproj_file --no-restore --no-build --logger "console;verbosity=detailed"
diff --git a/.ci/windows/gha-setup.ps1 b/.ci/windows/gha-setup.ps1
index 605b851250..2f1cf929c2 100644
--- a/.ci/windows/gha-setup.ps1
+++ b/.ci/windows/gha-setup.ps1
@@ -65,6 +65,7 @@ $erlang_home = (Get-ItemProperty -LiteralPath $erlang_reg_path\$erlang_erts_vers
Write-Host "[INFO] Setting ERLANG_HOME to '$erlang_home'..."
$env:ERLANG_HOME = $erlang_home
[Environment]::SetEnvironmentVariable('ERLANG_HOME', $erlang_home, 'Machine')
+Add-Content -Verbose -LiteralPath $env:GITHUB_ENV -Value "ERLANG_HOME=$erlang_home"
Write-Host "[INFO] Setting RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS..."
$env:RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS = '-rabbitmq_stream advertised_host localhost'
@@ -189,6 +190,17 @@ Write-Host '[INFO] Enabling plugins...'
& $rabbitmq_plugins_path enable rabbitmq_management rabbitmq_stream rabbitmq_stream_management rabbitmq_amqp1_0
echo Q | openssl s_client -connect localhost:5671 -CAfile "$certs_dir/ca_certificate.pem" -cert "$certs_dir/client_localhost_certificate.pem" -key "$certs_dir/client_localhost_key.pem" -pass pass:grapefruit
-if ($LASTEXITCODE -ne 0) {
+if ($LASTEXITCODE -ne 0)
+{
throw "[ERROR] 'openssl s_client' returned error: $LASTEXITCODE"
}
+
+
+$rabbitmqctl_path = Resolve-Path -LiteralPath `
+ (Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmqctl.bat')
+
+Write-Host "[INFO] Setting RABBITMQ_RABBITMQCTL_PATH to '$rabbitmqctl_path'..."
+$env:RABBITMQ_RABBITMQCTL_PATH = $rabbitmqctl_path
+[Environment]::SetEnvironmentVariable('RABBITMQ_RABBITMQCTL_PATH', $rabbitmqctl_path, 'Machine')
+Add-Content -Verbose -LiteralPath $env:GITHUB_OUTPUT -Value "path=$rabbitmqctl_path"
+Add-Content -Verbose -LiteralPath $env:GITHUB_ENV -Value "RABBITMQ_RABBITMQCTL_PATH=$rabbitmqctl_path"
diff --git a/.ci/windows/rabbitmq.conf.in b/.ci/windows/rabbitmq.conf.in
index 0923dbe6c7..dfcb44c51e 100644
--- a/.ci/windows/rabbitmq.conf.in
+++ b/.ci/windows/rabbitmq.conf.in
@@ -1,5 +1,8 @@
log.console = false
-log.file.level = debug
+log.exchange = false
+log.file.level = info
+# log.connection.level = warning
+# log.channel.level = warning
listeners.tcp.default = 5672
listeners.ssl.default = 5671
reverse_dns_lookups = false
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000000..a9209d0db8
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,5 @@
+# https://github.com/rabbitmq/rabbitmq-dotnet-client/commit/1713f50eb2dc52a97184f3857f70841dd55b5bef
+1713f50eb2dc52a97184f3857f70841dd55b5bef
+67c02d79d3ae48fea7de93c758dce91a51d14988
+# Revert the above
+6b1a06bd429f395891a3230cad92e674dcbbb0d2
diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml
index a7e4d6aa5b..38b1c51317 100644
--- a/.github/workflows/build-test.yaml
+++ b/.github/workflows/build-test.yaml
@@ -5,7 +5,7 @@ on:
jobs:
build-win32:
- name: build/test on windows-latest
+ name: build, unit test on windows-latest
runs-on: windows-latest
# https://github.com/NuGet/Home/issues/11548
env:
@@ -15,13 +15,6 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- - name: Cache installers
- uses: actions/cache@v3
- with:
- # Note: the cache path is relative to the workspace directory
- # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action
- path: ~/installers
- key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }}
- name: Cache NuGet packages
uses: actions/cache@v3
with:
@@ -31,24 +24,99 @@ jobs:
key: ${{ runner.os }}-v1-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-v1-nuget-
- - name: Install and Start RabbitMQ
- run: .\.ci\windows\gha-setup.ps1
- - name: List NuGet sources
- run: dotnet nuget locals all --list
- name: Build (Debug)
run: dotnet build ${{ github.workspace }}\Build.csproj
- name: Verify
- run: dotnet format ${{ github.workspace }}\RabbitMQDotNetClient.sln --verify-no-changes --verbosity=diagnostic
- - name: Test
- run: .\.ci\windows\gha-run-tests.ps1
+ run: dotnet format ${{ github.workspace }}\RabbitMQDotNetClient.sln --no-restore --verify-no-changes --verbosity=diagnostic
+ - name: APIApproval Test
+ run: dotnet test "${{ github.workspace }}\projects\Test\Unit\Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --filter='FullyQualifiedName=Test.Unit.APIApproval.Approve'
+ - name: Unit Tests
+ run: dotnet test "${{ github.workspace }}\projects\Test\Unit\Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
+ - name: Upload Build (Debug)
+ uses: actions/upload-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-win32
+ path: |
+ projects/Test/Unit/bin
+ projects/Test/AsyncIntegration/bin
+ projects/Test/Integration/bin
+ projects/Test/SequentialIntegration/bin
+ projects/RabbitMQ.*/bin
+ integration-win32:
+ name: integration test on windows-latest
+ needs: build-win32
+ runs-on: windows-latest
+ # https://github.com/NuGet/Home/issues/11548
+ env:
+ NUGET_CERT_REVOCATION_MODE: offline
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Cache installers
+ uses: actions/cache@v3
+ with:
+ # Note: the cache path is relative to the workspace directory
+ # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action
+ path: ~/installers
+ key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }}
+ - name: Download Build (Debug)
+ uses: actions/download-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-win32
+ path: projects
+ - name: Install and Start RabbitMQ
+ id: install-start-rabbitmq
+ run: .\.ci\windows\gha-setup.ps1
+ - name: Async Integration Tests
+ run: dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=${{ steps.install-start-rabbitmq.outputs.path }}" --environment 'RABBITMQ_LONG_RUNNING_TESTS=false' "${{ github.workspace }}\projects\Test\AsyncIntegration\AsyncIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
+ - name: Integration Tests
+ run: dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=${{ steps.install-start-rabbitmq.outputs.path }}" --environment 'RABBITMQ_LONG_RUNNING_TESTS=false' --environment 'PASSWORD=grapefruit' --environment SSL_CERTS_DIR="${{ github.workspace }}\.ci\certs" "${{ github.workspace }}\projects\Test\Integration\Integration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
- name: Maybe upload RabbitMQ logs
if: failure()
uses: actions/upload-artifact@v3
with:
- name: rabbitmq-logs
+ name: rabbitmq-logs-integration-win32
path: ~/AppData/Roaming/RabbitMQ/log/
- build:
- name: build/test on ubuntu-latest
+ sequential-integration-win32:
+ name: sequential integration test on windows-latest
+ needs: build-win32
+ runs-on: windows-latest
+ # https://github.com/NuGet/Home/issues/11548
+ env:
+ NUGET_CERT_REVOCATION_MODE: offline
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Cache installers
+ uses: actions/cache@v3
+ with:
+ # Note: the cache path is relative to the workspace directory
+ # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action
+ path: ~/installers
+ key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }}
+ - name: Download Build (Debug)
+ uses: actions/download-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-win32
+ path: projects
+ - name: Install and Start RabbitMQ
+ id: install-start-rabbitmq
+ run: .\.ci\windows\gha-setup.ps1
+ - name: Sequential Integration Tests
+ run: dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=${{ steps.install-start-rabbitmq.outputs.path }}" "${{ github.workspace }}\projects\Test\SequentialIntegration\SequentialIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
+ - name: Maybe upload RabbitMQ logs
+ if: failure()
+ uses: actions/upload-artifact@v3
+ with:
+ name: rabbitmq-logs-sequential-integration-win32
+ path: ~/AppData/Roaming/RabbitMQ/log/
+
+ build-ubuntu:
+ name: build, unit test on ubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Clone repository
@@ -68,26 +136,93 @@ jobs:
key: ${{ runner.os }}-v1-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-v1-nuget-
- - name: Start RabbitMQ
- id: start-rabbitmq
- run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh
- - name: List NuGet sources
- run: dotnet nuget locals all --list
- name: Build (Debug)
run: dotnet build ${{ github.workspace }}/Build.csproj
- name: Verify
- run: dotnet format ${{ github.workspace }}/RabbitMQDotNetClient.sln --verify-no-changes --verbosity=diagnostic
- - name: Test
+ run: dotnet format ${{ github.workspace }}/RabbitMQDotNetClient.sln --no-restore --verify-no-changes --verbosity=diagnostic
+ - name: APIApproval Test
+ run: dotnet test "${{ github.workspace }}/projects/Test/Unit/Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --filter='FullyQualifiedName=Test.Unit.APIApproval.Approve'
+ - name: Unit Tests
+ run: dotnet test "${{ github.workspace }}/projects/Test/Unit/Unit.csproj" --no-restore --no-build --verbosity=diagnostic --logger 'console;verbosity=detailed'
+ - name: Upload Build (Debug)
+ uses: actions/upload-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-ubuntu
+ path: |
+ projects/Test/Unit/bin
+ projects/Test/AsyncIntegration/bin
+ projects/Test/Integration/bin
+ projects/Test/SequentialIntegration/bin
+ projects/RabbitMQ.*/bin
+ integration-ubuntu:
+ name: integration test on ubuntu-latest
+ needs: build-ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 6.x
+ - name: Download Build (Debug)
+ uses: actions/download-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-ubuntu
+ path: projects
+ - name: Start RabbitMQ
+ id: start-rabbitmq
+ run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh
+ - name: Async Integration Tests
run: |
dotnet test \
--environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:${{ steps.start-rabbitmq.outputs.id }}" \
- --environment 'RABBITMQ_LONG_RUNNING_TESTS=true' \
+ "${{ github.workspace }}/projects/Test/AsyncIntegration/AsyncIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
+ - name: Integration Tests
+ run: |
+ dotnet test \
+ --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:${{ steps.start-rabbitmq.outputs.id }}" \
+ --environment 'RABBITMQ_LONG_RUNNING_TESTS=false' \
--environment 'PASSWORD=grapefruit' \
--environment SSL_CERTS_DIR="${{ github.workspace }}/.ci/certs" \
- "${{ github.workspace }}/projects/Unit/Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --framework 'net6.0'
+ "${{ github.workspace }}/projects/Test/Integration/Integration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
+ - name: Maybe upload RabbitMQ logs
+ if: failure()
+ uses: actions/upload-artifact@v3
+ with:
+ name: rabbitmq-logs-integration-ubuntu
+ path: ${{ github.workspace }}/.ci/ubuntu/log/
+ sequential-integration-ubuntu:
+ name: sequential integration test on ubuntu-latest
+ needs: build-ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 6.x
+ - name: Download Build (Debug)
+ uses: actions/download-artifact@v3
+ with:
+ name: rabbitmq-dotnet-client-build-ubuntu
+ path: projects
+ - name: Start RabbitMQ
+ id: start-rabbitmq
+ run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh
+ - name: Sequential Integration Tests
+ run: |
+ dotnet test \
+ --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:${{ steps.start-rabbitmq.outputs.id }}" \
+ "${{ github.workspace }}/projects/Test/SequentialIntegration/SequentialIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed'
- name: Maybe upload RabbitMQ logs
if: failure()
uses: actions/upload-artifact@v3
with:
- name: rabbitmq-logs
+ name: rabbitmq-logs-sequential-integration-ubuntu
path: ${{ github.workspace }}/.ci/ubuntu/log/
diff --git a/.gitignore b/.gitignore
index 19a14ef6f4..ff820f3350 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,8 +52,8 @@ build/
BenchmarkDotNet.Artifacts/*
-projects/Unit/APIApproval.Approve.received.txt
-projects/Unit/APIApproval.Approve.*.received.txt
+projects/Test/Unit/APIApproval.Approve.received.txt
+projects/Test/Unit/APIApproval.Approve.*.received.txt
# Visual Studio 2015 cache/options directory
.vs/
@@ -115,7 +115,7 @@ UpgradeLog*.htm
# Unit tests
-projects/Unit*/TestResult.xml
+projects/Test/Unit*/TestResult.xml
# Development scripts
diff --git a/Build.csproj b/Build.csproj
index b4c88b63a2..c59632d35f 100644
--- a/Build.csproj
+++ b/Build.csproj
@@ -9,10 +9,14 @@
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/RUNNING_TESTS.md b/RUNNING_TESTS.md
index a25a008221..f8b398ede4 100644
--- a/RUNNING_TESTS.md
+++ b/RUNNING_TESTS.md
@@ -71,7 +71,7 @@ in this example, it should be `./rabbitmq-server/deps/rabbit/sbin/rabbitmqctl`.
It is possible to override the location using `RABBITMQ_RABBITMQCTL_PATH`:
```
-RABBITMQ_RABBITMQCTL_PATH=/path/to/rabbitmqctl dotnet test projects/Unit
+RABBITMQ_RABBITMQCTL_PATH=/path/to/rabbitmqctl dotnet test projects/Test/Unit.csproj
```
### Option Three: Using a Docker Container
@@ -110,9 +110,9 @@ Running individual tests and fixtures on Windows is trivial using the Visual Stu
To run a specific tests fixture on MacOS or Linux, use the NUnit filter expressions to select the tests to be run:
``` shell
-dotnet test projects/Unit --filter "Name~TestAmqpUriParseFail"
+dotnet test projects/Test/Unit.csproj --filter "Name~TestAmqpUriParseFail"
-dotnet test projects/Unit --filter "FullyQualifiedName~RabbitMQ.Client.Unit.TestHeartbeats"
+dotnet test projects/Test/Unit.csproj --filter "FullyQualifiedName~RabbitMQ.Client.Unit.TestHeartbeats"
```
## Running Tests for a Specific .NET Target
diff --git a/RabbitMQDotNetClient.sln b/RabbitMQDotNetClient.sln
index 4d22b5e523..efdd0fcf97 100644
--- a/RabbitMQDotNetClient.sln
+++ b/RabbitMQDotNetClient.sln
@@ -9,19 +9,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client", "projects\RabbitMQ.Client\RabbitMQ.Client.csproj", "{8C554257-5ECC-45DB-873D-560BFBB74EC8}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unit", "projects\Unit\Unit.csproj", "{B8FAC024-CC03-4067-9FFC-02846FB8AE48}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unit", "projects\Test\Unit\Unit.csproj", "{B8FAC024-CC03-4067-9FFC-02846FB8AE48}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "projects\Benchmarks\Benchmarks.csproj", "{38D72C9A-68E9-4653-B0CE-C7BA9FFD91D0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MassPublish", "projects\TestApplications\MassPublish\MassPublish.csproj", "{0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MassPublish", "projects\Test\Applications\MassPublish\MassPublish.csproj", "{0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestApplications", "TestApplications", "{D21B282C-49E6-4A30-887B-9626D94B8D69}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Applications", "Applications", "{D21B282C-49E6-4A30-887B-9626D94B8D69}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateChannel", "projects\TestApplications\CreateChannel\CreateChannel.csproj", "{4A589408-F3A3-40E1-A6DF-F5E620F7CA31}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateChannel", "projects\Test\Applications\CreateChannel\CreateChannel.csproj", "{4A589408-F3A3-40E1-A6DF-F5E620F7CA31}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client.OAuth2", "projects\RabbitMQ.Client.OAuth2\RabbitMQ.Client.OAuth2.csproj", "{794C7B31-0E9A-44A4-B285-0F3CAF6209F1}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2Test", "projects\OAuth2Test\OAuth2Test.csproj", "{897D13F0-AF06-444A-9072-CF7E809A4A2C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2", "projects\Test\OAuth2\OAuth2.csproj", "{897D13F0-AF06-444A-9072-CF7E809A4A2C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Integration", "projects\Test\Integration\Integration.csproj", "{B01347D8-C327-471B-A1FE-7B86F7684A27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SequentialIntegration", "projects\Test\SequentialIntegration\SequentialIntegration.csproj", "{F25725D7-2978-45F4-B90F-25D6F8B71C9E}"
+ ProjectSection(ProjectDependencies) = postProject
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "projects\Test\Common\Common.csproj", "{C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncIntegration", "projects\Test\AsyncIntegration\AsyncIntegration.csproj", "{D98F96C5-F7FB-45FC-92A0-9133850FB432}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -57,13 +73,36 @@ Global
{897D13F0-AF06-444A-9072-CF7E809A4A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{897D13F0-AF06-444A-9072-CF7E809A4A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{897D13F0-AF06-444A-9072-CF7E809A4A2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B01347D8-C327-471B-A1FE-7B86F7684A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B01347D8-C327-471B-A1FE-7B86F7684A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B01347D8-C327-471B-A1FE-7B86F7684A27}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B01347D8-C327-471B-A1FE-7B86F7684A27}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D98F96C5-F7FB-45FC-92A0-9133850FB432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D98F96C5-F7FB-45FC-92A0-9133850FB432}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D98F96C5-F7FB-45FC-92A0-9133850FB432}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D98F96C5-F7FB-45FC-92A0-9133850FB432}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
+ {B8FAC024-CC03-4067-9FFC-02846FB8AE48} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
{0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6} = {D21B282C-49E6-4A30-887B-9626D94B8D69}
+ {D21B282C-49E6-4A30-887B-9626D94B8D69} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
{4A589408-F3A3-40E1-A6DF-F5E620F7CA31} = {D21B282C-49E6-4A30-887B-9626D94B8D69}
+ {897D13F0-AF06-444A-9072-CF7E809A4A2C} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
+ {B01347D8-C327-471B-A1FE-7B86F7684A27} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
+ {F25725D7-2978-45F4-B90F-25D6F8B71C9E} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
+ {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
+ {D98F96C5-F7FB-45FC-92A0-9133850FB432} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3C6A0C44-FA63-4101-BBF9-2598641167D1}
diff --git a/build.ps1 b/build.ps1
index 9b630e8011..caae1b570a 100644
--- a/build.ps1
+++ b/build.ps1
@@ -14,7 +14,7 @@ Write-Host "Done building." -ForegroundColor "Green"
if ($RunTests)
{
- $unit_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath 'projects' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj')
+ $unit_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath 'projects' | Join-Path -ChildPath 'Test' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj')
Write-Host "Running Unit / Integration tests from '$unit_csproj_file' (all frameworks)" -ForegroundColor "Magenta"
dotnet test $unit_csproj_file --no-restore --no-build --logger "console;verbosity=detailed"
if ($LastExitCode -ne 0) {
diff --git a/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs b/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs
index 573db2a277..0fbf03c05a 100644
--- a/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs
+++ b/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs
@@ -18,7 +18,8 @@ public AsyncBasicConsumerFake(ManualResetEventSlim autoResetEvent)
_autoResetEvent = autoResetEvent;
}
- public Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ public Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
if (Interlocked.Increment(ref _current) == Count)
{
@@ -29,7 +30,7 @@ public Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redel
}
void IBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
- in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
if (Interlocked.Increment(ref _current) == Count)
{
diff --git a/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs b/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs
index b751091c4d..2a6d272a22 100644
--- a/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs
+++ b/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs
@@ -1,4 +1,5 @@
-using System.Threading;
+using System;
+using System.Threading;
using BenchmarkDotNet.Attributes;
using RabbitMQ.Client;
using RabbitMQ.Client.ConsumerDispatching;
@@ -18,7 +19,15 @@ public class ConsumerDispatcherBase
protected readonly string _exchange = "Exchange";
protected readonly string _routingKey = "RoutingKey";
protected readonly ReadOnlyBasicProperties _properties = new ReadOnlyBasicProperties();
- protected readonly byte[] _body = new byte[512];
+ protected readonly RentedMemory _body;
+
+ public ConsumerDispatcherBase()
+ {
+ var r = new Random();
+ byte[] body = new byte[512];
+ r.NextBytes(body);
+ _body = new RentedMemory(body);
+ }
}
public class BasicDeliverConsumerDispatching : ConsumerDispatcherBase
@@ -36,12 +45,13 @@ public void SetUpAsyncConsumer()
_dispatcher = new AsyncConsumerDispatcher(null, Concurrency);
_dispatcher.HandleBasicConsumeOk(_consumer, _consumerTag);
}
+
[Benchmark]
public void AsyncConsumerDispatcher()
{
for (int i = 0; i < Count; i++)
{
- _dispatcher.HandleBasicDeliver(_consumerTag, _deliveryTag, false, _exchange, _routingKey, _properties, _body, _body);
+ _dispatcher.HandleBasicDeliver(_consumerTag, _deliveryTag, false, _exchange, _routingKey, _properties, _body);
}
_autoResetEvent.Wait();
_autoResetEvent.Reset();
@@ -54,12 +64,13 @@ public void SetUpConsumer()
_dispatcher = new ConsumerDispatcher(null, Concurrency);
_dispatcher.HandleBasicConsumeOk(_consumer, _consumerTag);
}
+
[Benchmark]
public void ConsumerDispatcher()
{
for (int i = 0; i < Count; i++)
{
- _dispatcher.HandleBasicDeliver(_consumerTag, _deliveryTag, false, _exchange, _routingKey, _properties, _body, _body);
+ _dispatcher.HandleBasicDeliver(_consumerTag, _deliveryTag, false, _exchange, _routingKey, _properties, _body);
}
_autoResetEvent.Wait();
_autoResetEvent.Reset();
diff --git a/projects/Benchmarks/WireFormatting/MethodFraming.cs b/projects/Benchmarks/WireFormatting/MethodFraming.cs
index e2f032341e..7e66796e5f 100644
--- a/projects/Benchmarks/WireFormatting/MethodFraming.cs
+++ b/projects/Benchmarks/WireFormatting/MethodFraming.cs
@@ -19,7 +19,7 @@ public class MethodFramingBasicAck
public ushort Channel { get; set; }
[Benchmark]
- public ReadOnlyMemory BasicAckWrite() => Framing.SerializeToFrames(ref _basicAck, Channel);
+ internal RentedMemory BasicAckWrite() => Framing.SerializeToFrames(ref _basicAck, Channel);
}
[Config(typeof(Config))]
@@ -41,13 +41,13 @@ public class MethodFramingBasicPublish
public int FrameMax { get; set; }
[Benchmark]
- public ReadOnlyMemory BasicPublishWriteNonEmpty() => Framing.SerializeToFrames(ref _basicPublish, ref _properties, _body, Channel, FrameMax);
+ internal RentedMemory BasicPublishWriteNonEmpty() => Framing.SerializeToFrames(ref _basicPublish, ref _properties, _body, Channel, FrameMax);
[Benchmark]
- public ReadOnlyMemory BasicPublishWrite() => Framing.SerializeToFrames(ref _basicPublish, ref _propertiesEmpty, _bodyEmpty, Channel, FrameMax);
+ internal RentedMemory BasicPublishWrite() => Framing.SerializeToFrames(ref _basicPublish, ref _propertiesEmpty, _bodyEmpty, Channel, FrameMax);
[Benchmark]
- public ReadOnlyMemory BasicPublishMemoryWrite() => Framing.SerializeToFrames(ref _basicPublishMemory, ref _propertiesEmpty, _bodyEmpty, Channel, FrameMax);
+ internal RentedMemory BasicPublishMemoryWrite() => Framing.SerializeToFrames(ref _basicPublishMemory, ref _propertiesEmpty, _bodyEmpty, Channel, FrameMax);
}
[Config(typeof(Config))]
@@ -60,6 +60,6 @@ public class MethodFramingChannelClose
public ushort Channel { get; set; }
[Benchmark]
- public ReadOnlyMemory ChannelCloseWrite() => Framing.SerializeToFrames(ref _channelClose, Channel);
+ internal RentedMemory ChannelCloseWrite() => Framing.SerializeToFrames(ref _channelClose, Channel);
}
}
diff --git a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
index 68ebb42a02..5517d2dca2 100644
--- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
+++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
@@ -45,9 +45,21 @@
+
+ <_Parameter1>Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
<_Parameter1>Unit, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+ <_Parameter1>AsyncIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+ <_Parameter1>Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+ <_Parameter1>SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
<_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
diff --git a/projects/RabbitMQ.Client/client/ClientArrayPool.cs b/projects/RabbitMQ.Client/client/ClientArrayPool.cs
new file mode 100644
index 0000000000..d84e3c8cb5
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/ClientArrayPool.cs
@@ -0,0 +1,107 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+
+namespace RabbitMQ.Client
+{
+ internal static class ClientArrayPool
+ {
+ private static readonly ConcurrentDictionary _checkouts;
+
+ private static readonly bool s_useArrayPool = true;
+ private static readonly bool s_trackCheckouts = false;
+
+ static ClientArrayPool()
+ {
+ if (false == bool.TryParse(Environment.GetEnvironmentVariable("RABBITMQ_CLIENT_USE_ARRAY_POOL"), out s_useArrayPool))
+ {
+ s_useArrayPool = true;
+ }
+
+ if (s_trackCheckouts)
+ {
+ _checkouts = new ConcurrentDictionary();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static byte[] Rent(int minimumLength)
+ {
+ byte[] rv;
+
+ if (s_useArrayPool)
+ {
+ rv = ArrayPool.Shared.Rent(minimumLength);
+ }
+ else
+ {
+ rv = new byte[minimumLength];
+ }
+
+ if (s_trackCheckouts)
+ {
+ if (_checkouts.ContainsKey(rv))
+ {
+ throw new InvalidOperationException("ARRAY ALREADY RENTED");
+ }
+ else
+ {
+ _checkouts[rv] = true;
+ }
+ }
+
+ return rv;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void Return(byte[] array)
+ {
+ if (array != null && array.Length > 0)
+ {
+ if (s_trackCheckouts && array.Length > 0)
+ {
+ if (false == _checkouts.TryRemove(array, out _))
+ {
+ throw new InvalidOperationException("ARRAY NOT RENTED");
+ }
+ }
+ if (s_useArrayPool)
+ {
+ ArrayPool.Shared.Return(array, clearArray: true);
+ }
+ }
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client/client/RentedMemory.cs b/projects/RabbitMQ.Client/client/RentedMemory.cs
new file mode 100644
index 0000000000..9f65570750
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/RentedMemory.cs
@@ -0,0 +1,97 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+
+namespace RabbitMQ.Client
+{
+ public struct RentedMemory : IDisposable
+ {
+ private readonly int _size;
+ private readonly ReadOnlyMemory _memory;
+ private readonly byte[] _rentedArray;
+ private bool _disposedValue;
+
+ internal RentedMemory(byte[] rentedArray)
+ : this(rentedArray.Length, new ReadOnlyMemory(rentedArray), rentedArray)
+ {
+ }
+
+ internal RentedMemory(ReadOnlyMemory memory, byte[] rentedArray)
+ : this(memory.Length, memory, rentedArray)
+ {
+ }
+
+ internal RentedMemory(int size, ReadOnlyMemory memory, byte[] rentedArray)
+ {
+ _size = size;
+ _memory = memory;
+ _rentedArray = rentedArray;
+ }
+
+ public int Size => _size;
+
+ public ReadOnlyMemory Memory => _memory;
+
+ public ReadOnlySpan Span => _memory.Span;
+
+ public static implicit operator byte[](RentedMemory m)
+ {
+ return m._memory.ToArray();
+ }
+
+ internal byte[] RentedArray => _rentedArray;
+
+ internal RentedMemory Copy()
+ {
+ return new RentedMemory(_size, new ReadOnlyMemory(_memory.ToArray()), null);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing && _rentedArray != null)
+ {
+ ClientArrayPool.Return(_rentedArray);
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs b/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs
index 2465fd167b..b735d14be3 100644
--- a/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs
+++ b/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs
@@ -33,9 +33,6 @@
namespace RabbitMQ.Client
{
- // time representations in mainstream languages: the horror, the horror
- // see in particular the difference between .NET 1.x and .NET 2.0's versions of DateTime
-
///
/// Structure holding an AMQP timestamp, a posix 64-bit time_t.
///
diff --git a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
index a0c0af161a..336b3c5d55 100644
--- a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
@@ -113,7 +113,7 @@ public virtual Task HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
// Nothing to do here.
return Task.CompletedTask;
@@ -165,7 +165,8 @@ void IBasicConsumer.HandleBasicConsumeOk(string consumerTag)
throw new InvalidOperationException("Should never be called. Enable 'DispatchConsumersAsync'.");
}
- void IBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ void IBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
throw new InvalidOperationException("Should never be called. Enable 'DispatchConsumersAsync'.");
}
diff --git a/projects/RabbitMQ.Client/client/api/BasicGetResult.cs b/projects/RabbitMQ.Client/client/api/BasicGetResult.cs
index 4e9a9d1cc9..fbdff2f615 100644
--- a/projects/RabbitMQ.Client/client/api/BasicGetResult.cs
+++ b/projects/RabbitMQ.Client/client/api/BasicGetResult.cs
@@ -29,19 +29,14 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
-using System;
-using System.Buffers;
-
namespace RabbitMQ.Client
{
/// Represents Basic.GetOk responses from the server.
///
/// Basic.Get either returns an instance of this class, or null if a Basic.GetEmpty was received.
///
- public sealed class BasicGetResult : IDisposable
+ public sealed class BasicGetResult
{
- private readonly byte[] _rentedArray;
-
///
/// Sets the new instance's properties from the arguments passed in.
///
@@ -53,7 +48,7 @@ public sealed class BasicGetResult : IDisposable
/// The Basic-class content header properties for the message.
/// The body
public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey,
- uint messageCount, in ReadOnlyBasicProperties basicProperties, ReadOnlyMemory body)
+ uint messageCount, in ReadOnlyBasicProperties basicProperties, RentedMemory body)
{
DeliveryTag = deliveryTag;
Redelivered = redelivered;
@@ -61,32 +56,14 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri
RoutingKey = routingKey;
MessageCount = messageCount;
BasicProperties = basicProperties;
- Body = body;
+ /*
+ * Note: copying the body here is critical. The only other option to ensure the rented memory
+ * is returned correctly is to make this class implement IDisposable, but then it would be on the
+ * user to dispose of it. Basic.Get is discouraged, anyway, so a copy here is preferable.
+ */
+ Body = body.Copy();
}
- ///
- /// Sets the new instance's properties from the arguments passed in.
- ///
- /// Delivery tag for the message.
- /// Redelivered flag for the message
- /// The exchange this message was published to.
- /// Routing key with which the message was published.
- /// The number of messages pending on the queue, excluding the message being delivered.
- /// The Basic-class content header properties for the message.
- /// The body
- /// The rented array which body is part of.
- public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey,
- uint messageCount, in ReadOnlyBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray)
- {
- DeliveryTag = deliveryTag;
- Redelivered = redelivered;
- Exchange = exchange;
- RoutingKey = routingKey;
- MessageCount = messageCount;
- BasicProperties = basicProperties;
- Body = body;
- _rentedArray = rentedArray;
- }
///
/// Retrieves the Basic-class content header properties for this message.
@@ -96,7 +73,7 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri
///
/// Retrieves the body of this message.
///
- public ReadOnlyMemory Body { get; }
+ public RentedMemory Body { get; }
///
/// Retrieve the delivery tag for this message. See also .
@@ -126,14 +103,5 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri
/// Retrieve the routing key with which this message was published.
///
public string RoutingKey { get; }
-
- ///
- public void Dispose()
- {
- if (_rentedArray != null)
- {
- ArrayPool.Shared.Return(_rentedArray);
- }
- }
}
}
diff --git a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
index 82d7ff6d24..ade86d3804 100644
--- a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
+++ b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
@@ -36,6 +36,7 @@
using System.Reflection;
using System.Security.Authentication;
using System.Text;
+using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
using RabbitMQ.Client.Framing.Impl;
using RabbitMQ.Client.Impl;
@@ -410,6 +411,19 @@ public IConnection CreateConnection()
return CreateConnection(ClientProvidedName);
}
+ ///
+ /// Asynchronously reate a connection to one of the endpoints provided by the IEndpointResolver
+ /// returned by the EndpointResolverFactory. By default the configured
+ /// hostname and port are used.
+ ///
+ ///
+ /// When the configured hostname was not reachable.
+ ///
+ public ValueTask CreateConnectionAsync()
+ {
+ return CreateConnectionAsync(ClientProvidedName);
+ }
+
///
/// Create a connection to one of the endpoints provided by the IEndpointResolver
/// returned by the EndpointResolverFactory. By default the configured
@@ -429,6 +443,25 @@ public IConnection CreateConnection(string clientProvidedName)
return CreateConnection(EndpointResolverFactory(LocalEndpoints()), clientProvidedName);
}
+ ///
+ /// Asynchronously create a connection to one of the endpoints provided by the IEndpointResolver
+ /// returned by the EndpointResolverFactory. By default the configured
+ /// hostname and port are used.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ ///
+ /// When the configured hostname was not reachable.
+ ///
+ public ValueTask CreateConnectionAsync(string clientProvidedName)
+ {
+ return CreateConnectionAsync(EndpointResolverFactory(LocalEndpoints()), clientProvidedName);
+ }
+
///
/// Create a connection using a list of hostnames using the configured port.
/// By default each hostname is tried in a random order until a successful connection is
@@ -448,6 +481,25 @@ public IConnection CreateConnection(IList hostnames)
return CreateConnection(hostnames, ClientProvidedName);
}
+ ///
+ /// Asynchronously create a connection using a list of hostnames using the configured port.
+ /// By default each hostname is tried in a random order until a successful connection is
+ /// found or the list is exhausted using the DefaultEndpointResolver.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of hostnames to use for the initial
+ /// connection and recovery.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ public ValueTask CreateConnectionAsync(IList hostnames)
+ {
+ return CreateConnectionAsync(hostnames, ClientProvidedName);
+ }
+
///
/// Create a connection using a list of hostnames using the configured port.
/// By default each endpoint is tried in a random order until a successful connection is
@@ -474,6 +526,32 @@ public IConnection CreateConnection(IList hostnames, string clientProvid
return CreateConnection(EndpointResolverFactory(endpoints), clientProvidedName);
}
+ ///
+ /// Asynchronously create a connection using a list of hostnames using the configured port.
+ /// By default each endpoint is tried in a random order until a successful connection is
+ /// found or the list is exhausted.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of hostnames to use for the initial
+ /// connection and recovery.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ public ValueTask CreateConnectionAsync(IList hostnames, string clientProvidedName)
+ {
+ IEnumerable endpoints = hostnames.Select(h => new AmqpTcpEndpoint(h, Port, Ssl, MaxMessageSize));
+ return CreateConnectionAsync(EndpointResolverFactory(endpoints), clientProvidedName);
+ }
+
///
/// Create a connection using a list of endpoints. By default each endpoint will be tried
/// in a random order until a successful connection is found or the list is exhausted.
@@ -492,6 +570,24 @@ public IConnection CreateConnection(IList endpoints)
return CreateConnection(endpoints, ClientProvidedName);
}
+ ///
+ /// Asynchronously create a connection using a list of endpoints. By default each endpoint will be tried
+ /// in a random order until a successful connection is found or the list is exhausted.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of endpoints to use for the initial
+ /// connection and recovery.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ public ValueTask CreateConnectionAsync(IList endpoints)
+ {
+ return CreateConnectionAsync(endpoints, ClientProvidedName);
+ }
+
///
/// Create a connection using a list of endpoints. By default each endpoint will be tried
/// in a random order until a successful connection is found or the list is exhausted.
@@ -516,6 +612,30 @@ public IConnection CreateConnection(IList endpoints, string cli
return CreateConnection(EndpointResolverFactory(endpoints), clientProvidedName);
}
+ ///
+ /// Asynchronously create a connection using a list of endpoints. By default each endpoint will be tried
+ /// in a random order until a successful connection is found or the list is exhausted.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of endpoints to use for the initial
+ /// connection and recovery.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ public ValueTask CreateConnectionAsync(IList endpoints, string clientProvidedName)
+ {
+ return CreateConnectionAsync(EndpointResolverFactory(endpoints), clientProvidedName);
+ }
+
///
/// Create a connection using an IEndpointResolver.
///
@@ -539,10 +659,52 @@ public IConnection CreateConnection(IEndpointResolver endpointResolver, string c
{
if (AutomaticRecoveryEnabled)
{
- return new AutorecoveringConnection(config, endpointResolver);
+ var c = new AutorecoveringConnection(config, endpointResolver);
+ return c.Open();
+ }
+ else
+ {
+ var c = new Connection(config, endpointResolver.SelectOne(CreateFrameHandler));
+ return c.Open();
}
+ }
+ catch (Exception e)
+ {
+ throw new BrokerUnreachableException(e);
+ }
+ }
- return new Connection(config, endpointResolver.SelectOne(CreateFrameHandler));
+ ///
+ /// Asynchronously create a connection using an IEndpointResolver.
+ ///
+ ///
+ /// The endpointResolver that returns the endpoints to use for the connection attempt.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ public ValueTask CreateConnectionAsync(IEndpointResolver endpointResolver, string clientProvidedName)
+ {
+ ConnectionConfig config = CreateConfig(clientProvidedName);
+ try
+ {
+ if (AutomaticRecoveryEnabled)
+ {
+ var c = new AutorecoveringConnection(config, endpointResolver);
+ return c.OpenAsync();
+ }
+ else
+ {
+ var c = new Connection(config, endpointResolver.SelectOne(CreateFrameHandler));
+ return c.OpenAsync();
+ }
}
catch (Exception e)
{
diff --git a/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs
index 15b40ea808..5a82e98feb 100644
--- a/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs
@@ -153,7 +153,7 @@ public virtual void HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
// Nothing to do here.
}
diff --git a/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs
index 291eda1293..d2a0aec07c 100644
--- a/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs
@@ -1,4 +1,3 @@
-using System;
using System.Threading.Tasks;
using RabbitMQ.Client.Events;
@@ -52,7 +51,7 @@ Task HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body);
+ RentedMemory body);
///
/// Called when the channel shuts down.
diff --git a/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs
index d5244e973e..0285f37717 100644
--- a/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs
@@ -95,7 +95,7 @@ void HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body);
+ RentedMemory body);
///
/// Called when the channel shuts down.
diff --git a/projects/RabbitMQ.Client/client/api/IChannel.cs b/projects/RabbitMQ.Client/client/api/IChannel.cs
index 3f7f4693e2..5ded578ed2 100644
--- a/projects/RabbitMQ.Client/client/api/IChannel.cs
+++ b/projects/RabbitMQ.Client/client/api/IChannel.cs
@@ -58,8 +58,9 @@ public interface IChannel : IDisposable
///
ShutdownEventArgs CloseReason { get; }
- /// Signalled when an unexpected message is delivered
+ /// Signalled when an unexpected message is delivered.
///
+ ///
/// Under certain circumstances it is possible for a channel to receive a
/// message delivery which does not match any consumer which is currently
/// set up via basicConsume(). This will occur after the following sequence
@@ -79,7 +80,8 @@ public interface IChannel : IDisposable
/// such deliveries. If no default consumer is registered an
/// InvalidOperationException will be thrown when such a delivery arrives.
///
- /// Most people will not need to use this.
+ /// Most people will not need to use this.
+ ///
IBasicConsumer DefaultConsumer { get; set; }
///
@@ -89,7 +91,8 @@ public interface IChannel : IDisposable
///
/// Returns true if the channel is still in a state where it can be used.
- /// Identical to checking if equals null.
+ /// Identical to checking if equals null.
+ ///
bool IsOpen { get; }
///
@@ -115,17 +118,6 @@ public interface IChannel : IDisposable
///
event EventHandler BasicNacks;
- ///
- /// All messages received before this fires that haven't been ack'ed will be redelivered.
- /// All messages received afterwards won't be.
- ///
- ///
- /// Handlers for this event are invoked by the connection thread.
- /// It is sometimes useful to allow that thread to know that a recover-ok
- /// has been received, rather than the thread that invoked .
- ///
- event EventHandler BasicRecoverOk;
-
///
/// Signalled when a Basic.Return command arrives from the broker.
///
@@ -151,42 +143,89 @@ public interface IChannel : IDisposable
///
event EventHandler ChannelShutdown;
- ///
- /// Acknowledge one or more delivered message(s).
- ///
+ /// Acknknowledges one or more messages.
+ /// The delivery tag.
+ /// Ack all messages up to the delivery tag if set to true.
void BasicAck(ulong deliveryTag, bool multiple);
- ///
- /// Delete a Basic content-class consumer.
- ///
+ /// Asynchronously acknknowledges one or more messages.
+ /// The delivery tag.
+ /// Ack all messages up to the delivery tag if set to true.
+ ValueTask BasicAckAsync(ulong deliveryTag, bool multiple);
+
+ /// Cancel a Basic content-class consumer.
+ /// The consumer tag.
void BasicCancel(string consumerTag);
+ /// Asynchronously cancel a Basic content-class consumer.
+ /// The consumer tag.
+ ValueTask BasicCancelAsync(string consumerTag);
+
///
/// Same as BasicCancel but sets nowait to true and returns void (as there
/// will be no response from the server).
///
+ /// The consumer tag.
void BasicCancelNoWait(string consumerTag);
/// Start a Basic content-class consumer.
- string BasicConsume(
- string queue,
- bool autoAck,
- string consumerTag,
- bool noLocal,
- bool exclusive,
- IDictionary arguments,
- IBasicConsumer consumer);
+ /// The queue.
+ /// If set to true, automatically ack messages.
+ /// The consumer tag.
+ /// If set to true, this consumer will not receive messages published by the same connection.
+ /// If set to true, the consumer is exclusive.
+ /// Consumer arguments.
+ /// The consumer, an instance of
+ ///
+ string BasicConsume(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive, IDictionary arguments, IBasicConsumer consumer);
+
+ /// Asynchronously start a Basic content-class consumer.
+ /// The queue.
+ /// If set to true, automatically ack messages.
+ /// The consumer tag.
+ /// If set to true, this consumer will not receive messages published by the same connection.
+ /// If set to true, the consumer is exclusive.
+ /// Consumer arguments.
+ /// The consumer, an instance of
+ ///
+ ValueTask BasicConsumeAsync(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive, IDictionary arguments, IBasicConsumer consumer);
///
/// Retrieve an individual message, if
/// one is available; returns null if the server answers that
- /// no messages are currently available. See also .
+ /// no messages are currently available. See also .
///
+ /// The queue.
+ /// If set to true, automatically ack the message.
+ ///
BasicGetResult BasicGet(string queue, bool autoAck);
- /// Reject one or more delivered message(s).
+ ///
+ /// Asynchronously retrieve an individual message, if
+ /// one is available; returns null if the server answers that
+ /// no messages are currently available. See also .
+ ///
+ /// The queue.
+ /// If set to true, automatically ack the message.
+ ///
+ ValueTask BasicGetAsync(string queue, bool autoAck);
+
+ ///
+ /// Nack one or more delivered message(s).
+ ///
+ /// The delivery tag.
+ /// If set to true, nack all messages up to the current tag.
+ /// If set to true, requeue nack'd messages.
void BasicNack(ulong deliveryTag, bool multiple, bool requeue);
+ ///
+ /// Asynchronously nack one or more delivered message(s).
+ ///
+ /// The delivery tag.
+ /// If set to true, nack all messages up to the current tag.
+ /// If set to true, requeue nack'd messages.
+ ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue);
+
#nullable enable
///
@@ -238,23 +277,27 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
///
/// Configures QoS parameters of the Basic content-class.
///
+ /// Size of the prefetch in bytes.
+ /// The prefetch count.
+ /// If set to true, use global prefetch.
+ /// See the Consumer Prefetch documentation.
void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
///
- /// Indicates that a consumer has recovered.
- /// Deprecated. Should not be used.
- ///
- void BasicRecover(bool requeue);
-
- ///
- /// Indicates that a consumer has recovered.
- /// Deprecated. Should not be used.
+ /// Configures QoS parameters of the Basic content-class.
///
- void BasicRecoverAsync(bool requeue);
+ /// Size of the prefetch in bytes.
+ /// The prefetch count.
+ /// If set to true, use global prefetch.
+ /// See the Consumer Prefetch documentation.
+ ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global);
/// Reject a delivered message.
void BasicReject(ulong deliveryTag, bool requeue);
+ /// Reject a delivered message.
+ ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue);
+
/// Close this session.
/// The reply code to send for closing (See under "Reply Codes" in the AMQP specification).
/// The reply text to send for closing.
@@ -262,10 +305,19 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
void Close(ushort replyCode, string replyText, bool abort);
///
- /// Enable publisher acknowledgements.
+ /// Asynchronously close this session.
///
+ /// The instance containing the close data.
+ /// Whether or not the close is an abort (ignoring certain exceptions).
+ ///
+ ValueTask CloseAsync(ShutdownEventArgs reason, bool abort);
+
+ /// Enable publisher confirmations.
void ConfirmSelect();
+ /// Asynchronously enable publisher confirmations.
+ ValueTask ConfirmSelectAsync();
+
///
/// Bind an exchange to an exchange.
///
@@ -276,6 +328,16 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
///
void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments);
+ ///
+ /// Asynchronously binds an exchange to an exchange.
+ ///
+ ///
+ ///
+ /// Routing key must be shorter than 255 bytes.
+ ///
+ ///
+ ValueTask ExchangeBindAsync(string destination, string source, string routingKey, IDictionary arguments);
+
///
/// Like ExchangeBind but sets nowait to true.
///
@@ -289,10 +351,17 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
/// Declare an exchange.
///
/// The exchange is declared non-passive and non-internal.
- /// The "nowait" option is not exercised.
+ /// The "nowait" option is not used.
///
void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments);
+ /// Asynchronously declare an exchange.
+ ///
+ /// The exchange is declared non-internal.
+ /// The "nowait" option is not used.
+ ///
+ ValueTask ExchangeDeclareAsync(string exchange, string type, bool passive, bool durable, bool autoDelete, IDictionary arguments);
+
///
/// Same as ExchangeDeclare but sets nowait to true and returns void (as there
/// will be no response from the server).
@@ -315,6 +384,11 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
///
void ExchangeDelete(string exchange, bool ifUnused);
+ ///
+ /// Asynchronously delete an exchange.
+ ///
+ ValueTask ExchangeDeleteAsync(string exchange, bool ifUnused);
+
///
/// Like ExchangeDelete but sets nowait to true.
///
@@ -328,6 +402,14 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
///
void ExchangeUnbind(string destination, string source, string routingKey, IDictionary arguments);
+ ///
+ /// Asynchronously unbind an exchange from an exchange.
+ ///
+ ///
+ /// Routing key must be shorter than 255 bytes.
+ ///
+ ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments);
+
///
/// Like ExchangeUnbind but sets nowait to true.
///
@@ -341,13 +423,27 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
///
/// Bind a queue to an exchange.
///
+ /// The queue.
+ /// The exchange.
+ /// The routing key.
+ /// The arguments.
///
- ///
- /// Routing key must be shorter than 255 bytes.
- ///
+ /// Routing key must be shorter than 255 bytes.
///
void QueueBind(string queue, string exchange, string routingKey, IDictionary arguments);
+ ///
+ /// Asynchronously bind a queue to an exchange.
+ ///
+ /// The queue.
+ /// The exchange.
+ /// The routing key.
+ /// The arguments.
+ ///
+ /// Routing key must be shorter than 255 bytes.
+ ///
+ ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments);
+
/// Same as QueueBind but sets nowait parameter to true.
///
///
@@ -370,11 +466,12 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
/// Asynchronously declares a queue. See the Queues guide to learn more.
///
/// The name of the queue. Pass an empty string to make the server generate a name.
+ /// Set to true
to passively declare the queue (i.e. check for its existence)
/// Should this queue will survive a broker restart?
/// Should this queue use be limited to its declaring connection? Such a queue will be deleted when its declaring connection closes.
/// Should this queue be auto-deleted when its last consumer (if any) unsubscribes?
/// Optional; additional queue arguments, e.g. "x-queue-type"
- ValueTask QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments);
+ ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments);
///
/// Declares a queue. See the Queues guide to learn more.
@@ -411,52 +508,84 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
uint ConsumerCount(string queue);
///
- /// Delete a queue.
+ /// Deletes a queue. See the Queues guide to learn more.
+ ///
+ /// The name of the queue.
+ /// Only delete the queue if it is unused.
+ /// Only delete the queue if it is empty.
+ /// Returns the number of messages purged during deletion.
+ uint QueueDelete(string queue, bool ifUnused, bool ifEmpty);
+
+ ///
+ /// Asynchronously deletes a queue. See the Queues guide to learn more.
///
///
///Returns the number of messages purged during queue deletion.
///
- uint QueueDelete(string queue, bool ifUnused, bool ifEmpty);
+ ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty);
///
///Same as QueueDelete but sets nowait parameter to true
///and returns void (as there will be no response from the server)
///
+ /// The name of the queue.
+ /// Only delete the queue if it is unused.
+ /// Only delete the queue if it is empty.
+ /// Returns the number of messages purged during deletion.
void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty);
- ///
- /// Purge a queue of messages.
- ///
- ///
- /// Returns the number of messages purged.
- ///
+ /// Asynchronously purge a queue of messages.
+ /// The queue.
+ /// Returns the number of messages purged.
uint QueuePurge(string queue);
+ /// Asynchronously purge a queue of messages.
+ /// The queue.
+ /// Returns the number of messages purged.
+ ValueTask QueuePurgeAsync(string queue);
+
///
/// Unbind a queue from an exchange.
///
+ /// The queue.
+ /// The exchange.
+ /// The routing key.
+ /// The arguments.
///
- ///
- /// Routing key must be shorter than 255 bytes.
- ///
+ /// Routing key must be shorter than 255 bytes.
///
void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments);
///
- /// Commit this session's active TX transaction.
+ /// Asynchronously unbind a queue from an exchange.
///
+ /// The queue.
+ /// The exchange.
+ /// The routing key.
+ /// The arguments.
+ ///
+ /// Routing key must be shorter than 255 bytes.
+ ///
+ ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments);
+
+ /// Commit this session's active TX transaction.
void TxCommit();
- ///
- /// Roll back this session's active TX transaction.
- ///
+ /// Asynchronously commit this session's active TX transaction.
+ ValueTask TxCommitAsync();
+
+ /// Roll back this session's active TX transaction.
void TxRollback();
- ///
- /// Enable TX mode for this session.
- ///
+ /// Asynchronously roll back this session's active TX transaction.
+ ValueTask TxRollbackAsync();
+
+ /// Enable TX mode for this session.
void TxSelect();
+ /// Asynchronously enable TX mode for this session.
+ ValueTask TxSelectAsync();
+
///
/// Wait until all published messages on this channel have been confirmed.
///
diff --git a/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs b/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs
index 6beb1d6bb5..22b7463fe5 100644
--- a/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs
+++ b/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs
@@ -250,15 +250,32 @@ public static void Close(this IChannel channel)
channel.Close(Constants.ReplySuccess, "Goodbye", false);
}
- /// Close this session.
+ /// Asynchronously close this session.
+ ///
+ /// If the session is already closed (or closing), then this
+ /// method does nothing but wait for the in-progress close
+ /// operation to complete. This method will not return to the
+ /// caller until the shutdown is complete.
+ ///
+ public static ValueTask CloseAsync(this IChannel channel)
+ {
+ var reason = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.ReplySuccess, "Goodbye");
+ return channel.CloseAsync(reason, false);
+ }
+
+ ///
+ /// Close this channel.
+ ///
+ /// The channel.
+ /// The reply code.
+ /// The reply text.
///
/// The method behaves in the same way as Close(), with the only
/// difference that the channel is closed with the given channel
/// close code and message.
///
/// The close code (See under "Reply Codes" in the AMQP specification)
- ///
- ///
+ ///
/// A message indicating the reason for closing the channel
///
///
diff --git a/projects/RabbitMQ.Client/client/api/IConnection.cs b/projects/RabbitMQ.Client/client/api/IConnection.cs
index 745efe79e5..104b715d8e 100644
--- a/projects/RabbitMQ.Client/client/api/IConnection.cs
+++ b/projects/RabbitMQ.Client/client/api/IConnection.cs
@@ -31,6 +31,7 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
@@ -229,5 +230,10 @@ public interface IConnection : INetworkConnection, IDisposable
/// Create and return a fresh channel, session, and channel.
///
IChannel CreateChannel();
+
+ ///
+ /// Asynchronously create and return a fresh channel, session, and channel.
+ ///
+ ValueTask CreateChannelAsync();
}
}
diff --git a/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
index b1480cf887..1c9e63fcf2 100644
--- a/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
+++ b/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
@@ -31,7 +31,7 @@
using System;
using System.Collections.Generic;
-
+using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
namespace RabbitMQ.Client
@@ -102,6 +102,11 @@ public interface IConnectionFactory
///
IConnection CreateConnection();
+ ///
+ /// Asynchronously create a connection to the specified endpoint.
+ ///
+ ValueTask CreateConnectionAsync();
+
///
/// Create a connection to the specified endpoint.
///
@@ -114,6 +119,18 @@ public interface IConnectionFactory
/// Open connection
IConnection CreateConnection(string clientProvidedName);
+ ///
+ /// Asynchronously create a connection to the specified endpoint.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ValueTask CreateConnectionAsync(string clientProvidedName);
+
///
/// Connects to the first reachable hostname from the list.
///
@@ -121,6 +138,13 @@ public interface IConnectionFactory
/// Open connection
IConnection CreateConnection(IList hostnames);
+ ///
+ /// Asynchronously connects to the first reachable hostname from the list.
+ ///
+ /// List of host names to use
+ /// Open connection
+ ValueTask CreateConnectionAsync(IList hostnames);
+
///
/// Connects to the first reachable hostname from the list.
///
@@ -134,6 +158,19 @@ public interface IConnectionFactory
/// Open connection
IConnection CreateConnection(IList hostnames, string clientProvidedName);
+ ///
+ /// Asynchronously connects to the first reachable hostname from the list.
+ ///
+ /// List of host names to use
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ValueTask CreateConnectionAsync(IList hostnames, string clientProvidedName);
+
///
/// Create a connection using a list of endpoints.
/// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
@@ -148,6 +185,20 @@ public interface IConnectionFactory
///
IConnection CreateConnection(IList endpoints);
+ ///
+ /// Asynchronously create a connection using a list of endpoints.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of endpoints to use for the initial
+ /// connection and recovery.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ ValueTask CreateConnectionAsync(IList endpoints);
+
///
/// Create a connection using a list of endpoints.
/// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
@@ -168,6 +219,26 @@ public interface IConnectionFactory
///
IConnection CreateConnection(IList endpoints, string clientProvidedName);
+ ///
+ /// Asynchronously create a connection using a list of endpoints.
+ /// The selection behaviour can be overridden by configuring the EndpointResolverFactory.
+ ///
+ ///
+ /// List of endpoints to use for the initial
+ /// connection and recovery.
+ ///
+ ///
+ /// Application-specific connection name, will be displayed in the management UI
+ /// if RabbitMQ server supports it. This value doesn't have to be unique and cannot
+ /// be used as a connection identifier, e.g. in HTTP API requests.
+ /// This value is supposed to be human-readable.
+ ///
+ /// Open connection
+ ///
+ /// When no hostname was reachable.
+ ///
+ ValueTask CreateConnectionAsync(IList endpoints, string clientProvidedName);
+
///
/// Amount of time protocol handshake operations are allowed to take before
/// timing out.
diff --git a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
index 2dc046badc..e48816c861 100644
--- a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
@@ -1,4 +1,3 @@
-using System;
using System.Threading.Tasks;
using RabbitMQ.Client.Impl;
@@ -71,7 +70,8 @@ public override async Task HandleBasicConsumeOk(string consumerTag)
}
///Fires the Received event.
- public override Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ public override Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
// No need to call base, it's empty.
return _receivedWrapper.InvokeAsync(this, new BasicDeliverEventArgs(consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body));
diff --git a/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
index fbd497bb0f..8d89a8912d 100644
--- a/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
@@ -50,7 +50,7 @@ public BasicDeliverEventArgs(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
ConsumerTag = consumerTag;
DeliveryTag = deliveryTag;
@@ -65,7 +65,7 @@ public BasicDeliverEventArgs(string consumerTag,
public ReadOnlyBasicProperties BasicProperties { get; set; }
///The message body.
- public ReadOnlyMemory Body { get; set; }
+ public RentedMemory Body { get; set; }
///The consumer tag of the consumer that the message
///was delivered to.
diff --git a/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
index 67220f1244..419c586a5c 100644
--- a/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
@@ -38,25 +38,36 @@ namespace RabbitMQ.Client.Events
public class BasicReturnEventArgs : EventArgs
{
///The content header of the message.
- public ReadOnlyBasicProperties BasicProperties { get; set; }
+ public readonly ReadOnlyBasicProperties BasicProperties;
///The message body.
- public ReadOnlyMemory Body { get; set; }
+ public readonly RentedMemory Body;
///The exchange the returned message was originally
///published to.
- public string Exchange { get; set; }
+ public readonly string Exchange;
///The AMQP reason code for the return. See
///RabbitMQ.Client.Framing.*.Constants.
- public ushort ReplyCode { get; set; }
+ public readonly ushort ReplyCode;
///Human-readable text from the broker describing the
///reason for the return.
- public string ReplyText { get; set; }
+ public readonly string ReplyText;
///The routing key used when the message was
///originally published.
- public string RoutingKey { get; set; }
+ public readonly string RoutingKey;
+
+ public BasicReturnEventArgs(ushort replyCode, string replyText, string exchange, string routingKey,
+ ReadOnlyBasicProperties basicProperties, RentedMemory body)
+ {
+ ReplyCode = replyCode;
+ ReplyText = replyText;
+ Exchange = exchange;
+ RoutingKey = routingKey;
+ BasicProperties = basicProperties;
+ Body = body.Copy();
+ }
}
}
diff --git a/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs
index 681247ea56..79b9516c76 100644
--- a/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs
@@ -84,7 +84,8 @@ public override void HandleBasicConsumeOk(string consumerTag)
/// Accessing the body at a later point is unsafe as its memory can
/// be already released.
///
- public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
base.HandleBasicDeliver(consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body);
Received?.Invoke(
diff --git a/projects/RabbitMQ.Client/client/framing/Channel.cs b/projects/RabbitMQ.Client/client/framing/Channel.cs
index a59cceb1e3..7b107a5eaa 100644
--- a/projects/RabbitMQ.Client/client/framing/Channel.cs
+++ b/projects/RabbitMQ.Client/client/framing/Channel.cs
@@ -62,11 +62,6 @@ public override void _Private_BasicGet(string queue, bool autoAck)
ChannelSend(new BasicGet(queue, autoAck));
}
- public override void _Private_BasicRecover(bool requeue)
- {
- ChannelSend(new BasicRecover(requeue));
- }
-
public override void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId)
{
ChannelSend(new ChannelClose(replyCode, replyText, classId, methodId));
@@ -234,19 +229,26 @@ public override void BasicAck(ulong deliveryTag, bool multiple)
ChannelSend(new BasicAck(deliveryTag, multiple));
}
+ public override ValueTask BasicAckAsync(ulong deliveryTag, bool multiple)
+ {
+ var method = new BasicAck(deliveryTag, multiple);
+ return ModelSendAsync(method);
+ }
+
public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue)
{
ChannelSend(new BasicNack(deliveryTag, multiple, requeue));
}
- public override void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
+ public override ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue)
{
- ChannelRpc(new BasicQos(prefetchSize, prefetchCount, global), ProtocolCommandId.BasicQosOk);
+ var method = new BasicNack(deliveryTag, multiple, requeue);
+ return ModelSendAsync(method);
}
- public override void BasicRecoverAsync(bool requeue)
+ public override void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
{
- ChannelSend(new BasicRecoverAsync(requeue));
+ ChannelRpc(new BasicQos(prefetchSize, prefetchCount, global), ProtocolCommandId.BasicQosOk);
}
public override void BasicReject(ulong deliveryTag, bool requeue)
@@ -254,6 +256,12 @@ public override void BasicReject(ulong deliveryTag, bool requeue)
ChannelSend(new BasicReject(deliveryTag, requeue));
}
+ public override ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue)
+ {
+ var method = new BasicReject(deliveryTag, requeue);
+ return ModelSendAsync(method);
+ }
+
public override void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments)
{
ChannelRpc(new QueueUnbind(queue, exchange, routingKey, arguments), ProtocolCommandId.QueueUnbindOk);
@@ -295,36 +303,26 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd)
}
case ProtocolCommandId.BasicCancelOk:
{
- HandleBasicCancelOk(in cmd);
- return true;
+ return HandleBasicCancelOk(in cmd);
}
case ProtocolCommandId.BasicConsumeOk:
{
- HandleBasicConsumeOk(in cmd);
- return true;
+ return HandleBasicConsumeOk(in cmd);
}
case ProtocolCommandId.BasicGetEmpty:
{
- cmd.ReturnMethodBuffer();
- HandleBasicGetEmpty();
+ HandleBasicGetEmpty(in cmd);
return true;
}
case ProtocolCommandId.BasicGetOk:
{
- HandleBasicGetOk(in cmd);
- return true;
+ return HandleBasicGetOk(in cmd);
}
case ProtocolCommandId.BasicNack:
{
HandleBasicNack(in cmd);
return true;
}
- case ProtocolCommandId.BasicRecoverOk:
- {
- cmd.ReturnMethodBuffer();
- HandleBasicRecoverOk();
- return true;
- }
case ProtocolCommandId.BasicReturn:
{
HandleBasicReturn(in cmd);
@@ -337,8 +335,7 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd)
}
case ProtocolCommandId.ChannelCloseOk:
{
- cmd.ReturnMethodBuffer();
- HandleChannelCloseOk();
+ HandleChannelCloseOk(in cmd);
return true;
}
case ProtocolCommandId.ChannelFlow:
@@ -373,8 +370,7 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd)
}
case ProtocolCommandId.ConnectionUnblocked:
{
- cmd.ReturnMethodBuffer();
- HandleConnectionUnblocked();
+ HandleConnectionUnblocked(in cmd);
return true;
}
case ProtocolCommandId.QueueDeclareOk:
diff --git a/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs b/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs
new file mode 100644
index 0000000000..df938fa658
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs
@@ -0,0 +1,479 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client.client.framing;
+using RabbitMQ.Client.ConsumerDispatching;
+using RabbitMQ.Client.Exceptions;
+using RabbitMQ.Client.Framing.Impl;
+
+namespace RabbitMQ.Client.Impl
+{
+ internal abstract class AsyncRpcContinuation : IRpcContinuation, IDisposable
+ {
+ private readonly CancellationTokenSource _ct;
+ private readonly ConfiguredTaskAwaitable _taskAwaitable;
+
+ protected readonly TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private bool _disposedValue;
+
+ public AsyncRpcContinuation(TimeSpan continuationTimeout)
+ {
+ _ct = new CancellationTokenSource(continuationTimeout);
+ _ct.Token.Register(() =>
+ {
+ if (_tcs.TrySetCanceled())
+ {
+ // TODO LRB rabbitmq/rabbitmq-dotnet-client#1347
+ // Cancellation was successful, does this mean we should set a TimeoutException
+ // in the same manner as BlockingCell?
+ }
+ }, useSynchronizationContext: false);
+
+ _taskAwaitable = _tcs.Task.ConfigureAwait(false);
+ }
+
+ public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
+ {
+ return _taskAwaitable.GetAwaiter();
+ }
+
+ // TODO LRB #1347
+ // What to do if setting a result fails?
+ public abstract void HandleCommand(in IncomingCommand cmd);
+
+ public virtual void HandleChannelShutdown(ShutdownEventArgs reason)
+ {
+ _tcs.SetException(new OperationInterruptedException(reason));
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _ct.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ internal class ConnectionSecureOrTuneContinuation : AsyncRpcContinuation
+ {
+ public ConnectionSecureOrTuneContinuation(TimeSpan continuationTimeout) : base(continuationTimeout)
+ {
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.ConnectionSecure)
+ {
+ var secure = new ConnectionSecure(cmd.MethodSpan);
+ _tcs.TrySetResult(new ConnectionSecureOrTune { m_challenge = secure._challenge });
+ }
+ else if (cmd.CommandId == ProtocolCommandId.ConnectionTune)
+ {
+ var tune = new ConnectionTune(cmd.MethodSpan);
+ // TODO LRB rabbitmq/rabbitmq-dotnet-client#1347
+ // What to do if setting a result fails?
+ _tcs.TrySetResult(new ConnectionSecureOrTune
+ {
+ m_tuneDetails = new() { m_channelMax = tune._channelMax, m_frameMax = tune._frameMax, m_heartbeatInSeconds = tune._heartbeat }
+ });
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class SimpleAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ private readonly ProtocolCommandId _expectedCommandId;
+
+ public SimpleAsyncRpcContinuation(ProtocolCommandId expectedCommandId, TimeSpan continuationTimeout) : base(continuationTimeout)
+ {
+ _expectedCommandId = expectedCommandId;
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == _expectedCommandId)
+ {
+ _tcs.TrySetResult(true);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class BasicCancelAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ private readonly string _consumerTag;
+ private readonly IConsumerDispatcher _consumerDispatcher;
+
+ public BasicCancelAsyncRpcContinuation(string consumerTag, IConsumerDispatcher consumerDispatcher, TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.BasicCancelOk, continuationTimeout)
+ {
+ _consumerTag = consumerTag;
+ _consumerDispatcher = consumerDispatcher;
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.BasicCancelOk)
+ {
+ var method = new Client.Framing.Impl.BasicCancelOk(cmd.MethodSpan);
+ _tcs.TrySetResult(true);
+ Debug.Assert(_consumerTag == method._consumerTag);
+ _consumerDispatcher.HandleBasicCancelOk(_consumerTag);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class BasicConsumeAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ private readonly IBasicConsumer _consumer;
+ private readonly IConsumerDispatcher _consumerDispatcher;
+
+ public BasicConsumeAsyncRpcContinuation(IBasicConsumer consumer, IConsumerDispatcher consumerDispatcher, TimeSpan continuationTimeout)
+ : base(continuationTimeout)
+ {
+ _consumer = consumer;
+ _consumerDispatcher = consumerDispatcher;
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.BasicConsumeOk)
+ {
+ var method = new Client.Framing.Impl.BasicConsumeOk(cmd.MethodSpan);
+ _tcs.TrySetResult(method._consumerTag);
+ _consumerDispatcher.HandleBasicConsumeOk(_consumer, method._consumerTag);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class BasicGetAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ private readonly Func _adjustDeliveryTag;
+
+ public BasicGetAsyncRpcContinuation(Func adjustDeliveryTag, TimeSpan continuationTimeout)
+ : base(continuationTimeout)
+ {
+ _adjustDeliveryTag = adjustDeliveryTag;
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.BasicGetOk)
+ {
+ var method = new Client.Framing.Impl.BasicGetOk(cmd.MethodSpan);
+ var header = new ReadOnlyBasicProperties(cmd.HeaderSpan);
+
+ var result = new BasicGetResult(
+ _adjustDeliveryTag(method._deliveryTag),
+ method._redelivered,
+ method._exchange,
+ method._routingKey,
+ method._messageCount,
+ header,
+ cmd.Body);
+
+ _tcs.TrySetResult(result);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ /*
+ * Note: since the BasicGetResult ctor copies the rented array in cmd.Body,
+ * we want to return all buffers here
+ */
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class BasicQosAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public BasicQosAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.BasicQosOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ChannelOpenAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ChannelOpenAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ChannelOpenOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ChannelCloseAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ChannelCloseAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ChannelCloseOk, continuationTimeout)
+ {
+ }
+
+ public override void HandleChannelShutdown(ShutdownEventArgs reason)
+ {
+ // Nothing to do here!
+ }
+
+ public void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
+ {
+ _tcs.TrySetResult(true);
+ }
+ }
+
+ internal class ConfirmSelectAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ConfirmSelectAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ConfirmSelectOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ExchangeBindAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ExchangeBindAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ExchangeBindOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ExchangeDeclareAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ExchangeDeclareAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ExchangeDeclareOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ExchangeDeleteAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ExchangeDeleteAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ExchangeDeleteOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class ExchangeUnbindAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public ExchangeUnbindAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.ExchangeUnbindOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class QueueDeclareAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ public QueueDeclareAsyncRpcContinuation(TimeSpan continuationTimeout) : base(continuationTimeout)
+ {
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.QueueDeclareOk)
+ {
+ var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodSpan);
+ var result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
+ _tcs.TrySetResult(result);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class QueueBindAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public QueueBindAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.QueueBindOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class QueueUnbindAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public QueueUnbindAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.QueueUnbindOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class QueueDeleteAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ public QueueDeleteAsyncRpcContinuation(TimeSpan continuationTimeout) : base(continuationTimeout)
+ {
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.QueueDeleteOk)
+ {
+ var method = new Client.Framing.Impl.QueueDeleteOk(cmd.MethodSpan);
+ _tcs.TrySetResult(method._messageCount);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class QueuePurgeAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ public QueuePurgeAsyncRpcContinuation(TimeSpan continuationTimeout) : base(continuationTimeout)
+ {
+ }
+
+ public override void HandleCommand(in IncomingCommand cmd)
+ {
+ try
+ {
+ if (cmd.CommandId == ProtocolCommandId.QueuePurgeOk)
+ {
+ var method = new Client.Framing.Impl.QueuePurgeOk(cmd.MethodSpan);
+ _tcs.TrySetResult(method._messageCount);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ }
+
+ internal class TxCommitAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public TxCommitAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.TxCommitOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class TxRollbackAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public TxRollbackAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.TxRollbackOk, continuationTimeout)
+ {
+ }
+ }
+
+ internal class TxSelectAsyncRpcContinuation : SimpleAsyncRpcContinuation
+ {
+ public TxSelectAsyncRpcContinuation(TimeSpan continuationTimeout)
+ : base(ProtocolCommandId.TxSelectOk, continuationTimeout)
+ {
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs
index ed3a60efc9..a9d9d0c77e 100644
--- a/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs
+++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs
@@ -87,12 +87,6 @@ public event EventHandler BasicNacks
remove => InnerChannel.BasicNacks -= value;
}
- public event EventHandler BasicRecoverOk
- {
- add => InnerChannel.BasicRecoverOk += value;
- remove => InnerChannel.BasicRecoverOk -= value;
- }
-
public event EventHandler BasicReturn
{
add => InnerChannel.BasicReturn += value;
@@ -251,6 +245,19 @@ public void Close(ushort replyCode, string replyText, bool abort)
}
}
+ public async ValueTask CloseAsync(ShutdownEventArgs reason, bool abort)
+ {
+ ThrowIfDisposed();
+ try
+ {
+ await _innerChannel.CloseAsync(reason, abort);
+ }
+ finally
+ {
+ _connection.DeleteRecordedChannel(this);
+ }
+ }
+
public override string ToString()
=> InnerChannel.ToString();
@@ -273,6 +280,9 @@ public void Dispose()
public void BasicAck(ulong deliveryTag, bool multiple)
=> InnerChannel.BasicAck(deliveryTag, multiple);
+ public ValueTask BasicAckAsync(ulong deliveryTag, bool multiple)
+ => InnerChannel.BasicAckAsync(deliveryTag, multiple);
+
public void BasicCancel(string consumerTag)
{
ThrowIfDisposed();
@@ -280,6 +290,13 @@ public void BasicCancel(string consumerTag)
_innerChannel.BasicCancel(consumerTag);
}
+ public ValueTask BasicCancelAsync(string consumerTag)
+ {
+ ThrowIfDisposed();
+ _connection.DeleteRecordedConsumer(consumerTag);
+ return _innerChannel.BasicCancelAsync(consumerTag);
+ }
+
public void BasicCancelNoWait(string consumerTag)
{
ThrowIfDisposed();
@@ -287,14 +304,8 @@ public void BasicCancelNoWait(string consumerTag)
_innerChannel.BasicCancelNoWait(consumerTag);
}
- public string BasicConsume(
- string queue,
- bool autoAck,
- string consumerTag,
- bool noLocal,
- bool exclusive,
- IDictionary arguments,
- IBasicConsumer consumer)
+ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive,
+ IDictionary arguments, IBasicConsumer consumer)
{
string resultConsumerTag = InnerChannel.BasicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumer);
var rc = new RecordedConsumer(channel: this, consumer: consumer, consumerTag: resultConsumerTag,
@@ -304,12 +315,29 @@ public string BasicConsume(
return resultConsumerTag;
}
+ public async ValueTask BasicConsumeAsync(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive,
+ IDictionary arguments, IBasicConsumer consumer)
+ {
+ string resultConsumerTag = await InnerChannel.BasicConsumeAsync(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumer);
+ var rc = new RecordedConsumer(channel: this, consumer: consumer, consumerTag: resultConsumerTag,
+ queue: queue, autoAck: autoAck, exclusive: exclusive, arguments: arguments);
+ _connection.RecordConsumer(rc);
+ _recordedConsumerTags.Add(resultConsumerTag);
+ return resultConsumerTag;
+ }
+
public BasicGetResult BasicGet(string queue, bool autoAck)
=> InnerChannel.BasicGet(queue, autoAck);
+ public ValueTask BasicGetAsync(string queue, bool autoAck)
+ => InnerChannel.BasicGetAsync(queue, autoAck);
+
public void BasicNack(ulong deliveryTag, bool multiple, bool requeue)
=> InnerChannel.BasicNack(deliveryTag, multiple, requeue);
+ public ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue)
+ => InnerChannel.BasicNackAsync(deliveryTag, multiple, requeue);
+
public void BasicPublish(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory body, bool mandatory)
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
=> InnerChannel.BasicPublish(exchange, routingKey, in basicProperties, body, mandatory);
@@ -329,6 +357,7 @@ public ValueTask BasicPublishAsync(CachedString exchange, CachedStr
public void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
{
ThrowIfDisposed();
+
if (global)
{
_prefetchCountGlobal = prefetchCount;
@@ -337,24 +366,45 @@ public void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
{
_prefetchCountConsumer = prefetchCount;
}
+
_innerChannel.BasicQos(prefetchSize, prefetchCount, global);
}
- public void BasicRecover(bool requeue)
- => InnerChannel.BasicRecover(requeue);
+ public ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global)
+ {
+ ThrowIfDisposed();
+
+ if (global)
+ {
+ _prefetchCountGlobal = prefetchCount;
+ }
+ else
+ {
+ _prefetchCountConsumer = prefetchCount;
+ }
- public void BasicRecoverAsync(bool requeue)
- => InnerChannel.BasicRecoverAsync(requeue);
+ return _innerChannel.BasicQosAsync(prefetchSize, prefetchCount, global);
+ }
public void BasicReject(ulong deliveryTag, bool requeue)
=> InnerChannel.BasicReject(deliveryTag, requeue);
+ public ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue)
+ => InnerChannel.BasicRejectAsync(deliveryTag, requeue);
+
public void ConfirmSelect()
{
InnerChannel.ConfirmSelect();
_usesPublisherConfirms = true;
}
+ public ValueTask ConfirmSelectAsync()
+ {
+ var task = InnerChannel.ConfirmSelectAsync();
+ _usesPublisherConfirms = true;
+ return task;
+ }
+
public void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments)
{
ThrowIfDisposed();
@@ -362,20 +412,33 @@ public void ExchangeBind(string destination, string source, string routingKey, I
_innerChannel.ExchangeBind(destination, source, routingKey, arguments);
}
+ public async ValueTask ExchangeBindAsync(string destination, string source, string routingKey, IDictionary arguments)
+ {
+ await InnerChannel.ExchangeBindAsync(destination, source, routingKey, arguments);
+ _connection.RecordBinding(new RecordedBinding(false, destination, source, routingKey, arguments));
+ }
+
public void ExchangeBindNoWait(string destination, string source, string routingKey, IDictionary arguments)
=> InnerChannel.ExchangeBindNoWait(destination, source, routingKey, arguments);
public void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- _innerChannel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments);
+ InnerChannel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments);
_connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments));
}
+ public async ValueTask ExchangeDeclareAsync(string exchange, string type, bool passive, bool durable, bool autoDelete, IDictionary arguments)
+ {
+ await InnerChannel.ExchangeDeclareAsync(exchange, type, passive, durable, autoDelete, arguments);
+ if (false == passive)
+ {
+ _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments));
+ }
+ }
+
public void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- _innerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments);
+ InnerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments);
_connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments));
}
@@ -388,6 +451,12 @@ public void ExchangeDelete(string exchange, bool ifUnused)
_connection.DeleteRecordedExchange(exchange);
}
+ public async ValueTask ExchangeDeleteAsync(string exchange, bool ifUnused)
+ {
+ await InnerChannel.ExchangeDeleteAsync(exchange, ifUnused);
+ _connection.DeleteRecordedExchange(exchange);
+ }
+
public void ExchangeDeleteNoWait(string exchange, bool ifUnused)
{
InnerChannel.ExchangeDeleteNoWait(exchange, ifUnused);
@@ -402,6 +471,13 @@ public void ExchangeUnbind(string destination, string source, string routingKey,
_connection.DeleteAutoDeleteExchange(source);
}
+ public async ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments)
+ {
+ await InnerChannel.ExchangeUnbindAsync(destination, source, routingKey, arguments);
+ _connection.DeleteRecordedBinding(new RecordedBinding(false, destination, source, routingKey, arguments));
+ _connection.DeleteAutoDeleteExchange(source);
+ }
+
public void ExchangeUnbindNoWait(string destination, string source, string routingKey, IDictionary arguments)
=> InnerChannel.ExchangeUnbind(destination, source, routingKey, arguments);
@@ -417,27 +493,30 @@ public void QueueBindNoWait(string queue, string exchange, string routingKey, ID
public QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- QueueDeclareOk result = _innerChannel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments);
+ QueueDeclareOk result = InnerChannel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments);
_connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments));
return result;
}
public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- _innerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments);
+ InnerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments);
_connection.RecordQueue(new RecordedQueue(queue, queue.Length == 0, durable, exclusive, autoDelete, arguments));
}
- public async ValueTask QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
+ public async ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- QueueDeclareOk result = await _innerChannel.QueueDeclareAsync(queue, durable, exclusive, autoDelete, arguments);
- _connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments));
+ QueueDeclareOk result = await InnerChannel.QueueDeclareAsync(queue, passive, durable, exclusive, autoDelete, arguments);
+ if (false == passive)
+ {
+ _connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments));
+ }
return result;
}
+ public ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments)
+ => InnerChannel.QueueBindAsync(queue, exchange, routingKey, arguments);
+
public QueueDeclareOk QueueDeclarePassive(string queue)
=> InnerChannel.QueueDeclarePassive(queue);
@@ -449,8 +528,14 @@ public uint ConsumerCount(string queue)
public uint QueueDelete(string queue, bool ifUnused, bool ifEmpty)
{
- ThrowIfDisposed();
- uint result = _innerChannel.QueueDelete(queue, ifUnused, ifEmpty);
+ uint result = InnerChannel.QueueDelete(queue, ifUnused, ifEmpty);
+ _connection.DeleteRecordedQueue(queue);
+ return result;
+ }
+
+ public async ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty)
+ {
+ uint result = await InnerChannel.QueueDeleteAsync(queue, ifUnused, ifEmpty);
_connection.DeleteRecordedQueue(queue);
return result;
}
@@ -464,6 +549,9 @@ public void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty)
public uint QueuePurge(string queue)
=> InnerChannel.QueuePurge(queue);
+ public ValueTask QueuePurgeAsync(string queue)
+ => InnerChannel.QueuePurgeAsync(queue);
+
public void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments)
{
ThrowIfDisposed();
@@ -472,18 +560,38 @@ public void QueueUnbind(string queue, string exchange, string routingKey, IDicti
_connection.DeleteAutoDeleteExchange(exchange);
}
+ public async ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments)
+ {
+ ThrowIfDisposed();
+ _connection.DeleteRecordedBinding(new RecordedBinding(true, queue, exchange, routingKey, arguments));
+ await _innerChannel.QueueUnbindAsync(queue, exchange, routingKey, arguments);
+ _connection.DeleteAutoDeleteExchange(exchange);
+ }
+
public void TxCommit()
=> InnerChannel.TxCommit();
+ public ValueTask TxCommitAsync()
+ => InnerChannel.TxCommitAsync();
+
public void TxRollback()
=> InnerChannel.TxRollback();
+ public ValueTask TxRollbackAsync()
+ => InnerChannel.TxRollbackAsync();
+
public void TxSelect()
{
InnerChannel.TxSelect();
_usesTransactions = true;
}
+ public ValueTask TxSelectAsync()
+ {
+ _usesTransactions = true;
+ return InnerChannel.TxSelectAsync();
+ }
+
public Task WaitForConfirmsAsync(CancellationToken token = default)
=> InnerChannel.WaitForConfirmsAsync(token);
diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs
index 625ba1b2d4..76f3e6b58e 100644
--- a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs
+++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs
@@ -32,6 +32,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Impl;
@@ -57,7 +58,7 @@ private Connection InnerConnection
}
}
- public AutorecoveringConnection(ConnectionConfig config, IEndpointResolver endpoints)
+ internal AutorecoveringConnection(ConnectionConfig config, IEndpointResolver endpoints)
{
_config = config;
_endpoints = endpoints;
@@ -77,6 +78,18 @@ public AutorecoveringConnection(ConnectionConfig config, IEndpointResolver endpo
ConnectionShutdown += HandleConnectionShutdown;
}
+ internal IConnection Open()
+ {
+ InnerConnection.Open();
+ return this;
+ }
+
+ internal async ValueTask OpenAsync()
+ {
+ await InnerConnection.OpenAsync();
+ return this;
+ }
+
public event EventHandler RecoverySucceeded
{
add => _recoverySucceededWrapper.AddHandler(value);
@@ -170,10 +183,20 @@ public RecoveryAwareChannel CreateNonRecoveringChannel()
return result;
}
+ public async ValueTask CreateNonRecoveringChannelAsync()
+ {
+ ISession session = InnerConnection.CreateSession();
+ var result = new RecoveryAwareChannel(_config, session);
+ return await result.OpenAsync() as RecoveryAwareChannel;
+ }
+
public override string ToString()
=> $"AutorecoveringConnection({InnerConnection.Id},{Endpoint},{GetHashCode()})";
- internal IFrameHandler FrameHandler => InnerConnection.FrameHandler;
+ internal void CloseFrameHandler()
+ {
+ InnerConnection.FrameHandler.Close();
+ }
///API-side invocation of updating the secret.
public void UpdateSecret(string newSecret, string reason)
@@ -202,6 +225,15 @@ public IChannel CreateChannel()
return m;
}
+ public async ValueTask CreateChannelAsync()
+ {
+ EnsureIsOpen();
+ RecoveryAwareChannel recoveryAwareChannel = await CreateNonRecoveringChannelAsync();
+ AutorecoveringChannel m = new AutorecoveringChannel(this, recoveryAwareChannel);
+ RecordChannel(m);
+ return m;
+ }
+
public void Dispose()
{
if (_disposed)
diff --git a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs
index decf6ac84f..46f30ed61b 100644
--- a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs
+++ b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs
@@ -30,8 +30,8 @@
//---------------------------------------------------------------------------
using System;
-using System.Buffers;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
@@ -52,7 +52,7 @@ internal abstract class ChannelBase : IChannel, IRecoverable
internal TaskCompletionSource m_connectionStartCell;
// AMQP only allows one RPC operation to be active at a time.
- private readonly SemaphoreSlim _rpcSemaphore = new SemaphoreSlim(1, 1);
+ protected readonly SemaphoreSlim _rpcSemaphore = new SemaphoreSlim(1, 1);
private readonly RpcContinuationQueue _continuationQueue = new RpcContinuationQueue();
private readonly ManualResetEventSlim _flowControlBlock = new ManualResetEventSlim(true);
@@ -76,7 +76,6 @@ protected ChannelBase(ConnectionConfig config, ISession session)
Action onException = (exception, context) => OnCallbackException(CallbackExceptionEventArgs.Build(exception, context));
_basicAcksWrapper = new EventingWrapper("OnBasicAck", onException);
_basicNacksWrapper = new EventingWrapper("OnBasicNack", onException);
- _basicRecoverOkWrapper = new EventingWrapper("OnBasicRecover", onException);
_basicReturnWrapper = new EventingWrapper("OnBasicReturn", onException);
_callbackExceptionWrapper = new EventingWrapper(string.Empty, (exception, context) => { });
_flowControlWrapper = new EventingWrapper("OnFlowControl", onException);
@@ -104,13 +103,6 @@ public event EventHandler BasicNacks
}
private EventingWrapper _basicNacksWrapper;
- public event EventHandler BasicRecoverOk
- {
- add => _basicRecoverOkWrapper.AddHandler(value);
- remove => _basicRecoverOkWrapper.RemoveHandler(value);
- }
- private EventingWrapper _basicRecoverOkWrapper;
-
public event EventHandler BasicReturn
{
add => _basicReturnWrapper.AddHandler(value);
@@ -183,7 +175,6 @@ protected void TakeOver(ChannelBase other)
{
_basicAcksWrapper.Takeover(other._basicAcksWrapper);
_basicNacksWrapper.Takeover(other._basicNacksWrapper);
- _basicRecoverOkWrapper.Takeover(other._basicRecoverOkWrapper);
_basicReturnWrapper.Takeover(other._basicReturnWrapper);
_callbackExceptionWrapper.Takeover(other._callbackExceptionWrapper);
_flowControlWrapper.Takeover(other._flowControlWrapper);
@@ -193,23 +184,68 @@ protected void TakeOver(ChannelBase other)
public void Close(ushort replyCode, string replyText, bool abort)
{
- _ = CloseAsync(new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText), abort);
- }
-
- private async Task CloseAsync(ShutdownEventArgs reason, bool abort)
- {
+ var reason = new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText);
var k = new ShutdownContinuation();
ChannelShutdown += k.OnConnectionShutdown;
try
{
ConsumerDispatcher.Quiesce();
+
if (SetCloseReason(reason))
{
_Private_ChannelClose(reason.ReplyCode, reason.ReplyText, 0, 0);
}
k.Wait(TimeSpan.FromMilliseconds(10000));
+ ConsumerDispatcher.WaitForShutdownAsync().ConfigureAwait(false);
+ }
+ catch (AlreadyClosedException)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ catch (IOException)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ catch (Exception)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ finally
+ {
+ ChannelShutdown -= k.OnConnectionShutdown;
+ }
+ }
+
+ public async ValueTask CloseAsync(ShutdownEventArgs reason, bool abort)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ using var k = new ChannelCloseAsyncRpcContinuation(ContinuationTimeout);
+ try
+ {
+ ChannelShutdown += k.OnConnectionShutdown;
+ Enqueue(k);
+ ConsumerDispatcher.Quiesce();
+
+ if (SetCloseReason(reason))
+ {
+ var method = new ChannelClose(reason.ReplyCode, reason.ReplyText, reason.ClassId, reason.MethodId);
+ await ModelSendAsync(method).ConfigureAwait(false);
+ }
+
+ bool result = await k;
+ Debug.Assert(result);
+
await ConsumerDispatcher.WaitForShutdownAsync().ConfigureAwait(false);
}
catch (AlreadyClosedException)
@@ -235,6 +271,7 @@ private async Task CloseAsync(ShutdownEventArgs reason, bool abort)
}
finally
{
+ _rpcSemaphore.Release();
ChannelShutdown -= k.OnConnectionShutdown;
}
}
@@ -246,11 +283,12 @@ internal async ValueTask ConnectionOpenAsync(string virtualHost)
internal async ValueTask ConnectionSecureOkAsync(byte[] response)
{
- var k = new ConnectionSecureOrTuneContinuation();
await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
- Enqueue(k);
try
{
+ using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout);
+ Enqueue(k);
+
try
{
_Private_ConnectionSecureOk(response);
@@ -273,13 +311,16 @@ internal async ValueTask ConnectionSecureOkAsync(byte[]
internal async ValueTask ConnectionStartOkAsync(IDictionary clientProperties, string mechanism, byte[] response,
string locale)
{
- var k = new ConnectionSecureOrTuneContinuation();
await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
- Enqueue(k);
try
{
+ using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout);
+ Enqueue(k);
+
try
{
+ // TODO LRB rabbitmq/rabbitmq-dotnet-client#1347
+ // Not yet async
_Private_ConnectionStartOk(clientProperties, mechanism, response, locale);
}
catch (AlreadyClosedException)
@@ -311,6 +352,27 @@ protected void Enqueue(IRpcContinuation k)
}
}
+ internal async ValueTask OpenAsync()
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ using var k = new ChannelOpenAsyncRpcContinuation(ContinuationTimeout);
+ try
+ {
+ Enqueue(k);
+
+ var method = new ChannelOpen();
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return this;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
internal void FinishClose()
{
var reason = CloseReason;
@@ -335,54 +397,57 @@ protected void ChannelRpc(in TMethod method, ProtocolCommandId returnCo
where TMethod : struct, IOutgoingAmqpMethod
{
var k = new SimpleBlockingRpcContinuation();
- IncomingCommand reply;
+ IncomingCommand reply = default;
_rpcSemaphore.Wait();
try
{
Enqueue(k);
Session.Transmit(in method);
k.GetReply(ContinuationTimeout, out reply);
+
+ if (reply.CommandId != returnCommandId)
+ {
+ throw new UnexpectedMethodException(reply.CommandId, returnCommandId);
+ }
}
finally
{
+ reply.ReturnBuffers();
_rpcSemaphore.Release();
}
-
- reply.ReturnMethodBuffer();
-
- if (reply.CommandId != returnCommandId)
- {
- throw new UnexpectedMethodException(reply.CommandId, returnCommandId);
- }
}
- protected TReturn ChannelRpc(in TMethod method, ProtocolCommandId returnCommandId, Func, TReturn> createFunc)
+ protected TReturn ChannelRpc(in TMethod method, ProtocolCommandId returnCommandId, Func createFunc)
where TMethod : struct, IOutgoingAmqpMethod
{
- var k = new SimpleBlockingRpcContinuation();
- IncomingCommand reply;
-
- _rpcSemaphore.Wait();
+ IncomingCommand reply = default;
try
{
- Enqueue(k);
- Session.Transmit(in method);
- k.GetReply(ContinuationTimeout, out reply);
+ var k = new SimpleBlockingRpcContinuation();
+
+ _rpcSemaphore.Wait();
+ try
+ {
+ Enqueue(k);
+ Session.Transmit(in method);
+ k.GetReply(ContinuationTimeout, out reply);
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+
+ if (reply.CommandId != returnCommandId)
+ {
+ throw new UnexpectedMethodException(reply.CommandId, returnCommandId);
+ }
+
+ return createFunc(reply.Method);
}
finally
{
- _rpcSemaphore.Release();
- }
-
- if (reply.CommandId != returnCommandId)
- {
- reply.ReturnMethodBuffer();
- throw new UnexpectedMethodException(reply.CommandId, returnCommandId);
+ reply.ReturnBuffers();
}
-
- var returnValue = createFunc(reply.MethodBytes);
- reply.ReturnMethodBuffer();
- return returnValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -468,7 +533,8 @@ private void OnSessionShutdown(object sender, ShutdownEventArgs reason)
internal bool SetCloseReason(ShutdownEventArgs reason)
{
- return System.Threading.Interlocked.CompareExchange(ref _closeReason, reason, null) is null;
+ // NB: this ensures that Close is only called once on a channel
+ return Interlocked.CompareExchange(ref _closeReason, reason, null) is null;
}
public override string ToString()
@@ -485,6 +551,7 @@ protected virtual void Dispose(bool disposing)
{
// dispose managed resources
this.Abort();
+ _rpcSemaphore.Dispose();
}
// dispose unmanaged resources
@@ -494,37 +561,49 @@ protected virtual void Dispose(bool disposing)
protected void HandleBasicAck(in IncomingCommand cmd)
{
- var ack = new BasicAck(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- if (!_basicAcksWrapper.IsEmpty)
+ try
{
- var args = new BasicAckEventArgs
+ var ack = new BasicAck(cmd.MethodSpan);
+ if (!_basicAcksWrapper.IsEmpty)
{
- DeliveryTag = ack._deliveryTag,
- Multiple = ack._multiple
- };
- _basicAcksWrapper.Invoke(this, args);
- }
+ var args = new BasicAckEventArgs
+ {
+ DeliveryTag = ack._deliveryTag,
+ Multiple = ack._multiple
+ };
+ _basicAcksWrapper.Invoke(this, args);
+ }
- HandleAckNack(ack._deliveryTag, ack._multiple, false);
+ HandleAckNack(ack._deliveryTag, ack._multiple, false);
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected void HandleBasicNack(in IncomingCommand cmd)
{
- var nack = new BasicNack(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- if (!_basicNacksWrapper.IsEmpty)
+ try
{
- var args = new BasicNackEventArgs
+ var nack = new BasicNack(cmd.MethodSpan);
+ if (!_basicNacksWrapper.IsEmpty)
{
- DeliveryTag = nack._deliveryTag,
- Multiple = nack._multiple,
- Requeue = nack._requeue
- };
- _basicNacksWrapper.Invoke(this, args);
- }
+ var args = new BasicNackEventArgs
+ {
+ DeliveryTag = nack._deliveryTag,
+ Multiple = nack._multiple,
+ Requeue = nack._requeue
+ };
+ _basicNacksWrapper.Invoke(this, args);
+ }
- HandleAckNack(nack._deliveryTag, nack._multiple, true);
+ HandleAckNack(nack._deliveryTag, nack._multiple, true);
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected void HandleAckNack(ulong deliveryTag, bool multiple, bool isNack)
@@ -574,234 +653,350 @@ protected void HandleAckNack(ulong deliveryTag, bool multiple, bool isNack)
protected void HandleBasicCancel(in IncomingCommand cmd)
{
- var consumerTag = new Client.Framing.Impl.BasicCancel(cmd.MethodBytes.Span)._consumerTag;
- cmd.ReturnMethodBuffer();
- ConsumerDispatcher.HandleBasicCancel(consumerTag);
+ try
+ {
+ var consumerTag = new Client.Framing.Impl.BasicCancel(cmd.MethodSpan)._consumerTag;
+ ConsumerDispatcher.HandleBasicCancel(consumerTag);
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
- protected void HandleBasicCancelOk(in IncomingCommand cmd)
+ protected bool HandleBasicCancelOk(in IncomingCommand cmd)
{
- var k = (BasicConsumerRpcContinuation)_continuationQueue.Next();
- var consumerTag = new Client.Framing.Impl.BasicCancelOk(cmd.MethodBytes.Span)._consumerTag;
- cmd.ReturnMethodBuffer();
- ConsumerDispatcher.HandleBasicCancelOk(consumerTag);
- k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ if (_continuationQueue.TryPeek(out var k))
+ {
+ try
+ {
+ _continuationQueue.Next();
+ string consumerTag = new Client.Framing.Impl.BasicCancelOk(cmd.MethodSpan)._consumerTag;
+ ConsumerDispatcher.HandleBasicCancelOk(consumerTag);
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ else
+ {
+ return false;
+ }
}
- protected void HandleBasicConsumeOk(in IncomingCommand cmd)
+ protected bool HandleBasicConsumeOk(in IncomingCommand cmd)
{
- var consumerTag = new Client.Framing.Impl.BasicConsumeOk(cmd.MethodBytes.Span)._consumerTag;
- cmd.ReturnMethodBuffer();
- var k = (BasicConsumerRpcContinuation)_continuationQueue.Next();
- k.m_consumerTag = consumerTag;
- ConsumerDispatcher.HandleBasicConsumeOk(k.m_consumer, consumerTag);
- k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ if (_continuationQueue.TryPeek(out var k))
+ {
+ try
+ {
+ _continuationQueue.Next();
+ var consumerTag = new Client.Framing.Impl.BasicConsumeOk(cmd.MethodSpan)._consumerTag;
+ k.m_consumerTag = consumerTag;
+ ConsumerDispatcher.HandleBasicConsumeOk(k.m_consumer, consumerTag);
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ else
+ {
+ return false;
+ }
}
protected void HandleBasicDeliver(in IncomingCommand cmd)
{
- var method = new Client.Framing.Impl.BasicDeliver(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- var header = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span);
- cmd.ReturnHeaderBuffer();
-
- ConsumerDispatcher.HandleBasicDeliver(
- method._consumerTag,
- AdjustDeliveryTag(method._deliveryTag),
- method._redelivered,
- method._exchange,
- method._routingKey,
- header,
- cmd.Body,
- cmd.TakeoverBody());
- }
-
- protected void HandleBasicGetOk(in IncomingCommand cmd)
- {
- var method = new BasicGetOk(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- var header = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span);
- cmd.ReturnHeaderBuffer();
-
- var k = (BasicGetRpcContinuation)_continuationQueue.Next();
- k.m_result = new BasicGetResult(
- AdjustDeliveryTag(method._deliveryTag),
- method._redelivered,
- method._exchange,
- method._routingKey,
- method._messageCount,
- header,
- cmd.Body,
- cmd.TakeoverBody());
- k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ try
+ {
+ var method = new Client.Framing.Impl.BasicDeliver(cmd.MethodSpan);
+ var header = new ReadOnlyBasicProperties(cmd.HeaderSpan);
+ ConsumerDispatcher.HandleBasicDeliver(
+ method._consumerTag,
+ AdjustDeliveryTag(method._deliveryTag),
+ method._redelivered,
+ method._exchange,
+ method._routingKey,
+ header,
+ cmd.Body);
+ }
+ finally
+ {
+ /*
+ * Note: do not return the Body as it is necessary for handling
+ * the Basic.Deliver method by client code
+ */
+ cmd.ReturnMethodAndHeaderBuffers();
+ }
}
- protected virtual ulong AdjustDeliveryTag(ulong deliveryTag)
+ protected bool HandleBasicGetOk(in IncomingCommand cmd)
{
- return deliveryTag;
+ if (_continuationQueue.TryPeek(out var k))
+ {
+ try
+ {
+ var method = new BasicGetOk(cmd.MethodSpan);
+ var header = new ReadOnlyBasicProperties(cmd.HeaderSpan);
+ _continuationQueue.Next();
+ k.m_result = new BasicGetResult(
+ AdjustDeliveryTag(method._deliveryTag),
+ method._redelivered,
+ method._exchange,
+ method._routingKey,
+ method._messageCount,
+ header,
+ cmd.Body);
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ /*
+ * Note: since the BasicGetResult ctor copies the rented array in cmd.Body,
+ * we want to return all buffers here
+ */
+ cmd.ReturnBuffers();
+ }
+ }
+ else
+ {
+ return false;
+ }
}
- protected void HandleBasicGetEmpty()
+ protected virtual ulong AdjustDeliveryTag(ulong deliveryTag)
{
- var k = (BasicGetRpcContinuation)_continuationQueue.Next();
- k.m_result = null;
- k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return deliveryTag;
}
- protected void HandleBasicRecoverOk()
+ protected void HandleBasicGetEmpty(in IncomingCommand cmd)
{
- var k = (SimpleBlockingRpcContinuation)_continuationQueue.Next();
- _basicRecoverOkWrapper.Invoke(this, EventArgs.Empty);
- k.HandleCommand(IncomingCommand.Empty);
+ try
+ {
+ var k = (BasicGetRpcContinuation)_continuationQueue.Next();
+ k.m_result = null;
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected void HandleBasicReturn(in IncomingCommand cmd)
{
- if (!_basicReturnWrapper.IsEmpty)
+ try
{
- var basicReturn = new BasicReturn(cmd.MethodBytes.Span);
- var e = new BasicReturnEventArgs
+ if (!_basicReturnWrapper.IsEmpty)
{
- ReplyCode = basicReturn._replyCode,
- ReplyText = basicReturn._replyText,
- Exchange = basicReturn._exchange,
- RoutingKey = basicReturn._routingKey,
- BasicProperties = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span),
- Body = cmd.Body
- };
- _basicReturnWrapper.Invoke(this, e);
+ var basicReturn = new BasicReturn(cmd.MethodSpan);
+ var e = new BasicReturnEventArgs(basicReturn._replyCode, basicReturn._replyText,
+ basicReturn._exchange, basicReturn._routingKey,
+ new ReadOnlyBasicProperties(cmd.HeaderSpan), cmd.Body);
+ _basicReturnWrapper.Invoke(this, e);
+ }
+ }
+ finally
+ {
+ /*
+ * Note: since the BasicReturnEventArgs ctor copies the rented array in cmd.Body,
+ * we want to return all buffers here
+ */
+ cmd.ReturnBuffers();
}
- cmd.ReturnMethodBuffer();
- cmd.ReturnHeaderBuffer();
- ArrayPool.Shared.Return(cmd.TakeoverBody());
}
protected void HandleChannelClose(in IncomingCommand cmd)
{
- var channelClose = new ChannelClose(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- SetCloseReason(new ShutdownEventArgs(ShutdownInitiator.Peer,
- channelClose._replyCode,
- channelClose._replyText,
- channelClose._classId,
- channelClose._methodId));
-
- Session.Close(CloseReason, false);
try
{
+ var channelClose = new ChannelClose(cmd.MethodSpan);
+ SetCloseReason(new ShutdownEventArgs(ShutdownInitiator.Peer,
+ channelClose._replyCode,
+ channelClose._replyText,
+ channelClose._classId,
+ channelClose._methodId));
+
+ Session.Close(CloseReason, false);
+
_Private_ChannelCloseOk();
}
finally
{
+ cmd.ReturnBuffers();
Session.Notify();
}
}
- protected void HandleChannelCloseOk()
+ protected void HandleChannelCloseOk(in IncomingCommand cmd)
{
- FinishClose();
+ try
+ {
+ /*
+ * Note:
+ * This call _must_ come before completing the async continuation
+ */
+ FinishClose();
+
+ if (_continuationQueue.TryPeek(out var k))
+ {
+ _continuationQueue.Next();
+ k.HandleCommand(cmd);
+ }
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected void HandleChannelFlow(in IncomingCommand cmd)
{
- var active = new ChannelFlow(cmd.MethodBytes.Span)._active;
- cmd.ReturnMethodBuffer();
- if (active)
- {
- _flowControlBlock.Set();
- }
- else
+ try
{
- _flowControlBlock.Reset();
- }
+ var active = new ChannelFlow(cmd.MethodSpan)._active;
+ if (active)
+ {
+ _flowControlBlock.Set();
+ }
+ else
+ {
+ _flowControlBlock.Reset();
+ }
- _Private_ChannelFlowOk(active);
+ _Private_ChannelFlowOk(active);
- if (!_flowControlWrapper.IsEmpty)
+ if (!_flowControlWrapper.IsEmpty)
+ {
+ _flowControlWrapper.Invoke(this, new FlowControlEventArgs(active));
+ }
+ }
+ finally
{
- _flowControlWrapper.Invoke(this, new FlowControlEventArgs(active));
+ cmd.ReturnBuffers();
}
}
protected void HandleConnectionBlocked(in IncomingCommand cmd)
{
- var reason = new ConnectionBlocked(cmd.MethodBytes.Span)._reason;
- cmd.ReturnMethodBuffer();
- Session.Connection.HandleConnectionBlocked(reason);
+ try
+ {
+ var reason = new ConnectionBlocked(cmd.MethodSpan)._reason;
+ Session.Connection.HandleConnectionBlocked(reason);
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected void HandleConnectionClose(in IncomingCommand cmd)
{
- var method = new ConnectionClose(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- var reason = new ShutdownEventArgs(ShutdownInitiator.Peer, method._replyCode, method._replyText, method._classId, method._methodId);
try
{
- Session.Connection.InternalClose(reason);
- _Private_ConnectionCloseOk();
- SetCloseReason(Session.Connection.CloseReason);
- }
- catch (IOException)
- {
- // Ignored. We're only trying to be polite by sending
- // the close-ok, after all.
+ var method = new ConnectionClose(cmd.MethodSpan);
+ var reason = new ShutdownEventArgs(ShutdownInitiator.Peer, method._replyCode, method._replyText, method._classId, method._methodId);
+ try
+ {
+ Session.Connection.InternalClose(reason);
+ _Private_ConnectionCloseOk();
+ SetCloseReason(Session.Connection.CloseReason);
+ }
+ catch (IOException)
+ {
+ // Ignored. We're only trying to be polite by sending
+ // the close-ok, after all.
+ }
+ catch (AlreadyClosedException)
+ {
+ // Ignored. We're only trying to be polite by sending
+ // the close-ok, after all.
+ }
}
- catch (AlreadyClosedException)
+ finally
{
- // Ignored. We're only trying to be polite by sending
- // the close-ok, after all.
+ cmd.ReturnBuffers();
}
}
protected void HandleConnectionSecure(in IncomingCommand cmd)
{
- var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
+ using var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
k.HandleCommand(IncomingCommand.Empty); // release the continuation.
}
protected void HandleConnectionStart(in IncomingCommand cmd)
{
- if (m_connectionStartCell is null)
+ try
{
- var reason = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.CommandInvalid, "Unexpected Connection.Start");
- Session.Connection.Close(reason, false, InternalConstants.DefaultConnectionCloseTimeout);
- }
+ if (m_connectionStartCell is null)
+ {
+ var reason = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.CommandInvalid, "Unexpected Connection.Start");
+ Session.Connection.Close(reason, false, InternalConstants.DefaultConnectionCloseTimeout);
+ }
- var method = new ConnectionStart(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- var details = new ConnectionStartDetails
+ var method = new ConnectionStart(cmd.MethodSpan);
+ var details = new ConnectionStartDetails
+ {
+ m_versionMajor = method._versionMajor,
+ m_versionMinor = method._versionMinor,
+ m_serverProperties = method._serverProperties,
+ m_mechanisms = method._mechanisms,
+ m_locales = method._locales
+ };
+ m_connectionStartCell?.SetResult(details);
+ m_connectionStartCell = null;
+ }
+ finally
{
- m_versionMajor = method._versionMajor,
- m_versionMinor = method._versionMinor,
- m_serverProperties = method._serverProperties,
- m_mechanisms = method._mechanisms,
- m_locales = method._locales
- };
- m_connectionStartCell?.SetResult(details);
- m_connectionStartCell = null;
+ cmd.ReturnBuffers();
+ }
}
protected void HandleConnectionTune(in IncomingCommand cmd)
{
- var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
- k.HandleCommand(cmd); // release the continuation.
+ using var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
+ /*
+ * Note: releases the continuation and returns the buffers
+ */
+ k.HandleCommand(cmd);
}
- protected void HandleConnectionUnblocked()
+ protected void HandleConnectionUnblocked(in IncomingCommand cmd)
{
- Session.Connection.HandleConnectionUnblocked();
+ try
+ {
+ Session.Connection.HandleConnectionUnblocked();
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
protected bool HandleQueueDeclareOk(in IncomingCommand cmd)
{
if (_continuationQueue.TryPeek(out var k))
{
- _continuationQueue.Next();
- var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodBytes.Span);
- cmd.ReturnMethodBuffer();
- k.m_result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
- k.HandleCommand(IncomingCommand.Empty); // release the continuation.
- return true;
+ try
+ {
+ _continuationQueue.Next();
+ var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodSpan);
+ k.m_result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
}
else
{
@@ -815,8 +1010,6 @@ protected bool HandleQueueDeclareOk(in IncomingCommand cmd)
public abstract void _Private_BasicGet(string queue, bool autoAck);
- public abstract void _Private_BasicRecover(bool requeue);
-
public abstract void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId);
public abstract void _Private_ChannelCloseOk();
@@ -857,10 +1050,11 @@ protected bool HandleQueueDeclareOk(in IncomingCommand cmd)
public abstract void BasicAck(ulong deliveryTag, bool multiple);
+ public abstract ValueTask BasicAckAsync(ulong deliveryTag, bool multiple);
+
public void BasicCancel(string consumerTag)
{
- var k = new BasicConsumerRpcContinuation { m_consumerTag = consumerTag };
-
+ var k = new BasicConsumeRpcContinuation { m_consumerTag = consumerTag };
_rpcSemaphore.Wait();
try
{
@@ -874,13 +1068,35 @@ public void BasicCancel(string consumerTag)
}
}
- public void BasicCancelNoWait(string consumerTag)
+ public async ValueTask BasicCancelAsync(string consumerTag)
{
- _Private_BasicCancel(consumerTag, true);
- ConsumerDispatcher.GetAndRemoveConsumer(consumerTag);
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new BasicCancelAsyncRpcContinuation(consumerTag, ConsumerDispatcher, ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new Client.Framing.Impl.BasicCancel(consumerTag, false);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
+ public void BasicCancelNoWait(string consumerTag)
+ {
+ _Private_BasicCancel(consumerTag, true);
+ ConsumerDispatcher.GetAndRemoveConsumer(consumerTag);
}
- public string BasicConsume(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive, IDictionary arguments, IBasicConsumer consumer)
+ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive,
+ IDictionary arguments, IBasicConsumer consumer)
{
// TODO: Replace with flag
if (ConsumerDispatcher is AsyncConsumerDispatcher)
@@ -892,7 +1108,7 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
}
}
- var k = new BasicConsumerRpcContinuation { m_consumer = consumer };
+ var k = new BasicConsumeRpcContinuation { m_consumer = consumer };
_rpcSemaphore.Wait();
try
@@ -900,8 +1116,7 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
Enqueue(k);
// Non-nowait. We have an unconventional means of getting
// the RPC response, but a response is still expected.
- _Private_BasicConsume(queue, consumerTag, noLocal, autoAck, exclusive,
- /*nowait:*/ false, arguments);
+ _Private_BasicConsume(queue, consumerTag, noLocal, autoAck, exclusive, false, arguments);
k.GetReply(ContinuationTimeout);
}
finally
@@ -914,6 +1129,35 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
return actualConsumerTag;
}
+ public async ValueTask BasicConsumeAsync(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive,
+ IDictionary arguments, IBasicConsumer consumer)
+ {
+ if (ConsumerDispatcher is AsyncConsumerDispatcher)
+ {
+ if (!(consumer is IAsyncBasicConsumer))
+ {
+ // TODO: Friendly message
+ throw new InvalidOperationException("In the async mode you have to use an async consumer");
+ }
+ }
+
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new BasicConsumeAsyncRpcContinuation(consumer, ConsumerDispatcher, ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new Client.Framing.Impl.BasicConsume(queue, consumerTag, noLocal, autoAck, exclusive, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ return await k;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public BasicGetResult BasicGet(string queue, bool autoAck)
{
var k = new BasicGetRpcContinuation();
@@ -933,8 +1177,29 @@ public BasicGetResult BasicGet(string queue, bool autoAck)
return k.m_result;
}
+ public async ValueTask BasicGetAsync(string queue, bool autoAck)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new BasicGetAsyncRpcContinuation(AdjustDeliveryTag, ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new BasicGet(queue, autoAck);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ return await k;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public abstract void BasicNack(ulong deliveryTag, bool multiple, bool requeue);
+ public abstract ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue);
+
public void BasicPublish(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory body, bool mandatory)
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
{
@@ -1012,16 +1277,20 @@ public void UpdateSecret(string newSecret, string reason)
public abstract void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
- public void BasicRecover(bool requeue)
+ public async ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global)
{
- var k = new SimpleBlockingRpcContinuation();
-
- _rpcSemaphore.Wait();
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
try
{
+ using var k = new BasicQosAsyncRpcContinuation(ContinuationTimeout);
Enqueue(k);
- _Private_BasicRecover(requeue);
- k.GetReply(ContinuationTimeout);
+
+ var method = new BasicQos(prefetchSize, prefetchCount, global);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
}
finally
{
@@ -1029,10 +1298,10 @@ public void BasicRecover(bool requeue)
}
}
- public abstract void BasicRecoverAsync(bool requeue);
-
public abstract void BasicReject(ulong deliveryTag, bool requeue);
+ public abstract ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue);
+
public void ConfirmSelect()
{
if (NextPublishSeqNo == 0UL)
@@ -1044,11 +1313,60 @@ public void ConfirmSelect()
_Private_ConfirmSelect(false);
}
+ public async ValueTask ConfirmSelectAsync()
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ if (NextPublishSeqNo == 0UL)
+ {
+ _confirmsTaskCompletionSources = new List>();
+ NextPublishSeqNo = 1;
+ }
+
+ using var k = new ConfirmSelectAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new ConfirmSelect(false);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments)
{
_Private_ExchangeBind(destination, source, routingKey, false, arguments);
}
+ public async ValueTask ExchangeBindAsync(string destination, string source, string routingKey, IDictionary arguments)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new ExchangeBindAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new ExchangeBind(destination, source, routingKey, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void ExchangeBindNoWait(string destination, string source, string routingKey, IDictionary arguments)
{
_Private_ExchangeBind(destination, source, routingKey, true, arguments);
@@ -1059,6 +1377,27 @@ public void ExchangeDeclare(string exchange, string type, bool durable, bool aut
_Private_ExchangeDeclare(exchange, type, false, durable, autoDelete, false, false, arguments);
}
+ public async ValueTask ExchangeDeclareAsync(string exchange, string type, bool passive, bool durable, bool autoDelete, IDictionary arguments)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new ExchangeDeclareAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new ExchangeDeclare(exchange, type, passive, durable, autoDelete, false, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments)
{
_Private_ExchangeDeclare(exchange, type, false, durable, autoDelete, false, true, arguments);
@@ -1074,6 +1413,27 @@ public void ExchangeDelete(string exchange, bool ifUnused)
_Private_ExchangeDelete(exchange, ifUnused, false);
}
+ public async ValueTask ExchangeDeleteAsync(string exchange, bool ifUnused)
+ {
+ using var k = new ExchangeDeleteAsyncRpcContinuation(ContinuationTimeout);
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ Enqueue(k);
+
+ var method = new ExchangeDelete(exchange, ifUnused, Nowait: false);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void ExchangeDeleteNoWait(string exchange, bool ifUnused)
{
_Private_ExchangeDelete(exchange, ifUnused, true);
@@ -1084,6 +1444,27 @@ public void ExchangeUnbind(string destination, string source, string routingKey,
_Private_ExchangeUnbind(destination, source, routingKey, false, arguments);
}
+ public async ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new ExchangeUnbindAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new ExchangeUnbind(destination, source, routingKey, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void ExchangeUnbindNoWait(string destination, string source, string routingKey, IDictionary arguments)
{
_Private_ExchangeUnbind(destination, source, routingKey, true, arguments);
@@ -1101,12 +1482,52 @@ public void QueueBindNoWait(string queue, string exchange, string routingKey, ID
public QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
- return QueueDeclare(queue, false, durable, exclusive, autoDelete, arguments);
+ return DoQueueDeclare(queue, false, durable, exclusive, autoDelete, arguments);
+ }
+
+ public async ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new QueueDeclareAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ QueueDeclareOk result = await k;
+ if (false == passive)
+ {
+ CurrentQueue = result.QueueName;
+ }
+ return result;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
}
- public ValueTask QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
+ public async ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments)
{
- return QueueDeclareAsync(queue, false, durable, exclusive, autoDelete, arguments);
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new QueueBindAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new QueueBind(queue, exchange, routingKey, false, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
}
public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
@@ -1116,7 +1537,7 @@ public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool
public QueueDeclareOk QueueDeclarePassive(string queue)
{
- return QueueDeclare(queue, true, false, false, false, null);
+ return DoQueueDeclare(queue, true, false, false, false, null);
}
public uint MessageCount(string queue)
@@ -1136,6 +1557,25 @@ public uint QueueDelete(string queue, bool ifUnused, bool ifEmpty)
return _Private_QueueDelete(queue, ifUnused, ifEmpty, false);
}
+ public async ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ var k = new QueueDeleteAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new QueueDelete(queue, ifUnused, ifEmpty, false);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ return await k;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty)
{
_Private_QueueDelete(queue, ifUnused, ifEmpty, true);
@@ -1146,14 +1586,117 @@ public uint QueuePurge(string queue)
return _Private_QueuePurge(queue, false);
}
+ public async ValueTask QueuePurgeAsync(string queue)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ var k = new QueuePurgeAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new QueuePurge(queue, false);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ return await k;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public abstract void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments);
+ public async ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments)
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new QueueUnbindAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new QueueUnbind(queue, exchange, routingKey, arguments);
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public abstract void TxCommit();
+ public async ValueTask TxCommitAsync()
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new TxCommitAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new TxCommit();
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public abstract void TxRollback();
+ public async ValueTask TxRollbackAsync()
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new TxRollbackAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new TxRollback();
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
public abstract void TxSelect();
+ public async ValueTask TxSelectAsync()
+ {
+ await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ using var k = new TxSelectAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
+ var method = new TxSelect();
+ await ModelSendAsync(method).ConfigureAwait(false);
+
+ bool result = await k;
+ Debug.Assert(result);
+ return;
+ }
+ finally
+ {
+ _rpcSemaphore.Release();
+ }
+ }
+
private List> _confirmsTaskCompletionSources;
public Task WaitForConfirmsAsync(CancellationToken token = default)
@@ -1227,17 +1770,20 @@ await CloseAsync(
new IOException("nack received")),
false).ConfigureAwait(false);
}
- catch (TaskCanceledException exception)
+ catch (TaskCanceledException)
{
+ const string msg = "timed out waiting for acks";
+ var ex = new IOException(msg);
await CloseAsync(new ShutdownEventArgs(ShutdownInitiator.Library,
Constants.ReplySuccess,
- "Timed out waiting for acks",
- exception),
+ msg,
+ ex),
false).ConfigureAwait(false);
+ throw ex;
}
}
- private QueueDeclareOk QueueDeclare(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
+ private QueueDeclareOk DoQueueDeclare(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
var k = new QueueDeclareRpcContinuation();
@@ -1257,71 +1803,5 @@ private QueueDeclareOk QueueDeclare(string queue, bool passive, bool durable, bo
CurrentQueue = result.QueueName;
return result;
}
-
- private async ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
- {
- var k = new QueueDeclareAsyncRpcContinuation();
- await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
- try
- {
- Enqueue(k);
-
- var method = new QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments);
- await ModelSendAsync(method).ConfigureAwait(false);
-
- QueueDeclareOk result = await k;
- CurrentQueue = result.QueueName;
- return result;
- }
- finally
- {
- _rpcSemaphore.Release();
- }
- }
-
- public class BasicConsumerRpcContinuation : SimpleBlockingRpcContinuation
- {
- public IBasicConsumer m_consumer;
- public string m_consumerTag;
- }
-
- public class BasicGetRpcContinuation : SimpleBlockingRpcContinuation
- {
- public BasicGetResult m_result;
- }
-
- public class ConnectionStartRpcContinuation : SimpleBlockingRpcContinuation
- {
- public ConnectionSecureOrTune m_result;
- }
-
- public class QueueDeclareRpcContinuation : SimpleBlockingRpcContinuation
- {
- public QueueDeclareOk m_result;
- }
-
- public class QueueDeclareAsyncRpcContinuation : AsyncRpcContinuation
- {
- public override void HandleCommand(in IncomingCommand cmd)
- {
- try
- {
- var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodBytes.Span);
- var result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
- if (cmd.CommandId == ProtocolCommandId.QueueDeclareOk)
- {
- _tcs.TrySetResult(result);
- }
- else
- {
- _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
- }
- }
- finally
- {
- cmd.ReturnMethodBuffer();
- }
- }
- }
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs b/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs
index d03708232c..86b381087f 100644
--- a/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs
+++ b/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs
@@ -30,7 +30,6 @@
//---------------------------------------------------------------------------
using System;
-using System.Buffers;
using RabbitMQ.Client.client.framing;
using RabbitMQ.Client.Exceptions;
using RabbitMQ.Client.Framing.Impl;
@@ -45,13 +44,13 @@ internal sealed class CommandAssembler
private const int MaxArrayOfBytesSize = 2_147_483_591;
private ProtocolCommandId _commandId;
- private ReadOnlyMemory _methodBytes;
+ private ReadOnlyMemory _methodMemory;
private byte[]? _rentedMethodArray;
- private ReadOnlyMemory _headerBytes;
+ private ReadOnlyMemory _headerMemory;
private byte[]? _rentedHeaderArray;
- private ReadOnlyMemory _bodyBytes;
+ private ReadOnlyMemory _bodyMemory;
private byte[]? _rentedBodyArray;
- private int _remainingBodyBytes;
+ private int _remainingBodyByteCount;
private int _offset;
private AssemblyState _state;
@@ -63,13 +62,13 @@ public CommandAssembler()
private void Reset()
{
_commandId = default;
- _methodBytes = ReadOnlyMemory.Empty;
+ _methodMemory = ReadOnlyMemory.Empty;
_rentedMethodArray = null;
- _headerBytes = ReadOnlyMemory.Empty;
+ _headerMemory = ReadOnlyMemory.Empty;
_rentedHeaderArray = null;
- _bodyBytes = ReadOnlyMemory.Empty;
+ _bodyMemory = ReadOnlyMemory.Empty;
_rentedBodyArray = null;
- _remainingBodyBytes = 0;
+ _remainingBodyByteCount = 0;
_offset = 0;
_state = AssemblyState.ExpectingMethod;
}
@@ -98,7 +97,12 @@ public bool HandleFrame(in InboundFrame frame, out IncomingCommand command)
}
RabbitMqClientEventSource.Log.CommandReceived();
- command = new IncomingCommand(_commandId, _methodBytes, _rentedMethodArray, _headerBytes, _rentedHeaderArray, _bodyBytes, _rentedBodyArray);
+
+ var method = new RentedMemory(_methodMemory, _rentedMethodArray);
+ var header = new RentedMemory(_headerMemory, _rentedHeaderArray);
+ var body = new RentedMemory(_bodyMemory, _rentedBodyArray);
+
+ command = new IncomingCommand(_commandId, method, header, body);
Reset();
return shallReturn;
}
@@ -112,7 +116,7 @@ private void ParseMethodFrame(in InboundFrame frame)
_rentedMethodArray = frame.TakeoverPayload();
_commandId = (ProtocolCommandId)NetworkOrderDeserializer.ReadUInt32(frame.Payload.Span);
- _methodBytes = frame.Payload.Slice(4);
+ _methodMemory = frame.Payload.Slice(4);
switch (_commandId)
{
@@ -149,9 +153,9 @@ private bool ParseHeaderFrame(in InboundFrame frame)
}
_rentedHeaderArray = totalBodyBytes != 0 ? frame.TakeoverPayload() : Array.Empty();
- _headerBytes = frame.Payload.Slice(12);
+ _headerMemory = frame.Payload.Slice(12);
- _remainingBodyBytes = (int)totalBodyBytes;
+ _remainingBodyByteCount = (int)totalBodyBytes;
UpdateContentBodyState();
return _rentedHeaderArray.Length == 0;
}
@@ -164,29 +168,29 @@ private bool ParseBodyFrame(in InboundFrame frame)
}
int payloadLength = frame.Payload.Length;
- if (payloadLength > _remainingBodyBytes)
+ if (payloadLength > _remainingBodyByteCount)
{
- throw new MalformedFrameException($"Overlong content body received - {_remainingBodyBytes} bytes remaining, {payloadLength} bytes received");
+ throw new MalformedFrameException($"Overlong content body received - {_remainingBodyByteCount} bytes remaining, {payloadLength} bytes received");
}
if (_rentedBodyArray is null)
{
// check for single frame payload for an early exit
- if (payloadLength == _remainingBodyBytes)
+ if (payloadLength == _remainingBodyByteCount)
{
_rentedBodyArray = frame.TakeoverPayload();
- _bodyBytes = frame.Payload;
+ _bodyMemory = frame.Payload;
_state = AssemblyState.Complete;
return false;
}
// Is returned by IncomingCommand.ReturnPayload in Session.HandleFrame
- _rentedBodyArray = ArrayPool.Shared.Rent(_remainingBodyBytes);
- _bodyBytes = new ReadOnlyMemory(_rentedBodyArray, 0, _remainingBodyBytes);
+ _rentedBodyArray = ClientArrayPool.Rent(_remainingBodyByteCount);
+ _bodyMemory = new ReadOnlyMemory(_rentedBodyArray, 0, _remainingBodyByteCount);
}
frame.Payload.Span.CopyTo(_rentedBodyArray.AsSpan(_offset));
- _remainingBodyBytes -= payloadLength;
+ _remainingBodyByteCount -= payloadLength;
_offset += payloadLength;
UpdateContentBodyState();
return true;
@@ -194,7 +198,7 @@ private bool ParseBodyFrame(in InboundFrame frame)
private void UpdateContentBodyState()
{
- _state = _remainingBodyBytes > 0 ? AssemblyState.ExpectingContentBody : AssemblyState.Complete;
+ _state = _remainingBodyByteCount > 0 ? AssemblyState.ExpectingContentBody : AssemblyState.Complete;
}
private enum AssemblyState
diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
index ea561d6986..a753e251b7 100644
--- a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
+++ b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
@@ -36,7 +36,6 @@
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using RabbitMQ.Client.Impl;
-using RabbitMQ.Client.Logging;
namespace RabbitMQ.Client.Framing.Impl
{
@@ -70,13 +69,6 @@ internal void HandleConnectionUnblocked()
}
}
- private async ValueTask OpenAsync()
- {
- RabbitMqClientEventSource.Log.ConnectionOpened();
- await StartAndTuneAsync().ConfigureAwait(false);
- await _channel0.ConnectionOpenAsync(_config.VirtualHost);
- }
-
private async ValueTask StartAndTuneAsync()
{
var connectionStartCell = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs
index 52bb68d2ec..bbfeda1be9 100644
--- a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs
+++ b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs
@@ -144,6 +144,8 @@ private void HandleMainLoopException(ShutdownEventArgs reason)
{
if (!SetCloseReason(reason))
{
+ // TODO LRB
+ // reason.Cause could be an Exception, should we use that?
LogCloseError("Unexpected Main Loop Exception while closing: " + reason, new Exception(reason.ToString()));
return;
}
diff --git a/projects/RabbitMQ.Client/client/impl/Connection.cs b/projects/RabbitMQ.Client/client/impl/Connection.cs
index dd3a6cdf45..423fe987bd 100644
--- a/projects/RabbitMQ.Client/client/impl/Connection.cs
+++ b/projects/RabbitMQ.Client/client/impl/Connection.cs
@@ -58,7 +58,7 @@ internal sealed partial class Connection : IConnection
private ShutdownEventArgs? _closeReason;
public ShutdownEventArgs? CloseReason => Volatile.Read(ref _closeReason);
- public Connection(ConnectionConfig config, IFrameHandler frameHandler)
+ internal Connection(ConnectionConfig config, IFrameHandler frameHandler)
{
_config = config;
_frameHandler = frameHandler;
@@ -78,23 +78,7 @@ public Connection(ConnectionConfig config, IFrameHandler frameHandler)
["capabilities"] = Protocol.Capabilities,
["connection_name"] = ClientProvidedName
};
-
_mainLoopTask = Task.Run(MainLoop);
- try
- {
- /*
- * TODO FUTURE
- * Connection should not happen in ctor, instead change
- * the API so that it's awaitable
- */
- OpenAsync().AsTask().GetAwaiter().GetResult();
- }
- catch
- {
- var ea = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.InternalError, "FailedOpen");
- Close(ea, true, TimeSpan.FromSeconds(5));
- throw;
- }
}
public Guid Id => _id;
@@ -227,6 +211,29 @@ internal void TakeOver(Connection other)
_connectionShutdownWrapper.Takeover(other._connectionShutdownWrapper);
}
+ internal IConnection Open()
+ {
+ return OpenAsync().GetAwaiter().GetResult();
+ }
+
+ internal async ValueTask OpenAsync()
+ {
+ try
+ {
+ RabbitMqClientEventSource.Log.ConnectionOpened();
+ await StartAndTuneAsync().ConfigureAwait(false);
+ await _channel0.ConnectionOpenAsync(_config.VirtualHost);
+ return this;
+ }
+ catch
+ {
+ var ea = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.InternalError, "FailedOpen");
+ // TODO CloseAsync
+ Close(ea, true, TimeSpan.FromSeconds(5));
+ throw;
+ }
+ }
+
public IChannel CreateChannel()
{
EnsureIsOpen();
@@ -236,6 +243,14 @@ public IChannel CreateChannel()
return channel;
}
+ public ValueTask CreateChannelAsync()
+ {
+ EnsureIsOpen();
+ ISession session = CreateSession();
+ var channel = new Channel(_config, session);
+ return channel.OpenAsync();
+ }
+
internal ISession CreateSession()
{
return _sessionManager.Create();
@@ -306,13 +321,11 @@ internal void Close(ShutdownEventArgs reason, bool abort, TimeSpan timeout)
throw;
}
}
-#pragma warning disable 0168
- catch (NotSupportedException nse)
+ catch (NotSupportedException)
{
// buffered stream had unread data in it and Flush()
// was called, ignore to not confuse the user
}
-#pragma warning restore 0168
catch (IOException ioe)
{
if (_channel0.CloseReason is null)
@@ -405,18 +418,18 @@ internal void OnCallbackException(CallbackExceptionEventArgs args)
_callbackExceptionWrapper.Invoke(this, args);
}
- internal void Write(ReadOnlyMemory memory)
+ internal void Write(RentedMemory frames)
{
- var task = _frameHandler.WriteAsync(memory);
+ var task = _frameHandler.WriteAsync(frames);
if (!task.IsCompletedSuccessfully)
{
task.AsTask().GetAwaiter().GetResult();
}
}
- internal ValueTask WriteAsync(ReadOnlyMemory memory)
+ internal ValueTask WriteAsync(RentedMemory frames)
{
- return _frameHandler.WriteAsync(memory);
+ return _frameHandler.WriteAsync(frames);
}
public void Dispose()
diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/AsyncConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/AsyncConsumerDispatcher.cs
index b1f80582dc..0f24800423 100644
--- a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/AsyncConsumerDispatcher.cs
+++ b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/AsyncConsumerDispatcher.cs
@@ -1,5 +1,4 @@
using System;
-using System.Buffers;
using System.Threading.Tasks;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Impl;
@@ -18,30 +17,33 @@ protected override async Task ProcessChannelAsync()
{
while (await _reader.WaitToReadAsync().ConfigureAwait(false))
{
- while (_reader.TryRead(out var work))
+ while (_reader.TryRead(out WorkStruct work))
{
- try
+ using (work)
{
- var task = work.WorkType switch
+ try
{
- WorkType.Deliver => work.AsyncConsumer.HandleBasicDeliver(work.ConsumerTag, work.DeliveryTag, work.Redelivered, work.Exchange, work.RoutingKey, work.BasicProperties, work.Body),
- WorkType.Cancel => work.AsyncConsumer.HandleBasicCancel(work.ConsumerTag),
- WorkType.CancelOk => work.AsyncConsumer.HandleBasicCancelOk(work.ConsumerTag),
- WorkType.ConsumeOk => work.AsyncConsumer.HandleBasicConsumeOk(work.ConsumerTag),
- WorkType.Shutdown => work.AsyncConsumer.HandleChannelShutdown(_channel, work.Reason),
- _ => Task.CompletedTask
- };
- await task.ConfigureAwait(false);
- }
- catch (Exception e)
- {
- _channel.OnCallbackException(CallbackExceptionEventArgs.Build(e, work.WorkType.ToString(), work.Consumer));
- }
- finally
- {
- if (work.RentedArray != null)
+ Task task = work.WorkType switch
+ {
+ WorkType.Deliver => work.AsyncConsumer.HandleBasicDeliver(
+ work.ConsumerTag, work.DeliveryTag, work.Redelivered,
+ work.Exchange, work.RoutingKey, work.BasicProperties, work.Body),
+
+ WorkType.Cancel => work.AsyncConsumer.HandleBasicCancel(work.ConsumerTag),
+
+ WorkType.CancelOk => work.AsyncConsumer.HandleBasicCancelOk(work.ConsumerTag),
+
+ WorkType.ConsumeOk => work.AsyncConsumer.HandleBasicConsumeOk(work.ConsumerTag),
+
+ WorkType.Shutdown => work.AsyncConsumer.HandleChannelShutdown(_channel, work.Reason),
+
+ _ => Task.CompletedTask
+ };
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception e)
{
- ArrayPool.Shared.Return(work.RentedArray);
+ _channel.OnCallbackException(CallbackExceptionEventArgs.Build(e, work.WorkType.ToString(), work.Consumer));
}
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcher.cs
index 09aa5cc627..164d5d9e76 100644
--- a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcher.cs
+++ b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcher.cs
@@ -1,5 +1,4 @@
using System;
-using System.Buffers;
using System.Threading.Tasks;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Impl;
@@ -20,38 +19,36 @@ protected override async Task ProcessChannelAsync()
{
while (_reader.TryRead(out var work))
{
- try
+ using (work)
{
- var consumer = work.Consumer;
- var consumerTag = work.ConsumerTag;
- switch (work.WorkType)
+ try
{
- case WorkType.Deliver:
- consumer.HandleBasicDeliver(consumerTag, work.DeliveryTag, work.Redelivered, work.Exchange, work.RoutingKey, work.BasicProperties, work.Body);
- break;
- case WorkType.Cancel:
- consumer.HandleBasicCancel(consumerTag);
- break;
- case WorkType.CancelOk:
- consumer.HandleBasicCancelOk(consumerTag);
- break;
- case WorkType.ConsumeOk:
- consumer.HandleBasicConsumeOk(consumerTag);
- break;
- case WorkType.Shutdown:
- consumer.HandleChannelShutdown(_channel, work.Reason);
- break;
+ var consumer = work.Consumer;
+ var consumerTag = work.ConsumerTag;
+ switch (work.WorkType)
+ {
+ case WorkType.Deliver:
+ consumer.HandleBasicDeliver(
+ consumerTag, work.DeliveryTag, work.Redelivered,
+ work.Exchange, work.RoutingKey, work.BasicProperties, work.Body);
+ break;
+ case WorkType.Cancel:
+ consumer.HandleBasicCancel(consumerTag);
+ break;
+ case WorkType.CancelOk:
+ consumer.HandleBasicCancelOk(consumerTag);
+ break;
+ case WorkType.ConsumeOk:
+ consumer.HandleBasicConsumeOk(consumerTag);
+ break;
+ case WorkType.Shutdown:
+ consumer.HandleChannelShutdown(_channel, work.Reason);
+ break;
+ }
}
- }
- catch (Exception e)
- {
- _channel.OnCallbackException(CallbackExceptionEventArgs.Build(e, work.WorkType.ToString(), work.Consumer));
- }
- finally
- {
- if (work.RentedArray != null)
+ catch (Exception e)
{
- ArrayPool.Shared.Return(work.RentedArray);
+ _channel.OnCallbackException(CallbackExceptionEventArgs.Build(e, work.WorkType.ToString(), work.Consumer));
}
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcherChannelBase.cs b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcherChannelBase.cs
index f53218e6bd..648480473c 100644
--- a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcherChannelBase.cs
+++ b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/ConsumerDispatcherChannelBase.cs
@@ -54,11 +54,11 @@ public void HandleBasicConsumeOk(IBasicConsumer consumer, string consumerTag)
}
public void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered,
- string exchange, string routingKey, in ReadOnlyBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray)
+ string exchange, string routingKey, in ReadOnlyBasicProperties basicProperties, RentedMemory body)
{
if (!IsShutdown)
{
- _writer.TryWrite(new WorkStruct(GetConsumerOrDefault(consumerTag), consumerTag, deliveryTag, redelivered, exchange, routingKey, basicProperties, body, rentedArray));
+ _writer.TryWrite(new WorkStruct(GetConsumerOrDefault(consumerTag), consumerTag, deliveryTag, redelivered, exchange, routingKey, basicProperties, body));
}
}
@@ -101,7 +101,7 @@ public Task WaitForShutdownAsync()
protected abstract Task ProcessChannelAsync();
- protected readonly struct WorkStruct
+ protected readonly struct WorkStruct : IDisposable
{
public readonly IBasicConsumer Consumer;
public IAsyncBasicConsumer AsyncConsumer => (IAsyncBasicConsumer)Consumer;
@@ -111,8 +111,7 @@ protected readonly struct WorkStruct
public readonly string? Exchange;
public readonly string? RoutingKey;
public readonly ReadOnlyBasicProperties BasicProperties;
- public readonly ReadOnlyMemory Body;
- public readonly byte[]? RentedArray;
+ public readonly RentedMemory Body;
public readonly ShutdownEventArgs? Reason;
public readonly WorkType WorkType;
@@ -133,7 +132,7 @@ public WorkStruct(IBasicConsumer consumer, ShutdownEventArgs reason)
}
public WorkStruct(IBasicConsumer consumer, string consumerTag, ulong deliveryTag, bool redelivered,
- string exchange, string routingKey, in ReadOnlyBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray)
+ string exchange, string routingKey, in ReadOnlyBasicProperties basicProperties, RentedMemory body)
{
WorkType = WorkType.Deliver;
Consumer = consumer;
@@ -144,9 +143,10 @@ public WorkStruct(IBasicConsumer consumer, string consumerTag, ulong deliveryTag
RoutingKey = routingKey;
BasicProperties = basicProperties;
Body = body;
- RentedArray = rentedArray;
Reason = default;
}
+
+ public void Dispose() => Body.Dispose();
}
protected enum WorkType : byte
diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/FallbackConsumer.cs b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/FallbackConsumer.cs
index 3510be5e5d..01da938d9b 100644
--- a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/FallbackConsumer.cs
+++ b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/FallbackConsumer.cs
@@ -37,8 +37,8 @@ void IBasicConsumer.HandleBasicConsumeOk(string consumerTag)
ESLog.Info($"Unhandled {nameof(IBasicConsumer.HandleBasicConsumeOk)} for tag {consumerTag}");
}
- void IBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ void IBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
ESLog.Info($"Unhandled {nameof(IBasicConsumer.HandleBasicDeliver)} for tag {consumerTag}");
}
@@ -66,8 +66,8 @@ Task IAsyncBasicConsumer.HandleBasicConsumeOk(string consumerTag)
return Task.CompletedTask;
}
- Task IAsyncBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ Task IAsyncBasicConsumer.HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
((IBasicConsumer)this).HandleBasicDeliver(consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body);
return Task.CompletedTask;
diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/IConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/IConsumerDispatcher.cs
index ba7c462529..57113acba3 100644
--- a/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/IConsumerDispatcher.cs
+++ b/projects/RabbitMQ.Client/client/impl/ConsumerDispatching/IConsumerDispatcher.cs
@@ -29,7 +29,6 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
-using System;
using System.Threading.Tasks;
namespace RabbitMQ.Client.ConsumerDispatching
@@ -51,8 +50,7 @@ void HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties basicProperties,
- ReadOnlyMemory body,
- byte[] rentedArray);
+ RentedMemory body);
void HandleBasicCancelOk(string consumerTag);
diff --git a/projects/RabbitMQ.Client/client/impl/Frame.cs b/projects/RabbitMQ.Client/client/impl/Frame.cs
index 08f4c351b5..09acf8a40e 100644
--- a/projects/RabbitMQ.Client/client/impl/Frame.cs
+++ b/projects/RabbitMQ.Client/client/impl/Frame.cs
@@ -31,12 +31,9 @@
using System;
using System.Buffers;
-using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
-using System.Net.Sockets;
using System.Runtime.CompilerServices;
-using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
@@ -142,31 +139,33 @@ internal static class Heartbeat
///
private static ReadOnlySpan Payload => new byte[] { Constants.FrameHeartbeat, 0, 0, 0, 0, 0, 0, Constants.FrameEnd };
- public static Memory GetHeartbeatFrame()
+ public static RentedMemory GetHeartbeatFrame()
{
// Is returned by SocketFrameHandler.WriteLoop
- byte[] buffer = ArrayPool.Shared.Rent(FrameSize);
+ byte[] buffer = ClientArrayPool.Rent(FrameSize);
Payload.CopyTo(buffer);
- return new Memory(buffer, 0, FrameSize);
+ var mem = new ReadOnlyMemory(buffer, 0, FrameSize);
+ return new RentedMemory(FrameSize, mem, buffer);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlyMemory SerializeToFrames(ref T method, ushort channelNumber)
+ public static RentedMemory SerializeToFrames(ref T method, ushort channelNumber)
where T : struct, IOutgoingAmqpMethod
{
int size = Method.FrameSize + method.GetRequiredBufferSize();
// Will be returned by SocketFrameWriter.WriteLoop
- var array = ArrayPool.Shared.Rent(size);
+ byte[] array = ClientArrayPool.Rent(size);
int offset = Method.WriteTo(array, channelNumber, ref method);
System.Diagnostics.Debug.Assert(offset == size, $"Serialized to wrong size, expect {size}, offset {offset}");
- return new ReadOnlyMemory(array, 0, size);
+ var mem = new ReadOnlyMemory(array, 0, size);
+ return new RentedMemory(size, mem, array);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlyMemory SerializeToFrames(ref TMethod method, ref THeader header, ReadOnlyMemory body, ushort channelNumber, int maxBodyPayloadBytes)
+ public static RentedMemory SerializeToFrames(ref TMethod method, ref THeader header, ReadOnlyMemory body, ushort channelNumber, int maxBodyPayloadBytes)
where TMethod : struct, IOutgoingAmqpMethod
where THeader : IAmqpHeader
{
@@ -176,11 +175,11 @@ public static ReadOnlyMemory SerializeToFrames(ref TMeth
BodySegment.FrameSize * GetBodyFrameCount(maxBodyPayloadBytes, remainingBodyBytes) + remainingBodyBytes;
// Will be returned by SocketFrameWriter.WriteLoop
- var array = ArrayPool.Shared.Rent(size);
+ byte[] array = ClientArrayPool.Rent(size);
int offset = Method.WriteTo(array, channelNumber, ref method);
offset += Header.WriteTo(array.AsSpan(offset), channelNumber, ref header, remainingBodyBytes);
- var bodySpan = body.Span;
+ ReadOnlySpan bodySpan = body.Span;
while (remainingBodyBytes > 0)
{
int frameSize = remainingBodyBytes > maxBodyPayloadBytes ? maxBodyPayloadBytes : remainingBodyBytes;
@@ -189,7 +188,8 @@ public static ReadOnlyMemory SerializeToFrames(ref TMeth
}
System.Diagnostics.Debug.Assert(offset == size, $"Serialized to wrong size, expect {size}, offset {offset}");
- return new ReadOnlyMemory(array, 0, size);
+ var mem = new ReadOnlyMemory(array, 0, size);
+ return new RentedMemory(size, mem, array);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -338,14 +338,14 @@ internal static bool TryReadFrame(ref ReadOnlySequence buffer, uint maxMes
}
else
{
- byte[] payloadBytes = ArrayPool.Shared.Rent(readSize);
+ byte[] payloadBytes = ClientArrayPool.Rent(readSize);
ReadOnlySequence framePayload = buffer.Slice(7, readSize);
framePayload.CopyTo(payloadBytes);
if (payloadBytes[payloadSize] != Constants.FrameEnd)
{
var frameEndMarker = payloadBytes[payloadSize];
- ArrayPool.Shared.Return(payloadBytes);
+ ClientArrayPool.Return(payloadBytes);
throw new MalformedFrameException($"Bad frame end marker: {frameEndMarker}");
}
@@ -364,7 +364,7 @@ public byte[] TakeoverPayload()
public void ReturnPayload()
{
- ArrayPool.Shared.Return(_rentedArray);
+ ClientArrayPool.Return(_rentedArray);
}
public override string ToString()
diff --git a/projects/RabbitMQ.Client/client/impl/IFrameHandler.cs b/projects/RabbitMQ.Client/client/impl/IFrameHandler.cs
index fdc3ef7628..5b3cdd51a6 100644
--- a/projects/RabbitMQ.Client/client/impl/IFrameHandler.cs
+++ b/projects/RabbitMQ.Client/client/impl/IFrameHandler.cs
@@ -67,6 +67,6 @@ internal interface IFrameHandler
ValueTask SendHeaderAsync();
- ValueTask WriteAsync(ReadOnlyMemory memory);
+ ValueTask WriteAsync(RentedMemory frames);
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/ISession.cs b/projects/RabbitMQ.Client/client/impl/ISession.cs
index bc30b8f5dd..1d5a34f76b 100644
--- a/projects/RabbitMQ.Client/client/impl/ISession.cs
+++ b/projects/RabbitMQ.Client/client/impl/ISession.cs
@@ -70,15 +70,21 @@ internal interface ISession
event EventHandler SessionShutdown;
void Close(ShutdownEventArgs reason);
+
void Close(ShutdownEventArgs reason, bool notify);
+
bool HandleFrame(in InboundFrame frame);
+
void Notify();
+
void Transmit(in T cmd) where T : struct, IOutgoingAmqpMethod;
- ValueTask TransmitAsync(in T cmd) where T : struct, IOutgoingAmqpMethod;
+
void Transmit(in TMethod cmd, in THeader header, ReadOnlyMemory body)
where TMethod : struct, IOutgoingAmqpMethod
where THeader : IAmqpHeader;
+ ValueTask TransmitAsync(in T cmd) where T : struct, IOutgoingAmqpMethod;
+
ValueTask TransmitAsync(in TMethod cmd, in THeader header, ReadOnlyMemory body)
where TMethod : struct, IOutgoingAmqpMethod
where THeader : IAmqpHeader;
diff --git a/projects/RabbitMQ.Client/client/impl/IncomingCommand.cs b/projects/RabbitMQ.Client/client/impl/IncomingCommand.cs
index 5f3aa7fbb8..d5fb43d016 100644
--- a/projects/RabbitMQ.Client/client/impl/IncomingCommand.cs
+++ b/projects/RabbitMQ.Client/client/impl/IncomingCommand.cs
@@ -1,5 +1,35 @@
-using System;
-using System.Buffers;
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
using RabbitMQ.Client.client.framing;
namespace RabbitMQ.Client.Impl
@@ -10,41 +40,55 @@ internal readonly struct IncomingCommand
public readonly ProtocolCommandId CommandId;
- public readonly ReadOnlyMemory MethodBytes;
- private readonly byte[] _rentedMethodBytes;
-
- public readonly ReadOnlyMemory HeaderBytes;
- private readonly byte[] _rentedHeaderArray;
-
- public readonly ReadOnlyMemory Body;
- private readonly byte[] _rentedBodyArray;
+ public readonly RentedMemory Method;
+ public readonly RentedMemory Header;
+ public readonly RentedMemory Body;
- public bool IsEmpty => CommandId is default(ProtocolCommandId);
+ public readonly bool IsEmpty => CommandId is default(ProtocolCommandId);
- public IncomingCommand(ProtocolCommandId commandId, ReadOnlyMemory methodBytes, byte[] rentedMethodArray, ReadOnlyMemory headerBytes, byte[] rentedHeaderArray, ReadOnlyMemory body, byte[] rentedBodyArray)
+ public IncomingCommand(ProtocolCommandId commandId,
+ RentedMemory method, RentedMemory header, RentedMemory body)
{
CommandId = commandId;
- MethodBytes = methodBytes;
- _rentedMethodBytes = rentedMethodArray;
- HeaderBytes = headerBytes;
- _rentedHeaderArray = rentedHeaderArray;
+ Method = method;
+ Header = header;
Body = body;
- _rentedBodyArray = rentedBodyArray;
}
- public byte[] TakeoverBody()
+ public ReadOnlySpan MethodSpan
+ {
+ get
+ {
+ return Method.Memory.Span;
+ }
+ }
+
+ public ReadOnlySpan HeaderSpan
+ {
+ get
+ {
+ return Header.Memory.Span;
+ }
+ }
+
+ public ReadOnlySpan BodySpan
{
- return _rentedBodyArray;
+ get
+ {
+ return Body.Memory.Span;
+ }
}
- public void ReturnHeaderBuffer()
+ public void ReturnMethodAndHeaderBuffers()
{
- ArrayPool.Shared.Return(_rentedHeaderArray);
+ Method.Dispose();
+ Header.Dispose();
}
- public void ReturnMethodBuffer()
+ public void ReturnBuffers()
{
- ArrayPool.Shared.Return(_rentedMethodBytes);
+ ReturnMethodAndHeaderBuffers();
+ Body.Dispose();
}
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs b/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs
index e97415d713..49a3294d0b 100644
--- a/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs
+++ b/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs
@@ -29,6 +29,7 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using System.Threading.Tasks;
using RabbitMQ.Client.Framing.Impl;
namespace RabbitMQ.Client.Impl
@@ -70,6 +71,19 @@ public override void BasicAck(ulong deliveryTag, bool multiple)
}
}
+ public override ValueTask BasicAckAsync(ulong deliveryTag, bool multiple)
+ {
+ ulong realTag = deliveryTag - ActiveDeliveryTagOffset;
+ if (realTag > 0 && realTag <= deliveryTag)
+ {
+ return base.BasicAckAsync(realTag, multiple);
+ }
+ else
+ {
+ return default;
+ }
+ }
+
public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue)
{
ulong realTag = deliveryTag - ActiveDeliveryTagOffset;
@@ -79,6 +93,19 @@ public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue)
}
}
+ public override ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue)
+ {
+ ulong realTag = deliveryTag - ActiveDeliveryTagOffset;
+ if (realTag > 0 && realTag <= deliveryTag)
+ {
+ return base.BasicNackAsync(realTag, multiple, requeue);
+ }
+ else
+ {
+ return default;
+ }
+ }
+
public override void BasicReject(ulong deliveryTag, bool requeue)
{
ulong realTag = deliveryTag - ActiveDeliveryTagOffset;
@@ -87,5 +114,18 @@ public override void BasicReject(ulong deliveryTag, bool requeue)
base.BasicReject(realTag, requeue);
}
}
+
+ public override ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue)
+ {
+ ulong realTag = deliveryTag - ActiveDeliveryTagOffset;
+ if (realTag > 0 && realTag <= deliveryTag)
+ {
+ return base.BasicRejectAsync(realTag, requeue);
+ }
+ else
+ {
+ return default;
+ }
+ }
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/SimpleBlockingRpcContinuation.cs b/projects/RabbitMQ.Client/client/impl/RpcContinuations.cs
similarity index 62%
rename from projects/RabbitMQ.Client/client/impl/SimpleBlockingRpcContinuation.cs
rename to projects/RabbitMQ.Client/client/impl/RpcContinuations.cs
index bb40f6be82..b84687ca45 100644
--- a/projects/RabbitMQ.Client/client/impl/SimpleBlockingRpcContinuation.cs
+++ b/projects/RabbitMQ.Client/client/impl/RpcContinuations.cs
@@ -30,57 +30,11 @@
//---------------------------------------------------------------------------
using System;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using RabbitMQ.Client.client.framing;
using RabbitMQ.Client.Exceptions;
-using RabbitMQ.Client.Framing.Impl;
using RabbitMQ.Util;
namespace RabbitMQ.Client.Impl
{
- internal abstract class AsyncRpcContinuation : IRpcContinuation
- {
- protected readonly TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
-
- public TaskAwaiter GetAwaiter() => _tcs.Task.GetAwaiter();
-
- public abstract void HandleCommand(in IncomingCommand cmd);
-
- public void HandleChannelShutdown(ShutdownEventArgs reason) => _tcs.SetException(new OperationInterruptedException(reason));
- }
-
- internal class ConnectionSecureOrTuneContinuation : AsyncRpcContinuation
- {
- public override void HandleCommand(in IncomingCommand cmd)
- {
- try
- {
- if (cmd.CommandId == ProtocolCommandId.ConnectionSecure)
- {
- var secure = new ConnectionSecure(cmd.MethodBytes.Span);
- _tcs.TrySetResult(new ConnectionSecureOrTune { m_challenge = secure._challenge });
- }
- else if (cmd.CommandId == ProtocolCommandId.ConnectionTune)
- {
- var tune = new ConnectionTune(cmd.MethodBytes.Span);
- _tcs.TrySetResult(new ConnectionSecureOrTune
- {
- m_tuneDetails = new() { m_channelMax = tune._channelMax, m_frameMax = tune._frameMax, m_heartbeatInSeconds = tune._heartbeat }
- });
- }
- else
- {
- _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
- }
- }
- finally
- {
- cmd.ReturnMethodBuffer();
- }
- }
- }
-
internal class SimpleBlockingRpcContinuation : IRpcContinuation
{
private readonly BlockingCell> m_cell = new BlockingCell>();
@@ -121,4 +75,20 @@ public void HandleChannelShutdown(ShutdownEventArgs reason)
m_cell.ContinueWithValue(Either.Right(reason));
}
}
+
+ internal class BasicConsumeRpcContinuation : SimpleBlockingRpcContinuation
+ {
+ public IBasicConsumer m_consumer;
+ public string m_consumerTag;
+ }
+
+ internal class BasicGetRpcContinuation : SimpleBlockingRpcContinuation
+ {
+ public BasicGetResult m_result;
+ }
+
+ internal class QueueDeclareRpcContinuation : SimpleBlockingRpcContinuation
+ {
+ public QueueDeclareOk m_result;
+ }
}
diff --git a/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs b/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs
index c09bdd1b56..62701ad844 100644
--- a/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs
+++ b/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs
@@ -30,12 +30,10 @@
//---------------------------------------------------------------------------
using System;
-using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
-using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -90,21 +88,22 @@ internal sealed class SocketFrameHandler : IFrameHandler
{
private readonly AmqpTcpEndpoint _amqpTcpEndpoint;
private readonly ITcpClient _socket;
- private readonly ChannelWriter> _channelWriter;
- private readonly ChannelReader> _channelReader;
- private readonly PipeWriter _pipeWriter;
+ private readonly ChannelWriter _channelWriter;
+ private readonly ChannelReader _channelReader;
private readonly PipeReader _pipeReader;
+ private readonly PipeWriter _pipeWriter;
private readonly Task _writerTask;
private readonly object _semaphore = new object();
private bool _closed;
- private static ReadOnlySpan ProtocolHeader => new byte[] { (byte)'A', (byte)'M', (byte)'Q', (byte)'P', 0, 0, 9, 1 };
+
+ private static ReadOnlyMemory ProtocolHeader => new byte[] { (byte)'A', (byte)'M', (byte)'Q', (byte)'P', 0, 0, 9, 1 };
public SocketFrameHandler(AmqpTcpEndpoint endpoint,
Func socketFactory,
TimeSpan connectionTimeout, TimeSpan readTimeout, TimeSpan writeTimeout)
{
_amqpTcpEndpoint = endpoint;
- var channel = Channel.CreateBounded>(
+ var channel = Channel.CreateBounded(
new BoundedChannelOptions(128)
{
AllowSynchronousContinuations = false,
@@ -166,8 +165,8 @@ public SocketFrameHandler(AmqpTcpEndpoint endpoint,
}
}
- _pipeWriter = PipeWriter.Create(netstream);
_pipeReader = PipeReader.Create(netstream);
+ _pipeWriter = PipeWriter.Create(netstream);
WriteTimeout = writeTimeout;
_writerTask = Task.Run(WriteLoop);
@@ -274,22 +273,22 @@ public bool TryReadFrame(out InboundFrame frame)
public async ValueTask SendHeaderAsync()
{
- _pipeWriter.Write(ProtocolHeader);
+ await _pipeWriter.WriteAsync(ProtocolHeader).ConfigureAwait(false);
await _pipeWriter.FlushAsync().ConfigureAwait(false);
}
- public ValueTask WriteAsync(ReadOnlyMemory memory)
+ public async ValueTask WriteAsync(RentedMemory frames)
{
if (_closed)
{
-#if NET6_0_OR_GREATER
- return ValueTask.CompletedTask;
-#else
- return new ValueTask(Task.CompletedTask);
-#endif
+ frames.Dispose();
+ await Task.Yield();
+ }
+ else
+ {
+ await _channelWriter.WriteAsync(frames)
+ .ConfigureAwait(false);
}
-
- return _channelWriter.WriteAsync(memory);
}
private async Task WriteLoop()
@@ -298,12 +297,18 @@ private async Task WriteLoop()
{
while (await _channelReader.WaitToReadAsync().ConfigureAwait(false))
{
- while (_channelReader.TryRead(out ReadOnlyMemory memory))
+ if (_channelReader.TryRead(out RentedMemory frames))
{
- MemoryMarshal.TryGetArray(memory, out ArraySegment segment);
- _pipeWriter.Write(memory.Span);
- RabbitMqClientEventSource.Log.CommandSent(segment.Count);
- ArrayPool.Shared.Return(segment.Array);
+ try
+ {
+ await _pipeWriter.WriteAsync(frames.Memory)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ RabbitMqClientEventSource.Log.CommandSent(frames.Size);
+ frames.Dispose();
+ }
}
await _pipeWriter.FlushAsync()
diff --git a/projects/TestApplications/CreateChannel/CreateChannel.csproj b/projects/Test/Applications/CreateChannel/CreateChannel.csproj
similarity index 83%
rename from projects/TestApplications/CreateChannel/CreateChannel.csproj
rename to projects/Test/Applications/CreateChannel/CreateChannel.csproj
index 9fe7c226d7..9316c348d1 100644
--- a/projects/TestApplications/CreateChannel/CreateChannel.csproj
+++ b/projects/Test/Applications/CreateChannel/CreateChannel.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/projects/TestApplications/CreateChannel/Program.cs b/projects/Test/Applications/CreateChannel/Program.cs
similarity index 100%
rename from projects/TestApplications/CreateChannel/Program.cs
rename to projects/Test/Applications/CreateChannel/Program.cs
diff --git a/projects/TestApplications/MassPublish/MassPublish.csproj b/projects/Test/Applications/MassPublish/MassPublish.csproj
similarity index 77%
rename from projects/TestApplications/MassPublish/MassPublish.csproj
rename to projects/Test/Applications/MassPublish/MassPublish.csproj
index 9fe7c226d7..b57323cb5a 100644
--- a/projects/TestApplications/MassPublish/MassPublish.csproj
+++ b/projects/Test/Applications/MassPublish/MassPublish.csproj
@@ -9,11 +9,12 @@
+ latest
Exe
-
+
diff --git a/projects/Test/Applications/MassPublish/Program.cs b/projects/Test/Applications/MassPublish/Program.cs
new file mode 100644
index 0000000000..abd8820e4c
--- /dev/null
+++ b/projects/Test/Applications/MassPublish/Program.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+
+namespace MassPublish
+{
+ static class Program
+ {
+ const string AppId = "MassPublish";
+ const string ExchangeName = "MassPublish-ex";
+ const string QueueName = "MassPublish-queue";
+ const string RoutingKey = "MassPublish-queue";
+ const string ConsumerTag = "MassPublish-consumer";
+ static readonly int ConnectionCount = Environment.ProcessorCount;
+
+ const int BatchesToSend = 64;
+ const int ItemsPerBatch = 8192;
+ const int TotalMessages = BatchesToSend * ItemsPerBatch;
+
+ static int s_messagesSent;
+ static int s_messagesReceived;
+
+ static readonly TaskCompletionSource s_consumeDoneEvent = new();
+
+ static readonly BasicProperties s_properties = new() { AppId = AppId };
+
+ static readonly ConnectionFactory s_publishConnectionFactory = new();
+ static readonly ConnectionFactory s_consumeConnectionFactory = new()
+ {
+ ClientProvidedName = AppId + "-CONSUME",
+ DispatchConsumersAsync = true
+ };
+
+ static readonly Random s_random;
+ static readonly byte[] s_payload;
+ static readonly bool s_debug = false;
+
+ static Program()
+ {
+ s_random = new Random();
+ s_payload = GetRandomBody(1024 * 4);
+ }
+
+ static async Task Main()
+ {
+
+ ThreadPool.SetMinThreads(16 * Environment.ProcessorCount, 16 * Environment.ProcessorCount);
+
+ using IConnection consumeConnection = s_consumeConnectionFactory.CreateConnection();
+ consumeConnection.ConnectionShutdown += Connection_ConnectionShutdown;
+
+ using IChannel consumeChannel = consumeConnection.CreateChannel();
+ consumeChannel.ChannelShutdown += Channel_ChannelShutdown;
+ await consumeChannel.BasicQosAsync(prefetchSize: 0, prefetchCount: 128, global: false);
+
+ await consumeChannel.ExchangeDeclareAsync(exchange: ExchangeName,
+ type: ExchangeType.Direct, passive: false, durable: false, autoDelete: false, arguments: null);
+
+ await consumeChannel.QueueDeclareAsync(queue: QueueName,
+ passive: false, durable: false, exclusive: false, autoDelete: false, arguments: null);
+
+ var asyncListener = new AsyncEventingBasicConsumer(consumeChannel);
+ asyncListener.Received += AsyncListener_Received;
+
+ await consumeChannel.QueueBindAsync(queue: QueueName, exchange: ExchangeName, routingKey: RoutingKey, arguments: null);
+
+ await consumeChannel.BasicConsumeAsync(queue: QueueName, autoAck: true, consumerTag: ConsumerTag,
+ noLocal: false, exclusive: false, arguments: null, asyncListener);
+
+ var publishConnections = new List();
+ for (int i = 0; i < ConnectionCount; i++)
+ {
+ IConnection publishConnection = s_publishConnectionFactory.CreateConnection($"{AppId}-PUBLISH-{i}");
+ publishConnection.ConnectionShutdown += Connection_ConnectionShutdown;
+ publishConnections.Add(publishConnection);
+ }
+
+ var publishTasks = new List();
+ var watch = Stopwatch.StartNew();
+
+ for (int batchIdx = 0; batchIdx < BatchesToSend; batchIdx++)
+ {
+ int idx = s_random.Next(publishConnections.Count);
+ IConnection publishConnection = publishConnections[idx];
+
+ publishTasks.Add(Task.Run(async () =>
+ {
+ using IChannel publishChannel = publishConnection.CreateChannel();
+ publishChannel.ChannelShutdown += Channel_ChannelShutdown;
+
+ await publishChannel.ConfirmSelectAsync();
+
+ for (int i = 0; i < ItemsPerBatch; i++)
+ {
+ await publishChannel.BasicPublishAsync(exchange: ExchangeName, routingKey: RoutingKey,
+ basicProperties: s_properties, body: s_payload, mandatory: true);
+ Interlocked.Increment(ref s_messagesSent);
+ }
+
+ await publishChannel.WaitForConfirmsOrDieAsync();
+
+ if (s_debug)
+ {
+ Console.WriteLine("[DEBUG] channel {0} done publishing and waiting for confirms", publishChannel.ChannelNumber);
+ }
+ }));
+ }
+
+ Console.WriteLine($"Sending {BatchesToSend} batches for {ItemsPerBatch} items per batch => Total messages: {TotalMessages}");
+ Console.WriteLine();
+ Console.WriteLine(" Sent | Received");
+
+ while (false == s_consumeDoneEvent.Task.Wait(500))
+ {
+ Console.WriteLine($"{s_messagesSent,5} | {s_messagesReceived,5}");
+ }
+ watch.Stop();
+ await Task.WhenAll(publishTasks.ToArray());
+
+ Console.WriteLine($"{s_messagesSent,5} | {s_messagesReceived,5}");
+ Console.WriteLine();
+ Console.WriteLine($"Took {watch.Elapsed.TotalMilliseconds} ms");
+
+ foreach (IConnection c in publishConnections)
+ {
+ if (s_debug)
+ {
+ Console.WriteLine("[DEBUG] closing connection: {0}", c.ClientProvidedName);
+ }
+ c.Close();
+ }
+ }
+
+ private static void PublishChannel_BasicNacks(object sender, BasicNackEventArgs e)
+ {
+ Console.Error.WriteLine("[ERROR] unexpected nack on publish: {0}", e);
+ }
+
+ private static void Connection_ConnectionShutdown(object sender, ShutdownEventArgs e)
+ {
+ if (e.Initiator != ShutdownInitiator.Application)
+ {
+ Console.Error.WriteLine("[ERROR] unexpected connection shutdown: {0}", e);
+ s_consumeDoneEvent.TrySetResult(false);
+ }
+ }
+
+ private static void Channel_ChannelShutdown(object sender, ShutdownEventArgs e)
+ {
+ if (e.Initiator != ShutdownInitiator.Application)
+ {
+ Console.Error.WriteLine("[ERROR] unexpected channel shutdown: {0}", e);
+ s_consumeDoneEvent.TrySetResult(false);
+ }
+ }
+
+ private static Task AsyncListener_Received(object sender, BasicDeliverEventArgs @event)
+ {
+ if (Interlocked.Increment(ref s_messagesReceived) == TotalMessages)
+ {
+ s_consumeDoneEvent.SetResult(true);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private static byte[] GetRandomBody(int size)
+ {
+ var body = new byte[size];
+ s_random.NextBytes(body);
+ return body;
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/AsyncIntegration.csproj b/projects/Test/AsyncIntegration/AsyncIntegration.csproj
new file mode 100644
index 0000000000..ca232068b7
--- /dev/null
+++ b/projects/Test/AsyncIntegration/AsyncIntegration.csproj
@@ -0,0 +1,54 @@
+
+
+
+ net6.0;net472
+
+
+
+ net6.0
+
+
+
+ ../../rabbit.snk
+ true
+ latest
+ 7.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
diff --git a/projects/Test/AsyncIntegration/AsyncIntegrationFixture.cs b/projects/Test/AsyncIntegration/AsyncIntegrationFixture.cs
new file mode 100644
index 0000000000..e7426d7518
--- /dev/null
+++ b/projects/Test/AsyncIntegration/AsyncIntegrationFixture.cs
@@ -0,0 +1,91 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class AsyncIntegrationFixture : IntegrationFixture, IAsyncLifetime
+ {
+ protected readonly bool _dispatchConsumersAsync = false;
+ protected readonly ushort _consumerDispatchConcurrency = 1;
+
+ public AsyncIntegrationFixture(ITestOutputHelper output,
+ bool dispatchConsumersAsync = false, ushort consumerDispatchConcurrency = 1) : base(output)
+ {
+ _dispatchConsumersAsync = dispatchConsumersAsync;
+ _consumerDispatchConcurrency = consumerDispatchConcurrency;
+ }
+
+ protected override void SetUp()
+ {
+ // InitializeAsync
+ }
+
+ public virtual async Task InitializeAsync()
+ {
+ _connFactory = CreateConnectionFactory();
+ _connFactory.DispatchConsumersAsync = _dispatchConsumersAsync;
+ _connFactory.ConsumerDispatchConcurrency = _consumerDispatchConcurrency;
+
+ _conn = await _connFactory.CreateConnectionAsync();
+ if (_connFactory.AutomaticRecoveryEnabled)
+ {
+ Assert.IsType(_conn);
+ }
+ else
+ {
+ Assert.IsType(_conn);
+ }
+
+ _channel = await _conn.CreateChannelAsync();
+ }
+
+ public virtual async Task DisposeAsync()
+ {
+ try
+ {
+ await _channel.CloseAsync();
+ _conn.Close();
+ }
+ finally
+ {
+ _channel.Dispose();
+ _conn.Dispose();
+ _channel = null;
+ _conn = null;
+ }
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/TestAsyncConsumer.cs b/projects/Test/AsyncIntegration/TestAsyncConsumer.cs
new file mode 100644
index 0000000000..07f959c002
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestAsyncConsumer.cs
@@ -0,0 +1,469 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestAsyncConsumer : AsyncIntegrationFixture
+ {
+ private readonly ShutdownEventArgs _closeArgs = new ShutdownEventArgs(ShutdownInitiator.Application, Constants.ReplySuccess, "normal shutdown");
+
+ public TestAsyncConsumer(ITestOutputHelper output) : base(output, true, 2)
+ {
+ }
+
+ [Fact]
+ public async Task TestBasicRoundtripConcurrent()
+ {
+ QueueDeclareOk q = _channel.QueueDeclare();
+ string publish1 = GetUniqueString(1024);
+ byte[] body = _encoding.GetBytes(publish1);
+ _channel.BasicPublish("", q.QueueName, body);
+
+ string publish2 = GetUniqueString(1024);
+ body = _encoding.GetBytes(publish2);
+ _channel.BasicPublish("", q.QueueName, body);
+
+ var consumer = new AsyncEventingBasicConsumer(_channel);
+
+ var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var maximumWaitTime = TimeSpan.FromSeconds(10);
+ var tokenSource = new CancellationTokenSource(maximumWaitTime);
+ tokenSource.Token.Register(() =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+
+ _conn.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(_conn, ea, () =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+ };
+
+ _channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(_channel, ea, () =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+ };
+
+ consumer.Received += async (o, a) =>
+ {
+ string decoded = _encoding.GetString(a.Body);
+ if (decoded == publish1)
+ {
+ publish1SyncSource.TrySetResult(true);
+ await publish2SyncSource.Task;
+ }
+ else if (decoded == publish2)
+ {
+ publish2SyncSource.TrySetResult(true);
+ await publish1SyncSource.Task;
+ }
+ };
+
+ _channel.BasicConsume(q.QueueName, true, consumer);
+
+ // ensure we get a delivery
+ await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task);
+
+ bool result1 = await publish1SyncSource.Task;
+ Assert.True(result1, $"1 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}");
+
+ bool result2 = await publish2SyncSource.Task;
+ Assert.True(result2, $"2 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}");
+ }
+
+ [Fact]
+ public async Task TestBasicRoundtripConcurrentManyMessages()
+ {
+ const int publish_total = 4096;
+ string queueName = $"{nameof(TestBasicRoundtripConcurrentManyMessages)}-{Guid.NewGuid()}";
+
+ string publish1 = GetUniqueString(32768);
+ byte[] body1 = _encoding.GetBytes(publish1);
+ string publish2 = GetUniqueString(32768);
+ byte[] body2 = _encoding.GetBytes(publish2);
+
+ var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var maximumWaitTime = TimeSpan.FromSeconds(30);
+ var tokenSource = new CancellationTokenSource(maximumWaitTime);
+ tokenSource.Token.Register(() =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+
+ _conn.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(_conn, ea, () =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+ };
+
+ _channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(_channel, ea, () =>
+ {
+ publish1SyncSource.TrySetResult(false);
+ publish2SyncSource.TrySetResult(false);
+ });
+ };
+
+ QueueDeclareOk q = _channel.QueueDeclare(queue: queueName, exclusive: false, durable: true);
+ Assert.Equal(q.QueueName, queueName);
+
+ Task publishTask = Task.Run(async () =>
+ {
+ using (IChannel m = await _conn.CreateChannelAsync())
+ {
+ QueueDeclareOk q = _channel.QueueDeclare(queue: queueName, exclusive: false, durable: true);
+ for (int i = 0; i < publish_total; i++)
+ {
+ await _channel.BasicPublishAsync(string.Empty, queueName, body1);
+ await _channel.BasicPublishAsync(string.Empty, queueName, body2);
+ }
+ }
+ });
+
+ Task consumeTask = Task.Run(async () =>
+ {
+ using (IChannel m = await _conn.CreateChannelAsync())
+ {
+ var consumer = new AsyncEventingBasicConsumer(m);
+
+ int publish1_count = 0;
+ int publish2_count = 0;
+
+ consumer.Received += async (o, a) =>
+ {
+ string decoded = _encoding.GetString(a.Body);
+ if (decoded == publish1)
+ {
+ if (Interlocked.Increment(ref publish1_count) >= publish_total)
+ {
+ publish1SyncSource.TrySetResult(true);
+ await publish2SyncSource.Task;
+ }
+ }
+ else if (decoded == publish2)
+ {
+ if (Interlocked.Increment(ref publish2_count) >= publish_total)
+ {
+ publish2SyncSource.TrySetResult(true);
+ await publish1SyncSource.Task;
+ }
+ }
+ };
+
+ _channel.BasicConsume(queueName, true, consumer);
+
+ // ensure we get a delivery
+ await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task);
+
+ bool result1 = await publish1SyncSource.Task;
+ Assert.True(result1, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}");
+
+ bool result2 = await publish2SyncSource.Task;
+ Assert.True(result2, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}");
+ }
+ });
+
+ await Task.WhenAll(publishTask, consumeTask);
+ }
+
+ [Fact]
+ public async Task TestBasicRejectAsync()
+ {
+ var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var cf = CreateConnectionFactory();
+ cf.DispatchConsumersAsync = true;
+
+ using IConnection connection = await cf.CreateConnectionAsync();
+ using IChannel channel = await connection.CreateChannelAsync();
+
+ connection.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(connection, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(channel, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ var consumer = new AsyncEventingBasicConsumer(channel);
+ consumer.Received += async (object sender, BasicDeliverEventArgs args) =>
+ {
+ var c = sender as AsyncEventingBasicConsumer;
+ Assert.NotNull(c);
+ await channel.BasicCancelAsync(c.ConsumerTags[0]);
+ await channel.BasicRejectAsync(args.DeliveryTag, true);
+ publishSyncSource.TrySetResult(true);
+ };
+
+ QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null);
+ string queueName = q.QueueName;
+ const string publish1 = "sync-hi-1";
+ byte[] _body = _encoding.GetBytes(publish1);
+ await channel.BasicPublishAsync(string.Empty, queueName, _body);
+
+ await channel.BasicConsumeAsync(queue: queueName, autoAck: false,
+ consumerTag: string.Empty, noLocal: false, exclusive: false,
+ arguments: null, consumer);
+
+ Assert.True(await publishSyncSource.Task);
+
+ uint messageCount, consumerCount = 0;
+ ushort tries = 5;
+ do
+ {
+ QueueDeclareOk result = await channel.QueueDeclareAsync(queue: queueName, passive: true, false, false, false, null);
+ consumerCount = result.ConsumerCount;
+ messageCount = result.MessageCount;
+ if (consumerCount == 0 && messageCount > 0)
+ {
+ break;
+ }
+ else
+ {
+ await Task.Delay(500);
+ }
+ } while (tries-- > 0);
+
+ if (tries == 0)
+ {
+ Assert.Fail("[ERROR] failed waiting for MessageCount > 0 && ConsumerCount == 0");
+ }
+ else
+ {
+ Assert.Equal((uint)1, messageCount);
+ Assert.Equal((uint)0, consumerCount);
+ }
+
+ // Note: closing channel explicitly just to test it.
+ await channel.CloseAsync(_closeArgs, false);
+ }
+
+ [Fact]
+ public async Task TestBasicAckAsync()
+ {
+ const int messageCount = 1024;
+ int messagesReceived = 0;
+
+ var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var cf = CreateConnectionFactory();
+ cf.DispatchConsumersAsync = true;
+
+ using IConnection connection = await cf.CreateConnectionAsync();
+ using IChannel channel = await connection.CreateChannelAsync();
+
+ connection.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(connection, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(channel, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ await channel.ConfirmSelectAsync();
+
+ var consumer = new AsyncEventingBasicConsumer(channel);
+ consumer.Received += async (object sender, BasicDeliverEventArgs args) =>
+ {
+ var c = sender as AsyncEventingBasicConsumer;
+ Assert.NotNull(c);
+ await channel.BasicAckAsync(args.DeliveryTag, false);
+ messagesReceived++;
+ if (messagesReceived == messageCount)
+ {
+ publishSyncSource.SetResult(true);
+ }
+ };
+
+ QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null);
+ string queueName = q.QueueName;
+
+ await channel.BasicQosAsync(0, 1, false);
+ await channel.BasicConsumeAsync(queue: queueName, autoAck: false,
+ consumerTag: string.Empty, noLocal: false, exclusive: false,
+ arguments: null, consumer);
+
+ var publishTask = Task.Run(async () =>
+ {
+ for (int i = 0; i < messageCount; i++)
+ {
+ byte[] _body = _encoding.GetBytes(Guid.NewGuid().ToString());
+ await channel.BasicPublishAsync(string.Empty, queueName, _body);
+ }
+ });
+
+ await channel.WaitForConfirmsOrDieAsync();
+ Assert.True(await publishSyncSource.Task);
+
+ Assert.Equal(messageCount, messagesReceived);
+
+ // Note: closing channel explicitly just to test it.
+ await channel.CloseAsync(_closeArgs, false);
+ }
+
+ [Fact]
+ public async Task TestBasicNackAsync()
+ {
+ var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var cf = CreateConnectionFactory();
+ cf.DispatchConsumersAsync = true;
+
+ using IConnection connection = await cf.CreateConnectionAsync();
+ using IChannel channel = await connection.CreateChannelAsync();
+
+ connection.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(connection, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(channel, ea, () =>
+ {
+ publishSyncSource.TrySetResult(false);
+ });
+ };
+
+ var consumer = new AsyncEventingBasicConsumer(channel);
+ consumer.Received += async (object sender, BasicDeliverEventArgs args) =>
+ {
+ var c = sender as AsyncEventingBasicConsumer;
+ Assert.NotNull(c);
+ await channel.BasicCancelAsync(c.ConsumerTags[0]);
+ await channel.BasicNackAsync(args.DeliveryTag, false, true);
+ publishSyncSource.SetResult(true);
+ };
+
+ QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, false, false, null);
+ string queueName = q.QueueName;
+ const string publish1 = "sync-hi-1";
+ byte[] _body = _encoding.GetBytes(publish1);
+ await channel.BasicPublishAsync(string.Empty, queueName, _body);
+
+ await channel.BasicConsumeAsync(queue: queueName, autoAck: false,
+ consumerTag: string.Empty, noLocal: false, exclusive: false,
+ arguments: null, consumer);
+
+ Assert.True(await publishSyncSource.Task);
+
+ uint messageCount, consumerCount = 0;
+ ushort tries = 5;
+ do
+ {
+ QueueDeclareOk result = await channel.QueueDeclareAsync(queue: queueName, passive: true, false, false, false, null);
+ consumerCount = result.ConsumerCount;
+ messageCount = result.MessageCount;
+ if (consumerCount == 0 && messageCount > 0)
+ {
+ break;
+ }
+ else
+ {
+ await Task.Delay(500);
+ }
+ } while (tries-- > 0);
+
+ if (tries == 0)
+ {
+ Assert.Fail("[ERROR] failed waiting for MessageCount > 0 && ConsumerCount == 0");
+ }
+ else
+ {
+ Assert.Equal((uint)1, messageCount);
+ Assert.Equal((uint)0, consumerCount);
+ }
+
+ // Note: closing channel explicitly just to test it.
+ await channel.CloseAsync(_closeArgs, false);
+ }
+
+ [Fact]
+ public async Task NonAsyncConsumerShouldThrowInvalidOperationException()
+ {
+ bool sawException = false;
+ QueueDeclareOk q = await _channel.QueueDeclareAsync(string.Empty, false, false, false, false, null);
+ await _channel.BasicPublishAsync(string.Empty, q.QueueName, GetRandomBody(1024));
+ var consumer = new EventingBasicConsumer(_channel);
+ try
+ {
+ string consumerTag = await _channel.BasicConsumeAsync(q.QueueName, false, string.Empty, false, false, null, consumer);
+ }
+ catch (InvalidOperationException)
+ {
+ sawException = true;
+ }
+ Assert.True(sawException, "did not see expected InvalidOperationException");
+ }
+ }
+}
diff --git a/projects/Unit/TestAsyncConsumerExceptions.cs b/projects/Test/AsyncIntegration/TestAsyncConsumerExceptions.cs
similarity index 68%
rename from projects/Unit/TestAsyncConsumerExceptions.cs
rename to projects/Test/AsyncIntegration/TestAsyncConsumerExceptions.cs
index 610f7388f8..664ab14101 100644
--- a/projects/Unit/TestAsyncConsumerExceptions.cs
+++ b/projects/Test/AsyncIntegration/TestAsyncConsumerExceptions.cs
@@ -32,86 +32,80 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.AsyncIntegration
{
- public class TestAsyncConsumerExceptions : IntegrationFixture
+ public class TestAsyncConsumerExceptions : AsyncIntegrationFixture
{
private static readonly Exception TestException = new Exception("oops");
- public TestAsyncConsumerExceptions(ITestOutputHelper output) : base(output)
+ public TestAsyncConsumerExceptions(ITestOutputHelper output) : base(output, true, 1)
{
}
- protected void TestExceptionHandlingWith(IBasicConsumer consumer,
- Action action)
- {
- var resetEvent = new AutoResetEvent(false);
- bool notified = false;
- string q = _channel.QueueDeclare();
-
- _channel.CallbackException += (m, evt) =>
- {
- if (evt.Exception != TestException) return;
-
- notified = true;
- resetEvent.Set();
- };
-
- string tag = _channel.BasicConsume(q, true, consumer);
- action(_channel, q, consumer, tag);
- resetEvent.WaitOne(2000);
-
- Assert.True(notified);
- }
-
- protected override void SetUp()
- {
- _connFactory = new ConnectionFactory
- {
- DispatchConsumersAsync = true
- };
-
- _conn = _connFactory.CreateConnection();
- _channel = _conn.CreateChannel();
- }
-
[Fact]
- public void TestCancelNotificationExceptionHandling()
+ public async Task TestCancelNotificationExceptionHandling()
{
IBasicConsumer consumer = new ConsumerFailingOnCancel(_channel);
- TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.QueueDelete(q));
+ await TestExceptionHandlingWith(consumer, async (ch, q, c, ct) =>
+ {
+ await ch.QueueDeleteAsync(q, false, false);
+ });
}
[Fact]
- public void TestConsumerCancelOkExceptionHandling()
+ public async Task TestConsumerCancelOkExceptionHandling()
{
IBasicConsumer consumer = new ConsumerFailingOnCancelOk(_channel);
- TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicCancel(ct));
+ await TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.BasicCancelAsync(ct));
}
[Fact]
- public void TestConsumerConsumeOkExceptionHandling()
+ public async Task TestConsumerConsumeOkExceptionHandling()
{
IBasicConsumer consumer = new ConsumerFailingOnConsumeOk(_channel);
- TestExceptionHandlingWith(consumer, (m, q, c, ct) => { });
+ await TestExceptionHandlingWith(consumer, async (ch, q, c, ct) => await Task.Yield());
}
[Fact]
- public void TestConsumerShutdownExceptionHandling()
+ public async Task TestConsumerShutdownExceptionHandling()
{
IBasicConsumer consumer = new ConsumerFailingOnShutdown(_channel);
- TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.Close());
+ await TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.CloseAsync());
}
[Fact]
- public void TestDeliveryExceptionHandling()
+ public async Task TestDeliveryExceptionHandling()
{
IBasicConsumer consumer = new ConsumerFailingOnDelivery(_channel);
- TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicPublish("", q, _encoding.GetBytes("msg")));
+ await TestExceptionHandlingWith(consumer, (ch, q, c, ct) =>
+ ch.BasicPublishAsync("", q, _encoding.GetBytes("msg")));
+ }
+
+ protected async Task TestExceptionHandlingWith(IBasicConsumer consumer,
+ Func action)
+ {
+ var waitSpan = TimeSpan.FromSeconds(2);
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var cts = new CancellationTokenSource(waitSpan);
+ cts.Token.Register(() => tcs.TrySetResult(false));
+
+ string q = await _channel.QueueDeclareAsync(string.Empty, false, false, true, false, null);
+ _channel.CallbackException += (ch, evt) =>
+ {
+ if (evt.Exception == TestException)
+ {
+ tcs.SetResult(true);
+ }
+ };
+
+ string tag = await _channel.BasicConsumeAsync(q, true, string.Empty, false, false, null, consumer);
+ await action(_channel, q, consumer, tag);
+ Assert.True(await tcs.Task);
}
private class ConsumerFailingOnDelivery : AsyncEventingBasicConsumer
@@ -126,7 +120,7 @@ public override Task HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
return Task.FromException(TestException);
}
diff --git a/projects/Test/AsyncIntegration/TestBasicGetAsync.cs b/projects/Test/AsyncIntegration/TestBasicGetAsync.cs
new file mode 100644
index 0000000000..21e7cfc505
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestBasicGetAsync.cs
@@ -0,0 +1,62 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestBasicGetAsync : AsyncIntegrationFixture
+ {
+ public TestBasicGetAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task TestBasicGet()
+ {
+ const string msg = "for async basic.get";
+
+ QueueDeclareOk queueResult = await _channel.QueueDeclareAsync(string.Empty, false, true, true, true, null);
+ string queueName = queueResult.QueueName;
+
+ await _channel.BasicPublishAsync(string.Empty, queueName, _encoding.GetBytes(msg), true);
+
+ BasicGetResult getResult = await _channel.BasicGetAsync(queueName, true);
+ Assert.Equal(msg, _encoding.GetString(getResult.Body));
+
+ QueueDeclareOk queueResultPassive = await _channel.QueueDeclareAsync(queueName, true, true, true, true, null);
+ Assert.Equal((uint)0, queueResultPassive.MessageCount);
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/TestBasicPublishAsync.cs b/projects/Test/AsyncIntegration/TestBasicPublishAsync.cs
new file mode 100644
index 0000000000..7bfa6d4275
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestBasicPublishAsync.cs
@@ -0,0 +1,74 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestBasicPublishAsync : AsyncIntegrationFixture
+ {
+ public TestBasicPublishAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task TestQueuePurgeAsync()
+ {
+ const int messageCount = 1024;
+
+ var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ await _channel.ConfirmSelectAsync();
+
+ QueueDeclareOk q = await _channel.QueueDeclareAsync(string.Empty, false, false, true, false, null);
+ string queueName = q.QueueName;
+
+ var publishTask = Task.Run(async () =>
+ {
+ for (int i = 0; i < messageCount; i++)
+ {
+ byte[] body = _encoding.GetBytes(Guid.NewGuid().ToString());
+ await _channel.BasicPublishAsync(string.Empty, queueName, body);
+ }
+ publishSyncSource.SetResult(true);
+ });
+
+ await _channel.WaitForConfirmsOrDieAsync();
+ Assert.True(await publishSyncSource.Task);
+
+ Assert.Equal((uint)messageCount, await _channel.QueuePurgeAsync(queueName));
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/TestConcurrentAccessWithSharedConnectionAsync.cs b/projects/Test/AsyncIntegration/TestConcurrentAccessWithSharedConnectionAsync.cs
new file mode 100644
index 0000000000..e085412eb7
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestConcurrentAccessWithSharedConnectionAsync.cs
@@ -0,0 +1,161 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestConcurrentAccessWithSharedConnectionAsync : AsyncIntegrationFixture
+ {
+ private const ushort _messageCount = 200;
+
+ public TestConcurrentAccessWithSharedConnectionAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ public override async Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ _conn.ConnectionShutdown += HandleConnectionShutdown;
+ await _channel.CloseAsync();
+ }
+
+ [Fact]
+ public Task TestConcurrentChannelOpenAndPublishingWithBlankMessagesAsync()
+ {
+ return TestConcurrentChannelOpenAndPublishingWithBodyAsync(Array.Empty(), 30);
+ }
+
+ [Fact]
+ public Task TestConcurrentChannelOpenAndPublishingSize64Async()
+ {
+ return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(64);
+ }
+
+ [Fact]
+ public Task TestConcurrentChannelOpenAndPublishingSize256Async()
+ {
+ return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(256);
+ }
+
+ [Fact]
+ public Task TestConcurrentChannelOpenAndPublishingSize1024Async()
+ {
+ return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(1024);
+ }
+
+ private Task TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(ushort length, int iterations = 30)
+ {
+ byte[] body = GetRandomBody(length);
+ return TestConcurrentChannelOpenAndPublishingWithBodyAsync(body, iterations);
+ }
+
+ private Task TestConcurrentChannelOpenAndPublishingWithBodyAsync(byte[] body, int iterations)
+ {
+ return TestConcurrentChannelOperationsAsync(async (conn) =>
+ {
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var tokenSource = new CancellationTokenSource(LongWaitSpan);
+ tokenSource.Token.Register(() =>
+ {
+ tcs.TrySetResult(false);
+ });
+
+ using (IChannel ch = await _conn.CreateChannelAsync())
+ {
+ ch.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(ch, ea, () =>
+ {
+ tcs.SetResult(false);
+ });
+ };
+
+ await ch.ConfirmSelectAsync();
+
+ ch.BasicAcks += (object sender, BasicAckEventArgs e) =>
+ {
+ if (e.DeliveryTag >= _messageCount)
+ {
+ tcs.SetResult(true);
+ }
+ };
+
+ ch.BasicNacks += (object sender, BasicNackEventArgs e) =>
+ {
+ tcs.SetResult(false);
+ _output.WriteLine($"channel #{ch.ChannelNumber} saw a nack, deliveryTag: {e.DeliveryTag}, multiple: {e.Multiple}");
+ };
+
+ QueueDeclareOk q = await ch.QueueDeclareAsync(queue: string.Empty, passive: false, durable: false, exclusive: true, autoDelete: true, arguments: null);
+ for (ushort j = 0; j < _messageCount; j++)
+ {
+ await ch.BasicPublishAsync("", q.QueueName, body, mandatory: true);
+ }
+
+ Assert.True(await tcs.Task);
+ }
+ }, iterations);
+ }
+
+ private Task TestConcurrentChannelOperationsAsync(Func actions, int iterations)
+ {
+ return TestConcurrentChannelOperationsAsync(actions, iterations, LongWaitSpan);
+ }
+
+ private async Task TestConcurrentChannelOperationsAsync(Func action, int iterations, TimeSpan timeout)
+ {
+ var tasks = new List();
+ for (int i = 0; i < _processorCount; i++)
+ {
+ for (int j = 0; j < iterations; j++)
+ {
+ tasks.Add(action(_conn));
+ }
+ }
+ Task t = Task.WhenAll(tasks);
+ await t;
+ Assert.Equal(TaskStatus.RanToCompletion, t.Status);
+
+ // incorrect frame interleaving in these tests will result
+ // in an unrecoverable connection-level exception, thus
+ // closing the connection
+ Assert.True(_conn.IsOpen);
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/TestConfirmSelectAsync.cs b/projects/Test/AsyncIntegration/TestConfirmSelectAsync.cs
new file mode 100644
index 0000000000..6e53e0852a
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestConfirmSelectAsync.cs
@@ -0,0 +1,69 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestConfirmSelectAsync : AsyncIntegrationFixture
+ {
+ public TestConfirmSelectAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task TestConfirmSelectIdempotency()
+ {
+ await _channel.ConfirmSelectAsync();
+ Assert.Equal(1ul, _channel.NextPublishSeqNo);
+ await Publish();
+ Assert.Equal(2ul, _channel.NextPublishSeqNo);
+ await Publish();
+ Assert.Equal(3ul, _channel.NextPublishSeqNo);
+
+ await _channel.ConfirmSelectAsync();
+ await Publish();
+ Assert.Equal(4ul, _channel.NextPublishSeqNo);
+ await Publish();
+ Assert.Equal(5ul, _channel.NextPublishSeqNo);
+ await Publish();
+ Assert.Equal(6ul, _channel.NextPublishSeqNo);
+ }
+
+ private ValueTask Publish()
+ {
+ return _channel.BasicPublishAsync("", "amq.fanout", _encoding.GetBytes("message"));
+ }
+ }
+}
diff --git a/projects/Test/AsyncIntegration/TestExchangeDeclareAsync.cs b/projects/Test/AsyncIntegration/TestExchangeDeclareAsync.cs
new file mode 100644
index 0000000000..70b8c59182
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestExchangeDeclareAsync.cs
@@ -0,0 +1,103 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestExchangeDeclareAsync : AsyncIntegrationFixture
+ {
+ private readonly Random _rnd = new Random();
+
+ public TestExchangeDeclareAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task TestConcurrentExchangeDeclareAndBindAsync()
+ {
+ var exchangeNames = new ConcurrentBag();
+ var ts = new List();
+ NotSupportedException nse = null;
+ for (int i = 0; i < 256; i++)
+ {
+ async Task f()
+ {
+ try
+ {
+ await Task.Delay(_rnd.Next(5, 50));
+ string exchangeName = GenerateExchangeName();
+ await _channel.ExchangeDeclareAsync(exchange: exchangeName, type: "fanout", passive: false, false, false, null);
+ await _channel.ExchangeBindAsync(destination: "amq.fanout", source: exchangeName, routingKey: "unused", null);
+ exchangeNames.Add(exchangeName);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ }
+ var t = Task.Run(f);
+ ts.Add(t);
+ }
+
+ await Task.WhenAll(ts);
+ Assert.Null(nse);
+ ts.Clear();
+
+ foreach (string exchangeName in exchangeNames)
+ {
+ async Task f()
+ {
+ try
+ {
+ await Task.Delay(_rnd.Next(5, 50));
+ await _channel.ExchangeUnbindAsync(destination: "amq.fanout", source: exchangeName, routingKey: "unused", null);
+ await _channel.ExchangeDeleteAsync(exchange: exchangeName, ifUnused: false);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ }
+ var t = Task.Run(f);
+ ts.Add(t);
+ }
+
+ await Task.WhenAll(ts);
+ Assert.Null(nse);
+ }
+ }
+}
diff --git a/projects/Unit/TestExtensions.cs b/projects/Test/AsyncIntegration/TestExtensionsAsync.cs
similarity index 52%
rename from projects/Unit/TestExtensions.cs
rename to projects/Test/AsyncIntegration/TestExtensionsAsync.cs
index 2392011edb..1b484e167d 100644
--- a/projects/Unit/TestExtensions.cs
+++ b/projects/Test/AsyncIntegration/TestExtensionsAsync.cs
@@ -31,58 +31,62 @@
using System;
using System.Threading.Tasks;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.AsyncIntegration
{
- public class TestExtensions : IntegrationFixture
+ public class TestExtensionsAsync : AsyncIntegrationFixture
{
- public TestExtensions(ITestOutputHelper output) : base(output)
+ public TestExtensionsAsync(ITestOutputHelper output) : base(output)
{
}
[Fact]
public async Task TestConfirmBarrier()
{
- _channel.ConfirmSelect();
+ await _channel.ConfirmSelectAsync();
for (int i = 0; i < 10; i++)
{
- _channel.BasicPublish("", string.Empty);
+ await _channel.BasicPublishAsync("", string.Empty);
}
Assert.True(await _channel.WaitForConfirmsAsync());
}
[Fact]
- public async Task TestConfirmBeforeWait()
+ public Task TestConfirmBeforeWait()
{
- await Assert.ThrowsAsync(async () => await _channel.WaitForConfirmsAsync());
+ return Assert.ThrowsAsync(() => _channel.WaitForConfirmsAsync());
}
[Fact]
- public async Task TestExchangeBinding()
+ public async Task TestExchangeBindingAsync()
{
- _channel.ConfirmSelect();
+ await _channel.ConfirmSelectAsync();
- _channel.ExchangeDeclare("src", ExchangeType.Direct, false, false, null);
- _channel.ExchangeDeclare("dest", ExchangeType.Direct, false, false, null);
- string queue = _channel.QueueDeclare();
+ await _channel.ExchangeDeclareAsync("src", ExchangeType.Direct, false, false, false, null);
+ await _channel.ExchangeDeclareAsync("dest", ExchangeType.Direct, false, false, false, null);
+ string queue = await _channel.QueueDeclareAsync(string.Empty, false, false, true, false, null);
- _channel.ExchangeBind("dest", "src", string.Empty);
- _channel.ExchangeBind("dest", "src", string.Empty);
- _channel.QueueBind(queue, "dest", string.Empty);
+ await _channel.ExchangeBindAsync("dest", "src", string.Empty, null);
+ await _channel.ExchangeBindAsync("dest", "src", string.Empty, null);
+ await _channel.QueueBindAsync(queue, "dest", string.Empty, null);
- _channel.BasicPublish("src", string.Empty);
+ await _channel.BasicPublishAsync("src", string.Empty);
await _channel.WaitForConfirmsAsync();
- Assert.NotNull(_channel.BasicGet(queue, true));
+ Assert.NotNull(await _channel.BasicGetAsync(queue, true));
- _channel.ExchangeUnbind("dest", "src", string.Empty);
- _channel.BasicPublish("src", string.Empty);
+ await _channel.ExchangeUnbindAsync("dest", "src", string.Empty, null);
+ await _channel.BasicPublishAsync("src", string.Empty);
await _channel.WaitForConfirmsAsync();
- Assert.Null(_channel.BasicGet(queue, true));
- _channel.ExchangeDelete("src");
- _channel.ExchangeDelete("dest");
+ // TODO LRB rabbitmq/rabbitmq-dotnet-client#1347
+ // THIS DOES NOT WORK
+ // Assert.Null(await _channel.BasicGetAsync(queue, true));
+
+ await _channel.ExchangeDeleteAsync("src", false);
+ await _channel.ExchangeDeleteAsync("dest", false);
}
}
}
diff --git a/projects/Test/AsyncIntegration/TestFloodPublishingAsync.cs b/projects/Test/AsyncIntegration/TestFloodPublishingAsync.cs
new file mode 100644
index 0000000000..cf10ac31c1
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestFloodPublishingAsync.cs
@@ -0,0 +1,213 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestFloodPublishingAsync : AsyncIntegrationFixture
+ {
+ private static readonly TimeSpan TenSeconds = TimeSpan.FromSeconds(10);
+ private readonly byte[] _body = new byte[2048];
+
+ public TestFloodPublishingAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ public override Task InitializeAsync()
+ {
+ // NB: each test sets itself up
+ return Task.CompletedTask;
+ }
+
+ [Fact]
+ public async Task TestUnthrottledFloodPublishingAsync()
+ {
+ _connFactory = CreateConnectionFactory();
+ _connFactory.RequestedHeartbeat = TimeSpan.FromSeconds(60);
+ _connFactory.AutomaticRecoveryEnabled = false;
+ _conn = await _connFactory.CreateConnectionAsync();
+ Assert.IsNotType(_conn);
+ _channel = await _conn.CreateChannelAsync();
+
+ _conn.ConnectionShutdown += (_, ea) =>
+ {
+ HandleConnectionShutdown(_conn, ea, () =>
+ {
+ Assert.Fail("Unexpected connection shutdown!");
+ });
+ };
+
+ _channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(_channel, ea, () =>
+ {
+ Assert.Fail("Unexpected channel shutdown!");
+ });
+ };
+
+ var stopwatch = Stopwatch.StartNew();
+ int i = 0;
+ try
+ {
+ for (i = 0; i < 65535 * 64; i++)
+ {
+ if (i % 65536 == 0)
+ {
+ if (stopwatch.Elapsed > TenSeconds)
+ {
+ break;
+ }
+ }
+
+ await _channel.BasicPublishAsync(CachedString.Empty, CachedString.Empty, _body);
+ }
+ }
+ finally
+ {
+ stopwatch.Stop();
+ }
+
+ Assert.True(_conn.IsOpen);
+ }
+
+ [Fact]
+ public async Task TestMultithreadFloodPublishingAsync()
+ {
+ _connFactory = CreateConnectionFactory();
+ _connFactory.DispatchConsumersAsync = true;
+ _connFactory.AutomaticRecoveryEnabled = false;
+
+ _conn = await _connFactory.CreateConnectionAsync();
+ Assert.IsNotType(_conn);
+ _channel = await _conn.CreateChannelAsync();
+
+ string message = "Hello from test TestMultithreadFloodPublishing";
+ byte[] sendBody = _encoding.GetBytes(message);
+ int publishCount = 4096; // TODO LRB 1024?
+ int receivedCount = 0;
+
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ _conn.ConnectionShutdown += (o, ea) =>
+ {
+ HandleConnectionShutdown(_conn, ea, () =>
+ {
+ receivedCount = -1;
+ tcs.SetResult(false);
+ });
+ };
+
+ _channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(_channel, ea, () =>
+ {
+ receivedCount = -1;
+ tcs.SetResult(false);
+ });
+ };
+
+ QueueDeclareOk q = await _channel.QueueDeclareAsync(queue: string.Empty,
+ passive: false, durable: false, exclusive: true, autoDelete: false, arguments: null);
+ string queueName = q.QueueName;
+
+ Task pub = Task.Run(async () =>
+ {
+ bool stop = false;
+ using (IChannel pubCh = await _conn.CreateChannelAsync())
+ {
+ await pubCh.ConfirmSelectAsync();
+
+ pubCh.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(pubCh, ea, () =>
+ {
+ stop = true;
+ tcs.SetResult(false);
+ });
+ };
+
+ for (int i = 0; i < publishCount && false == stop; i++)
+ {
+ await pubCh.BasicPublishAsync(string.Empty, queueName, sendBody, true);
+ }
+
+ await pubCh.WaitForConfirmsOrDieAsync();
+ }
+ });
+
+ var cts = new CancellationTokenSource(WaitSpan);
+ cts.Token.Register(() =>
+ {
+ tcs.TrySetResult(false);
+ });
+
+ using (IChannel consumeCh = await _conn.CreateChannelAsync())
+ {
+ consumeCh.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(consumeCh, ea, () =>
+ {
+ tcs.SetResult(false);
+ });
+ };
+
+ var consumer = new AsyncEventingBasicConsumer(consumeCh);
+ consumer.Received += async (o, a) =>
+ {
+ string receivedMessage = _encoding.GetString(a.Body);
+ Assert.Equal(message, receivedMessage);
+ if (Interlocked.Increment(ref receivedCount) == publishCount)
+ {
+ tcs.SetResult(true);
+ }
+ await Task.Yield();
+ };
+
+ await consumeCh.BasicConsumeAsync(queue: queueName, autoAck: true,
+ consumerTag: string.Empty, noLocal: false, exclusive: false,
+ arguments: null, consumer: consumer);
+
+ Assert.True(await tcs.Task);
+ }
+
+ await pub;
+ Assert.Equal(publishCount, receivedCount);
+ }
+ }
+}
diff --git a/projects/Unit/TestMessageCount.cs b/projects/Test/AsyncIntegration/TestMessageCountAsync.cs
similarity index 79%
rename from projects/Unit/TestMessageCount.cs
rename to projects/Test/AsyncIntegration/TestMessageCountAsync.cs
index ccee33b724..55417d0732 100644
--- a/projects/Unit/TestMessageCount.cs
+++ b/projects/Test/AsyncIntegration/TestMessageCountAsync.cs
@@ -30,26 +30,27 @@
//---------------------------------------------------------------------------
using System.Threading.Tasks;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.AsyncIntegration
{
- public class TestMessageCount : IntegrationFixture
+ public class TestMessageCountAsync : AsyncIntegrationFixture
{
- public TestMessageCount(ITestOutputHelper output) : base(output)
+ public TestMessageCountAsync(ITestOutputHelper output) : base(output)
{
}
[Fact]
public async Task TestMessageCountMethod()
{
- _channel.ConfirmSelect();
+ await _channel.ConfirmSelectAsync();
string q = GenerateQueueName();
- _channel.QueueDeclare(queue: q, durable: false, exclusive: true, autoDelete: false, arguments: null);
+ await _channel.QueueDeclareAsync(queue: q, passive: false, durable: false, exclusive: true, autoDelete: false, arguments: null);
Assert.Equal(0u, _channel.MessageCount(q));
- _channel.BasicPublish("", q, _encoding.GetBytes("msg"));
+ await _channel.BasicPublishAsync("", q, _encoding.GetBytes("msg"));
await _channel.WaitForConfirmsAsync();
Assert.Equal(1u, _channel.MessageCount(q));
}
diff --git a/projects/Test/AsyncIntegration/TestPassiveDeclareAsync.cs b/projects/Test/AsyncIntegration/TestPassiveDeclareAsync.cs
new file mode 100644
index 0000000000..6d4f85f156
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestPassiveDeclareAsync.cs
@@ -0,0 +1,67 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Exceptions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestPassiveDeclareAsync : AsyncIntegrationFixture
+ {
+ public TestPassiveDeclareAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public Task TestPassiveExchangeDeclareWhenExchangeDoesNotExist()
+ {
+ return Assert.ThrowsAsync(() =>
+ {
+ var r = _channel.ExchangeDeclareAsync(Guid.NewGuid().ToString(), ExchangeType.Fanout, true, false, false, null);
+ return r.AsTask();
+ });
+ }
+
+ [Fact]
+ public Task TestPassiveQueueDeclareWhenQueueDoesNotExist()
+ {
+ return Assert.ThrowsAsync(() =>
+ {
+ var r = _channel.QueueDeclareAsync(Guid.NewGuid().ToString(), true, false, false, false, null);
+ return r.AsTask();
+ });
+ }
+ }
+}
diff --git a/projects/Unit/TestPublishSharedChannel.cs b/projects/Test/AsyncIntegration/TestPublishSharedChannelAsync.cs
similarity index 54%
rename from projects/Unit/TestPublishSharedChannel.cs
rename to projects/Test/AsyncIntegration/TestPublishSharedChannelAsync.cs
index aab3f9db4c..eb25891339 100644
--- a/projects/Unit/TestPublishSharedChannel.cs
+++ b/projects/Test/AsyncIntegration/TestPublishSharedChannelAsync.cs
@@ -30,14 +30,14 @@
//---------------------------------------------------------------------------
using System;
-using System.Threading;
using System.Threading.Tasks;
+using RabbitMQ.Client;
using Xunit;
+using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.AsyncIntegration
{
- [Collection("IntegrationFixture")]
- public class TestPublishSharedChannel
+ public class TestPublishSharedChannelAsync : AsyncIntegrationFixture
{
private const string QueueName = "TestPublishSharedChannel_Queue";
private static readonly CachedString ExchangeName = new CachedString("TestPublishSharedChannel_Ex");
@@ -49,62 +49,60 @@ public class TestPublishSharedChannel
private Exception _raisedException;
+ public TestPublishSharedChannelAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ public override Task InitializeAsync()
+ {
+ // NB: test sets up its own factory, conns, channels
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ return Task.CompletedTask;
+ }
+
+ public override Task DisposeAsync()
+ {
+ return Task.CompletedTask;
+ }
+
[Fact]
public async Task MultiThreadPublishOnSharedChannel()
{
- // Arrange
- var connFactory = new ConnectionFactory
- {
- RequestedHeartbeat = TimeSpan.FromSeconds(60),
- AutomaticRecoveryEnabled = false
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = false;
- using (IConnection conn = connFactory.CreateConnection())
+ using (IConnection conn = await cf.CreateConnectionAsync())
{
- conn.ConnectionShutdown += (_, args) =>
- {
- if (args.Initiator != ShutdownInitiator.Application)
- {
- Assert.Fail("Unexpected connection shutdown!");
- }
- };
+ Assert.IsNotType(conn);
+ conn.ConnectionShutdown += HandleConnectionShutdown;
- using (IChannel channel = conn.CreateChannel())
+ using (IChannel channel = await conn.CreateChannelAsync())
{
- channel.ExchangeDeclare(ExchangeName.Value, "topic", durable: false, autoDelete: true);
- channel.QueueDeclare(QueueName, false, false, true, null);
- channel.QueueBind(QueueName, ExchangeName.Value, PublishKey.Value, null);
-
- // Act
- var pubTask = Task.Run(() => NewFunction(channel));
- var pubTask2 = Task.Run(() => NewFunction(channel));
+ channel.ChannelShutdown += HandleChannelShutdown;
+ await channel.ExchangeDeclareAsync(ExchangeName.Value, ExchangeType.Topic, passive: false, durable: false, autoDelete: true, arguments: null);
+ await channel.QueueDeclareAsync(QueueName, false, false, false, true, null);
+ await channel.QueueBindAsync(QueueName, ExchangeName.Value, PublishKey.Value, null);
- await Task.WhenAll(pubTask, pubTask2);
- }
- }
-
- // Assert
- Assert.Null(_raisedException);
-
- void NewFunction(IChannel channel)
- {
- try
- {
- for (int i = 0; i < Loops; i++)
+ try
{
- for (int j = 0; j < Repeats; j++)
+ for (int i = 0; i < Loops; i++)
{
- channel.BasicPublish(ExchangeName, PublishKey, _body, false);
+ for (int j = 0; j < Repeats; j++)
+ {
+ await channel.BasicPublishAsync(ExchangeName, PublishKey, _body, false);
+ }
}
-
- Thread.Sleep(1);
}
- }
- catch (Exception e)
- {
- _raisedException = e;
+ catch (Exception e)
+ {
+ _raisedException = e;
+ }
}
}
+
+ Assert.Null(_raisedException);
}
}
}
diff --git a/projects/Unit/TestPublisherConfirms.cs b/projects/Test/AsyncIntegration/TestPublisherConfirmsAsync.cs
similarity index 62%
rename from projects/Unit/TestPublisherConfirms.cs
rename to projects/Test/AsyncIntegration/TestPublisherConfirmsAsync.cs
index 4f910b8784..97a6360c24 100644
--- a/projects/Unit/TestPublisherConfirms.cs
+++ b/projects/Test/AsyncIntegration/TestPublisherConfirmsAsync.cs
@@ -33,40 +33,37 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using RabbitMQ.Client;
using RabbitMQ.Client.Impl;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+// TODO rabbitmq/rabbitmq-dotnet-client#1347
+// Test mixed sync/async with WaitForConfirmsAsync
+namespace Test.AsyncIntegration
{
- public class TestPublisherConfirms : IntegrationFixture
+ public class TestPublisherConfirmsAsync : AsyncIntegrationFixture
{
- private const string QueueName = "RabbitMQ.Client.Unit.TestPublisherConfirms";
- private readonly byte[] _body = new byte[4096];
+ private readonly byte[] _messageBody;
- public TestPublisherConfirms(ITestOutputHelper output) : base(output)
+ public TestPublisherConfirmsAsync(ITestOutputHelper output) : base(output)
{
-#if NET6_0_OR_GREATER
- Random.Shared.NextBytes(_body);
-#else
- var rnd = new Random();
- rnd.NextBytes(_body);
-#endif
+ _messageBody = GetRandomBody(4096);
}
[Fact]
- public void TestWaitForConfirmsWithoutTimeout()
+ public Task TestWaitForConfirmsWithoutTimeoutAsync()
{
- TestWaitForConfirms(200, async (ch) =>
+ return TestWaitForConfirmsAsync(200, async (ch) =>
{
Assert.True(await ch.WaitForConfirmsAsync());
});
}
[Fact]
- public void TestWaitForConfirmsWithTimeout()
+ public Task TestWaitForConfirmsWithTimeout()
{
- TestWaitForConfirms(200, async (ch) =>
+ return TestWaitForConfirmsAsync(200, async (ch) =>
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4)))
{
@@ -76,12 +73,12 @@ public void TestWaitForConfirmsWithTimeout()
}
[Fact]
- public void TestWaitForConfirmsWithTimeout_MightThrowTaskCanceledException()
+ public async Task TestWaitForConfirmsWithTimeoutAsync_MightThrowTaskCanceledException()
{
bool waitResult = false;
- bool sawTaskCanceled = false;
+ bool sawException = false;
- TestWaitForConfirms(10000, async (ch) =>
+ Task t = TestWaitForConfirmsAsync(10000, async (ch) =>
{
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1)))
{
@@ -89,23 +86,25 @@ public void TestWaitForConfirmsWithTimeout_MightThrowTaskCanceledException()
{
waitResult = await ch.WaitForConfirmsAsync(cts.Token);
}
- catch (TaskCanceledException)
+ catch
{
- sawTaskCanceled = true;
+ sawException = true;
}
}
});
- if (waitResult == false && sawTaskCanceled == false)
+ await t;
+
+ if (waitResult == false && sawException == false)
{
- Assert.Fail("test failed, both waitResult and sawTaskCanceled are still false");
+ Assert.Fail("test failed, both waitResult and sawException are still false");
}
}
[Fact]
- public void TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_ReturnFalse()
+ public Task TestWaitForConfirmsWithTimeoutAsync_MessageNacked_WaitingHasTimedout_ReturnFalse()
{
- TestWaitForConfirms(2000, async (ch) =>
+ return TestWaitForConfirmsAsync(2000, async (ch) =>
{
IChannel actualChannel = ((AutorecoveringChannel)ch).InnerChannel;
actualChannel
@@ -121,13 +120,14 @@ public void TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_Retu
}
[Fact]
- public async Task TestWaitForConfirmsWithEvents()
+ public async Task TestWaitForConfirmsWithEventsAsync()
{
- using (IChannel ch = _conn.CreateChannel())
+ string queueName = string.Format("{0}:{1}", _testDisplayName, Guid.NewGuid());
+ using (IChannel ch = await _conn.CreateChannelAsync())
{
- ch.ConfirmSelect();
+ await ch.ConfirmSelectAsync();
+ await ch.QueueDeclareAsync(queue: queueName, passive: false, durable: false, exclusive: false, autoDelete: false, arguments: null);
- ch.QueueDeclare(QueueName);
int n = 200;
// number of event handler invocations
int c = 0;
@@ -136,12 +136,14 @@ public async Task TestWaitForConfirmsWithEvents()
{
Interlocked.Increment(ref c);
};
+
try
{
for (int i = 0; i < n; i++)
{
- ch.BasicPublish("", QueueName, _encoding.GetBytes("msg"));
+ await ch.BasicPublishAsync("", queueName, _encoding.GetBytes("msg"));
}
+
await ch.WaitForConfirmsAsync();
// Note: number of event invocations is not guaranteed
@@ -152,32 +154,33 @@ public async Task TestWaitForConfirmsWithEvents()
}
finally
{
- ch.QueueDelete(QueueName);
+ await ch.QueueDeleteAsync(queue: queueName, ifUnused: false, ifEmpty: false);
}
}
}
- protected void TestWaitForConfirms(int numberOfMessagesToPublish, Action fn)
+ private async Task TestWaitForConfirmsAsync(int numberOfMessagesToPublish, Func fn)
{
- using (IChannel ch = _conn.CreateChannel())
+ string queueName = string.Format("{0}:{1}", _testDisplayName, Guid.NewGuid());
+ using (IChannel ch = await _conn.CreateChannelAsync())
{
var props = new BasicProperties { Persistent = true };
- ch.ConfirmSelect();
- ch.QueueDeclare(QueueName);
+ await ch.ConfirmSelectAsync();
+ await ch.QueueDeclareAsync(queue: queueName, passive: false, durable: false, exclusive: false, autoDelete: false, arguments: null);
for (int i = 0; i < numberOfMessagesToPublish; i++)
{
- ch.BasicPublish(exchange: "", routingKey: QueueName, body: _body, mandatory: true, basicProperties: props);
+ await ch.BasicPublishAsync(exchange: string.Empty, routingKey: queueName, body: _messageBody, mandatory: true, basicProperties: props);
}
try
{
- fn(ch);
+ await fn(ch);
}
finally
{
- ch.QueueDelete(QueueName);
+ await ch.QueueDeleteAsync(queue: queueName, ifUnused: false, ifEmpty: false);
}
}
}
diff --git a/projects/Test/AsyncIntegration/TestQueueDeclareAsync.cs b/projects/Test/AsyncIntegration/TestQueueDeclareAsync.cs
new file mode 100644
index 0000000000..da7607f43e
--- /dev/null
+++ b/projects/Test/AsyncIntegration/TestQueueDeclareAsync.cs
@@ -0,0 +1,147 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.AsyncIntegration
+{
+ public class TestQueueDeclareAsync : AsyncIntegrationFixture
+ {
+ public TestQueueDeclareAsync(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async void TestQueueDeclare()
+ {
+ string q = GenerateQueueName();
+
+ QueueDeclareOk declareResult = await _channel.QueueDeclareAsync(q, passive: false, false, false, false, null);
+ Assert.Equal(q, declareResult.QueueName);
+
+ QueueDeclareOk passiveDeclareResult = await _channel.QueueDeclareAsync(q, passive: true, false, false, false, null);
+ Assert.Equal(q, passiveDeclareResult.QueueName);
+ }
+
+ [Fact]
+ public async void TestConcurrentQueueDeclareAndBindAsync()
+ {
+ bool sawShutdown = false;
+
+ _conn.ConnectionShutdown += (o, ea) =>
+ {
+ if (ea.Initiator == ShutdownInitiator.Peer)
+ {
+ sawShutdown = true;
+ }
+ };
+
+ _channel.ChannelShutdown += (o, ea) =>
+ {
+ HandleChannelShutdown(_channel, ea, () =>
+ {
+ if (ea.Initiator == ShutdownInitiator.Peer)
+ {
+ sawShutdown = true;
+ }
+ });
+ };
+
+ var ts = new List();
+ var qs = new ConcurrentBag();
+
+ NotSupportedException nse = null;
+ for (int i = 0; i < 256; i++)
+ {
+ async Task f()
+ {
+ try
+ {
+ // sleep for a random amount of time to increase the chances
+ // of thread interleaving. MK.
+ await Task.Delay(S_Random.Next(5, 50));
+ QueueDeclareOk r = await _channel.QueueDeclareAsync(queue: string.Empty, passive: false, false, false, false, null);
+ string queueName = r.QueueName;
+ await _channel.QueueBindAsync(queue: queueName, exchange: "amq.fanout", routingKey: queueName, null);
+ qs.Add(queueName);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ }
+ var t = Task.Run(f);
+ ts.Add(t);
+ }
+
+ await Task.WhenAll(ts);
+ Assert.Null(nse);
+ ts.Clear();
+
+ nse = null;
+ foreach (string q in qs)
+ {
+ async Task f()
+ {
+ string qname = q;
+ try
+ {
+ await Task.Delay(S_Random.Next(5, 50));
+
+ QueueDeclareOk r = await _channel.QueueDeclareAsync(qname, passive: true, false, false, false, null);
+ Assert.Equal(qname, r.QueueName);
+
+ await _channel.QueueUnbindAsync(queue: qname, exchange: "amq.fanout", routingKey: qname, null);
+
+ uint deletedMessageCount = await _channel.QueueDeleteAsync(qname, false, false);
+ Assert.Equal((uint)0, deletedMessageCount);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ }
+ var t = Task.Run(f);
+ ts.Add(t);
+ }
+
+ await Task.WhenAll(ts);
+ Assert.Null(nse);
+ Assert.False(sawShutdown);
+ }
+ }
+}
diff --git a/projects/Test/Common/Common.csproj b/projects/Test/Common/Common.csproj
new file mode 100644
index 0000000000..215713acfa
--- /dev/null
+++ b/projects/Test/Common/Common.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net6.0;net472
+
+
+
+ net6.0
+
+
+
+ ../../rabbit.snk
+ true
+ latest
+ 7.0
+ false
+
+
+
+
+
+ <_Parameter1>Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+ <_Parameter1>SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/Test/Common/IntegrationFixture.cs b/projects/Test/Common/IntegrationFixture.cs
new file mode 100644
index 0000000000..97745bf938
--- /dev/null
+++ b/projects/Test/Common/IntegrationFixture.cs
@@ -0,0 +1,365 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Framing.Impl;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test
+{
+ public class IntegrationFixture : IDisposable
+ {
+ private static bool s_isRunningInCI = false;
+ private static bool s_isWindows = false;
+ private static int _connectionIdx = 0;
+
+ protected readonly RabbitMQCtl _rabbitMQCtl;
+
+ protected ConnectionFactory _connFactory;
+ protected IConnection _conn;
+ protected IChannel _channel;
+
+ protected static readonly Encoding _encoding = new UTF8Encoding();
+ protected static readonly int _processorCount = Environment.ProcessorCount;
+
+ protected readonly ITestOutputHelper _output;
+ protected readonly string _testDisplayName;
+
+ public static readonly TimeSpan WaitSpan;
+ public static readonly TimeSpan LongWaitSpan;
+ public static readonly TimeSpan RecoveryInterval = TimeSpan.FromSeconds(2);
+ public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5);
+ public static readonly Random S_Random;
+
+ static IntegrationFixture()
+ {
+ S_Random = new Random();
+ InitIsRunningInCI();
+ InitIsWindows();
+
+ int threadCount;
+ if (s_isRunningInCI)
+ {
+ threadCount = _processorCount * 16;
+ WaitSpan = TimeSpan.FromSeconds(60);
+ LongWaitSpan = TimeSpan.FromSeconds(120);
+ }
+ else
+ {
+ // Assuming that dev machines have more cores
+ threadCount = _processorCount * 8;
+ WaitSpan = TimeSpan.FromSeconds(30);
+ LongWaitSpan = TimeSpan.FromSeconds(60);
+ }
+
+ ThreadPool.SetMinThreads(threadCount, threadCount);
+ }
+
+ public IntegrationFixture(ITestOutputHelper output)
+ {
+ _output = output;
+ _rabbitMQCtl = new RabbitMQCtl(_output);
+
+ var type = _output.GetType();
+ var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
+ var test = (ITest)testMember.GetValue(output);
+ _testDisplayName = test.DisplayName
+ .Replace("Test.", string.Empty)
+ .Replace("AsyncIntegration.", "AI.")
+ .Replace("Integration.", "I.")
+ .Replace("SequentialIntegration.", "SI.");
+
+ SetUp();
+ }
+
+ protected virtual void SetUp()
+ {
+ if (_connFactory == null)
+ {
+ _connFactory = CreateConnectionFactory();
+ }
+
+ if (_conn == null)
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+ }
+ }
+
+ public virtual void Dispose()
+ {
+ if (_channel != null)
+ {
+ _channel.Dispose();
+ }
+
+ if (_conn != null)
+ {
+ _conn.Dispose();
+ }
+
+ TearDown();
+ }
+
+ protected virtual void TearDown()
+ {
+ }
+
+ protected bool IsRunningInCI
+ {
+ get { return s_isRunningInCI; }
+ }
+
+ protected bool IsWindows
+ {
+ get { return s_isWindows; }
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnection(IList hostnames)
+ {
+ return CreateAutorecoveringConnection(RecoveryInterval, hostnames);
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan interval, IList hostnames)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ // tests that use this helper will likely list unreachable hosts;
+ // make sure we time out quickly on those
+ cf.RequestedConnectionTimeout = TimeSpan.FromSeconds(1);
+ cf.NetworkRecoveryInterval = interval;
+ return (AutorecoveringConnection)cf.CreateConnection(hostnames);
+ }
+
+ protected void WithTemporaryChannel(Action action)
+ {
+ IChannel channel = _conn.CreateChannel();
+
+ try
+ {
+ action(channel);
+ }
+ finally
+ {
+ channel.Abort();
+ }
+ }
+
+ protected string GenerateExchangeName()
+ {
+ return $"{_testDisplayName}-exchange-{Guid.NewGuid()}";
+ }
+
+ protected string GenerateQueueName()
+ {
+ return $"{_testDisplayName}-queue-{Guid.NewGuid()}";
+ }
+
+ protected void WithTemporaryNonExclusiveQueue(Action action)
+ {
+ WithTemporaryNonExclusiveQueue(_channel, action);
+ }
+
+ protected void WithTemporaryNonExclusiveQueue(IChannel channel, Action action)
+ {
+ WithTemporaryNonExclusiveQueue(channel, action, GenerateQueueName());
+ }
+
+ protected void WithTemporaryNonExclusiveQueue(IChannel channel, Action action, string queue)
+ {
+ try
+ {
+ channel.QueueDeclare(queue, false, false, false, null);
+ action(channel, queue);
+ }
+ finally
+ {
+ WithTemporaryChannel(tm => tm.QueueDelete(queue));
+ }
+ }
+
+ protected void AssertMessageCount(string q, uint count)
+ {
+ WithTemporaryChannel((m) =>
+ {
+ RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q);
+ Assert.Equal(count, ok.MessageCount);
+ });
+ }
+
+ protected void AssertShutdownError(ShutdownEventArgs args, int code)
+ {
+ Assert.Equal(args.ReplyCode, code);
+ }
+
+ protected void AssertPreconditionFailed(ShutdownEventArgs args)
+ {
+ AssertShutdownError(args, Constants.PreconditionFailed);
+ }
+
+ protected void WaitOn(object o)
+ {
+ lock (o)
+ {
+ Monitor.Wait(o, TimingFixture.TestTimeout);
+ }
+ }
+
+ protected void Wait(ManualResetEventSlim latch, string desc)
+ {
+ Assert.True(latch.Wait(WaitSpan),
+ $"waiting {WaitSpan.TotalSeconds} seconds on a latch for '{desc}' timed out");
+ }
+
+ protected void Wait(ManualResetEventSlim latch, TimeSpan timeSpan, string desc)
+ {
+ Assert.True(latch.Wait(timeSpan),
+ $"waiting {timeSpan.TotalSeconds} seconds on a latch for '{desc}' timed out");
+ }
+
+ protected ConnectionFactory CreateConnectionFactory()
+ {
+ string now = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
+ return new ConnectionFactory
+ {
+ ClientProvidedName = $"{_testDisplayName}:{now}:{GetConnectionIdx()}",
+ ContinuationTimeout = WaitSpan,
+ HandshakeContinuationTimeout = WaitSpan,
+ };
+ }
+
+ protected void HandleConnectionShutdown(object sender, ShutdownEventArgs args)
+ {
+ if (args.Initiator != ShutdownInitiator.Application)
+ {
+ IConnection conn = (IConnection)sender;
+ _output.WriteLine($"{_testDisplayName} connection {conn.ClientProvidedName} shut down: {args}");
+ }
+ }
+
+ protected void HandleConnectionShutdown(IConnection conn, ShutdownEventArgs args, Action a)
+ {
+ if (args.Initiator != ShutdownInitiator.Application)
+ {
+ _output.WriteLine($"{_testDisplayName} connection {conn.ClientProvidedName} shut down: {args}");
+ a();
+ }
+ }
+
+ protected void HandleChannelShutdown(object sender, ShutdownEventArgs args)
+ {
+ if (args.Initiator != ShutdownInitiator.Application)
+ {
+ IChannel ch = (IChannel)sender;
+ _output.WriteLine($"{_testDisplayName} channel {ch.ChannelNumber} shut down: {args}");
+ }
+ }
+
+ protected void HandleChannelShutdown(IChannel ch, ShutdownEventArgs args, Action a)
+ {
+ if (args.Initiator != ShutdownInitiator.Application)
+ {
+ _output.WriteLine($"{_testDisplayName} channel {ch.ChannelNumber} shut down: {args}");
+ a();
+ }
+ }
+
+ private static void InitIsRunningInCI()
+ {
+ bool ci;
+ if (bool.TryParse(Environment.GetEnvironmentVariable("CI"), out ci))
+ {
+ if (ci == true)
+ {
+ s_isRunningInCI = true;
+ }
+ }
+ else if (bool.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out ci))
+ {
+ if (ci == true)
+ {
+ s_isRunningInCI = true;
+ }
+ }
+ else
+ {
+ s_isRunningInCI = false;
+ }
+ }
+
+ private static void InitIsWindows()
+ {
+ PlatformID platform = Environment.OSVersion.Platform;
+ if (platform == PlatformID.Win32NT)
+ {
+ s_isWindows = true;
+ return;
+ }
+
+ string os = Environment.GetEnvironmentVariable("OS");
+ if (os != null)
+ {
+ os = os.Trim();
+ s_isWindows = os == "Windows_NT";
+ return;
+ }
+ }
+
+ private static int GetConnectionIdx()
+ {
+ return Interlocked.Increment(ref _connectionIdx);
+ }
+
+ protected static string GetUniqueString(ushort length)
+ {
+ byte[] bytes = GetRandomBody(length);
+ return Convert.ToBase64String(bytes);
+ }
+
+ protected static byte[] GetRandomBody(ushort size)
+ {
+ var body = new byte[size];
+#if NET6_0_OR_GREATER
+ Random.Shared.NextBytes(body);
+#else
+ S_Random.NextBytes(body);
+#endif
+ return body;
+ }
+ }
+}
diff --git a/projects/Unit/RabbitMQCtl.cs b/projects/Test/Common/RabbitMQCtl.cs
similarity index 67%
rename from projects/Unit/RabbitMQCtl.cs
rename to projects/Test/Common/RabbitMQCtl.cs
index 7a12e97f51..8faf25ba39 100644
--- a/projects/Unit/RabbitMQCtl.cs
+++ b/projects/Test/Common/RabbitMQCtl.cs
@@ -29,72 +29,56 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
-#pragma warning disable 2002
-
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
-using System.Threading;
+using RabbitMQ.Client;
+using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test
{
-#nullable enable
- public static class RabbitMQCtl
+ public class RabbitMQCtl
{
private static readonly char[] newLine = new char[] { '\n' };
private static readonly Func s_invokeRabbitMqCtl = GetRabbitMqCtlInvokeAction();
+ private static readonly Regex s_getConnectionProperties =
+ new Regex(@"^(?<[^>]*>)\s\[.*""connection_name"",""(?[^""]*)"".*\]$", RegexOptions.Multiline | RegexOptions.Compiled);
- private static Func GetRabbitMqCtlInvokeAction()
- {
- string precomputedArguments;
- string? envVariable = Environment.GetEnvironmentVariable("RABBITMQ_RABBITMQCTL_PATH");
+ private readonly ITestOutputHelper _output;
- if (!string.IsNullOrWhiteSpace(envVariable))
- {
- const string DockerPrefix = "DOCKER:";
- if (envVariable.StartsWith(DockerPrefix))
- {
- // Call docker
- precomputedArguments = $"exec {envVariable.Substring(DockerPrefix.Length)} rabbitmqctl ";
- return args => CreateProcess("docker", precomputedArguments + args);
- }
-
- // call the path from the env var
- return args => CreateProcess(envVariable, args);
- }
+ public RabbitMQCtl(ITestOutputHelper output)
+ {
+ _output = output;
+ }
- // Try default
- string umbrellaRabbitmqctlPath;
- string providedRabbitmqctlPath;
+ public void CloseConnection(IConnection conn)
+ {
+ CloseConnection(GetConnectionPid(conn.ClientProvidedName));
+ }
- if (IsRunningOnMonoOrDotNetCore())
- {
- umbrellaRabbitmqctlPath = "../../../../../../rabbit/scripts/rabbitmqctl";
- providedRabbitmqctlPath = "rabbitmqctl";
- }
- else
- {
- umbrellaRabbitmqctlPath = @"..\..\..\..\..\..\rabbit\scripts\rabbitmqctl.bat";
- providedRabbitmqctlPath = "rabbitmqctl.bat";
- }
+ public void AddUser(string username, string password)
+ {
+ ExecRabbitMQCtl($"add_user {username} {password}");
+ }
- string path = File.Exists(umbrellaRabbitmqctlPath) ? umbrellaRabbitmqctlPath : providedRabbitmqctlPath;
+ public void ChangePassword(string username, string password)
+ {
+ ExecRabbitMQCtl($"change_password {username} {password}");
+ }
- if (IsRunningOnMonoOrDotNetCore())
- {
- return args => CreateProcess(path, args);
- }
+ public void SetPermissions(string username, string conf, string write, string read)
+ {
+ ExecRabbitMQCtl($"set_permissions {username} \"{conf}\" \"{write}\" \"${read}\" ");
+ }
- precomputedArguments = $"/c \"\"{path}\" ";
- return args => CreateProcess("cmd.exe", precomputedArguments + args);
+ public void DeleteUser(string username)
+ {
+ ExecRabbitMQCtl($"delete_user {username}");
}
- //
- // Shelling Out
- //
- private static string ExecRabbitMQCtl(string args)
+ public string ExecRabbitMQCtl(string args)
{
try
{
@@ -118,66 +102,61 @@ private static string ExecRabbitMQCtl(string args)
}
}
- private static Process CreateProcess(string cmd, string arguments, string? workDirectory = null)
+ private void ReportExecFailure(string cmd, string args, string msg)
{
- return new Process
- {
- StartInfo =
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardError = true,
- RedirectStandardOutput = true,
- FileName = cmd,
- Arguments = arguments,
- WorkingDirectory = workDirectory
- }
- };
+ _output.WriteLine($"Failure while running {cmd} {args}:\n{msg}");
}
- private static void ReportExecFailure(string cmd, string args, string msg)
+ private static Func GetRabbitMqCtlInvokeAction()
{
- Console.WriteLine($"Failure while running {cmd} {args}:\n{msg}");
- }
+ string precomputedArguments;
+ string envVariable = Environment.GetEnvironmentVariable("RABBITMQ_RABBITMQCTL_PATH");
- private static bool IsRunningOnMonoOrDotNetCore()
- {
-#if NETCOREAPP
- return true;
-#else
- return Type.GetType("Mono.Runtime") != null;
-#endif
- }
+ if (!string.IsNullOrWhiteSpace(envVariable))
+ {
+ const string DockerPrefix = "DOCKER:";
+ if (envVariable.StartsWith(DockerPrefix))
+ {
+ // Call docker
+ precomputedArguments = $"exec {envVariable.Substring(DockerPrefix.Length)} rabbitmqctl ";
+ return args => CreateProcess("docker", precomputedArguments + args);
+ }
+ else
+ {
+ // call the path from the env var
+ return args => CreateProcess(envVariable, args);
+ }
+ }
- //
- // Flow Control
- //
- public static void Block(IConnection conn, Encoding encoding)
- {
- ExecRabbitMQCtl("set_vm_memory_high_watermark 0.000000001");
- // give rabbitmqctl some time to do its job
- Thread.Sleep(1200);
- Publish(conn, encoding);
- }
+ // Try default
+ string umbrellaRabbitmqctlPath;
+ string providedRabbitmqctlPath;
- public static void Publish(IConnection conn, Encoding encoding)
- {
- IChannel ch = conn.CreateChannel();
- ch.BasicPublish("amq.fanout", "", encoding.GetBytes("message"));
- }
+ if (IsRunningOnMonoOrDotNetCore())
+ {
+ umbrellaRabbitmqctlPath = "../../../../../../rabbit/scripts/rabbitmqctl";
+ providedRabbitmqctlPath = "rabbitmqctl";
+ }
+ else
+ {
+ umbrellaRabbitmqctlPath = @"..\..\..\..\..\..\rabbit\scripts\rabbitmqctl.bat";
+ providedRabbitmqctlPath = "rabbitmqctl.bat";
+ }
- public static void Unblock()
- {
- ExecRabbitMQCtl("set_vm_memory_high_watermark 0.4");
- }
+ string path = File.Exists(umbrellaRabbitmqctlPath) ? umbrellaRabbitmqctlPath : providedRabbitmqctlPath;
- public static void CloseConnection(IConnection conn)
- {
- CloseConnection(GetConnectionPid(conn.ClientProvidedName));
+ if (IsRunningOnMonoOrDotNetCore())
+ {
+ return args => CreateProcess(path, args);
+ }
+ else
+ {
+ precomputedArguments = $"/c \"\"{path}\" ";
+ return args => CreateProcess("cmd.exe", precomputedArguments + args);
+ }
}
- private static readonly Regex s_getConnectionProperties = new Regex(@"^(?<[^>]*>)\s\[.*""connection_name"",""(?[^""]*)"".*\]$", RegexOptions.Multiline | RegexOptions.Compiled);
- private static string GetConnectionPid(string connectionName)
+ private string GetConnectionPid(string connectionName)
{
string stdout = ExecRabbitMQCtl("list_connections --silent pid client_properties");
@@ -195,65 +174,41 @@ private static string GetConnectionPid(string connectionName)
throw new Exception($"No connection found with name: {connectionName}");
}
- private static void CloseConnection(string pid)
+ private void CloseConnection(string pid)
{
ExecRabbitMQCtl($"close_connection \"{pid}\" \"Closed via rabbitmqctl\"");
}
- public static void CloseAllConnections()
- {
- foreach (var pid in EnumerateConnectionsPid())
- {
- CloseConnection(pid);
- }
- }
-
- private static string[] EnumerateConnectionsPid()
- {
- string rabbitmqCtlResult = ExecRabbitMQCtl("list_connections --silent pid");
- return rabbitmqCtlResult.Split(newLine, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public static void RestartRabbitMQ()
- {
- StopRabbitMQ();
- Thread.Sleep(500);
- StartRabbitMQ();
- AwaitRabbitMQ();
- }
-
- public static void StopRabbitMQ()
- {
- ExecRabbitMQCtl("stop_app");
- }
-
- public static void StartRabbitMQ()
- {
- ExecRabbitMQCtl("start_app");
- }
-
- public static void AwaitRabbitMQ()
- {
- ExecRabbitMQCtl("await_startup");
- }
-
- public static void AddUser(string username, string password)
- {
- ExecRabbitMQCtl($"add_user {username} {password}");
- }
- public static void ChangePassword(string username, string password)
+ private static bool IsRunningOnMonoOrDotNetCore()
{
- ExecRabbitMQCtl($"change_password {username} {password}");
+#if NETCOREAPP
+ return true;
+#else
+ return Type.GetType("Mono.Runtime") != null;
+#endif
}
- public static void SetPermissions(string username, string conf, string write, string read)
+ private static void Publish(IConnection conn, Encoding encoding)
{
- ExecRabbitMQCtl($"set_permissions {username} \"{conf}\" \"{write}\" \"${read}\" ");
+ IChannel ch = conn.CreateChannel();
+ ch.BasicPublish("amq.fanout", "", encoding.GetBytes("message"));
}
- public static void DeleteUser(string username)
+ private static Process CreateProcess(string cmd, string arguments, string workDirectory = null)
{
- ExecRabbitMQCtl($"delete_user {username}");
+ return new Process
+ {
+ StartInfo =
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ FileName = cmd,
+ Arguments = arguments,
+ WorkingDirectory = workDirectory
+ }
+ };
}
}
}
diff --git a/projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs b/projects/Test/Common/TimingFixture.cs
similarity index 67%
rename from projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs
rename to projects/Test/Common/TimingFixture.cs
index e2c76097ea..23ce3a31ce 100644
--- a/projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs
+++ b/projects/Test/Common/TimingFixture.cs
@@ -30,30 +30,17 @@
//---------------------------------------------------------------------------
using System;
-using RabbitMQ.Client.client.framing;
-using RabbitMQ.Client.Impl;
-namespace RabbitMQ.Client.Framing.Impl
+namespace Test
{
- internal readonly struct BasicRecoverAsync : IOutgoingAmqpMethod
+ public static class TimingFixture
{
- public readonly bool _requeue;
-
- public BasicRecoverAsync(bool Requeue)
- {
- _requeue = Requeue;
- }
-
- public ProtocolCommandId ProtocolCommandId => ProtocolCommandId.BasicRecoverAsync;
-
- public int WriteTo(Span span)
- {
- return WireFormatting.WriteBits(ref span.GetStart(), _requeue);
- }
-
- public int GetRequiredBufferSize()
- {
- return 1; // bytes for bit fields
- }
+ public static readonly TimeSpan TimingInterval = TimeSpan.FromMilliseconds(300);
+ public static readonly TimeSpan TimingInterval_2X = TimeSpan.FromMilliseconds(600);
+ public static readonly TimeSpan TimingInterval_4X = TimeSpan.FromMilliseconds(1200);
+ public static readonly TimeSpan TimingInterval_8X = TimeSpan.FromMilliseconds(2400);
+ public static readonly TimeSpan TimingInterval_16X = TimeSpan.FromMilliseconds(4800);
+ public static readonly TimeSpan SafetyMargin = TimeSpan.FromMilliseconds(150);
+ public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5);
}
}
diff --git a/projects/Test/Integration/Integration.csproj b/projects/Test/Integration/Integration.csproj
new file mode 100644
index 0000000000..55ae9c0e83
--- /dev/null
+++ b/projects/Test/Integration/Integration.csproj
@@ -0,0 +1,51 @@
+
+
+
+ net6.0;net472
+
+
+
+ net6.0
+
+
+
+ ../../rabbit.snk
+ true
+ latest
+ 7.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
diff --git a/projects/Unit/SslEnv.cs b/projects/Test/Integration/SslEnv.cs
similarity index 85%
rename from projects/Unit/SslEnv.cs
rename to projects/Test/Integration/SslEnv.cs
index bc286a9d3d..272eab1d5d 100644
--- a/projects/Unit/SslEnv.cs
+++ b/projects/Test/Integration/SslEnv.cs
@@ -32,13 +32,13 @@
using System;
using System.IO;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class SslEnv
{
private readonly string _certPassphrase;
private readonly string _certPath;
- private readonly string _hostname;
+ private const string _hostname = "localhost";
private readonly string _sslDir;
private readonly bool _isSslConfigured;
private readonly bool _isGithubActions;
@@ -48,21 +48,14 @@ public SslEnv()
_sslDir = Environment.GetEnvironmentVariable("SSL_CERTS_DIR");
_certPassphrase = Environment.GetEnvironmentVariable("PASSWORD");
- Boolean.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out _isGithubActions);
-
_isSslConfigured = Directory.Exists(_sslDir) &&
(false == String.IsNullOrEmpty(_certPassphrase));
- if (_isGithubActions)
- {
- _hostname = "localhost";
- }
- else
+ if (_isSslConfigured)
{
- _hostname = System.Net.Dns.GetHostName();
+ Boolean.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out _isGithubActions);
+ _certPath = Path.Combine(_sslDir, $"client_{_hostname}.p12");
}
-
- _certPath = Path.Combine(_sslDir, $"client_{_hostname}.p12");
}
public string CertPath
diff --git a/projects/Unit/TestAuth.cs b/projects/Test/Integration/TestAuth.cs
similarity index 78%
rename from projects/Unit/TestAuth.cs
rename to projects/Test/Integration/TestAuth.cs
index 8761cd35d3..790de61041 100644
--- a/projects/Unit/TestAuth.cs
+++ b/projects/Test/Integration/TestAuth.cs
@@ -31,20 +31,29 @@
using RabbitMQ.Client.Exceptions;
using Xunit;
+using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
- [Collection("IntegrationFixture")]
- public class TestAuth
+ public class TestAuth : IntegrationFixture
{
+ public TestAuth(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void SetUp()
+ {
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
+
[Fact]
public void TestAuthFailure()
{
- ConnectionFactory connFactory = new ConnectionFactory
- {
- UserName = "guest",
- Password = "incorrect-password"
- };
+ var connFactory = CreateConnectionFactory();
+ connFactory.UserName = "guest";
+ connFactory.Password = "incorrect-password";
try
{
diff --git a/projects/Unit/TestBasicGet.cs b/projects/Test/Integration/TestBasicGet.cs
similarity index 68%
rename from projects/Unit/TestBasicGet.cs
rename to projects/Test/Integration/TestBasicGet.cs
index 5dba9cee8d..d249a2f109 100644
--- a/projects/Unit/TestBasicGet.cs
+++ b/projects/Test/Integration/TestBasicGet.cs
@@ -29,11 +29,13 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using System;
+using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestBasicGet : IntegrationFixture
{
@@ -70,9 +72,44 @@ public void TestBasicGetWithNonEmptyResponseAndAutoAckMode()
WithNonEmptyQueue((channel, queue) =>
{
BasicGetResult res = channel.BasicGet(queue, true);
- Assert.Equal(msg, _encoding.GetString(res.Body.ToArray()));
+ Assert.Equal(msg, _encoding.GetString(res.Body));
AssertMessageCount(queue, 0);
}, msg);
}
+
+ private void EnsureNotEmpty(string q, string body)
+ {
+ WithTemporaryChannel(x => x.BasicPublish("", q, _encoding.GetBytes(body)));
+ }
+
+ private void WithClosedChannel(Action action)
+ {
+ IChannel channel = _conn.CreateChannel();
+ channel.Close();
+ action(channel);
+ }
+
+ private void WithNonEmptyQueue(Action action)
+ {
+ WithNonEmptyQueue(action, "msg");
+ }
+
+ private void WithNonEmptyQueue(Action action, string msg)
+ {
+ WithTemporaryNonExclusiveQueue((m, q) =>
+ {
+ EnsureNotEmpty(q, msg);
+ action(m, q);
+ });
+ }
+
+ private void WithEmptyQueue(Action action)
+ {
+ WithTemporaryNonExclusiveQueue((channel, queue) =>
+ {
+ channel.QueuePurge(queue);
+ action(channel, queue);
+ });
+ }
}
}
diff --git a/projects/Test/Integration/TestBasicPublish.cs b/projects/Test/Integration/TestBasicPublish.cs
new file mode 100644
index 0000000000..4d230cb07b
--- /dev/null
+++ b/projects/Test/Integration/TestBasicPublish.cs
@@ -0,0 +1,294 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Test.Integration
+{
+ public class TestBasicPublish : IntegrationFixture
+ {
+ public TestBasicPublish(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void SetUp()
+ {
+ /*
+ * NB: the only reason to do a custom SetUp
+ * is for the MaxMessageSize and QueuePurgeAsync tests,
+ * which use custom factory settings. This could be improved.
+ * TODO LRB rabbitmq/rabbitmq-server#1347
+ */
+ _connFactory = CreateConnectionFactory();
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
+
+ [Fact]
+ public void TestBasicRoundtripArray()
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+
+ QueueDeclareOk q = _channel.QueueDeclare();
+ var bp = new BasicProperties();
+ byte[] sendBody = _encoding.GetBytes("hi");
+ byte[] consumeBody = null;
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ consumeBody = a.Body;
+ are.Set();
+ };
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+
+ _channel.BasicPublish("", q.QueueName, bp, sendBody);
+ bool waitResFalse = are.WaitOne(5000);
+ _channel.BasicCancel(tag);
+
+ Assert.True(waitResFalse);
+ Assert.Equal(sendBody, consumeBody);
+ }
+
+ [Fact]
+ public void TestBasicRoundtripCachedString()
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+
+ CachedString exchangeName = new CachedString(string.Empty);
+ CachedString queueName = new CachedString(_channel.QueueDeclare().QueueName);
+ byte[] sendBody = _encoding.GetBytes("hi");
+ byte[] consumeBody = null;
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ consumeBody = a.Body;
+ are.Set();
+ };
+ string tag = _channel.BasicConsume(queueName.Value, true, consumer);
+
+ _channel.BasicPublish(exchangeName, queueName, sendBody);
+ bool waitResFalse = are.WaitOne(2000);
+ _channel.BasicCancel(tag);
+
+ Assert.True(waitResFalse);
+ Assert.Equal(sendBody, consumeBody);
+ }
+
+ [Fact]
+ public void TestBasicRoundtripReadOnlyMemory()
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+
+ QueueDeclareOk q = _channel.QueueDeclare();
+ byte[] sendBody = _encoding.GetBytes("hi");
+ byte[] consumeBody = null;
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ consumeBody = a.Body;
+ are.Set();
+ };
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+
+ _channel.BasicPublish("", q.QueueName, new ReadOnlyMemory(sendBody));
+ bool waitResFalse = are.WaitOne(2000);
+ _channel.BasicCancel(tag);
+
+ Assert.True(waitResFalse);
+ Assert.Equal(sendBody, consumeBody);
+ }
+
+ [Fact]
+ public void CanNotModifyPayloadAfterPublish()
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+
+ QueueDeclareOk q = _channel.QueueDeclare();
+ byte[] sendBody = new byte[1000];
+ var consumer = new EventingBasicConsumer(_channel);
+ var receivedMessage = new AutoResetEvent(false);
+ bool modified = true;
+ consumer.Received += (o, a) =>
+ {
+ if (a.Body.Span.IndexOf((byte)1) < 0)
+ {
+ modified = false;
+ }
+ receivedMessage.Set();
+ };
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+
+ _channel.BasicPublish("", q.QueueName, sendBody);
+ sendBody.AsSpan().Fill(1);
+
+ Assert.True(receivedMessage.WaitOne(5000));
+ Assert.False(modified, "Payload was modified after the return of BasicPublish");
+
+ _channel.BasicCancel(tag);
+ }
+
+ [Fact]
+ public void TestMaxMessageSize()
+ {
+ var re = new ManualResetEventSlim();
+ const ushort maxMsgSize = 1024;
+
+ int count = 0;
+ byte[] msg0 = _encoding.GetBytes("hi");
+ byte[] msg1 = GetRandomBody(maxMsgSize * 2);
+
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = false;
+ cf.TopologyRecoveryEnabled = false;
+ cf.MaxMessageSize = maxMsgSize;
+
+ bool sawConnectionShutdown = false;
+ bool sawChannelShutdown = false;
+ bool sawConsumerRegistered = false;
+ bool sawConsumerCancelled = false;
+
+ using (IConnection c = cf.CreateConnection())
+ {
+ c.ConnectionShutdown += (o, a) =>
+ {
+ sawConnectionShutdown = true;
+ };
+
+ Assert.Equal(maxMsgSize, cf.MaxMessageSize);
+ Assert.Equal(maxMsgSize, cf.Endpoint.MaxMessageSize);
+ Assert.Equal(maxMsgSize, c.Endpoint.MaxMessageSize);
+
+ using (IChannel channel = c.CreateChannel())
+ {
+ channel.ChannelShutdown += (o, a) =>
+ {
+ sawChannelShutdown = true;
+ };
+
+ channel.CallbackException += (o, a) =>
+ {
+ throw new XunitException("Unexpected channel.CallbackException");
+ };
+
+ QueueDeclareOk q = channel.QueueDeclare();
+
+ var consumer = new EventingBasicConsumer(channel);
+
+ consumer.Shutdown += (o, a) =>
+ {
+ re.Set();
+ };
+
+ consumer.Registered += (o, a) =>
+ {
+ sawConsumerRegistered = true;
+ };
+
+ consumer.Unregistered += (o, a) =>
+ {
+ throw new XunitException("Unexpected consumer.Unregistered");
+ };
+
+ consumer.ConsumerCancelled += (o, a) =>
+ {
+ sawConsumerCancelled = true;
+ };
+
+ consumer.Received += (o, a) =>
+ {
+ Interlocked.Increment(ref count);
+ };
+
+ string tag = channel.BasicConsume(q.QueueName, true, consumer);
+
+ channel.BasicPublish("", q.QueueName, msg0);
+ channel.BasicPublish("", q.QueueName, msg1);
+ Assert.True(re.Wait(TimeSpan.FromSeconds(5)));
+
+ Assert.Equal(1, count);
+ Assert.True(sawConnectionShutdown);
+ Assert.True(sawChannelShutdown);
+ Assert.True(sawConsumerRegistered);
+ Assert.True(sawConsumerCancelled);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestPropertiesRoundtrip_Headers()
+ {
+ _conn = _connFactory.CreateConnection();
+ _channel = _conn.CreateChannel();
+
+ var subject = new BasicProperties
+ {
+ Headers = new Dictionary()
+ };
+
+ QueueDeclareOk q = _channel.QueueDeclare();
+ var bp = new BasicProperties() { Headers = new Dictionary() };
+ bp.Headers["Hello"] = "World";
+ byte[] sendBody = _encoding.GetBytes("hi");
+ byte[] consumeBody = null;
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ string response = null;
+ consumer.Received += (o, a) =>
+ {
+ response = _encoding.GetString(a.BasicProperties.Headers["Hello"] as byte[]);
+ consumeBody = a.Body;
+ are.Set();
+ };
+
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+ _channel.BasicPublish("", q.QueueName, bp, sendBody);
+ bool waitResFalse = are.WaitOne(5000);
+ _channel.BasicCancel(tag);
+ Assert.True(waitResFalse);
+ Assert.Equal(sendBody, consumeBody);
+ Assert.Equal("World", response);
+ }
+ }
+}
diff --git a/projects/Unit/TestChannelAllocation.cs b/projects/Test/Integration/TestChannelAllocation.cs
similarity index 66%
rename from projects/Unit/TestChannelAllocation.cs
rename to projects/Test/Integration/TestChannelAllocation.cs
index 3a4671f6bf..bdb3f71a37 100644
--- a/projects/Unit/TestChannelAllocation.cs
+++ b/projects/Test/Integration/TestChannelAllocation.cs
@@ -31,26 +31,28 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
using RabbitMQ.Client.Impl;
using Xunit;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
- [Collection("IntegrationFixture")]
public class TestChannelAllocation : IDisposable
{
public const int CHANNEL_COUNT = 100;
IConnection _c;
- public int ChannelNumber(IChannel channel)
- {
- return ((AutorecoveringChannel)channel).ChannelNumber;
- }
-
public TestChannelAllocation()
{
- _c = new ConnectionFactory().CreateConnection();
+ var cf = new ConnectionFactory
+ {
+ ContinuationTimeout = IntegrationFixture.WaitSpan,
+ HandshakeContinuationTimeout = IntegrationFixture.WaitSpan,
+ ClientProvidedName = nameof(TestChannelAllocation)
+ };
+ _c = cf.CreateConnection();
}
public void Dispose() => _c.Close();
@@ -58,44 +60,63 @@ public TestChannelAllocation()
[Fact]
public void AllocateInOrder()
{
+ var channels = new List();
for (int i = 1; i <= CHANNEL_COUNT; i++)
- Assert.Equal(i, ChannelNumber(_c.CreateChannel()));
+ {
+ IChannel channel = _c.CreateChannel();
+ channels.Add(channel);
+ Assert.Equal(i, ChannelNumber(channel));
+ }
+
+ foreach (IChannel channel in channels)
+ {
+ channel.Dispose();
+ }
}
[Fact]
public void AllocateAfterFreeingLast()
{
- IChannel ch = _c.CreateChannel();
- Assert.Equal(1, ChannelNumber(ch));
- ch.Close();
- ch = _c.CreateChannel();
- Assert.Equal(1, ChannelNumber(ch));
+ using IChannel ch0 = _c.CreateChannel();
+ Assert.Equal(1, ChannelNumber(ch0));
+ ch0.Close();
+
+ using IChannel ch1 = _c.CreateChannel();
+ Assert.Equal(1, ChannelNumber(ch1));
}
- public int CompareChannels(IChannel x, IChannel y)
+ [Fact]
+ public async Task AllocateAfterFreeingLastAsync()
{
- int i = ChannelNumber(x);
- int j = ChannelNumber(y);
- return (i < j) ? -1 : (i == j) ? 0 : 1;
+ using IChannel ch0 = _c.CreateChannel();
+ Assert.Equal(1, ChannelNumber(ch0));
+ await ch0.CloseAsync();
+
+ using IChannel ch1 = _c.CreateChannel();
+ Assert.Equal(1, ChannelNumber(ch1));
}
[Fact]
public void AllocateAfterFreeingMany()
{
- List channels = new List();
+ var channels = new List();
for (int i = 1; i <= CHANNEL_COUNT; i++)
+ {
channels.Add(_c.CreateChannel());
+ }
foreach (IChannel channel in channels)
{
channel.Close();
}
- channels = new List();
+ channels.Clear();
for (int j = 1; j <= CHANNEL_COUNT; j++)
+ {
channels.Add(_c.CreateChannel());
+ }
// In the current implementation the list should actually
// already be sorted, but we don't want to force that behaviour
@@ -103,7 +124,22 @@ public void AllocateAfterFreeingMany()
int k = 1;
foreach (IChannel channel in channels)
+ {
Assert.Equal(k++, ChannelNumber(channel));
+ channel.Close();
+ }
+ }
+
+ public int ChannelNumber(IChannel channel)
+ {
+ return ((AutorecoveringChannel)channel).ChannelNumber;
+ }
+
+ public int CompareChannels(IChannel x, IChannel y)
+ {
+ int i = ChannelNumber(x);
+ int j = ChannelNumber(y);
+ return (i < j) ? -1 : (i == j) ? 0 : 1;
}
}
}
diff --git a/projects/Unit/TestChannelShutdown.cs b/projects/Test/Integration/TestChannelShutdown.cs
similarity index 94%
rename from projects/Unit/TestChannelShutdown.cs
rename to projects/Test/Integration/TestChannelShutdown.cs
index 04b385fe68..21fbbc6c55 100644
--- a/projects/Unit/TestChannelShutdown.cs
+++ b/projects/Test/Integration/TestChannelShutdown.cs
@@ -31,11 +31,12 @@
using System;
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Impl;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestChannelShutdown : IntegrationFixture
{
@@ -55,7 +56,7 @@ public void TestConsumerDispatcherShutdown()
};
Assert.False(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close");
_channel.Close();
- Wait(latch, TimeSpan.FromSeconds(3));
+ Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown");
Assert.True(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close");
}
}
diff --git a/projects/Unit/TestChannelSoftErrors.cs b/projects/Test/Integration/TestChannelSoftErrors.cs
similarity index 98%
rename from projects/Unit/TestChannelSoftErrors.cs
rename to projects/Test/Integration/TestChannelSoftErrors.cs
index 4f0bed6850..b369c6a048 100644
--- a/projects/Unit/TestChannelSoftErrors.cs
+++ b/projects/Test/Integration/TestChannelSoftErrors.cs
@@ -29,12 +29,13 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestChannelSoftErrors : IntegrationFixture
{
diff --git a/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs b/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs
new file mode 100644
index 0000000000..c1384bf383
--- /dev/null
+++ b/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs
@@ -0,0 +1,165 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.Integration
+{
+ public class TestConcurrentAccessWithSharedConnection : IntegrationFixture
+ {
+ private const ushort _messageCount = 200;
+
+ public TestConcurrentAccessWithSharedConnection(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void SetUp()
+ {
+ _connFactory = CreateConnectionFactory();
+ _conn = _connFactory.CreateConnection();
+ // NB: not creating _channel because this test suite doesn't use it.
+ Assert.Null(_channel);
+ }
+
+ [Fact]
+ public void TestConcurrentChannelOpenAndPublishingWithBlankMessages()
+ {
+ TestConcurrentChannelOpenAndPublishingWithBody(Array.Empty(), 30);
+ }
+
+ [Fact]
+ public void TestConcurrentChannelOpenAndPublishingSize64()
+ {
+ TestConcurrentChannelOpenAndPublishingWithBodyOfSize(64);
+ }
+
+ [Fact]
+ public void TestConcurrentChannelOpenAndPublishingSize256()
+ {
+ TestConcurrentChannelOpenAndPublishingWithBodyOfSize(256);
+ }
+
+ [Fact]
+ public void TestConcurrentChannelOpenAndPublishingSize1024()
+ {
+ TestConcurrentChannelOpenAndPublishingWithBodyOfSize(1024);
+ }
+
+ [Fact]
+ public void TestConcurrentChannelOpenCloseLoop()
+ {
+ TestConcurrentChannelOperations((conn) =>
+ {
+ using (IChannel ch = conn.CreateChannel())
+ {
+ ch.Close();
+ }
+ }, 50);
+ }
+
+ private void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(ushort length, int iterations = 30)
+ {
+ byte[] body = GetRandomBody(length);
+ TestConcurrentChannelOpenAndPublishingWithBody(body, iterations);
+ }
+
+ private void TestConcurrentChannelOpenAndPublishingWithBody(byte[] body, int iterations)
+ {
+ TestConcurrentChannelOperations((conn) =>
+ {
+ using (var localLatch = new ManualResetEvent(false))
+ {
+ // publishing on a shared channel is not supported
+ // and would missing the point of this test anyway
+ using (IChannel ch = _conn.CreateChannel())
+ {
+ ch.ConfirmSelect();
+
+ ch.BasicAcks += (object sender, BasicAckEventArgs e) =>
+ {
+ if (e.DeliveryTag >= _messageCount)
+ {
+ localLatch.Set();
+ }
+ };
+
+ ch.BasicNacks += (object sender, BasicNackEventArgs e) =>
+ {
+ localLatch.Set();
+ Assert.Fail("should never see a nack");
+ };
+
+ QueueDeclareOk q = ch.QueueDeclare(queue: string.Empty, exclusive: true, autoDelete: true);
+ for (ushort j = 0; j < _messageCount; j++)
+ {
+ ch.BasicPublish("", q.QueueName, body, true);
+ }
+
+ Assert.True(localLatch.WaitOne(WaitSpan));
+ }
+ }
+ }, iterations);
+ }
+
+ private void TestConcurrentChannelOperations(Action actions, int iterations)
+ {
+ TestConcurrentChannelOperations(actions, iterations, LongWaitSpan);
+ }
+
+ private void TestConcurrentChannelOperations(Action action, int iterations, TimeSpan timeout)
+ {
+ var tasks = new List();
+ for (int i = 0; i < _processorCount; i++)
+ {
+ tasks.Add(Task.Run(() =>
+ {
+ for (int j = 0; j < iterations; j++)
+ {
+ action(_conn);
+ }
+ }));
+ }
+ Assert.True(Task.WaitAll(tasks.ToArray(), timeout));
+
+ // incorrect frame interleaving in these tests will result
+ // in an unrecoverable connection-level exception, thus
+ // closing the connection
+ Assert.True(_conn.IsOpen);
+ }
+ }
+}
diff --git a/projects/Unit/TestConfirmSelect.cs b/projects/Test/Integration/TestConfirmSelect.cs
similarity index 97%
rename from projects/Unit/TestConfirmSelect.cs
rename to projects/Test/Integration/TestConfirmSelect.cs
index 102282f14f..eb3040f2de 100644
--- a/projects/Unit/TestConfirmSelect.cs
+++ b/projects/Test/Integration/TestConfirmSelect.cs
@@ -29,10 +29,11 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConfirmSelect : IntegrationFixture
{
diff --git a/projects/Unit/TestConnectionFactory.cs b/projects/Test/Integration/TestConnectionFactory.cs
similarity index 66%
rename from projects/Unit/TestConnectionFactory.cs
rename to projects/Test/Integration/TestConnectionFactory.cs
index 225c94f99d..e1c3e356ab 100644
--- a/projects/Unit/TestConnectionFactory.cs
+++ b/projects/Test/Integration/TestConnectionFactory.cs
@@ -30,13 +30,28 @@
//---------------------------------------------------------------------------
using System.Collections.Generic;
+using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using Xunit;
+using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
- public class TestConnectionFactory
+ public class TestConnectionFactory : IntegrationFixture
{
+ public TestConnectionFactory(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void SetUp()
+ {
+ // NB: nothing to do here since each test creates its own factory,
+ // connections and channels
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
+
[Fact]
public void TestProperties()
{
@@ -72,94 +87,95 @@ public void TestProperties()
[Fact]
public void TestCreateConnectionUsesSpecifiedPort()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true,
- HostName = "localhost",
- Port = 1234
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.HostName = "localhost";
+ cf.Port = 1234;
- Assert.Throws(() => { using IConnection conn = cf.CreateConnection(); });
+ Assert.Throws(() =>
+ {
+ using IConnection conn = cf.CreateConnection();
+ });
}
[Fact]
public void TestCreateConnectionWithClientProvidedNameUsesSpecifiedPort()
{
- var cf = new ConnectionFactory
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.HostName = "localhost";
+ cf.Port = 123;
+
+ Assert.Throws(() =>
{
- AutomaticRecoveryEnabled = true,
- HostName = "localhost",
- Port = 1234
- };
- Assert.Throws(() => { using IConnection conn = cf.CreateConnection("some_name"); });
+ using IConnection conn = cf.CreateConnection();
+ });
}
[Fact]
public void TestCreateConnectionWithClientProvidedNameUsesDefaultName()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = false,
- ClientProvidedName = "some_name"
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = false;
+ string expectedName = cf.ClientProvidedName;
+
using (IConnection conn = cf.CreateConnection())
{
- Assert.Equal("some_name", conn.ClientProvidedName);
- Assert.Equal("some_name", conn.ClientProperties["connection_name"]);
+ Assert.Equal(expectedName, conn.ClientProvidedName);
+ Assert.Equal(expectedName, conn.ClientProperties["connection_name"]);
}
}
[Fact]
public void TestCreateConnectionWithClientProvidedNameUsesNameArgumentValue()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = false
- };
- using (IConnection conn = cf.CreateConnection("some_name"))
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = false;
+ string expectedName = cf.ClientProvidedName;
+
+ using (IConnection conn = cf.CreateConnection(expectedName))
{
- Assert.Equal("some_name", conn.ClientProvidedName);
- Assert.Equal("some_name", conn.ClientProperties["connection_name"]);
+ Assert.Equal(expectedName, conn.ClientProvidedName);
+ Assert.Equal(expectedName, conn.ClientProperties["connection_name"]);
}
}
[Fact]
public void TestCreateConnectionWithClientProvidedNameAndAutorecoveryUsesNameArgumentValue()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true
- };
- using (IConnection conn = cf.CreateConnection("some_name"))
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ string expectedName = cf.ClientProvidedName;
+
+ using (IConnection conn = cf.CreateConnection(expectedName))
{
- Assert.Equal("some_name", conn.ClientProvidedName);
- Assert.Equal("some_name", conn.ClientProperties["connection_name"]);
+ Assert.Equal(expectedName, conn.ClientProvidedName);
+ Assert.Equal(expectedName, conn.ClientProperties["connection_name"]);
}
}
[Fact]
public void TestCreateConnectionAmqpTcpEndpointListAndClientProvidedName()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ string expectedName = cf.ClientProvidedName;
+
var xs = new List { new AmqpTcpEndpoint("localhost") };
- using (IConnection conn = cf.CreateConnection(xs, "some_name"))
+ using (IConnection conn = cf.CreateConnection(xs, expectedName))
{
- Assert.Equal("some_name", conn.ClientProvidedName);
- Assert.Equal("some_name", conn.ClientProperties["connection_name"]);
+ Assert.Equal(expectedName, conn.ClientProvidedName);
+ Assert.Equal(expectedName, conn.ClientProperties["connection_name"]);
}
}
[Fact]
public void TestCreateConnectionUsesDefaultPort()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true,
- HostName = "localhost"
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.HostName = "localhost";
+
using (IConnection conn = cf.CreateConnection())
{
Assert.Equal(5672, conn.Endpoint.Port);
@@ -169,11 +185,9 @@ public void TestCreateConnectionUsesDefaultPort()
[Fact]
public void TestCreateConnectionUsesDefaultMaxMessageSize()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true,
- HostName = "localhost"
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.HostName = "localhost";
Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, cf.MaxMessageSize);
Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, cf.Endpoint.MaxMessageSize);
@@ -187,12 +201,11 @@ public void TestCreateConnectionUsesDefaultMaxMessageSize()
[Fact]
public void TestCreateConnectionWithoutAutoRecoverySelectsAHostFromTheList()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = false,
- HostName = "not_localhost"
- };
- IConnection conn = cf.CreateConnection(new List { "localhost" }, "oregano");
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = false;
+ cf.HostName = "not_localhost";
+
+ IConnection conn = cf.CreateConnection(new List { "localhost" });
conn.Close();
conn.Dispose();
Assert.Equal("not_localhost", cf.HostName);
@@ -202,12 +215,10 @@ public void TestCreateConnectionWithoutAutoRecoverySelectsAHostFromTheList()
[Fact]
public void TestCreateConnectionWithAutoRecoveryUsesAmqpTcpEndpoint()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true,
- HostName = "not_localhost",
- Port = 1234
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.HostName = "not_localhost";
+ cf.Port = 1234;
var ep = new AmqpTcpEndpoint("localhost");
using (IConnection conn = cf.CreateConnection(new List { ep })) { }
}
@@ -215,10 +226,8 @@ public void TestCreateConnectionWithAutoRecoveryUsesAmqpTcpEndpoint()
[Fact]
public void TestCreateConnectionWithAutoRecoveryUsesInvalidAmqpTcpEndpoint()
{
- var cf = new ConnectionFactory
- {
- AutomaticRecoveryEnabled = true
- };
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
var ep = new AmqpTcpEndpoint("localhost", 1234);
Assert.Throws(() => { using IConnection conn = cf.CreateConnection(new List { ep }); });
}
@@ -226,11 +235,9 @@ public void TestCreateConnectionWithAutoRecoveryUsesInvalidAmqpTcpEndpoint()
[Fact]
public void TestCreateConnectionUsesAmqpTcpEndpoint()
{
- var cf = new ConnectionFactory
- {
- HostName = "not_localhost",
- Port = 1234
- };
+ var cf = CreateConnectionFactory();
+ cf.HostName = "not_localhost";
+ cf.Port = 1234;
var ep = new AmqpTcpEndpoint("localhost");
using (IConnection conn = cf.CreateConnection(new List { ep })) { }
}
@@ -238,33 +245,34 @@ public void TestCreateConnectionUsesAmqpTcpEndpoint()
[Fact]
public void TestCreateConnectionWithForcedAddressFamily()
{
- var cf = new ConnectionFactory
- {
- HostName = "not_localhost"
- };
+ var cf = CreateConnectionFactory();
+ cf.HostName = "not_localhost";
var ep = new AmqpTcpEndpoint("localhost")
{
AddressFamily = System.Net.Sockets.AddressFamily.InterNetwork
};
cf.Endpoint = ep;
- using (IConnection conn = cf.CreateConnection()) { };
+ using IConnection conn = cf.CreateConnection();
}
[Fact]
public void TestCreateConnectionUsesInvalidAmqpTcpEndpoint()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
var ep = new AmqpTcpEndpoint("localhost", 1234);
- Assert.Throws(() => { using (IConnection conn = cf.CreateConnection(new List { ep })) { } });
+ Assert.Throws(() =>
+ {
+ using IConnection conn = cf.CreateConnection(new List { ep });
+ });
}
[Fact]
public void TestCreateConnectioUsesValidEndpointWhenMultipleSupplied()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
var invalidEp = new AmqpTcpEndpoint("not_localhost");
var ep = new AmqpTcpEndpoint("localhost");
- using (IConnection conn = cf.CreateConnection(new List { invalidEp, ep })) { };
+ using IConnection conn = cf.CreateConnection(new List { invalidEp, ep });
}
[Fact]
@@ -276,7 +284,7 @@ public void TestCreateAmqpTCPEndPointOverridesMaxMessageSizeWhenGreaterThanMaxim
[Fact]
public void TestCreateConnectionUsesConfiguredMaxMessageSize()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
cf.MaxMessageSize = 1500;
using (IConnection conn = cf.CreateConnection())
{
@@ -286,7 +294,7 @@ public void TestCreateConnectionUsesConfiguredMaxMessageSize()
[Fact]
public void TestCreateConnectionWithAmqpEndpointListUsesAmqpTcpEndpointMaxMessageSize()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
cf.MaxMessageSize = 1500;
var ep = new AmqpTcpEndpoint("localhost");
Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, ep.MaxMessageSize);
@@ -299,7 +307,7 @@ public void TestCreateConnectionWithAmqpEndpointListUsesAmqpTcpEndpointMaxMessag
[Fact]
public void TestCreateConnectionWithAmqpEndpointResolverUsesAmqpTcpEndpointMaxMessageSize()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
cf.MaxMessageSize = 1500;
var ep = new AmqpTcpEndpoint("localhost", -1, new SslOption(), 1200);
using (IConnection conn = cf.CreateConnection(new List { ep }))
@@ -311,7 +319,7 @@ public void TestCreateConnectionWithAmqpEndpointResolverUsesAmqpTcpEndpointMaxMe
[Fact]
public void TestCreateConnectionWithHostnameListUsesConnectionFactoryMaxMessageSize()
{
- var cf = new ConnectionFactory();
+ var cf = CreateConnectionFactory();
cf.MaxMessageSize = 1500;
using (IConnection conn = cf.CreateConnection(new List { "localhost" }))
{
diff --git a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs b/projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs
similarity index 78%
rename from projects/Unit/TestConnectionFactoryContinuationTimeout.cs
rename to projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs
index 5eb79b4ef8..23022e8d8b 100644
--- a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs
+++ b/projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs
@@ -30,10 +30,11 @@
//---------------------------------------------------------------------------
using System;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConnectionFactoryContinuationTimeout : IntegrationFixture
{
@@ -41,6 +42,10 @@ public TestConnectionFactoryContinuationTimeout(ITestOutputHelper output) : base
{
}
+ protected override void SetUp()
+ {
+ }
+
[Fact]
public void TestConnectionFactoryContinuationTimeoutOnRecoveringConnection()
{
@@ -57,8 +62,19 @@ public void TestConnectionFactoryContinuationTimeoutOnNonRecoveringConnection()
var continuationTimeout = TimeSpan.FromSeconds(777);
using (IConnection c = CreateConnectionWithContinuationTimeout(false, continuationTimeout))
{
- Assert.Equal(continuationTimeout, c.CreateChannel().ContinuationTimeout);
+ using (IChannel ch = c.CreateChannel())
+ {
+ Assert.Equal(continuationTimeout, ch.ContinuationTimeout);
+ }
}
}
+
+ private IConnection CreateConnectionWithContinuationTimeout(bool automaticRecoveryEnabled, TimeSpan continuationTimeout)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = automaticRecoveryEnabled;
+ cf.ContinuationTimeout = continuationTimeout;
+ return cf.CreateConnection();
+ }
}
}
diff --git a/projects/Unit/TestConnectionShutdown.cs b/projects/Test/Integration/TestConnectionShutdown.cs
similarity index 84%
rename from projects/Unit/TestConnectionShutdown.cs
rename to projects/Test/Integration/TestConnectionShutdown.cs
index d063742fde..5489ec4611 100644
--- a/projects/Unit/TestConnectionShutdown.cs
+++ b/projects/Test/Integration/TestConnectionShutdown.cs
@@ -31,12 +31,13 @@
using System;
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Framing.Impl;
using RabbitMQ.Client.Impl;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConnectionShutdown : IntegrationFixture
{
@@ -47,9 +48,6 @@ public TestConnectionShutdown(ITestOutputHelper output) : base(output)
[Fact]
public void TestCleanClosureWithSocketClosedOutOfBand()
{
- _conn = CreateAutorecoveringConnection();
- _channel = _conn.CreateChannel();
-
var latch = new ManualResetEventSlim(false);
_channel.ChannelShutdown += (channel, args) =>
{
@@ -57,18 +55,15 @@ public void TestCleanClosureWithSocketClosedOutOfBand()
};
var c = (AutorecoveringConnection)_conn;
- c.FrameHandler.Close();
+ c.CloseFrameHandler();
_conn.Close(TimeSpan.FromSeconds(4));
- Wait(latch, TimeSpan.FromSeconds(5));
+ Wait(latch, TimeSpan.FromSeconds(5), "channel shutdown");
}
[Fact]
public void TestAbortWithSocketClosedOutOfBand()
{
- _conn = CreateAutorecoveringConnection();
- _channel = _conn.CreateChannel();
-
var latch = new ManualResetEventSlim(false);
_channel.ChannelShutdown += (channel, args) =>
{
@@ -76,19 +71,16 @@ public void TestAbortWithSocketClosedOutOfBand()
};
var c = (AutorecoveringConnection)_conn;
- c.FrameHandler.Close();
+ c.CloseFrameHandler();
_conn.Abort();
// default Connection.Abort() timeout and then some
- Wait(latch, TimeSpan.FromSeconds(6));
+ Wait(latch, TimeSpan.FromSeconds(6), "channel shutdown");
}
[Fact]
public void TestDisposedWithSocketClosedOutOfBand()
{
- _conn = CreateAutorecoveringConnection();
- _channel = _conn.CreateChannel();
-
var latch = new ManualResetEventSlim(false);
_channel.ChannelShutdown += (channel, args) =>
{
@@ -96,10 +88,10 @@ public void TestDisposedWithSocketClosedOutOfBand()
};
var c = (AutorecoveringConnection)_conn;
- c.FrameHandler.Close();
+ c.CloseFrameHandler();
_conn.Dispose();
- Wait(latch, TimeSpan.FromSeconds(3));
+ Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown");
}
[Fact]
@@ -113,7 +105,7 @@ public void TestShutdownSignalPropagationToChannels()
};
_conn.Close();
- Wait(latch, TimeSpan.FromSeconds(3));
+ Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown");
}
[Fact]
@@ -128,7 +120,7 @@ public void TestConsumerDispatcherShutdown()
};
Assert.False(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close");
_conn.Close();
- Wait(latch, TimeSpan.FromSeconds(3));
+ Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown");
Assert.True(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close");
}
}
diff --git a/projects/Test/Integration/TestConsumer.cs b/projects/Test/Integration/TestConsumer.cs
new file mode 100644
index 0000000000..9aa3082eb4
--- /dev/null
+++ b/projects/Test/Integration/TestConsumer.cs
@@ -0,0 +1,142 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.Integration
+{
+ public class TestConsumer : IntegrationFixture
+ {
+ private readonly byte[] _body = GetRandomBody(64);
+ private readonly ShutdownEventArgs _closeArgs = new ShutdownEventArgs(ShutdownInitiator.Application, Constants.ReplySuccess, "normal shutdown");
+
+ public TestConsumer(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void TestBasicRoundtrip()
+ {
+ QueueDeclareOk q = _channel.QueueDeclare();
+ _channel.BasicPublish("", q.QueueName, _body);
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ are.Set();
+ };
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+ // ensure we get a delivery
+ bool waitRes = are.WaitOne(2000);
+ Assert.True(waitRes);
+ // unsubscribe and ensure no further deliveries
+ _channel.BasicCancel(tag);
+ _channel.BasicPublish("", q.QueueName, _body);
+ bool waitResFalse = are.WaitOne(2000);
+ Assert.False(waitResFalse);
+ }
+
+ [Fact]
+ public void TestBasicRoundtripNoWait()
+ {
+ QueueDeclareOk q = _channel.QueueDeclare();
+ _channel.BasicPublish("", q.QueueName, _body);
+ var consumer = new EventingBasicConsumer(_channel);
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ are.Set();
+ };
+ string tag = _channel.BasicConsume(q.QueueName, true, consumer);
+ // ensure we get a delivery
+ bool waitRes = are.WaitOne(2000);
+ Assert.True(waitRes);
+ // unsubscribe and ensure no further deliveries
+ _channel.BasicCancelNoWait(tag);
+ _channel.BasicPublish("", q.QueueName, _body);
+ bool waitResFalse = are.WaitOne(2000);
+ Assert.False(waitResFalse);
+ }
+
+ [Fact]
+ public void ConcurrentEventingTestForReceived()
+ {
+ const int NumberOfThreads = 4;
+ const int NumberOfRegistrations = 5000;
+
+ var called = new byte[NumberOfThreads * NumberOfRegistrations];
+
+ QueueDeclareOk q = _channel.QueueDeclare();
+ var consumer = new EventingBasicConsumer(_channel);
+ _channel.BasicConsume(q.QueueName, true, consumer);
+ var countdownEvent = new CountdownEvent(NumberOfThreads);
+ for (int i = 0; i < NumberOfThreads; i++)
+ {
+ int threadIndex = i;
+ Task.Run(() =>
+ {
+ int start = threadIndex * NumberOfRegistrations;
+ for (int j = start; j < start + NumberOfRegistrations; j++)
+ {
+ int receivedIndex = j;
+ consumer.Received += (sender, eventArgs) =>
+ {
+ called[receivedIndex] = 1;
+ };
+ }
+ countdownEvent.Signal();
+ });
+ }
+
+ countdownEvent.Wait();
+
+ // Add last receiver
+ var are = new AutoResetEvent(false);
+ consumer.Received += (o, a) =>
+ {
+ are.Set();
+ };
+
+ // Send message
+ _channel.BasicPublish("", q.QueueName, ReadOnlyMemory.Empty);
+ are.WaitOne(TimingFixture.TestTimeout);
+
+ // Check received messages
+ Assert.Equal(-1, called.AsSpan().IndexOf((byte)0));
+ }
+ }
+}
diff --git a/projects/Unit/TestConsumerCancelNotify.cs b/projects/Test/Integration/TestConsumerCancelNotify.cs
similarity index 99%
rename from projects/Unit/TestConsumerCancelNotify.cs
rename to projects/Test/Integration/TestConsumerCancelNotify.cs
index f298852965..152ed8e9f5 100644
--- a/projects/Unit/TestConsumerCancelNotify.cs
+++ b/projects/Test/Integration/TestConsumerCancelNotify.cs
@@ -31,11 +31,12 @@
using System.Linq;
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConsumerCancelNotify : IntegrationFixture
{
diff --git a/projects/Unit/TestConsumerCount.cs b/projects/Test/Integration/TestConsumerCount.cs
similarity index 97%
rename from projects/Unit/TestConsumerCount.cs
rename to projects/Test/Integration/TestConsumerCount.cs
index 1f846e8621..8b38ea0360 100644
--- a/projects/Unit/TestConsumerCount.cs
+++ b/projects/Test/Integration/TestConsumerCount.cs
@@ -29,11 +29,12 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConsumerCount : IntegrationFixture
{
diff --git a/projects/Unit/TestConsumerExceptions.cs b/projects/Test/Integration/TestConsumerExceptions.cs
similarity index 98%
rename from projects/Unit/TestConsumerExceptions.cs
rename to projects/Test/Integration/TestConsumerExceptions.cs
index 5bf62391fc..7f46861ae2 100644
--- a/projects/Unit/TestConsumerExceptions.cs
+++ b/projects/Test/Integration/TestConsumerExceptions.cs
@@ -31,10 +31,11 @@
using System;
using System.Threading;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConsumerExceptions : IntegrationFixture
{
@@ -50,7 +51,7 @@ public override void HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
throw new Exception("oops");
}
diff --git a/projects/Unit/TestConsumerOperationDispatch.cs b/projects/Test/Integration/TestConsumerOperationDispatch.cs
similarity index 87%
rename from projects/Unit/TestConsumerOperationDispatch.cs
rename to projects/Test/Integration/TestConsumerOperationDispatch.cs
index 1f3a1129e2..019cc4b952 100644
--- a/projects/Unit/TestConsumerOperationDispatch.cs
+++ b/projects/Test/Integration/TestConsumerOperationDispatch.cs
@@ -32,32 +32,33 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestConsumerOperationDispatch : IntegrationFixture
{
- public TestConsumerOperationDispatch(ITestOutputHelper output) : base(output)
- {
- }
+ // number of channels (and consumers)
+ private const int Y = 100;
+ // number of messages to be published
+ private const int N = 100;
- private readonly string _x = "dotnet.tests.consumer-operation-dispatch.fanout";
+ private static readonly CountdownEvent s_counter = new CountdownEvent(Y);
+
+ private const string _x = "dotnet.tests.consumer-operation-dispatch.fanout";
private readonly List _channels = new List();
private readonly List _queues = new List();
private readonly List _consumers = new List();
- // number of channels (and consumers)
- private const int Y = 100;
- // number of messages to be published
- private const int N = 100;
-
- public static CountdownEvent counter = new CountdownEvent(Y);
+ public TestConsumerOperationDispatch(ITestOutputHelper output) : base(output)
+ {
+ }
- public override void Dispose()
+ protected override void TearDown()
{
foreach (IChannel ch in _channels)
{
@@ -67,10 +68,7 @@ public override void Dispose()
}
}
- _queues.Clear();
- _consumers.Clear();
- counter.Reset();
- base.ReleaseResources();
+ s_counter.Reset();
}
private class CollectingConsumer : DefaultBasicConsumer
@@ -85,7 +83,7 @@ public CollectingConsumer(IChannel channel)
public override void HandleBasicDeliver(string consumerTag,
ulong deliveryTag, bool redelivered, string exchange, string routingKey,
- in ReadOnlyBasicProperties properties, ReadOnlyMemory body)
+ in ReadOnlyBasicProperties properties, RentedMemory body)
{
// we test concurrent dispatch from the moment basic.delivery is returned.
// delivery tags have guaranteed ordering and we verify that it is preserved
@@ -94,18 +92,19 @@ public override void HandleBasicDeliver(string consumerTag,
if (deliveryTag == N)
{
- counter.Signal();
+ s_counter.Signal();
}
Channel.BasicAck(deliveryTag: deliveryTag, multiple: false);
}
}
- [Fact]
+ [SkippableFact]
public void TestDeliveryOrderingWithSingleChannel()
{
- IChannel Ch = _conn.CreateChannel();
- Ch.ExchangeDeclare(_x, "fanout", durable: false);
+ Skip.If(IsRunningInCI && IsWindows, "TODO - test is slow in CI on Windows");
+
+ _channel.ExchangeDeclare(_x, "fanout", durable: false);
for (int i = 0; i < Y; i++)
{
@@ -121,9 +120,17 @@ public void TestDeliveryOrderingWithSingleChannel()
for (int i = 0; i < N; i++)
{
- Ch.BasicPublish(_x, "", _encoding.GetBytes("msg"));
+ _channel.BasicPublish(_x, "", _encoding.GetBytes("msg"));
+ }
+
+ if (IsRunningInCI)
+ {
+ s_counter.Wait(TimeSpan.FromMinutes(5));
+ }
+ else
+ {
+ s_counter.Wait(TimeSpan.FromMinutes(2));
}
- counter.Wait(TimeSpan.FromSeconds(120));
foreach (CollectingConsumer cons in _consumers)
{
@@ -145,9 +152,10 @@ public void TestDeliveryOrderingWithSingleChannel()
[Fact]
public void TestChannelShutdownDoesNotShutDownDispatcher()
{
+ _channel.ExchangeDeclare(_x, "fanout", durable: false);
+
IChannel ch1 = _conn.CreateChannel();
IChannel ch2 = _conn.CreateChannel();
- _channel.ExchangeDeclare(_x, "fanout", durable: false);
string q1 = ch1.QueueDeclare().QueueName;
string q2 = ch2.QueueDeclare().QueueName;
@@ -165,7 +173,7 @@ public void TestChannelShutdownDoesNotShutDownDispatcher()
ch1.Close();
ch2.BasicPublish(_x, "", _encoding.GetBytes("msg"));
- Wait(latch);
+ Wait(latch, "received event");
}
private class ShutdownLatchConsumer : DefaultBasicConsumer
@@ -203,7 +211,7 @@ public void TestChannelShutdownHandler()
_channel.BasicConsume(queue: q, autoAck: true, consumer: c);
_channel.Close();
- Wait(latch, TimeSpan.FromSeconds(5));
+ Wait(latch, TimeSpan.FromSeconds(5), "channel shutdown");
Assert.False(duplicateLatch.Wait(TimeSpan.FromSeconds(5)),
"event handler fired more than once");
}
diff --git a/projects/Unit/TestEventingConsumer.cs b/projects/Test/Integration/TestEventingConsumer.cs
similarity index 96%
rename from projects/Unit/TestEventingConsumer.cs
rename to projects/Test/Integration/TestEventingConsumer.cs
index 6660af4f13..c12da36c8f 100644
--- a/projects/Unit/TestEventingConsumer.cs
+++ b/projects/Test/Integration/TestEventingConsumer.cs
@@ -30,11 +30,12 @@
//---------------------------------------------------------------------------
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestEventingConsumer : IntegrationFixture
{
@@ -66,14 +67,14 @@ public void TestEventingConsumerRegistrationEvents()
};
string tag = _channel.BasicConsume(q, false, ec);
- Wait(registeredLatch);
+ Wait(registeredLatch, "consumer registered");
Assert.NotNull(registeredSender);
Assert.Equal(ec, registeredSender);
Assert.Equal(_channel, ((EventingBasicConsumer)registeredSender).Channel);
_channel.BasicCancel(tag);
- Wait(unregisteredLatch);
+ Wait(unregisteredLatch, "consumer unregistered");
Assert.NotNull(unregisteredSender);
Assert.Equal(ec, unregisteredSender);
Assert.Equal(_channel, ((EventingBasicConsumer)unregisteredSender).Channel);
diff --git a/projects/Unit/TestExceptionMessages.cs b/projects/Test/Integration/TestExceptionMessages.cs
similarity index 98%
rename from projects/Unit/TestExceptionMessages.cs
rename to projects/Test/Integration/TestExceptionMessages.cs
index 2f87a9f430..7901096f54 100644
--- a/projects/Unit/TestExceptionMessages.cs
+++ b/projects/Test/Integration/TestExceptionMessages.cs
@@ -34,7 +34,7 @@
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestExceptionMessages : IntegrationFixture
{
diff --git a/projects/Unit/TestExchangeDeclare.cs b/projects/Test/Integration/TestExchangeDeclare.cs
similarity index 60%
rename from projects/Unit/TestExchangeDeclare.cs
rename to projects/Test/Integration/TestExchangeDeclare.cs
index 555c58c1ad..47056d76ed 100644
--- a/projects/Unit/TestExchangeDeclare.cs
+++ b/projects/Test/Integration/TestExchangeDeclare.cs
@@ -32,35 +32,38 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestExchangeDeclare : IntegrationFixture
{
+ private readonly Random _rnd = new Random();
+
public TestExchangeDeclare(ITestOutputHelper output) : base(output)
{
}
[Fact]
- public void TestConcurrentExchangeDeclare()
+ public void TestConcurrentExchangeDeclareAndDelete()
{
- string x = GenerateExchangeName();
- Random rnd = new Random();
-
- List ts = new List();
+ var exchangeNames = new List();
+ var ts = new List();
NotSupportedException nse = null;
for (int i = 0; i < 256; i++)
{
- Thread t = new Thread(() =>
+ var t = new Thread(() =>
{
try
{
// sleep for a random amount of time to increase the chances
// of thread interleaving. MK.
- Thread.Sleep(rnd.Next(5, 500));
- _channel.ExchangeDeclare(x, "fanout", false, false, null);
+ Thread.Sleep(_rnd.Next(5, 500));
+ string exchangeName = GenerateExchangeName();
+ _channel.ExchangeDeclare(exchange: exchangeName, "fanout", false, false, null);
+ exchangeNames.Add(exchangeName);
}
catch (NotSupportedException e)
{
@@ -77,7 +80,34 @@ public void TestConcurrentExchangeDeclare()
}
Assert.Null(nse);
- _channel.ExchangeDelete(x);
+ ts.Clear();
+
+ foreach (string exchangeName in exchangeNames)
+ {
+ var t = new Thread((object ex) =>
+ {
+ try
+ {
+ // sleep for a random amount of time to increase the chances
+ // of thread interleaving. MK.
+ Thread.Sleep(_rnd.Next(5, 500));
+ _channel.ExchangeDelete((string)ex);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ });
+ ts.Add(t);
+ t.Start(exchangeName);
+ }
+
+ foreach (Thread t in ts)
+ {
+ t.Join();
+ }
+
+ Assert.Null(nse);
}
}
}
diff --git a/projects/Unit/TestHeartbeats.cs b/projects/Test/Integration/TestHeartbeats.cs
similarity index 62%
rename from projects/Unit/TestHeartbeats.cs
rename to projects/Test/Integration/TestHeartbeats.cs
index 40deb350f7..86e1600821 100644
--- a/projects/Unit/TestHeartbeats.cs
+++ b/projects/Test/Integration/TestHeartbeats.cs
@@ -31,34 +31,43 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Threading;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestHeartbeats : IntegrationFixture
{
+ private readonly TimeSpan _heartbeatTimeout = TimeSpan.FromSeconds(2);
+
public TestHeartbeats(ITestOutputHelper output) : base(output)
{
}
- private readonly TimeSpan _heartbeatTimeout = TimeSpan.FromSeconds(2);
+ protected override void SetUp()
+ {
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
- [Fact(Timeout = 35000)]
+ [SkippableFact(Timeout = 35000)]
[Trait("Category", "LongRunning")]
public void TestThatHeartbeatWriterUsesConfigurableInterval()
{
- var cf = new ConnectionFactory()
- {
- RequestedHeartbeat = _heartbeatTimeout,
- AutomaticRecoveryEnabled = false
- };
+ Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test");
+
+ var cf = CreateConnectionFactory();
+ cf.RequestedHeartbeat = _heartbeatTimeout;
+ cf.AutomaticRecoveryEnabled = false;
+
RunSingleConnectionTest(cf);
}
[SkippableFact]
+ [Trait("Category", "LongRunning")]
public void TestThatHeartbeatWriterWithTLSEnabled()
{
Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test");
@@ -66,12 +75,10 @@ public void TestThatHeartbeatWriterWithTLSEnabled()
var sslEnv = new SslEnv();
Skip.IfNot(sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test");
- var cf = new ConnectionFactory()
- {
- Port = 5671,
- RequestedHeartbeat = _heartbeatTimeout,
- AutomaticRecoveryEnabled = false
- };
+ var cf = CreateConnectionFactory();
+ cf.Port = 5671;
+ cf.RequestedHeartbeat = _heartbeatTimeout;
+ cf.AutomaticRecoveryEnabled = false;
cf.Ssl.ServerName = sslEnv.Hostname;
cf.Ssl.CertPath = sslEnv.CertPath;
@@ -81,43 +88,59 @@ public void TestThatHeartbeatWriterWithTLSEnabled()
RunSingleConnectionTest(cf);
}
- [Fact(Timeout = 90000)]
+ [SkippableFact(Timeout = 90000)]
[Trait("Category", "LongRunning")]
public void TestHundredsOfConnectionsWithRandomHeartbeatInterval()
{
- var rnd = new Random();
- List xs = new List();
- // Since we are using the ThreadPool, let's set MinThreads to a high-enough value.
- ThreadPool.SetMinThreads(200, 200);
- for (int i = 0; i < 200; i++)
+ Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test");
+
+ const ushort connectionCount = 200;
+
+ ThreadPool.GetMinThreads(out int origWorkerThreads, out int origCompletionPortThreads);
+ try
{
- ushort n = Convert.ToUInt16(rnd.Next(2, 6));
- var cf = new ConnectionFactory()
+ var rnd = new Random();
+ var conns = new List();
+
+ // Since we are using the ThreadPool, let's set MinThreads to a high-enough value.
+ ThreadPool.SetMinThreads(connectionCount, connectionCount);
+
+ try
{
- RequestedHeartbeat = TimeSpan.FromSeconds(n),
- AutomaticRecoveryEnabled = false
- };
- IConnection conn = cf.CreateConnection();
- xs.Add(conn);
- IChannel ch = conn.CreateChannel();
-
- conn.ConnectionShutdown += (sender, evt) =>
+ for (int i = 0; i < connectionCount; i++)
{
- CheckInitiator(evt);
- };
+ ushort n = Convert.ToUInt16(rnd.Next(2, 6));
+ var cf = CreateConnectionFactory();
+ cf.RequestedHeartbeat = TimeSpan.FromSeconds(n);
+ cf.AutomaticRecoveryEnabled = false;
+
+ IConnection conn = cf.CreateConnection($"_testDisplayName:{i}");
+ conns.Add(conn);
+ IChannel ch = conn.CreateChannel();
+ conn.ConnectionShutdown += (sender, evt) =>
+ {
+ CheckInitiator(evt);
+ };
+ }
+ SleepFor(60);
+ }
+ finally
+ {
+ foreach (IConnection conn in conns)
+ {
+ conn.Close();
+ }
+ }
}
-
- SleepFor(60);
-
- foreach (IConnection x in xs)
+ finally
{
- x.Close();
+ Assert.True(ThreadPool.SetMinThreads(origWorkerThreads, origCompletionPortThreads));
}
}
- protected void RunSingleConnectionTest(ConnectionFactory cf)
+ private void RunSingleConnectionTest(ConnectionFactory cf)
{
- using (IConnection conn = cf.CreateConnection())
+ using (IConnection conn = cf.CreateConnection(_testDisplayName))
{
using (IChannel ch = conn.CreateChannel())
{
@@ -145,11 +168,18 @@ protected void RunSingleConnectionTest(ConnectionFactory cf)
private bool LongRunningTestsEnabled()
{
string s = Environment.GetEnvironmentVariable("RABBITMQ_LONG_RUNNING_TESTS");
+
if (String.IsNullOrEmpty(s))
{
return false;
}
- return true;
+
+ if (Boolean.TryParse(s, out bool enabled))
+ {
+ return enabled;
+ }
+
+ return false;
}
private void SleepFor(int t)
diff --git a/projects/Unit/TestInitialConnection.cs b/projects/Test/Integration/TestInitialConnection.cs
similarity index 88%
rename from projects/Unit/TestInitialConnection.cs
rename to projects/Test/Integration/TestInitialConnection.cs
index f20afb9bb9..b0a34e0d36 100644
--- a/projects/Unit/TestInitialConnection.cs
+++ b/projects/Test/Integration/TestInitialConnection.cs
@@ -30,11 +30,12 @@
//---------------------------------------------------------------------------
using System.Collections.Generic;
+using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestInitialConnection : IntegrationFixture
{
@@ -45,7 +46,7 @@ public TestInitialConnection(ITestOutputHelper output) : base(output)
[Fact]
public void TestBasicConnectionRecoveryWithHostnameList()
{
- Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "127.0.0.1", "localhost" });
+ var c = CreateAutorecoveringConnection(new List() { "127.0.0.1", "localhost" });
Assert.True(c.IsOpen);
c.Close();
}
@@ -53,7 +54,7 @@ public void TestBasicConnectionRecoveryWithHostnameList()
[Fact]
public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts()
{
- Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "191.72.44.22", "127.0.0.1", "localhost" });
+ var c = CreateAutorecoveringConnection(new List() { "191.72.44.22", "127.0.0.1", "localhost" });
Assert.True(c.IsOpen);
c.Close();
}
diff --git a/projects/Unit/TestInvalidAck.cs b/projects/Test/Integration/TestInvalidAck.cs
similarity index 97%
rename from projects/Unit/TestInvalidAck.cs
rename to projects/Test/Integration/TestInvalidAck.cs
index 0d3d5dc750..427e8a9342 100644
--- a/projects/Unit/TestInvalidAck.cs
+++ b/projects/Test/Integration/TestInvalidAck.cs
@@ -30,10 +30,11 @@
//---------------------------------------------------------------------------
using System.Threading;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestInvalidAck : IntegrationFixture
{
diff --git a/projects/Unit/TestMainLoop.cs b/projects/Test/Integration/TestMainLoop.cs
similarity index 80%
rename from projects/Unit/TestMainLoop.cs
rename to projects/Test/Integration/TestMainLoop.cs
index 597f1898f3..ab29fc5c1a 100644
--- a/projects/Unit/TestMainLoop.cs
+++ b/projects/Test/Integration/TestMainLoop.cs
@@ -31,11 +31,12 @@
using System;
using System.Threading;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestMainLoop : IntegrationFixture
{
@@ -53,7 +54,7 @@ public override void HandleBasicDeliver(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ RentedMemory body)
{
throw new Exception("I am a bad consumer");
}
@@ -62,27 +63,24 @@ public override void HandleBasicDeliver(string consumerTag,
[Fact]
public void TestCloseWithFaultyConsumer()
{
- ConnectionFactory connFactory = new ConnectionFactory();
- IConnection c = connFactory.CreateConnection();
- IChannel m = _conn.CreateChannel();
object o = new object();
string q = GenerateQueueName();
- m.QueueDeclare(q, false, false, false, null);
+ _channel.QueueDeclare(q, false, false, false, null);
CallbackExceptionEventArgs ea = null;
- m.CallbackException += (_, evt) =>
+ _channel.CallbackException += (_, evt) =>
{
ea = evt;
- c.Close();
+ _channel.Close();
Monitor.PulseAll(o);
};
- m.BasicConsume(q, true, new FaultyConsumer(_channel));
- m.BasicPublish("", q, _encoding.GetBytes("message"));
+ _channel.BasicConsume(q, true, new FaultyConsumer(_channel));
+ _channel.BasicPublish("", q, _encoding.GetBytes("message"));
WaitOn(o);
Assert.NotNull(ea);
- Assert.False(c.IsOpen);
- Assert.Equal(200, c.CloseReason.ReplyCode);
+ Assert.False(_channel.IsOpen);
+ Assert.Equal(200, _channel.CloseReason.ReplyCode);
}
}
}
diff --git a/projects/Unit/TestNowait.cs b/projects/Test/Integration/TestNowait.cs
similarity index 98%
rename from projects/Unit/TestNowait.cs
rename to projects/Test/Integration/TestNowait.cs
index 33df3c69d4..4fa6f06b30 100644
--- a/projects/Unit/TestNowait.cs
+++ b/projects/Test/Integration/TestNowait.cs
@@ -29,10 +29,11 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestNoWait : IntegrationFixture
{
diff --git a/projects/Unit/TestPassiveDeclare.cs b/projects/Test/Integration/TestPassiveDeclare.cs
similarity index 98%
rename from projects/Unit/TestPassiveDeclare.cs
rename to projects/Test/Integration/TestPassiveDeclare.cs
index b4871d75bb..6063697159 100644
--- a/projects/Unit/TestPassiveDeclare.cs
+++ b/projects/Test/Integration/TestPassiveDeclare.cs
@@ -34,7 +34,7 @@
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestPassiveDeclare : IntegrationFixture
{
diff --git a/projects/Unit/TestQueueDeclare.cs b/projects/Test/Integration/TestQueueDeclare.cs
similarity index 63%
rename from projects/Unit/TestQueueDeclare.cs
rename to projects/Test/Integration/TestQueueDeclare.cs
index 8896468527..d068993c63 100644
--- a/projects/Unit/TestQueueDeclare.cs
+++ b/projects/Test/Integration/TestQueueDeclare.cs
@@ -32,11 +32,11 @@
using System;
using System.Collections.Generic;
using System.Threading;
-using System.Threading.Tasks;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestQueueDeclare : IntegrationFixture
{
@@ -45,20 +45,9 @@ public TestQueueDeclare(ITestOutputHelper output) : base(output)
}
[Fact]
- public async void TestQueueDeclareAsync()
- {
- string q = GenerateQueueName();
- QueueDeclareOk result = await _channel.QueueDeclareAsync(q, false, false, false, null);
- Assert.Equal(q, result.QueueName);
- }
-
- [Fact]
- [Trait("Category", "RequireSMP")]
public void TestConcurrentQueueDeclare()
{
- string q = GenerateQueueName();
- var rnd = new Random();
-
+ var qs = new List();
var ts = new List();
NotSupportedException nse = null;
for (int i = 0; i < 256; i++)
@@ -69,8 +58,10 @@ public void TestConcurrentQueueDeclare()
{
// sleep for a random amount of time to increase the chances
// of thread interleaving. MK.
- Thread.Sleep(rnd.Next(5, 50));
+ Thread.Sleep(S_Random.Next(5, 50));
+ string q = GenerateQueueName();
_channel.QueueDeclare(q, false, false, false, null);
+ qs.Add(q);
}
catch (NotSupportedException e)
{
@@ -87,41 +78,32 @@ public void TestConcurrentQueueDeclare()
}
Assert.Null(nse);
- _channel.QueueDelete(q);
- }
-
- [Fact]
- [Trait("Category", "RequireSMP")]
- public async void TestConcurrentQueueDeclareAsync()
- {
- string q = GenerateQueueName();
- var rnd = new Random();
+ ts.Clear();
- var ts = new List();
- NotSupportedException nse = null;
- for (int i = 0; i < 256; i++)
+ foreach (string queueName in qs)
{
- async Task f()
- {
- try
- {
- // sleep for a random amount of time to increase the chances
- // of thread interleaving. MK.
- await Task.Delay(rnd.Next(5, 50));
- QueueDeclareOk r = await _channel.QueueDeclareAsync(q, false, false, false, null);
- }
- catch (NotSupportedException e)
- {
- nse = e;
- }
- }
- var t = Task.Run(f);
+ var t = new Thread(() =>
+ {
+ try
+ {
+ Thread.Sleep(S_Random.Next(5, 50));
+ _channel.QueueDelete(queueName);
+ }
+ catch (NotSupportedException e)
+ {
+ nse = e;
+ }
+ });
ts.Add(t);
+ t.Start();
+ }
+
+ foreach (Thread t in ts)
+ {
+ t.Join();
}
- await Task.WhenAll(ts);
Assert.Null(nse);
- _channel.QueueDelete(q);
}
}
}
diff --git a/projects/Unit/TestSsl.cs b/projects/Test/Integration/TestSsl.cs
similarity index 70%
rename from projects/Unit/TestSsl.cs
rename to projects/Test/Integration/TestSsl.cs
index 4695b188e5..8152ec38ce 100644
--- a/projects/Unit/TestSsl.cs
+++ b/projects/Test/Integration/TestSsl.cs
@@ -29,39 +29,39 @@
// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
//---------------------------------------------------------------------------
-using System;
using System.IO;
using System.Net.Security;
-using System.Reflection;
using System.Security.Authentication;
+using RabbitMQ.Client;
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
- [Collection("IntegrationFixture")]
- public class TestSsl
+ public class TestSsl : IntegrationFixture
{
- private readonly ITestOutputHelper _output;
- private readonly string _testDisplayName;
private readonly SslEnv _sslEnv;
- public TestSsl(ITestOutputHelper output)
+ public TestSsl(ITestOutputHelper output) : base(output)
{
- _output = output;
- var type = _output.GetType();
- var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
- var test = (ITest)testMember.GetValue(output);
- _testDisplayName = test.DisplayName;
_sslEnv = new SslEnv();
}
+ protected override void SetUp()
+ {
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
+
[SkippableFact]
public void TestServerVerifiedIgnoringNameMismatch()
{
Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test");
- ConnectionFactory cf = new ConnectionFactory { Port = 5671 };
+ var cf = CreateConnectionFactory();
+ cf.Port = 5671;
+
cf.Ssl.ServerName = "*";
cf.Ssl.AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch;
cf.Ssl.Enabled = true;
@@ -73,7 +73,8 @@ public void TestServerVerified()
{
Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test");
- ConnectionFactory cf = new ConnectionFactory { Port = 5671 };
+ var cf = CreateConnectionFactory();
+ cf.Port = 5671;
cf.Ssl.ServerName = _sslEnv.Hostname;
cf.Ssl.Enabled = true;
SendReceive(cf);
@@ -85,10 +86,10 @@ public void TestClientAndServerVerified()
Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test");
string certPath = _sslEnv.CertPath;
- _output.WriteLine($"[INFO] certPath: {certPath}");
Assert.True(File.Exists(certPath));
- ConnectionFactory cf = new ConnectionFactory { Port = 5671 };
+ var cf = CreateConnectionFactory();
+ cf.Port = 5671;
cf.Ssl.ServerName = _sslEnv.Hostname;
cf.Ssl.CertPath = certPath;
cf.Ssl.CertPassphrase = _sslEnv.CertPassphrase;
@@ -102,19 +103,17 @@ public void TestNoClientCertificate()
{
Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test");
- ConnectionFactory cf = new ConnectionFactory
+ var cf = CreateConnectionFactory();
+ cf.Port = 5671;
+ cf.Ssl = new SslOption()
{
- Port = 5671,
- Ssl = new SslOption()
- {
- CertPath = null,
- Enabled = true,
- ServerName = _sslEnv.Hostname,
- Version = SslProtocols.None,
- AcceptablePolicyErrors =
- SslPolicyErrors.RemoteCertificateNotAvailable |
- SslPolicyErrors.RemoteCertificateNameMismatch
- }
+ CertPath = null,
+ Enabled = true,
+ ServerName = _sslEnv.Hostname,
+ Version = SslProtocols.None,
+ AcceptablePolicyErrors =
+ SslPolicyErrors.RemoteCertificateNotAvailable |
+ SslPolicyErrors.RemoteCertificateNameMismatch
};
SendReceive(cf);
@@ -122,7 +121,7 @@ public void TestNoClientCertificate()
private void SendReceive(ConnectionFactory cf)
{
- using (IConnection conn = cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"))
+ using (IConnection conn = cf.CreateConnection(_testDisplayName))
{
using (IChannel ch = conn.CreateChannel())
{
@@ -131,13 +130,13 @@ private void SendReceive(ConnectionFactory cf)
ch.QueueBind(qName, "Exchange_TestSslEndPoint", "Key_TestSslEndpoint", null);
string message = "Hello C# SSL Client World";
- byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message);
+ byte[] msgBytes = _encoding.GetBytes(message);
ch.BasicPublish("Exchange_TestSslEndPoint", "Key_TestSslEndpoint", msgBytes);
bool autoAck = false;
BasicGetResult result = ch.BasicGet(qName, autoAck);
- byte[] body = result.Body.ToArray();
- string resultMessage = System.Text.Encoding.UTF8.GetString(body);
+ byte[] body = result.Body;
+ string resultMessage = _encoding.GetString(body);
Assert.Equal(message, resultMessage);
}
diff --git a/projects/Unit/TestUpdateSecret.cs b/projects/Test/Integration/TestUpdateSecret.cs
similarity index 95%
rename from projects/Unit/TestUpdateSecret.cs
rename to projects/Test/Integration/TestUpdateSecret.cs
index b81ef5ce15..d2396ca831 100644
--- a/projects/Unit/TestUpdateSecret.cs
+++ b/projects/Test/Integration/TestUpdateSecret.cs
@@ -31,7 +31,7 @@
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.Integration
{
public class TestUpdateSecret : IntegrationFixture
{
@@ -39,7 +39,6 @@ public TestUpdateSecret(ITestOutputHelper output) : base(output)
{
}
- [IgnoreOnVersionsEarlierThan(3, 8)]
public void TestUpdatingConnectionSecret()
{
_conn.UpdateSecret("new-secret", "Test Case");
diff --git a/projects/OAuth2Test/APIApproval.Approve.verified.txt b/projects/Test/OAuth2/APIApproval.Approve.verified.txt
similarity index 100%
rename from projects/OAuth2Test/APIApproval.Approve.verified.txt
rename to projects/Test/OAuth2/APIApproval.Approve.verified.txt
diff --git a/projects/OAuth2Test/APIApproval.cs b/projects/Test/OAuth2/APIApproval.cs
similarity index 100%
rename from projects/OAuth2Test/APIApproval.cs
rename to projects/Test/OAuth2/APIApproval.cs
diff --git a/projects/OAuth2Test/OAuth2Test.csproj b/projects/Test/OAuth2/OAuth2.csproj
similarity index 75%
rename from projects/OAuth2Test/OAuth2Test.csproj
rename to projects/Test/OAuth2/OAuth2.csproj
index e262304e8f..e11a42abb9 100644
--- a/projects/OAuth2Test/OAuth2Test.csproj
+++ b/projects/Test/OAuth2/OAuth2.csproj
@@ -9,15 +9,15 @@
- ../rabbit.snk
+ ../../rabbit.snk
true
latest
7.0
-
-
+
+
@@ -27,16 +27,15 @@
all
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
diff --git a/projects/OAuth2Test/README.md b/projects/Test/OAuth2/README.md
similarity index 100%
rename from projects/OAuth2Test/README.md
rename to projects/Test/OAuth2/README.md
diff --git a/projects/OAuth2Test/RequestFormMatcher.cs b/projects/Test/OAuth2/RequestFormMatcher.cs
similarity index 100%
rename from projects/OAuth2Test/RequestFormMatcher.cs
rename to projects/Test/OAuth2/RequestFormMatcher.cs
diff --git a/projects/OAuth2Test/TestOAuth2.cs b/projects/Test/OAuth2/TestOAuth2.cs
similarity index 97%
rename from projects/OAuth2Test/TestOAuth2.cs
rename to projects/Test/OAuth2/TestOAuth2.cs
index 9792d678f7..ba9db4a41c 100644
--- a/projects/OAuth2Test/TestOAuth2.cs
+++ b/projects/Test/OAuth2/TestOAuth2.cs
@@ -79,7 +79,8 @@ public TestOAuth2(ITestOutputHelper testOutputHelper)
{
AutomaticRecoveryEnabled = true,
CredentialsProvider = GetCredentialsProvider(options),
- CredentialsRefresher = GetCredentialsRefresher()
+ CredentialsRefresher = GetCredentialsRefresher(),
+ ClientProvidedName = nameof(TestOAuth2)
};
_connection = connectionFactory.CreateConnection();
@@ -148,7 +149,7 @@ private async Task Publish(IChannel publisher)
private async ValueTask declareConsumer()
{
IChannel subscriber = _connection.CreateChannel();
- await subscriber.QueueDeclareAsync("testqueue", true, false, false, arguments: null);
+ await subscriber.QueueDeclareAsync(queue: "testqueue", passive: false, true, false, false, arguments: null);
subscriber.QueueBind("testqueue", Exchange, "hello");
return subscriber;
}
diff --git a/projects/OAuth2Test/TestOAuth2Client.cs b/projects/Test/OAuth2/TestOAuth2Client.cs
similarity index 100%
rename from projects/OAuth2Test/TestOAuth2Client.cs
rename to projects/Test/OAuth2/TestOAuth2Client.cs
diff --git a/projects/OAuth2Test/TestOAuth2ClientCredentialsProvider.cs b/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs
similarity index 100%
rename from projects/OAuth2Test/TestOAuth2ClientCredentialsProvider.cs
rename to projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs
diff --git a/projects/OAuth2Test/enabled_plugins b/projects/Test/OAuth2/enabled_plugins
similarity index 100%
rename from projects/OAuth2Test/enabled_plugins
rename to projects/Test/OAuth2/enabled_plugins
diff --git a/projects/OAuth2Test/keycloak/import/test-realm.json b/projects/Test/OAuth2/keycloak/import/test-realm.json
similarity index 100%
rename from projects/OAuth2Test/keycloak/import/test-realm.json
rename to projects/Test/OAuth2/keycloak/import/test-realm.json
diff --git a/projects/OAuth2Test/keycloak/rabbitmq.conf b/projects/Test/OAuth2/keycloak/rabbitmq.conf
similarity index 100%
rename from projects/OAuth2Test/keycloak/rabbitmq.conf
rename to projects/Test/OAuth2/keycloak/rabbitmq.conf
diff --git a/projects/OAuth2Test/keycloak/signing-key/signing-key.pem b/projects/Test/OAuth2/keycloak/signing-key/signing-key.pem
similarity index 100%
rename from projects/OAuth2Test/keycloak/signing-key/signing-key.pem
rename to projects/Test/OAuth2/keycloak/signing-key/signing-key.pem
diff --git a/projects/OAuth2Test/uaa/log4j2.properties b/projects/Test/OAuth2/uaa/log4j2.properties
similarity index 100%
rename from projects/OAuth2Test/uaa/log4j2.properties
rename to projects/Test/OAuth2/uaa/log4j2.properties
diff --git a/projects/OAuth2Test/uaa/rabbitmq.conf b/projects/Test/OAuth2/uaa/rabbitmq.conf
similarity index 100%
rename from projects/OAuth2Test/uaa/rabbitmq.conf
rename to projects/Test/OAuth2/uaa/rabbitmq.conf
diff --git a/projects/OAuth2Test/uaa/signing-key/signing-key.pem b/projects/Test/OAuth2/uaa/signing-key/signing-key.pem
similarity index 100%
rename from projects/OAuth2Test/uaa/signing-key/signing-key.pem
rename to projects/Test/OAuth2/uaa/signing-key/signing-key.pem
diff --git a/projects/OAuth2Test/uaa/uaa.yml b/projects/Test/OAuth2/uaa/uaa.yml
similarity index 100%
rename from projects/OAuth2Test/uaa/uaa.yml
rename to projects/Test/OAuth2/uaa/uaa.yml
diff --git a/projects/Test/SequentialIntegration/SequentialIntegration.csproj b/projects/Test/SequentialIntegration/SequentialIntegration.csproj
new file mode 100644
index 0000000000..9ab21fd9ea
--- /dev/null
+++ b/projects/Test/SequentialIntegration/SequentialIntegration.csproj
@@ -0,0 +1,50 @@
+
+
+
+ net6.0;net472
+
+
+
+ net6.0
+
+
+
+ ../../rabbit.snk
+ true
+ latest
+ 7.0
+ true
+
+
+
+
+
+
+
+
+
+
+ <_Parameter1>Xunit.CollectionBehavior.CollectionPerAssembly
+ <_Parameter1_IsLiteral>true
+ <_Parameter1_TypeName>Xunit.CollectionBehavior.CollectionPerAssembly
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
diff --git a/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs b/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs
new file mode 100644
index 0000000000..9d769bfc8f
--- /dev/null
+++ b/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs
@@ -0,0 +1,89 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading;
+using RabbitMQ.Client;
+using Xunit.Abstractions;
+
+namespace Test.SequentialIntegration
+{
+ public class SequentialIntegrationFixture : IntegrationFixture
+ {
+ public SequentialIntegrationFixture(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ public void Block()
+ {
+ _rabbitMQCtl.ExecRabbitMQCtl("set_vm_memory_high_watermark 0.000000001");
+ // give rabbitmqctl some time to do its job
+ Thread.Sleep(TimeSpan.FromSeconds(2));
+ Publish();
+ }
+
+ public void Unblock()
+ {
+ _rabbitMQCtl.ExecRabbitMQCtl("set_vm_memory_high_watermark 0.4");
+ }
+
+ public void RestartRabbitMQ()
+ {
+ StopRabbitMQ();
+ Thread.Sleep(TimeSpan.FromMilliseconds(500));
+ StartRabbitMQ();
+ AwaitRabbitMQ();
+ }
+
+ public void StopRabbitMQ()
+ {
+ _rabbitMQCtl.ExecRabbitMQCtl("stop_app");
+ }
+
+ public void StartRabbitMQ()
+ {
+ _rabbitMQCtl.ExecRabbitMQCtl("start_app");
+ }
+
+ private void AwaitRabbitMQ()
+ {
+ _rabbitMQCtl.ExecRabbitMQCtl("await_startup");
+ }
+
+ private void Publish()
+ {
+ using (IChannel ch = _conn.CreateChannel())
+ {
+ ch.BasicPublish("amq.fanout", "", _encoding.GetBytes("message"));
+ }
+ }
+ }
+}
diff --git a/projects/Unit/TestConnectionBlocked.cs b/projects/Test/SequentialIntegration/TestConnectionBlocked.cs
similarity index 96%
rename from projects/Unit/TestConnectionBlocked.cs
rename to projects/Test/SequentialIntegration/TestConnectionBlocked.cs
index 4d9986aad6..232a83b954 100644
--- a/projects/Unit/TestConnectionBlocked.cs
+++ b/projects/Test/SequentialIntegration/TestConnectionBlocked.cs
@@ -36,9 +36,9 @@
using Xunit;
using Xunit.Abstractions;
-namespace RabbitMQ.Client.Unit
+namespace Test.SequentialIntegration
{
- public class TestConnectionBlocked : IntegrationFixture
+ public class TestConnectionBlocked : SequentialIntegrationFixture
{
private readonly ManualResetEventSlim _connDisposed = new ManualResetEventSlim(false);
private readonly object _lockObject = new object();
@@ -100,7 +100,7 @@ public void TestDisposeOnBlockedConnectionDoesNotHang()
}
}
- protected override void ReleaseResources()
+ protected override void TearDown()
{
Unblock();
}
diff --git a/projects/Unit/TestConnectionRecovery.cs b/projects/Test/SequentialIntegration/TestConnectionRecovery.cs
similarity index 67%
rename from projects/Unit/TestConnectionRecovery.cs
rename to projects/Test/SequentialIntegration/TestConnectionRecovery.cs
index 2c8c42ab50..fada4e8948 100644
--- a/projects/Unit/TestConnectionRecovery.cs
+++ b/projects/Test/SequentialIntegration/TestConnectionRecovery.cs
@@ -31,9 +31,9 @@
using System;
using System.Collections.Generic;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using RabbitMQ.Client.Framing.Impl;
@@ -41,46 +41,25 @@
using Xunit;
using Xunit.Abstractions;
-#pragma warning disable 0618
-
-namespace RabbitMQ.Client.Unit
+namespace Test.SequentialIntegration
{
- public class TestConnectionRecovery : IntegrationFixture
+ public class TestConnectionRecovery : TestConnectionRecoveryBase
{
- private readonly byte[] _messageBody;
- private readonly ushort _totalMessageCount = 8192;
- private readonly ushort _closeAtCount = 16;
- private string _queueName;
+ private readonly string _queueName;
public TestConnectionRecovery(ITestOutputHelper output) : base(output)
- {
- var rnd = new Random();
- _messageBody = new byte[4096];
- rnd.NextBytes(_messageBody);
- }
-
- protected override void SetUp()
{
_queueName = $"TestConnectionRecovery-queue-{Guid.NewGuid()}";
- _conn = CreateAutorecoveringConnection();
- _channel = _conn.CreateChannel();
- _channel.QueueDelete(_queueName);
}
- protected override void ReleaseResources()
+ protected override void TearDown()
{
- // TODO LRB not really necessary
- if (_channel.IsOpen)
- {
- _channel.Close();
- }
-
- if (_conn.IsOpen)
- {
- _conn.Close();
- }
-
- Unblock();
+ var cf = CreateConnectionFactory();
+ cf.ClientProvidedName = cf.ClientProvidedName + "-TearDown";
+ using IConnection conn = cf.CreateConnection();
+ using IChannel ch = conn.CreateChannel();
+ ch.QueueDelete(_queueName);
+ base.TearDown();
}
[Fact]
@@ -93,16 +72,16 @@ public void TestBasicAckAfterChannelRecovery()
Assert.Equal(queueName, _queueName);
_channel.BasicQos(0, 1, false);
- string consumerTag = _channel.BasicConsume(queueName, false, cons);
+ _channel.BasicConsume(queueName, false, cons);
ManualResetEventSlim sl = PrepareForShutdown(_conn);
ManualResetEventSlim rl = PrepareForRecovery(_conn);
PublishMessagesWhileClosingConn(queueName);
- Wait(sl);
- Wait(rl);
- Wait(allMessagesSeenLatch);
+ Wait(sl, "connection shutdown");
+ Wait(rl, "connection recovery");
+ Wait(allMessagesSeenLatch, "all messages seen");
}
[Fact]
@@ -115,16 +94,16 @@ public void TestBasicNackAfterChannelRecovery()
Assert.Equal(queueName, _queueName);
_channel.BasicQos(0, 1, false);
- string consumerTag = _channel.BasicConsume(queueName, false, cons);
+ _channel.BasicConsume(queueName, false, cons);
ManualResetEventSlim sl = PrepareForShutdown(_conn);
ManualResetEventSlim rl = PrepareForRecovery(_conn);
PublishMessagesWhileClosingConn(queueName);
- Wait(sl);
- Wait(rl);
- Wait(allMessagesSeenLatch);
+ Wait(sl, "connection shutdown");
+ Wait(rl, "connection recovery");
+ Wait(allMessagesSeenLatch, "all messages seen");
}
[Fact]
@@ -137,16 +116,16 @@ public void TestBasicRejectAfterChannelRecovery()
Assert.Equal(queueName, _queueName);
_channel.BasicQos(0, 1, false);
- string consumerTag = _channel.BasicConsume(queueName, false, cons);
+ _channel.BasicConsume(queueName, false, cons);
ManualResetEventSlim sl = PrepareForShutdown(_conn);
ManualResetEventSlim rl = PrepareForRecovery(_conn);
PublishMessagesWhileClosingConn(queueName);
- Wait(sl);
- Wait(rl);
- Wait(allMessagesSeenLatch);
+ Wait(sl, "connection shutdown");
+ Wait(rl, "connection recovery");
+ Wait(allMessagesSeenLatch, "all messages seen");
}
[Fact]
@@ -180,7 +159,7 @@ public void TestBasicAckEventHandlerRecovery()
Assert.True(_channel.IsOpen);
WithTemporaryNonExclusiveQueue(_channel, (m, q) => m.BasicPublish("", q, _messageBody));
- Wait(latch);
+ Wait(latch, "basic acks/nacks");
}
[Fact]
@@ -191,86 +170,6 @@ public void TestBasicConnectionRecovery()
Assert.True(_conn.IsOpen);
}
- [Fact]
- public void TestBasicConnectionRecoveryWithHostnameList()
- {
- using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "127.0.0.1", "localhost" }))
- {
- Assert.True(c.IsOpen);
- CloseAndWaitForRecovery(c);
- Assert.True(c.IsOpen);
- }
- }
-
- [Fact]
- public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts()
- {
- using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "191.72.44.22", "127.0.0.1", "localhost" }))
- {
- Assert.True(c.IsOpen);
- CloseAndWaitForRecovery(c);
- Assert.True(c.IsOpen);
- }
- }
-
- [Fact]
- public void TestBasicConnectionRecoveryWithEndpointList()
- {
- using (AutorecoveringConnection c = CreateAutorecoveringConnection(
- new List
- {
- new AmqpTcpEndpoint("127.0.0.1"),
- new AmqpTcpEndpoint("localhost")
- }))
- {
- Assert.True(c.IsOpen);
- CloseAndWaitForRecovery(c);
- Assert.True(c.IsOpen);
- }
- }
-
- [Fact]
- public void TestBasicConnectionRecoveryStopsAfterManualClose()
- {
- Assert.True(_conn.IsOpen);
- AutorecoveringConnection c = CreateAutorecoveringConnection();
- var latch = new AutoResetEvent(false);
- c.ConnectionRecoveryError += (o, args) => latch.Set();
-
- try
- {
- StopRabbitMQ();
- latch.WaitOne(30000); // we got the failed reconnection event.
- bool triedRecoveryAfterClose = false;
- c.Close();
- Thread.Sleep(5000);
- c.ConnectionRecoveryError += (o, args) => triedRecoveryAfterClose = true;
- Thread.Sleep(10000);
- Assert.False(triedRecoveryAfterClose);
- }
- finally
- {
- StartRabbitMQ();
- }
- }
-
- [Fact]
- public void TestBasicConnectionRecoveryWithEndpointListAndUnreachableHosts()
- {
- using (AutorecoveringConnection c = CreateAutorecoveringConnection(
- new List
- {
- new AmqpTcpEndpoint("191.72.44.22"),
- new AmqpTcpEndpoint("127.0.0.1"),
- new AmqpTcpEndpoint("localhost")
- }))
- {
- Assert.True(c.IsOpen);
- CloseAndWaitForRecovery(c);
- Assert.True(c.IsOpen);
- }
- }
-
[Fact]
public void TestBasicConnectionRecoveryOnBrokerRestart()
{
@@ -304,7 +203,7 @@ public void TestBlockedListenersRecovery()
CloseAndWaitForRecovery();
Block();
- Wait(latch);
+ Wait(latch, "connection blocked");
Unblock();
}
@@ -344,104 +243,6 @@ public void TestClientNamedQueueRecoveryOnServerRestart()
}, s);
}
- [Fact]
- public void TestConsumerWorkServiceRecovery()
- {
- using (AutorecoveringConnection c = CreateAutorecoveringConnection())
- {
- IChannel m = c.CreateChannel();
- string q = m.QueueDeclare("dotnet-client.recovery.consumer_work_pool1",
- false, false, false, null).QueueName;
- var cons = new EventingBasicConsumer(m);
- m.BasicConsume(q, true, cons);
- AssertConsumerCount(m, q, 1);
-
- CloseAndWaitForRecovery(c);
-
- Assert.True(m.IsOpen);
- var latch = new ManualResetEventSlim(false);
- cons.Received += (s, args) => latch.Set();
-
- m.BasicPublish("", q, _encoding.GetBytes("msg"));
- Wait(latch);
-
- m.QueueDelete(q);
- }
- }
-
- [Fact]
- public void TestConsumerRecoveryOnClientNamedQueueWithOneRecovery()
- {
- string q0 = "dotnet-client.recovery.queue1";
- using (AutorecoveringConnection c = CreateAutorecoveringConnection())
- {
- IChannel m = c.CreateChannel();
- string q1 = m.QueueDeclare(q0, false, false, false, null).QueueName;
- Assert.Equal(q0, q1);
-
- var cons = new EventingBasicConsumer(m);
- m.BasicConsume(q1, true, cons);
- AssertConsumerCount(m, q1, 1);
-
- bool queueNameChangeAfterRecoveryCalled = false;
-
- c.QueueNameChangeAfterRecovery += (source, ea) => { queueNameChangeAfterRecoveryCalled = true; };
-
- CloseAndWaitForRecovery(c);
- AssertConsumerCount(m, q1, 1);
- Assert.False(queueNameChangeAfterRecoveryCalled);
-
- CloseAndWaitForRecovery(c);
- AssertConsumerCount(m, q1, 1);
- Assert.False(queueNameChangeAfterRecoveryCalled);
-
- CloseAndWaitForRecovery(c);
- AssertConsumerCount(m, q1, 1);
- Assert.False(queueNameChangeAfterRecoveryCalled);
-
- var latch = new ManualResetEventSlim(false);
- cons.Received += (s, args) => latch.Set();
-
- m.BasicPublish("", q1, _encoding.GetBytes("msg"));
- Wait(latch);
-
- m.QueueDelete(q1);
- }
- }
-
- [Fact]
- public void TestConsumerRecoveryWithServerNamedQueue()
- {
- // https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1238
- using (AutorecoveringConnection c = CreateAutorecoveringConnection())
- {
- IChannel ch = c.CreateChannel();
- QueueDeclareOk queueDeclareResult = ch.QueueDeclare(queue: string.Empty, durable: false, exclusive: true, autoDelete: true, arguments: null);
- string qname = queueDeclareResult.QueueName;
- Assert.False(string.IsNullOrEmpty(qname));
-
- var cons = new EventingBasicConsumer(ch);
- ch.BasicConsume(string.Empty, true, cons);
- AssertConsumerCount(ch, qname, 1);
-
- bool queueNameBeforeIsEqual = false;
- bool queueNameChangeAfterRecoveryCalled = false;
- string qnameAfterRecovery = null;
- c.QueueNameChangeAfterRecovery += (source, ea) =>
- {
- queueNameChangeAfterRecoveryCalled = true;
- queueNameBeforeIsEqual = qname.Equals(ea.NameBefore);
- qnameAfterRecovery = ea.NameAfter;
- };
-
- CloseAndWaitForRecovery(c);
-
- AssertConsumerCount(ch, qnameAfterRecovery, 1);
- Assert.True(queueNameChangeAfterRecoveryCalled);
- Assert.True(queueNameBeforeIsEqual);
- }
- }
-
[Fact]
public void TestConsumerRecoveryWithManyConsumers()
{
@@ -458,39 +259,11 @@ public void TestConsumerRecoveryWithManyConsumers()
((AutorecoveringConnection)_conn).ConsumerTagChangeAfterRecovery += (prev, current) => latch.Set();
CloseAndWaitForRecovery();
- Wait(latch);
+ Wait(latch, "consumer tag change after recovery");
Assert.True(_channel.IsOpen);
AssertConsumerCount(q, n);
}
- [Fact]
- public void TestCreateChannelOnClosedAutorecoveringConnectionDoesNotHang()
- {
- // we don't want this to recover quickly in this test
- AutorecoveringConnection c = CreateAutorecoveringConnection(TimeSpan.FromSeconds(20));
-
- try
- {
- c.Close();
- WaitForShutdown(c);
- Assert.False(c.IsOpen);
- c.CreateChannel();
- Assert.Fail("Expected an exception");
- }
- catch (AlreadyClosedException)
- {
- // expected
- }
- finally
- {
- StartRabbitMQ();
- if (c.IsOpen)
- {
- c.Abort();
- }
- }
- }
-
[Fact]
public void TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted()
{
@@ -532,7 +305,7 @@ public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDe
{
string x = Guid.NewGuid().ToString();
_channel.ExchangeDeclare(x, "fanout", false, true, null);
- QueueDeclareOk q = _channel.QueueDeclare();
+ RabbitMQ.Client.QueueDeclareOk q = _channel.QueueDeclare();
_channel.QueueBind(q, x, "");
_channel.QueueDelete(q);
}
@@ -547,7 +320,7 @@ public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUn
{
string x = Guid.NewGuid().ToString();
_channel.ExchangeDeclare(x, "fanout", false, true, null);
- QueueDeclareOk q = _channel.QueueDeclare();
+ RabbitMQ.Client.QueueDeclareOk q = _channel.QueueDeclare();
_channel.QueueBind(q, x, "");
_channel.QueueUnbind(q, x, "", null);
}
@@ -654,7 +427,7 @@ public void TestClientNamedTransientAutoDeleteQueueAndBindingRecovery()
ch.ExchangeDeclare(exchange: x, type: "fanout");
ch.BasicPublish(exchange: x, routingKey: "", body: _encoding.GetBytes("msg"));
WaitForConfirms(ch);
- QueueDeclareOk ok = ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null);
+ RabbitMQ.Client.QueueDeclareOk ok = ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null);
Assert.Equal(1u, ok.MessageCount);
ch.QueueDelete(q);
ch.ExchangeDelete(x);
@@ -680,14 +453,14 @@ public void TestServerNamedTransientAutoDeleteQueueAndBindingRecovery()
};
ch.QueueBind(queue: nameBefore, exchange: x, routingKey: "");
RestartServerAndWaitForRecovery();
- Wait(latch);
+ Wait(latch, "queue name change after recovery");
Assert.True(ch.IsOpen);
Assert.NotEqual(nameBefore, nameAfter);
ch.ConfirmSelect();
ch.ExchangeDeclare(exchange: x, type: "fanout");
ch.BasicPublish(exchange: x, routingKey: "", body: _encoding.GetBytes("msg"));
WaitForConfirms(ch);
- QueueDeclareOk ok = ch.QueueDeclarePassive(nameAfter);
+ RabbitMQ.Client.QueueDeclareOk ok = ch.QueueDeclarePassive(nameAfter);
Assert.Equal(1u, ok.MessageCount);
ch.QueueDelete(q);
ch.ExchangeDelete(x);
@@ -768,34 +541,6 @@ public void TestRecoveringConsumerHandlerOnConnection_EventArgumentsArePassedDow
Assert.Equal("event-handler-set-this-value", actualVal);
}
- [Fact]
- public void TestRecoveryWithTopologyDisabled()
- {
- AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryDisabled();
- IChannel ch = conn.CreateChannel();
- string s = "dotnet-client.test.recovery.q2";
- ch.QueueDelete(s);
- ch.QueueDeclare(s, false, true, false, null);
- ch.QueueDeclarePassive(s);
- Assert.True(ch.IsOpen);
-
- try
- {
- CloseAndWaitForRecovery(conn);
- Assert.True(ch.IsOpen);
- ch.QueueDeclarePassive(s);
- Assert.Fail("Expected an exception");
- }
- catch (OperationInterruptedException)
- {
- // expected
- }
- finally
- {
- conn.Abort();
- }
- }
-
[Fact]
public void TestServerNamedQueueRecovery()
{
@@ -812,7 +557,7 @@ public void TestServerNamedQueueRecovery()
connection.QueueNameChangeAfterRecovery += (source, ea) => { nameAfter = ea.NameAfter; };
CloseAndWaitForRecovery();
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.NotNull(nameAfter);
Assert.StartsWith("amq.", nameBefore);
@@ -851,7 +596,6 @@ public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerResta
try
{
StopRabbitMQ();
- Console.WriteLine("Stopped RabbitMQ. About to sleep for multiple recovery intervals...");
Thread.Sleep(7000);
}
finally
@@ -859,8 +603,8 @@ public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerResta
StartRabbitMQ();
}
- Wait(shutdownLatch, TimeSpan.FromSeconds(30));
- Wait(recoveryLatch, TimeSpan.FromSeconds(30));
+ Wait(shutdownLatch, WaitSpan, "connection shutdown");
+ Wait(recoveryLatch, WaitSpan, "connection recovery");
Assert.True(_conn.IsOpen);
Assert.True(counter >= 1);
}
@@ -906,7 +650,7 @@ public void TestRecoverTopologyOnDisposedChannel()
cons.Received += (s, args) => latch.Set();
_channel.BasicPublish("", q, _messageBody);
- Wait(latch);
+ Wait(latch, "received event");
_channel.QueueUnbind(q, x, rk);
_channel.ExchangeDelete(x);
@@ -1083,7 +827,7 @@ public void TestUnblockedListenersRecovery()
Block();
Unblock();
- Wait(latch);
+ Wait(latch, "connection unblocked");
}
[Fact]
@@ -1109,7 +853,7 @@ public void TestTopologyRecoveryQueueFilter()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
AssertQueueRecovery(ch, queueToRecover, false);
@@ -1153,7 +897,7 @@ public void TestTopologyRecoveryExchangeFilter()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
AssertExchangeRecovery(ch, exchangeToRecover);
@@ -1206,78 +950,11 @@ public void TestTopologyRecoveryBindingFilter()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
-
- Assert.True(ch.IsOpen);
- Assert.True(SendAndConsumeMessage(queueWithRecoveredBinding, exchange, bindingToRecover));
- Assert.False(SendAndConsumeMessage(queueWithIgnoredBinding, exchange, bindingToIgnore));
- }
- finally
- {
- conn.Abort();
- }
- }
-
- [Fact]
- public void TestTopologyRecoveryConsumerFilter()
- {
- var filter = new TopologyRecoveryFilter
- {
- ConsumerFilter = consumer => !consumer.ConsumerTag.Contains("filtered")
- };
- var latch = new ManualResetEventSlim(false);
- AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryFilter(filter);
- conn.RecoverySucceeded += (source, ea) => latch.Set();
- IChannel ch = conn.CreateChannel();
- ch.ConfirmSelect();
-
- var exchange = "topology.recovery.exchange";
- var queueWithRecoveredConsumer = "topology.recovery.queue.1";
- var queueWithIgnoredConsumer = "topology.recovery.queue.2";
- var binding1 = "recovered.binding.1";
- var binding2 = "recovered.binding.2";
-
- ch.ExchangeDeclare(exchange, "direct");
- ch.QueueDeclare(queueWithRecoveredConsumer, false, false, false, null);
- ch.QueueDeclare(queueWithIgnoredConsumer, false, false, false, null);
- ch.QueueBind(queueWithRecoveredConsumer, exchange, binding1);
- ch.QueueBind(queueWithIgnoredConsumer, exchange, binding2);
- ch.QueuePurge(queueWithRecoveredConsumer);
- ch.QueuePurge(queueWithIgnoredConsumer);
-
- var recoverLatch = new ManualResetEventSlim(false);
- var consumerToRecover = new EventingBasicConsumer(ch);
- consumerToRecover.Received += (source, ea) => recoverLatch.Set();
- ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover);
-
- var ignoredLatch = new ManualResetEventSlim(false);
- var consumerToIgnore = new EventingBasicConsumer(ch);
- consumerToIgnore.Received += (source, ea) => ignoredLatch.Set();
- ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore);
-
- try
- {
- CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
- ch.BasicPublish(exchange, binding1, Encoding.UTF8.GetBytes("test message"));
- ch.BasicPublish(exchange, binding2, Encoding.UTF8.GetBytes("test message"));
-
- Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5)));
- Assert.False(ignoredLatch.Wait(TimeSpan.FromSeconds(5)));
-
- ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore);
-
- try
- {
- ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover);
- Assert.Fail("Expected an exception");
- }
- catch (OperationInterruptedException e)
- {
- AssertShutdownError(e.ShutdownReason, 530); // NOT_ALLOWED - not allowed to reuse consumer tag
- }
+ Assert.True(SendAndConsumeMessage(_conn, queueWithRecoveredBinding, exchange, bindingToRecover));
+ Assert.False(SendAndConsumeMessage(_conn, queueWithIgnoredBinding, exchange, bindingToIgnore));
}
finally
{
@@ -1326,15 +1003,15 @@ public void TestTopologyRecoveryDefaultFilterRecoversAllEntities()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
AssertExchangeRecovery(ch, exchange);
ch.QueueDeclarePassive(queue1);
ch.QueueDeclarePassive(queue2);
- ch.BasicPublish(exchange, binding1, Encoding.UTF8.GetBytes("test message"));
- ch.BasicPublish(exchange, binding2, Encoding.UTF8.GetBytes("test message"));
+ ch.BasicPublish(exchange, binding1, _encoding.GetBytes("test message"));
+ ch.BasicPublish(exchange, binding2, _encoding.GetBytes("test message"));
Assert.True(consumerLatch1.Wait(TimeSpan.FromSeconds(5)));
Assert.True(consumerLatch2.Wait(TimeSpan.FromSeconds(5)));
@@ -1385,7 +1062,7 @@ public void TestTopologyRecoveryQueueExceptionHandler()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeded");
Assert.True(ch.IsOpen);
AssertQueueRecovery(ch, queueToRecoverSuccessfully, false);
@@ -1436,7 +1113,7 @@ public void TestTopologyRecoveryExchangeExceptionHandler()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
AssertExchangeRecovery(ch, exchangeToRecoverSuccessfully);
@@ -1499,11 +1176,11 @@ public void TestTopologyRecoveryBindingExceptionHandler()
try
{
CloseAndWaitForRecovery(conn);
- Wait(latch);
+ Wait(latch, "recovery succeeded");
Assert.True(ch.IsOpen);
- Assert.True(SendAndConsumeMessage(queueWithRecoveredBinding, exchange, bindingToRecoverSuccessfully));
- Assert.True(SendAndConsumeMessage(queueWithExceptionBinding, exchange, bindingToRecoverWithException));
+ Assert.True(SendAndConsumeMessage(conn, queueWithRecoveredBinding, exchange, bindingToRecoverSuccessfully));
+ Assert.True(SendAndConsumeMessage(conn, queueWithExceptionBinding, exchange, bindingToRecoverWithException));
}
finally
{
@@ -1555,11 +1232,11 @@ public void TestTopologyRecoveryConsumerExceptionHandler()
try
{
CloseAndWaitForShutdown(conn);
- Wait(latch, TimeSpan.FromSeconds(20));
+ Wait(latch, TimeSpan.FromSeconds(20), "recovery succeeded");
Assert.True(ch.IsOpen);
- ch.BasicPublish("", queueWithExceptionConsumer, Encoding.UTF8.GetBytes("test message"));
+ ch.BasicPublish("", queueWithExceptionConsumer, _encoding.GetBytes("test message"));
Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5)));
@@ -1578,236 +1255,5 @@ public void TestTopologyRecoveryConsumerExceptionHandler()
conn.Abort();
}
}
-
- internal bool SendAndConsumeMessage(string queue, string exchange, string routingKey)
- {
- using (var ch = _conn.CreateChannel())
- {
- var latch = new ManualResetEventSlim(false);
-
- var consumer = new AckingBasicConsumer(ch, 1, latch);
-
- ch.BasicConsume(queue, false, consumer);
-
- ch.BasicPublish(exchange, routingKey, Encoding.UTF8.GetBytes("test message"));
-
- return latch.Wait(TimeSpan.FromSeconds(5));
- }
- }
-
- internal void AssertExchangeRecovery(IChannel m, string x)
- {
- m.ConfirmSelect();
- WithTemporaryNonExclusiveQueue(m, (_, q) =>
- {
- string rk = "routing-key";
- m.QueueBind(q, x, rk);
- m.BasicPublish(x, rk, _messageBody);
-
- Assert.True(WaitForConfirms(m));
- m.ExchangeDeclarePassive(x);
- });
- }
-
- internal void AssertQueueRecovery(IChannel m, string q)
- {
- AssertQueueRecovery(m, q, true);
- }
-
- internal void AssertQueueRecovery(IChannel m, string q, bool exclusive, IDictionary arguments = null)
- {
- m.ConfirmSelect();
- m.QueueDeclarePassive(q);
- QueueDeclareOk ok1 = m.QueueDeclare(q, false, exclusive, false, arguments);
- Assert.Equal(0u, ok1.MessageCount);
- m.BasicPublish("", q, _messageBody);
- Assert.True(WaitForConfirms(m));
- QueueDeclareOk ok2 = m.QueueDeclare(q, false, exclusive, false, arguments);
- Assert.Equal(1u, ok2.MessageCount);
- }
-
- internal void AssertRecordedExchanges(AutorecoveringConnection c, int n)
- {
- Assert.Equal(n, c.RecordedExchangesCount);
- }
-
- internal void AssertRecordedQueues(AutorecoveringConnection c, int n)
- {
- Assert.Equal(n, c.RecordedQueuesCount);
- }
-
- internal void CloseAndWaitForRecovery()
- {
- CloseAndWaitForRecovery((AutorecoveringConnection)_conn);
- }
-
- internal void CloseAndWaitForRecovery(AutorecoveringConnection conn)
- {
- ManualResetEventSlim sl = PrepareForShutdown(conn);
- ManualResetEventSlim rl = PrepareForRecovery(conn);
- CloseConnection(conn);
- Wait(sl);
- Wait(rl);
- }
-
- internal void CloseAndWaitForShutdown(AutorecoveringConnection conn)
- {
- ManualResetEventSlim sl = PrepareForShutdown(conn);
- CloseConnection(conn);
- Wait(sl);
- }
-
- internal ManualResetEventSlim PrepareForRecovery(IConnection conn)
- {
- var latch = new ManualResetEventSlim(false);
-
- AutorecoveringConnection aconn = conn as AutorecoveringConnection;
- aconn.RecoverySucceeded += (source, ea) => latch.Set();
-
- return latch;
- }
-
- internal static ManualResetEventSlim PrepareForShutdown(IConnection conn)
- {
- var latch = new ManualResetEventSlim(false);
-
- AutorecoveringConnection aconn = conn as AutorecoveringConnection;
- aconn.ConnectionShutdown += (c, args) => latch.Set();
-
- return latch;
- }
-
- internal void RestartServerAndWaitForRecovery()
- {
- RestartServerAndWaitForRecovery((AutorecoveringConnection)_conn);
- }
-
- internal void RestartServerAndWaitForRecovery(AutorecoveringConnection conn)
- {
- ManualResetEventSlim sl = PrepareForShutdown(conn);
- ManualResetEventSlim rl = PrepareForRecovery(conn);
- RestartRabbitMQ();
- Wait(sl);
- Wait(rl);
- }
-
- internal void WaitForRecovery()
- {
- Wait(PrepareForRecovery((AutorecoveringConnection)_conn));
- }
-
- internal void WaitForRecovery(AutorecoveringConnection conn)
- {
- Wait(PrepareForRecovery(conn));
- }
-
- internal void WaitForShutdown()
- {
- Wait(PrepareForShutdown(_conn));
- }
-
- internal void WaitForShutdown(IConnection conn)
- {
- Wait(PrepareForShutdown(conn));
- }
-
- internal void PublishMessagesWhileClosingConn(string queueName)
- {
- using (AutorecoveringConnection publishingConn = CreateAutorecoveringConnection())
- {
- using (IChannel publishingChannel = publishingConn.CreateChannel())
- {
- for (ushort i = 0; i < _totalMessageCount; i++)
- {
- if (i == _closeAtCount)
- {
- CloseConnection(_conn);
- }
- publishingChannel.BasicPublish(string.Empty, queueName, _messageBody);
- }
- }
- }
- }
-
- public class AckingBasicConsumer : TestBasicConsumer
- {
- public AckingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
- : base(channel, totalMessageCount, allMessagesSeenLatch)
- {
- }
-
- public override void PostHandleDelivery(ulong deliveryTag)
- {
- Channel.BasicAck(deliveryTag, false);
- }
- }
-
- public class NackingBasicConsumer : TestBasicConsumer
- {
- public NackingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
- : base(channel, totalMessageCount, allMessagesSeenLatch)
- {
- }
-
- public override void PostHandleDelivery(ulong deliveryTag)
- {
- Channel.BasicNack(deliveryTag, false, false);
- }
- }
-
- public class RejectingBasicConsumer : TestBasicConsumer
- {
- public RejectingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
- : base(channel, totalMessageCount, allMessagesSeenLatch)
- {
- }
-
- public override void PostHandleDelivery(ulong deliveryTag)
- {
- Channel.BasicReject(deliveryTag, false);
- }
- }
-
- public class TestBasicConsumer : DefaultBasicConsumer
- {
- private readonly ManualResetEventSlim _allMessagesSeenLatch;
- private readonly ushort _totalMessageCount;
- private ushort _counter = 0;
-
- public TestBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
- : base(channel)
- {
- _totalMessageCount = totalMessageCount;
- _allMessagesSeenLatch = allMessagesSeenLatch;
- }
-
- public override void HandleBasicDeliver(string consumerTag,
- ulong deliveryTag,
- bool redelivered,
- string exchange,
- string routingKey,
- in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
- {
- try
- {
- PostHandleDelivery(deliveryTag);
- }
- finally
- {
- ++_counter;
- if (_counter >= _totalMessageCount)
- {
- _allMessagesSeenLatch.Set();
- }
- }
- }
-
- public virtual void PostHandleDelivery(ulong deliveryTag)
- {
- }
- }
}
}
-
-#pragma warning restore 0168
diff --git a/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs b/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs
new file mode 100644
index 0000000000..28152aa559
--- /dev/null
+++ b/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs
@@ -0,0 +1,389 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Framing.Impl;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.SequentialIntegration
+{
+ public class TestConnectionRecoveryBase : SequentialIntegrationFixture
+ {
+ protected readonly byte[] _messageBody;
+ protected const ushort _totalMessageCount = 8192;
+ protected const ushort _closeAtCount = 16;
+
+ public TestConnectionRecoveryBase(ITestOutputHelper output) : base(output)
+ {
+ _messageBody = GetRandomBody(4096);
+ }
+
+ protected override void TearDown()
+ {
+ Unblock();
+ }
+
+ protected void AssertConsumerCount(string q, int count)
+ {
+ WithTemporaryChannel((m) =>
+ {
+ RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q);
+ Assert.Equal((uint)count, ok.ConsumerCount);
+ });
+ }
+
+ protected void AssertConsumerCount(IChannel m, string q, uint count)
+ {
+ RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q);
+ Assert.Equal(count, ok.ConsumerCount);
+ }
+
+ protected void AssertExchangeRecovery(IChannel m, string x)
+ {
+ m.ConfirmSelect();
+ WithTemporaryNonExclusiveQueue(m, (_, q) =>
+ {
+ string rk = "routing-key";
+ m.QueueBind(q, x, rk);
+ m.BasicPublish(x, rk, _messageBody);
+
+ Assert.True(WaitForConfirms(m));
+ m.ExchangeDeclarePassive(x);
+ });
+ }
+
+ protected void AssertQueueRecovery(IChannel m, string q)
+ {
+ AssertQueueRecovery(m, q, true);
+ }
+
+ protected void AssertQueueRecovery(IChannel m, string q, bool exclusive, IDictionary arguments = null)
+ {
+ m.ConfirmSelect();
+ m.QueueDeclarePassive(q);
+ RabbitMQ.Client.QueueDeclareOk ok1 = m.QueueDeclare(q, false, exclusive, false, arguments);
+ Assert.Equal(0u, ok1.MessageCount);
+ m.BasicPublish("", q, _messageBody);
+ Assert.True(WaitForConfirms(m));
+ RabbitMQ.Client.QueueDeclareOk ok2 = m.QueueDeclare(q, false, exclusive, false, arguments);
+ Assert.Equal(1u, ok2.MessageCount);
+ }
+
+ internal void AssertRecordedExchanges(AutorecoveringConnection c, int n)
+ {
+ Assert.Equal(n, c.RecordedExchangesCount);
+ }
+
+ internal void AssertRecordedQueues(AutorecoveringConnection c, int n)
+ {
+ Assert.Equal(n, c.RecordedQueuesCount);
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnection()
+ {
+ return CreateAutorecoveringConnection(RecoveryInterval);
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan networkRecoveryInterval)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.NetworkRecoveryInterval = networkRecoveryInterval;
+ return (AutorecoveringConnection)cf.CreateConnection();
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnection(IList endpoints)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ // tests that use this helper will likely list unreachable hosts,
+ // make sure we time out quickly on those
+ cf.RequestedConnectionTimeout = TimeSpan.FromSeconds(1);
+ cf.NetworkRecoveryInterval = RecoveryInterval;
+ return (AutorecoveringConnection)cf.CreateConnection(endpoints);
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryDisabled()
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.TopologyRecoveryEnabled = false;
+ cf.NetworkRecoveryInterval = RecoveryInterval;
+ return (AutorecoveringConnection)cf.CreateConnection();
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryFilter(TopologyRecoveryFilter filter)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.TopologyRecoveryEnabled = true;
+ cf.TopologyRecoveryFilter = filter;
+ return (AutorecoveringConnection)cf.CreateConnection();
+ }
+
+ internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryExceptionHandler(TopologyRecoveryExceptionHandler handler)
+ {
+ var cf = CreateConnectionFactory();
+ cf.AutomaticRecoveryEnabled = true;
+ cf.TopologyRecoveryEnabled = true;
+ cf.TopologyRecoveryExceptionHandler = handler;
+ return (AutorecoveringConnection)cf.CreateConnection();
+ }
+
+ protected void CloseConnection(IConnection conn)
+ {
+ _rabbitMQCtl.CloseConnection(conn);
+ }
+
+ protected void CloseAndWaitForRecovery()
+ {
+ CloseAndWaitForRecovery((AutorecoveringConnection)_conn);
+ }
+
+ internal void CloseAndWaitForRecovery(AutorecoveringConnection conn)
+ {
+ ManualResetEventSlim sl = PrepareForShutdown(conn);
+ ManualResetEventSlim rl = PrepareForRecovery(conn);
+ CloseConnection(conn);
+ Wait(sl, "connection shutdown");
+ Wait(rl, "connection recovery");
+ }
+
+ internal void CloseAndWaitForShutdown(AutorecoveringConnection conn)
+ {
+ ManualResetEventSlim sl = PrepareForShutdown(conn);
+ CloseConnection(conn);
+ Wait(sl, "connection shutdown");
+ }
+
+ protected string DeclareNonDurableExchange(IChannel m, string x)
+ {
+ m.ExchangeDeclare(x, "fanout", false);
+ return x;
+ }
+
+ protected string DeclareNonDurableExchangeNoWait(IChannel m, string x)
+ {
+ m.ExchangeDeclareNoWait(x, "fanout", false, false, null);
+ return x;
+ }
+
+ protected ManualResetEventSlim PrepareForRecovery(IConnection conn)
+ {
+ var latch = new ManualResetEventSlim(false);
+
+ AutorecoveringConnection aconn = conn as AutorecoveringConnection;
+ aconn.RecoverySucceeded += (source, ea) => latch.Set();
+
+ return latch;
+ }
+
+ protected void PublishMessagesWhileClosingConn(string queueName)
+ {
+ using (AutorecoveringConnection publishingConn = CreateAutorecoveringConnection())
+ {
+ using (IChannel publishingChannel = publishingConn.CreateChannel())
+ {
+ for (ushort i = 0; i < _totalMessageCount; i++)
+ {
+ if (i == _closeAtCount)
+ {
+ CloseConnection(_conn);
+ }
+ publishingChannel.BasicPublish(string.Empty, queueName, _messageBody);
+ }
+ }
+ }
+ }
+
+ protected static ManualResetEventSlim PrepareForShutdown(IConnection conn)
+ {
+ var latch = new ManualResetEventSlim(false);
+
+ AutorecoveringConnection aconn = conn as AutorecoveringConnection;
+ aconn.ConnectionShutdown += (c, args) => latch.Set();
+
+ return latch;
+ }
+
+ protected void RestartServerAndWaitForRecovery()
+ {
+ RestartServerAndWaitForRecovery((AutorecoveringConnection)_conn);
+ }
+
+ internal void RestartServerAndWaitForRecovery(AutorecoveringConnection conn)
+ {
+ ManualResetEventSlim sl = PrepareForShutdown(conn);
+ ManualResetEventSlim rl = PrepareForRecovery(conn);
+ RestartRabbitMQ();
+ Wait(sl, "connection shutdown");
+ Wait(rl, "connection recovery");
+ }
+
+ protected bool WaitForConfirms(IChannel m)
+ {
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4));
+ return m.WaitForConfirmsAsync(cts.Token).GetAwaiter().GetResult();
+ }
+
+ protected void WaitForRecovery()
+ {
+ Wait(PrepareForRecovery((AutorecoveringConnection)_conn), "recovery succeded");
+ }
+
+ internal void WaitForRecovery(AutorecoveringConnection conn)
+ {
+ Wait(PrepareForRecovery(conn), "recovery succeeded");
+ }
+
+ protected void WaitForShutdown()
+ {
+ Wait(PrepareForShutdown(_conn), "connection shutdown");
+ }
+
+ protected void WaitForShutdown(IConnection conn)
+ {
+ Wait(PrepareForShutdown(conn), "connection shutdown");
+ }
+
+ protected void WithTemporaryQueueNoWait(IChannel channel, Action action, string queue)
+ {
+ try
+ {
+ channel.QueueDeclareNoWait(queue, false, true, false, null);
+ action(channel, queue);
+ }
+ finally
+ {
+ WithTemporaryChannel(x => x.QueueDelete(queue));
+ }
+ }
+
+ public class AckingBasicConsumer : TestBasicConsumer
+ {
+ public AckingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
+ : base(channel, totalMessageCount, allMessagesSeenLatch)
+ {
+ }
+
+ public override void PostHandleDelivery(ulong deliveryTag)
+ {
+ Channel.BasicAck(deliveryTag, false);
+ }
+ }
+
+ public class NackingBasicConsumer : TestBasicConsumer
+ {
+ public NackingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
+ : base(channel, totalMessageCount, allMessagesSeenLatch)
+ {
+ }
+
+ public override void PostHandleDelivery(ulong deliveryTag)
+ {
+ Channel.BasicNack(deliveryTag, false, false);
+ }
+ }
+
+ public class RejectingBasicConsumer : TestBasicConsumer
+ {
+ public RejectingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
+ : base(channel, totalMessageCount, allMessagesSeenLatch)
+ {
+ }
+
+ public override void PostHandleDelivery(ulong deliveryTag)
+ {
+ Channel.BasicReject(deliveryTag, false);
+ }
+ }
+
+ public class TestBasicConsumer : DefaultBasicConsumer
+ {
+ protected readonly ManualResetEventSlim _allMessagesSeenLatch;
+ protected readonly ushort _totalMessageCount;
+ protected ushort _counter = 0;
+
+ public TestBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch)
+ : base(channel)
+ {
+ _totalMessageCount = totalMessageCount;
+ _allMessagesSeenLatch = allMessagesSeenLatch;
+ }
+
+ public override void HandleBasicDeliver(string consumerTag,
+ ulong deliveryTag,
+ bool redelivered,
+ string exchange,
+ string routingKey,
+ in ReadOnlyBasicProperties properties,
+ RentedMemory body)
+ {
+ try
+ {
+ PostHandleDelivery(deliveryTag);
+ }
+ finally
+ {
+ ++_counter;
+ if (_counter >= _totalMessageCount)
+ {
+ _allMessagesSeenLatch.Set();
+ }
+ }
+ }
+
+ public virtual void PostHandleDelivery(ulong deliveryTag)
+ {
+ }
+ }
+
+ protected bool SendAndConsumeMessage(IConnection conn, string queue, string exchange, string routingKey)
+ {
+ using (IChannel ch = conn.CreateChannel())
+ {
+ var latch = new ManualResetEventSlim(false);
+
+ var consumer = new AckingBasicConsumer(ch, 1, latch);
+
+ ch.BasicConsume(queue, false, consumer);
+
+ ch.BasicPublish(exchange, routingKey, _encoding.GetBytes("test message"));
+
+ return latch.Wait(TimeSpan.FromSeconds(5));
+ }
+ }
+ }
+}
diff --git a/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs b/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs
new file mode 100644
index 0000000000..8832c5e92e
--- /dev/null
+++ b/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs
@@ -0,0 +1,333 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2020 VMware, Inc.
+//
+// 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
+//
+// https://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.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2020 VMware, Inc. All rights reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using RabbitMQ.Client.Exceptions;
+using RabbitMQ.Client.Framing.Impl;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test.SequentialIntegration
+{
+ public class TestConnectionRecoveryWithoutSetup : TestConnectionRecoveryBase
+ {
+ public TestConnectionRecoveryWithoutSetup(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void SetUp()
+ {
+ Assert.Null(_connFactory);
+ Assert.Null(_conn);
+ Assert.Null(_channel);
+ }
+
+ [Fact]
+ public void TestBasicConnectionRecoveryWithHostnameList()
+ {
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "127.0.0.1", "localhost" }))
+ {
+ Assert.True(c.IsOpen);
+ CloseAndWaitForRecovery(c);
+ Assert.True(c.IsOpen);
+ }
+ }
+
+ [Fact]
+ public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts()
+ {
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "191.72.44.22", "127.0.0.1", "localhost" }))
+ {
+ Assert.True(c.IsOpen);
+ CloseAndWaitForRecovery(c);
+ Assert.True(c.IsOpen);
+ }
+ }
+
+ [Fact]
+ public void TestBasicConnectionRecoveryWithEndpointList()
+ {
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection(
+ new List
+ {
+ new AmqpTcpEndpoint("127.0.0.1"),
+ new AmqpTcpEndpoint("localhost")
+ }))
+ {
+ Assert.True(c.IsOpen);
+ CloseAndWaitForRecovery(c);
+ Assert.True(c.IsOpen);
+ }
+ }
+
+ [Fact]
+ public void TestBasicConnectionRecoveryWithEndpointListAndUnreachableHosts()
+ {
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection(
+ new List
+ {
+ new AmqpTcpEndpoint("191.72.44.22"),
+ new AmqpTcpEndpoint("127.0.0.1"),
+ new AmqpTcpEndpoint("localhost")
+ }))
+ {
+ Assert.True(c.IsOpen);
+ CloseAndWaitForRecovery(c);
+ Assert.True(c.IsOpen);
+ }
+ }
+
+ [Fact]
+ public void TestConsumerWorkServiceRecovery()
+ {
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection())
+ {
+ IChannel m = c.CreateChannel();
+ string q = m.QueueDeclare("dotnet-client.recovery.consumer_work_pool1",
+ false, false, false, null).QueueName;
+ var cons = new EventingBasicConsumer(m);
+ m.BasicConsume(q, true, cons);
+ AssertConsumerCount(m, q, 1);
+
+ CloseAndWaitForRecovery(c);
+
+ Assert.True(m.IsOpen);
+ var latch = new ManualResetEventSlim(false);
+ cons.Received += (s, args) => latch.Set();
+
+ m.BasicPublish("", q, _encoding.GetBytes("msg"));
+ Wait(latch, "received event");
+
+ m.QueueDelete(q);
+ }
+ }
+
+ [Fact]
+ public void TestConsumerRecoveryOnClientNamedQueueWithOneRecovery()
+ {
+ string q0 = "dotnet-client.recovery.queue1";
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection())
+ {
+ IChannel m = c.CreateChannel();
+ string q1 = m.QueueDeclare(q0, false, false, false, null).QueueName;
+ Assert.Equal(q0, q1);
+
+ var cons = new EventingBasicConsumer(m);
+ m.BasicConsume(q1, true, cons);
+ AssertConsumerCount(m, q1, 1);
+
+ bool queueNameChangeAfterRecoveryCalled = false;
+
+ c.QueueNameChangeAfterRecovery += (source, ea) => { queueNameChangeAfterRecoveryCalled = true; };
+
+ CloseAndWaitForRecovery(c);
+ AssertConsumerCount(m, q1, 1);
+ Assert.False(queueNameChangeAfterRecoveryCalled);
+
+ CloseAndWaitForRecovery(c);
+ AssertConsumerCount(m, q1, 1);
+ Assert.False(queueNameChangeAfterRecoveryCalled);
+
+ CloseAndWaitForRecovery(c);
+ AssertConsumerCount(m, q1, 1);
+ Assert.False(queueNameChangeAfterRecoveryCalled);
+
+ var latch = new ManualResetEventSlim(false);
+ cons.Received += (s, args) => latch.Set();
+
+ m.BasicPublish("", q1, _encoding.GetBytes("msg"));
+ Wait(latch, "received event");
+
+ m.QueueDelete(q1);
+ }
+ }
+
+ [Fact]
+ public void TestConsumerRecoveryWithServerNamedQueue()
+ {
+ // https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1238
+ using (AutorecoveringConnection c = CreateAutorecoveringConnection())
+ {
+ IChannel ch = c.CreateChannel();
+ RabbitMQ.Client.QueueDeclareOk queueDeclareResult = ch.QueueDeclare(queue: string.Empty, durable: false, exclusive: true, autoDelete: true, arguments: null);
+ string qname = queueDeclareResult.QueueName;
+ Assert.False(string.IsNullOrEmpty(qname));
+
+ var cons = new EventingBasicConsumer(ch);
+ ch.BasicConsume(string.Empty, true, cons);
+ AssertConsumerCount(ch, qname, 1);
+
+ bool queueNameBeforeIsEqual = false;
+ bool queueNameChangeAfterRecoveryCalled = false;
+ string qnameAfterRecovery = null;
+ c.QueueNameChangeAfterRecovery += (source, ea) =>
+ {
+ queueNameChangeAfterRecoveryCalled = true;
+ queueNameBeforeIsEqual = qname.Equals(ea.NameBefore);
+ qnameAfterRecovery = ea.NameAfter;
+ };
+
+ CloseAndWaitForRecovery(c);
+
+ AssertConsumerCount(ch, qnameAfterRecovery, 1);
+ Assert.True(queueNameChangeAfterRecoveryCalled);
+ Assert.True(queueNameBeforeIsEqual);
+ }
+ }
+
+ [Fact]
+ public void TestCreateChannelOnClosedAutorecoveringConnectionDoesNotHang()
+ {
+ // we don't want this to recover quickly in this test
+ AutorecoveringConnection c = CreateAutorecoveringConnection(TimeSpan.FromSeconds(20));
+
+ try
+ {
+ c.Close();
+ WaitForShutdown(c);
+ Assert.False(c.IsOpen);
+ c.CreateChannel();
+ Assert.Fail("Expected an exception");
+ }
+ catch (AlreadyClosedException)
+ {
+ // expected
+ }
+ finally
+ {
+ StartRabbitMQ();
+ if (c.IsOpen)
+ {
+ c.Abort();
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTopologyRecoveryConsumerFilter()
+ {
+ var filter = new TopologyRecoveryFilter
+ {
+ ConsumerFilter = consumer => !consumer.ConsumerTag.Contains("filtered")
+ };
+ var latch = new ManualResetEventSlim(false);
+ AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryFilter(filter);
+ conn.RecoverySucceeded += (source, ea) => latch.Set();
+ IChannel ch = conn.CreateChannel();
+ ch.ConfirmSelect();
+
+ var exchange = "topology.recovery.exchange";
+ var queueWithRecoveredConsumer = "topology.recovery.queue.1";
+ var queueWithIgnoredConsumer = "topology.recovery.queue.2";
+ var binding1 = "recovered.binding.1";
+ var binding2 = "recovered.binding.2";
+
+ ch.ExchangeDeclare(exchange, "direct");
+ ch.QueueDeclare(queueWithRecoveredConsumer, false, false, false, null);
+ ch.QueueDeclare(queueWithIgnoredConsumer, false, false, false, null);
+ ch.QueueBind(queueWithRecoveredConsumer, exchange, binding1);
+ ch.QueueBind(queueWithIgnoredConsumer, exchange, binding2);
+ ch.QueuePurge(queueWithRecoveredConsumer);
+ ch.QueuePurge(queueWithIgnoredConsumer);
+
+ var recoverLatch = new ManualResetEventSlim(false);
+ var consumerToRecover = new EventingBasicConsumer(ch);
+ consumerToRecover.Received += (source, ea) => recoverLatch.Set();
+ ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover);
+
+ var ignoredLatch = new ManualResetEventSlim(false);
+ var consumerToIgnore = new EventingBasicConsumer(ch);
+ consumerToIgnore.Received += (source, ea) => ignoredLatch.Set();
+ ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore);
+
+ try
+ {
+ CloseAndWaitForRecovery(conn);
+ Wait(latch, "recovery succeeded");
+
+ Assert.True(ch.IsOpen);
+ ch.BasicPublish(exchange, binding1, _encoding.GetBytes("test message"));
+ ch.BasicPublish(exchange, binding2, _encoding.GetBytes("test message"));
+
+ Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5)));
+ Assert.False(ignoredLatch.Wait(TimeSpan.FromSeconds(5)));
+
+ ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore);
+
+ try
+ {
+ ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover);
+ Assert.Fail("Expected an exception");
+ }
+ catch (OperationInterruptedException e)
+ {
+ AssertShutdownError(e.ShutdownReason, 530); // NOT_ALLOWED - not allowed to reuse consumer tag
+ }
+ }
+ finally
+ {
+ conn.Abort();
+ }
+ }
+
+ [Fact]
+ public void TestRecoveryWithTopologyDisabled()
+ {
+ AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryDisabled();
+ IChannel ch = conn.CreateChannel();
+ string s = "dotnet-client.test.recovery.q2";
+ ch.QueueDelete(s);
+ ch.QueueDeclare(s, false, true, false, null);
+ ch.QueueDeclarePassive(s);
+ Assert.True(ch.IsOpen);
+
+ try
+ {
+ CloseAndWaitForRecovery(conn);
+ Assert.True(ch.IsOpen);
+ ch.QueueDeclarePassive(s);
+ Assert.Fail("Expected an exception");
+ }
+ catch (OperationInterruptedException)
+ {
+ // expected
+ }
+ finally
+ {
+ conn.Abort();
+ }
+ }
+ }
+}
diff --git a/projects/Unit/APIApproval.Approve.verified.txt b/projects/Test/Unit/APIApproval.Approve.verified.txt
similarity index 88%
rename from projects/Unit/APIApproval.Approve.verified.txt
rename to projects/Test/Unit/APIApproval.Approve.verified.txt
index 8df115691b..1902c40c20 100644
--- a/projects/Unit/APIApproval.Approve.verified.txt
+++ b/projects/Test/Unit/APIApproval.Approve.verified.txt
@@ -1,4 +1,8 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"AsyncIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Unit, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")]
namespace RabbitMQ.Client
{
@@ -49,7 +53,7 @@ namespace RabbitMQ.Client
public virtual System.Threading.Tasks.Task HandleBasicCancel(string consumerTag) { }
public virtual System.Threading.Tasks.Task HandleBasicCancelOk(string consumerTag) { }
public virtual System.Threading.Tasks.Task HandleBasicConsumeOk(string consumerTag) { }
- public virtual System.Threading.Tasks.Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in RabbitMQ.Client.ReadOnlyBasicProperties properties, System.ReadOnlyMemory body) { }
+ public virtual System.Threading.Tasks.Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, in RabbitMQ.Client.ReadOnlyBasicProperties properties, RabbitMQ.Client.RentedMemory body) { }
public virtual System.Threading.Tasks.Task HandleChannelShutdown(object channel, RabbitMQ.Client.ShutdownEventArgs reason) { }
public virtual System.Threading.Tasks.Task OnCancel(params string[] consumerTags) { }
}
@@ -62,18 +66,16 @@ namespace RabbitMQ.Client
public System.TimeSpan? ValidUntil { get; }
public void Refresh() { }
}
- public sealed class BasicGetResult : System.IDisposable
+ public sealed class BasicGetResult
{
- public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, in RabbitMQ.Client.ReadOnlyBasicProperties basicProperties, System.ReadOnlyMemory body) { }
- public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, in RabbitMQ.Client.ReadOnlyBasicProperties basicProperties, System.ReadOnlyMemory body, byte[] rentedArray) { }
+ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, in RabbitMQ.Client.ReadOnlyBasicProperties basicProperties, RabbitMQ.Client.RentedMemory body) { }
public RabbitMQ.Client.ReadOnlyBasicProperties BasicProperties { get; }
- public System.ReadOnlyMemory Body { get; }
+ public RabbitMQ.Client.RentedMemory Body { get; }
public ulong DeliveryTag { get; }
public string Exchange { get; }
public uint MessageCount { get; }
public bool Redelivered { get; }
public string RoutingKey { get; }
- public void Dispose() { }
}
public struct BasicProperties : RabbitMQ.Client.IAmqpHeader, RabbitMQ.Client.IAmqpWriteable, RabbitMQ.Client.IBasicProperties, RabbitMQ.Client.IReadOnlyBasicProperties
{
@@ -216,6 +218,13 @@ namespace RabbitMQ.Client
public RabbitMQ.Client.IConnection CreateConnection(RabbitMQ.Client.IEndpointResolver endpointResolver, string clientProvidedName) { }
public RabbitMQ.Client.IConnection CreateConnection(System.Collections.Generic.IList endpoints, string clientProvidedName) { }
public RabbitMQ.Client.IConnection CreateConnection(System.Collections.Generic.IList hostnames, string clientProvidedName) { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync() { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync(System.Collections.Generic.IList endpoints) { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync(System.Collections.Generic.IList hostnames) { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync(string clientProvidedName) { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync(RabbitMQ.Client.IEndpointResolver endpointResolver, string clientProvidedName) { }
+ public System.Threading.Tasks.ValueTask CreateConnectionAsync(System.Collections.Generic.IList