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/Makefile b/Makefile
new file mode 100644
index 0000000000..88b8227407
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+# vim: noexpandtab:ts=4:sw=4
+.PHONY: build test test-all
+
+RABBITMQ_DOCKER_NAME ?= rabbitmq-dotnet-client-rabbitmq
+
+build:
+ dotnet build $(CURDIR)/Build.csproj
+
+test:
+ dotnet test $(CURDIR)/projects/Test/Unit/Unit.csproj --logger 'console;verbosity=detailed'
+ dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:$$(docker inspect --format='{{.Id}}' $(RABBITMQ_DOCKER_NAME))" $(CURDIR)/projects/Test/AsyncIntegration/AsyncIntegration.csproj --logger 'console;verbosity=detailed'
+ dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:$$(docker inspect --format='{{.Id}}' $(RABBITMQ_DOCKER_NAME))" $(CURDIR)/projects/Test/Integration/Integration.csproj --logger 'console;verbosity=detailed'
+ dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:$$(docker inspect --format='{{.Id}}' $(RABBITMQ_DOCKER_NAME))" $(CURDIR)/projects/Test/SequentialIntegration/SequentialIntegration.csproj --logger 'console;verbosity=detailed'
+
+# Note:
+# You must have the expected OAuth2 environment set up for this target
+test-all:
+ dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:$$(docker inspect --format='{{.Id}}' $(RABBITMQ_DOCKER_NAME))" $(CURDIR)/Build.csproj --logger 'console;verbosity=detailed'
diff --git a/RUNNING_TESTS.md b/RUNNING_TESTS.md
index a25a008221..56de830df6 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
@@ -79,7 +79,7 @@ RABBITMQ_RABBITMQCTL_PATH=/path/to/rabbitmqctl dotnet test projects/Unit
It is also possible to run a RabbitMQ node in a
[Docker](https://www.docker.com/) container. Set the environment variable
`RABBITMQ_RABBITMQCTL_PATH` to `DOCKER:` (for example
-`DOCKER:rabbitmq01`). This tells the unit tests to run the `rabbitmqctl`
+`DOCKER:rabbitmq-dotnet-client-rabbitmq`). This tells the unit tests to run the `rabbitmqctl`
commands through Docker, in the format `docker exec rabbitmq01 rabbitmqctl
`:
@@ -87,6 +87,12 @@ commands through Docker, in the format `docker exec rabbitmq01 rabbitmqctl
docker run -d --hostname rabbitmq01 --name rabbitmq01 -p 15672:15672 -p 5672:5672 rabbitmq:3-management
```
+You should also be able to run the same script that sets up the Ubuntu 22 GitHub actions worker:
+
+```shell
+./.ci/ubuntu/gha-setup.sh
+```
+
## Running All Tests
Then, to run the tests use:
@@ -94,14 +100,18 @@ Then, to run the tests use:
### Windows
+Note that the `-RunTests` does not run the OAuth2 test suite.
+
```powershell
build.ps1 -RunTests
```
### MacOS, Linux, BSD:
+Note that the `test` target does not run the OAuth2 test suite.
+
```shell
-dotnet test ./Build.csproj
+make test
```
## Running Individual Suites or Test Cases
@@ -110,9 +120,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..2dbe9b0835 100644
--- a/build.ps1
+++ b/build.ps1
@@ -8,20 +8,30 @@ Write-Host "`tPSScriptRoot: $PSScriptRoot"
Write-Host "`tRunTests: $RunTests"
Write-Host "`tdotnet --version: $(dotnet --version)"
-Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta"
+Write-Host "[INFO] building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta"
dotnet build "$PSScriptRoot\Build.csproj"
-Write-Host "Done building." -ForegroundColor "Green"
+Write-Host "[INFO] 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')
- 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) {
- Write-Host "Error with tests, aborting build." -Foreground "Red"
- Exit 1
+ $tests_dir = Join-Path -Path $PSScriptRoot -ChildPath 'projects' | Join-Path -ChildPath 'Test'
+ $unit_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $tests_dir -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj')
+ $integration_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $tests_dir -ChildPath 'Integration' | Join-Path -ChildPath 'Integration.csproj')
+ $async_integration_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $tests_dir -ChildPath 'AsyncIntegration' | Join-Path -ChildPath 'AsyncIntegration.csproj')
+ $sequential_integration_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $tests_dir -ChildPath 'SequentialIntegration' | Join-Path -ChildPath 'SequentialIntegration.csproj')
+
+ foreach ($csproj_file in $unit_csproj_file, $integration_csproj_file, $async_integration_csproj_file, $sequential_integration_csproj_file)
+ {
+ Write-Host "[INFO] running Unit / Integration tests from '$csproj_file' (all frameworks)" -ForegroundColor "Magenta"
+ dotnet test $csproj_file --no-restore --no-build --logger "console;verbosity=detailed"
+ if ($LASTEXITCODE -ne 0)
+ {
+ Write-Host "[ERROR] tests errored, exiting" -Foreground "Red"
+ Exit 1
+ }
+ else
+ {
+ Write-Host "[INFO] tests passed" -ForegroundColor "Green"
+ }
}
- Write-Host "Tests passed!" -ForegroundColor "Green"
}
-
-Write-Host "Done."
diff --git a/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs b/projects/Benchmarks/ConsumerDispatching/AsyncBasicConsumerFake.cs
index 573db2a277..d9cf669f42 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, ReadOnlyMemory body)
{
if (Interlocked.Increment(ref _current) == Count)
{
diff --git a/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs b/projects/Benchmarks/ConsumerDispatching/ConsumerDispatcher.cs
index b751091c4d..d5918dc06d 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;
@@ -7,7 +8,7 @@ namespace RabbitMQ.Benchmarks
{
[Config(typeof(Config))]
[BenchmarkCategory("ConsumerDispatcher")]
- public class ConsumerDispatcherBase
+ internal class ConsumerDispatcherBase
{
protected static readonly ManualResetEventSlim _autoResetEvent = new ManualResetEventSlim(false);
@@ -18,10 +19,18 @@ 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
+ internal class BasicDeliverConsumerDispatching : ConsumerDispatcherBase
{
[Params(1, 30)]
public int Count { get; set; }
@@ -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..b6fe369179 100644
--- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
+++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj
@@ -38,16 +38,28 @@
-
+
-
+
+
+ <_Parameter1>Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
<_Parameter1>Unit, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+ <_Parameter1>AsyncIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+ <_Parameter1>Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
+
+ <_Parameter1>SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
+
<_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5
@@ -58,8 +70,8 @@
-
-
+
+
diff --git a/projects/RabbitMQ.Client/client/ClientArrayPool.cs b/projects/RabbitMQ.Client/client/ClientArrayPool.cs
new file mode 100644
index 0000000000..c682879915
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/ClientArrayPool.cs
@@ -0,0 +1,114 @@
+// 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 (bool.TryParse(
+ Environment.GetEnvironmentVariable("RABBITMQ_CLIENT_TRACK_ARRAY_POOL_CHECKOUTS"),
+ out s_trackCheckouts))
+ {
+ 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..b4679e3b6d
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/RentedMemory.cs
@@ -0,0 +1,88 @@
+// 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
+{
+ internal struct RentedMemory : IDisposable
+ {
+ private bool _disposedValue;
+
+ internal RentedMemory(byte[] rentedArray)
+ : this(new ReadOnlyMemory(rentedArray), rentedArray)
+ {
+ }
+
+ internal RentedMemory(ReadOnlyMemory memory, byte[] rentedArray)
+ {
+ Memory = memory;
+ RentedArray = rentedArray;
+ }
+
+ internal readonly ReadOnlyMemory Memory;
+
+ internal readonly byte[] ToArray()
+ {
+ return Memory.ToArray();
+ }
+
+ internal readonly int Size => Memory.Length;
+
+ internal readonly ReadOnlySpan Span => Memory.Span;
+
+ internal readonly byte[] RentedArray;
+
+ internal readonly ReadOnlyMemory CopyToMemory()
+ {
+ return new ReadOnlyMemory(Memory.ToArray());
+ }
+
+ 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/TaskExtensions.cs b/projects/RabbitMQ.Client/client/TaskExtensions.cs
new file mode 100644
index 0000000000..cd1daf9e8e
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/TaskExtensions.cs
@@ -0,0 +1,118 @@
+// 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;
+
+namespace RabbitMQ.Client
+{
+ internal static class TaskExtensions
+ {
+#if !NET6_0_OR_GREATER
+ private static readonly TaskContinuationOptions s_tco = TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously;
+ private static void continuation(Task t, object s) => t.Exception.Handle(e => true);
+#endif
+
+ public static Task TimeoutAfter(this Task task, TimeSpan timeout)
+ {
+#if NET6_0_OR_GREATER
+ if (task.IsCompletedSuccessfully)
+ {
+ return task;
+ }
+
+ return task.WaitAsync(timeout);
+#else
+ if (task.Status == TaskStatus.RanToCompletion)
+ {
+ return task;
+ }
+
+ return DoTimeoutAfter(task, timeout);
+
+ static async Task DoTimeoutAfter(Task task, TimeSpan timeout)
+ {
+ if (task == await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false))
+ {
+ await task.ConfigureAwait(false);
+ }
+ else
+ {
+ Task supressErrorTask = task.ContinueWith(
+ continuationAction: continuation,
+ state: null,
+ cancellationToken: CancellationToken.None,
+ continuationOptions: s_tco,
+ scheduler: TaskScheduler.Default);
+ throw new TimeoutException();
+ }
+ }
+#endif
+ }
+
+ public static async ValueTask TimeoutAfter(this ValueTask task, TimeSpan timeout)
+ {
+ if (task.IsCompletedSuccessfully)
+ {
+ return;
+ }
+
+#if NET6_0_OR_GREATER
+ Task actualTask = task.AsTask();
+ await actualTask.WaitAsync(timeout)
+ .ConfigureAwait(false);
+#else
+ await DoTimeoutAfter(task, timeout)
+ .ConfigureAwait(false);
+
+ async static ValueTask DoTimeoutAfter(ValueTask task, TimeSpan timeout)
+ {
+ Task actualTask = task.AsTask();
+ if (actualTask == await Task.WhenAny(actualTask, Task.Delay(timeout)).ConfigureAwait(false))
+ {
+ await actualTask.ConfigureAwait(false);
+ }
+ else
+ {
+ Task supressErrorTask = actualTask.ContinueWith(
+ continuationAction: continuation,
+ state: null,
+ cancellationToken: CancellationToken.None,
+ continuationOptions: s_tco,
+ scheduler: TaskScheduler.Default);
+ throw new TimeoutException();
+ }
+ }
+#endif
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs b/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs
index 2465fd167b..e540f837ab 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.
///
@@ -65,7 +62,7 @@ public AmqpTimestamp(long unixTime) : this()
///
/// Unix time in seconds.
///
- public long UnixTime { get; }
+ public readonly long UnixTime;
public bool Equals(AmqpTimestamp other) => UnixTime == other.UnixTime;
diff --git a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
index a0c0af161a..a7a0fe4035 100644
--- a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs
@@ -141,7 +141,8 @@ public virtual async Task OnCancel(params string[] consumerTags)
IsRunning = false;
if (!_consumerCancelledWrapper.IsEmpty)
{
- await _consumerCancelledWrapper.InvokeAsync(this, new ConsumerEventArgs(consumerTags)).ConfigureAwait(false);
+ await _consumerCancelledWrapper.InvokeAsync(this, new ConsumerEventArgs(consumerTags))
+ .ConfigureAwait(false);
}
foreach (string consumerTag in consumerTags)
{
@@ -165,7 +166,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, ReadOnlyMemory 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..64a23a5f1f 100644
--- a/projects/RabbitMQ.Client/client/api/BasicGetResult.cs
+++ b/projects/RabbitMQ.Client/client/api/BasicGetResult.cs
@@ -30,7 +30,6 @@
//---------------------------------------------------------------------------
using System;
-using System.Buffers;
namespace RabbitMQ.Client
{
@@ -38,10 +37,8 @@ namespace RabbitMQ.Client
///
/// 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.
///
@@ -64,49 +61,25 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri
Body = body;
}
- ///
- /// 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.
///
- public ReadOnlyBasicProperties BasicProperties { get; }
+ public readonly ReadOnlyBasicProperties BasicProperties;
///
/// Retrieves the body of this message.
///
- public ReadOnlyMemory Body { get; }
+ public readonly ReadOnlyMemory Body;
///
/// Retrieve the delivery tag for this message. See also .
///
- public ulong DeliveryTag { get; }
+ public readonly ulong DeliveryTag;
///
/// Retrieve the exchange this message was published to.
///
- public string Exchange { get; }
+ public readonly string Exchange;
///
/// Retrieve the number of messages pending on the queue, excluding the message being delivered.
@@ -115,25 +88,16 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri
/// Note that this figure is indicative, not reliable, and can
/// change arbitrarily as messages are added to the queue and removed by other clients.
///
- public uint MessageCount { get; }
+ public readonly uint MessageCount;
///
/// Retrieve the redelivered flag for this message.
///
- public bool Redelivered { get; }
+ public readonly bool Redelivered;
///
/// Retrieve the routing key with which this message was published.
///
- public string RoutingKey { get; }
-
- ///
- public void Dispose()
- {
- if (_rentedArray != null)
- {
- ArrayPool.Shared.Return(_rentedArray);
- }
- }
+ public readonly string RoutingKey;
}
}
diff --git a/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs b/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
index 4ff107df22..6d7817e13e 100644
--- a/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
+++ b/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
@@ -45,17 +45,17 @@ public sealed class ConnectionConfig
///
/// Virtual host to access during this connection.
///
- public string VirtualHost { get; }
+ public readonly string VirtualHost;
///
/// Username to use when authenticating to the server.
///
- public string UserName { get; }
+ public readonly string UserName;
///
/// Password to use when authenticating to the server.
///
- public string Password { get; }
+ public readonly string Password;
///
/// Default CredentialsProvider implementation. If set, this
@@ -67,86 +67,86 @@ public sealed class ConnectionConfig
///
/// SASL auth mechanisms to use.
///
- public IList AuthMechanisms { get; }
+ public readonly IEnumerable AuthMechanisms;
///
/// Dictionary of client properties to be sent to the server.
///
- public IDictionary ClientProperties { get; }
+ public readonly IDictionary ClientProperties;
///
/// Default client provided name to be used for connections.
///
- public string? ClientProvidedName { get; }
+ public readonly string? ClientProvidedName;
///
/// Maximum channel number to ask for.
///
- public ushort MaxChannelCount { get; }
+ public readonly ushort MaxChannelCount;
///
/// Frame-max parameter to ask for (in bytes).
///
- public uint MaxFrameSize { get; }
+ public readonly uint MaxFrameSize;
///
/// Set to false to make automatic connection recovery not recover topology (exchanges, queues, bindings, etc).
///
- public bool TopologyRecoveryEnabled { get; }
+ public readonly bool TopologyRecoveryEnabled;
///
/// Filter to include/exclude entities from topology recovery.
/// Default filter includes all entities in topology recovery.
///
- public TopologyRecoveryFilter TopologyRecoveryFilter { get; }
+ public readonly TopologyRecoveryFilter TopologyRecoveryFilter;
///
/// Custom logic for handling topology recovery exceptions that match the specified filters.
///
- public TopologyRecoveryExceptionHandler TopologyRecoveryExceptionHandler { get; }
+ public readonly TopologyRecoveryExceptionHandler TopologyRecoveryExceptionHandler;
///
/// Amount of time client will wait for before re-trying to recover connection.
///
- public TimeSpan NetworkRecoveryInterval { get; }
+ public readonly TimeSpan NetworkRecoveryInterval;
///
/// Heartbeat timeout to use when negotiating with the server.
///
- public TimeSpan HeartbeatInterval { get; }
+ public readonly TimeSpan HeartbeatInterval;
///
/// Amount of time protocol operations (e.g. queue.declare
) are allowed to take before timing out.
///
- public TimeSpan ContinuationTimeout { get; }
+ public readonly TimeSpan ContinuationTimeout;
///
/// Amount of time protocol handshake operations are allowed to take before timing out.
///
+ public readonly TimeSpan HandshakeContinuationTimeout;
- public TimeSpan HandshakeContinuationTimeout { get; }
///
/// Timeout setting for connection attempts.
///
- public TimeSpan RequestedConnectionTimeout { get; }
+ public readonly TimeSpan RequestedConnectionTimeout;
///
/// Set to true will enable an asynchronous consumer dispatcher which is compatible with .
///
- public bool DispatchConsumersAsync { get; }
+ public readonly bool DispatchConsumersAsync;
///
/// Set to a value greater than one to enable concurrent processing. For a concurrency greater than one
/// will be offloaded to the worker thread pool so it is important to choose the value for the concurrency wisely to avoid thread pool overloading.
/// can handle concurrency much more efficiently due to the non-blocking nature of the consumer.
///
- public int DispatchConsumerConcurrency { get; }
+ public readonly int DispatchConsumerConcurrency;
- internal Func FrameHandlerFactory { get; }
+ internal readonly Func FrameHandlerFactory;
internal ConnectionConfig(string virtualHost, string userName, string password,
ICredentialsProvider credentialsProvider, ICredentialsRefresher credentialsRefresher,
- IList authMechanisms,
+ IEnumerable authMechanisms,
IDictionary clientProperties, string? clientProvidedName,
ushort maxChannelCount, uint maxFrameSize, bool topologyRecoveryEnabled,
TopologyRecoveryFilter topologyRecoveryFilter, TopologyRecoveryExceptionHandler topologyRecoveryExceptionHandler,
diff --git a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
index 82d7ff6d24..769176203d 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;
@@ -149,12 +150,12 @@ public sealed class ConnectionFactory : ConnectionFactoryBase, IConnectionFactor
///
/// Default SASL auth mechanisms to use.
///
- public static readonly IList DefaultAuthMechanisms = new List(1) { new PlainMechanismFactory() };
+ public static readonly IEnumerable DefaultAuthMechanisms = new[] { new PlainMechanismFactory() };
///
/// SASL auth mechanisms to use.
///
- public IList AuthMechanisms { get; set; } = DefaultAuthMechanisms;
+ public IEnumerable AuthMechanisms { get; set; } = DefaultAuthMechanisms;
///
/// Address family used by default.
@@ -377,17 +378,21 @@ public Uri Uri
/// Given a list of mechanism names supported by the server, select a preferred mechanism,
/// or null if we have none in common.
///
- public IAuthMechanismFactory AuthMechanismFactory(IList mechanismNames)
+ public IAuthMechanismFactory AuthMechanismFactory(IEnumerable argServerMechanismNames)
{
+ string[] serverMechanismNames = argServerMechanismNames.ToArray();
+
// Our list is in order of preference, the server one is not.
- for (int index = 0; index < AuthMechanisms.Count; index++)
+ IAuthMechanismFactory[] authMechanisms = AuthMechanisms.ToArray();
+
+ for (int index = 0; index < authMechanisms.Length; index++)
{
- IAuthMechanismFactory factory = AuthMechanisms[index];
+ IAuthMechanismFactory factory = authMechanisms[index];
string factoryName = factory.Name;
- for (int i = 0; i < mechanismNames.Count; i++)
+ for (int i = 0; i < serverMechanismNames.Length; i++)
{
- if (string.Equals(mechanismNames[i], factoryName, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(serverMechanismNames[i], factoryName, StringComparison.OrdinalIgnoreCase))
{
return factory;
}
@@ -410,6 +415,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 +447,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
@@ -443,11 +480,30 @@ public IConnection CreateConnection(string clientProvidedName)
///
/// When no hostname was reachable.
///
- public IConnection CreateConnection(IList hostnames)
+ public IConnection CreateConnection(IEnumerable 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(IEnumerable 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
@@ -468,12 +524,38 @@ public IConnection CreateConnection(IList hostnames)
///
/// When no hostname was reachable.
///
- public IConnection CreateConnection(IList hostnames, string clientProvidedName)
+ public IConnection CreateConnection(IEnumerable hostnames, string clientProvidedName)
{
IEnumerable endpoints = hostnames.Select(h => new AmqpTcpEndpoint(h, Port, Ssl, MaxMessageSize));
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(IEnumerable 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.
@@ -487,11 +569,29 @@ public IConnection CreateConnection(IList hostnames, string clientProvid
///
/// When no hostname was reachable.
///
- public IConnection CreateConnection(IList endpoints)
+ public IConnection CreateConnection(IEnumerable 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(IEnumerable 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.
@@ -511,11 +611,35 @@ public IConnection CreateConnection(IList endpoints)
///
/// When no hostname was reachable.
///
- public IConnection CreateConnection(IList endpoints, string clientProvidedName)
+ public IConnection CreateConnection(IEnumerable endpoints, string clientProvidedName)
{
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(IEnumerable endpoints, string clientProvidedName)
+ {
+ return CreateConnectionAsync(EndpointResolverFactory(endpoints), clientProvidedName);
+ }
+
///
/// Create a connection using an IEndpointResolver.
///
@@ -539,10 +663,54 @@ public IConnection CreateConnection(IEndpointResolver endpointResolver, string c
{
if (AutomaticRecoveryEnabled)
{
- return new AutorecoveringConnection(config, endpointResolver);
+ var c = new AutorecoveringConnection(config, endpointResolver);
+ return (AutorecoveringConnection)c.Open();
+ }
+ else
+ {
+ var c = new Connection(config, endpointResolver.SelectOne(CreateFrameHandler));
+ return (Connection)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 async ValueTask CreateConnectionAsync(IEndpointResolver endpointResolver, string clientProvidedName)
+ {
+ ConnectionConfig config = CreateConfig(clientProvidedName);
+ try
+ {
+ if (AutomaticRecoveryEnabled)
+ {
+ var c = new AutorecoveringConnection(config, endpointResolver);
+ return await c.OpenAsync()
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ var c = new Connection(config, endpointResolver.SelectOne(CreateFrameHandler));
+ return await c.OpenAsync()
+ .ConfigureAwait(false);
+ }
}
catch (Exception e)
{
diff --git a/projects/RabbitMQ.Client/client/api/IChannel.cs b/projects/RabbitMQ.Client/client/api/IChannel.cs
index 3f7f4693e2..9d3248d22a 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,27 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou
void Close(ushort replyCode, string replyText, bool abort);
///
- /// Enable publisher acknowledgements.
+ /// Asynchronously 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.
+ /// Whether or not the close is an abort (ignoring certain exceptions).
+ ValueTask CloseAsync(ushort replyCode, string replyText, bool abort);
+
+ ///
+ /// 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 +336,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 +359,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 +392,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 +410,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 +431,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 +474,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 +516,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..ab5c9cac10 100644
--- a/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs
+++ b/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs
@@ -51,10 +51,33 @@ public static string BasicConsume(this IChannel channel,
return channel.BasicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumer);
}
+ /// Asynchronously start a Basic content-class consumer.
+ public static ValueTask BasicConsumeAsync(this IChannel channel,
+ IBasicConsumer consumer,
+ string queue,
+ bool autoAck = false,
+ string consumerTag = "",
+ bool noLocal = false,
+ bool exclusive = false,
+ IDictionary arguments = null)
+ {
+ return channel.BasicConsumeAsync(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumer);
+ }
+
/// Start a Basic content-class consumer.
- public static string BasicConsume(this IChannel channel, string queue, bool autoAck, IBasicConsumer consumer)
+ public static string BasicConsume(this IChannel channel, string queue,
+ bool autoAck,
+ IBasicConsumer consumer)
+ {
+ return channel.BasicConsume(queue, autoAck, string.Empty, false, false, null, consumer);
+ }
+
+ /// Asynchronously start a Basic content-class consumer.
+ public static ValueTask BasicConsumeAsync(this IChannel channel, string queue,
+ bool autoAck,
+ IBasicConsumer consumer)
{
- return channel.BasicConsume(queue, autoAck, "", false, false, null, consumer);
+ return channel.BasicConsumeAsync(queue, autoAck, string.Empty, false, false, null, consumer);
}
/// Start a Basic content-class consumer.
@@ -66,6 +89,15 @@ public static string BasicConsume(this IChannel channel, string queue,
return channel.BasicConsume(queue, autoAck, consumerTag, false, false, null, consumer);
}
+ /// Asynchronously start a Basic content-class consumer.
+ public static ValueTask BasicConsumeAsync(this IChannel channel, string queue,
+ bool autoAck,
+ string consumerTag,
+ IBasicConsumer consumer)
+ {
+ return channel.BasicConsumeAsync(queue, autoAck, consumerTag, false, false, null, consumer);
+ }
+
/// Start a Basic content-class consumer.
public static string BasicConsume(this IChannel channel, string queue,
bool autoAck,
@@ -76,6 +108,16 @@ public static string BasicConsume(this IChannel channel, string queue,
return channel.BasicConsume(queue, autoAck, consumerTag, false, false, arguments, consumer);
}
+ /// Asynchronously start a Basic content-class consumer.
+ public static ValueTask BasicConsumeAsync(this IChannel channel, string queue,
+ bool autoAck,
+ string consumerTag,
+ IDictionary arguments,
+ IBasicConsumer consumer)
+ {
+ return channel.BasicConsumeAsync(queue, autoAck, consumerTag, false, false, arguments, consumer);
+ }
+
#nullable enable
///
/// (Extension method) Convenience overload of BasicPublish.
@@ -89,21 +131,27 @@ public static void BasicPublish(this IChannel channel, PublicationAddress add
channel.BasicPublish(addr.ExchangeName, addr.RoutingKey, in basicProperties, body);
}
- public static void BasicPublish(this IChannel channel, string exchange, string routingKey, ReadOnlyMemory body = default, bool mandatory = false)
- => channel.BasicPublish(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
+ public static ValueTask BasicPublishAsync(this IChannel channel, PublicationAddress addr, in T basicProperties, ReadOnlyMemory body)
+ where T : IReadOnlyBasicProperties, IAmqpHeader
+ {
+ return channel.BasicPublishAsync(addr.ExchangeName, addr.RoutingKey, in basicProperties, body);
+ }
- public static void BasicPublish(this IChannel channel, CachedString exchange, CachedString routingKey, ReadOnlyMemory body = default, bool mandatory = false)
+ public static void BasicPublish(this IChannel channel, string exchange, string routingKey, ReadOnlyMemory body = default, bool mandatory = false)
=> channel.BasicPublish(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
public static ValueTask BasicPublishAsync(this IChannel channel, string exchange, string routingKey, ReadOnlyMemory body = default, bool mandatory = false)
=> channel.BasicPublishAsync(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
+ public static void BasicPublish(this IChannel channel, CachedString exchange, CachedString routingKey, ReadOnlyMemory body = default, bool mandatory = false)
+ => channel.BasicPublish(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
+
public static ValueTask BasicPublishAsync(this IChannel channel, CachedString exchange, CachedString routingKey, ReadOnlyMemory body = default, bool mandatory = false)
=> channel.BasicPublishAsync(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
#nullable disable
///
- /// (Spec method) Declare a queue.
+ /// Declare a queue.
///
public static QueueDeclareOk QueueDeclare(this IChannel channel, string queue = "", bool durable = false, bool exclusive = true,
bool autoDelete = true, IDictionary arguments = null)
@@ -112,7 +160,17 @@ public static QueueDeclareOk QueueDeclare(this IChannel channel, string queue =
}
///
- /// (Extension method) Bind an exchange to an exchange.
+ /// Asynchronously declare a queue.
+ ///
+ public static ValueTask QueueDeclareAsync(this IChannel channel, string queue = "", bool durable = false, bool exclusive = true,
+ bool autoDelete = true, IDictionary arguments = null)
+ {
+ return channel.QueueDeclareAsync(queue: queue, passive: false,
+ durable: durable, exclusive: exclusive, autoDelete: autoDelete, arguments: arguments);
+ }
+
+ ///
+ /// Bind an exchange to an exchange.
///
public static void ExchangeBind(this IChannel channel, string destination, string source, string routingKey, IDictionary arguments = null)
{
@@ -120,7 +178,15 @@ public static void ExchangeBind(this IChannel channel, string destination, strin
}
///
- /// (Extension method) Like exchange bind but sets nowait to true.
+ /// Asynchronously bind an exchange to an exchange.
+ ///
+ public static ValueTask ExchangeBindAsync(this IChannel channel, string destination, string source, string routingKey, IDictionary arguments = null)
+ {
+ return channel.ExchangeBindAsync(destination, source, routingKey, arguments);
+ }
+
+ ///
+ /// Like exchange bind but sets nowait to true.
///
public static void ExchangeBindNoWait(this IChannel channel, string destination, string source, string routingKey, IDictionary arguments = null)
{
@@ -128,7 +194,7 @@ public static void ExchangeBindNoWait(this IChannel channel, string destination,
}
///
- /// (Spec method) Declare an exchange.
+ /// Declare an exchange.
///
public static void ExchangeDeclare(this IChannel channel, string exchange, string type, bool durable = false, bool autoDelete = false,
IDictionary arguments = null)
@@ -137,7 +203,16 @@ public static void ExchangeDeclare(this IChannel channel, string exchange, strin
}
///
- /// (Extension method) Like ExchangeDeclare but sets nowait to true.
+ /// Asynchronously declare an exchange.
+ ///
+ public static ValueTask ExchangeDeclareAsync(this IChannel channel, string exchange, string type, bool durable = false, bool autoDelete = false,
+ IDictionary arguments = null)
+ {
+ return channel.ExchangeDeclareAsync(exchange, type, durable, autoDelete, arguments);
+ }
+
+ ///
+ /// Like ExchangeDeclare but sets nowait to true.
///
public static void ExchangeDeclareNoWait(this IChannel channel, string exchange, string type, bool durable = false, bool autoDelete = false,
IDictionary arguments = null)
@@ -146,7 +221,7 @@ public static void ExchangeDeclareNoWait(this IChannel channel, string exchange,
}
///
- /// (Spec method) Unbinds an exchange.
+ /// Unbinds an exchange.
///
public static void ExchangeUnbind(this IChannel channel, string destination,
string source,
@@ -157,7 +232,18 @@ public static void ExchangeUnbind(this IChannel channel, string destination,
}
///
- /// (Spec method) Deletes an exchange.
+ /// Asynchronously unbinds an exchange.
+ ///
+ public static ValueTask ExchangeUnbindAsync(this IChannel channel, string destination,
+ string source,
+ string routingKey,
+ IDictionary arguments = null)
+ {
+ return channel.ExchangeUnbindAsync(destination, source, routingKey, arguments);
+ }
+
+ ///
+ /// Deletes an exchange.
///
public static void ExchangeDelete(this IChannel channel, string exchange, bool ifUnused = false)
{
@@ -165,7 +251,15 @@ public static void ExchangeDelete(this IChannel channel, string exchange, bool i
}
///
- /// (Extension method) Like ExchangeDelete but sets nowait to true.
+ /// Asynchronously deletes an exchange.
+ ///
+ public static ValueTask ExchangeDeleteAsync(this IChannel channel, string exchange, bool ifUnused = false)
+ {
+ return channel.ExchangeDeleteAsync(exchange, ifUnused);
+ }
+
+ ///
+ /// Like ExchangeDelete but sets nowait to true.
///
public static void ExchangeDeleteNoWait(this IChannel channel, string exchange, bool ifUnused = false)
{
@@ -173,7 +267,7 @@ public static void ExchangeDeleteNoWait(this IChannel channel, string exchange,
}
///
- /// (Spec method) Binds a queue.
+ /// Binds a queue.
///
public static void QueueBind(this IChannel channel, string queue, string exchange, string routingKey, IDictionary arguments = null)
{
@@ -181,7 +275,15 @@ public static void QueueBind(this IChannel channel, string queue, string exchang
}
///
- /// (Spec method) Deletes a queue.
+ /// Asynchronously binds a queue.
+ ///
+ public static ValueTask QueueBindAsync(this IChannel channel, string queue, string exchange, string routingKey, IDictionary arguments = null)
+ {
+ return channel.QueueBindAsync(queue, exchange, routingKey, arguments);
+ }
+
+ ///
+ /// Deletes a queue.
///
public static uint QueueDelete(this IChannel channel, string queue, bool ifUnused = false, bool ifEmpty = false)
{
@@ -189,7 +291,15 @@ public static uint QueueDelete(this IChannel channel, string queue, bool ifUnuse
}
///
- /// (Extension method) Like QueueDelete but sets nowait to true.
+ /// Asynchronously deletes a queue.
+ ///
+ public static ValueTask QueueDeleteAsync(this IChannel channel, string queue, bool ifUnused = false, bool ifEmpty = false)
+ {
+ return channel.QueueDeleteAsync(queue, ifUnused, ifEmpty);
+ }
+
+ ///
+ /// Like QueueDelete but sets nowait to true.
///
public static void QueueDeleteNoWait(this IChannel channel, string queue, bool ifUnused = false, bool ifEmpty = false)
{
@@ -197,13 +307,21 @@ public static void QueueDeleteNoWait(this IChannel channel, string queue, bool i
}
///
- /// (Spec method) Unbinds a queue.
+ /// Unbinds a queue.
///
public static void QueueUnbind(this IChannel channel, string queue, string exchange, string routingKey, IDictionary arguments = null)
{
channel.QueueUnbind(queue, exchange, routingKey, arguments);
}
+ ///
+ /// Asynchronously unbinds a queue.
+ ///
+ public static ValueTask QueueUnbindAsync(this IChannel channel, string queue, string exchange, string routingKey, IDictionary arguments = null)
+ {
+ return channel.QueueUnbindAsync(queue, exchange, routingKey, arguments);
+ }
+
///
/// Abort this session.
///
@@ -220,6 +338,22 @@ public static void Abort(this IChannel channel)
channel.Close(Constants.ReplySuccess, "Goodbye", true);
}
+ ///
+ /// Asynchronously abort 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.
+ /// In comparison to normal method, will not throw
+ /// or or any other during closing channel.
+ ///
+ public static ValueTask AbortAsync(this IChannel channel)
+ {
+ return channel.CloseAsync(Constants.ReplySuccess, "Goodbye", true);
+ }
+
///
/// Abort this session.
///
@@ -250,15 +384,31 @@ 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)
+ {
+ return channel.CloseAsync(Constants.ReplySuccess, "Goodbye", 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
///
///
@@ -266,5 +416,26 @@ public static void Close(this IChannel channel, ushort replyCode, string replyTe
{
channel.Close(replyCode, replyText, false);
}
+
+ ///
+ /// Asynchronously 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
+ ///
+ ///
+ public static ValueTask CloseAsync(this IChannel channel, ushort replyCode, string replyText)
+ {
+ return channel.CloseAsync(replyCode, replyText, false);
+ }
}
}
diff --git a/projects/RabbitMQ.Client/client/api/IConnection.cs b/projects/RabbitMQ.Client/client/api/IConnection.cs
index 745efe79e5..4078e005af 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;
@@ -122,7 +123,7 @@ public interface IConnection : INetworkConnection, IDisposable
/// Returns the list of objects that contain information
/// about any errors reported while closing the connection in the order they appeared
///
- IList ShutdownReport { get; }
+ IEnumerable ShutdownReport { get; }
///
/// Application-specific connection name, will be displayed in the management UI
@@ -190,7 +191,7 @@ public interface IConnection : INetworkConnection, IDisposable
///
/// This event will never fire for connections that disable automatic recovery.
///
- event EventHandler QueueNameChangeAfterRecovery;
+ event EventHandler QueueNameChangedAfterRecovery;
///
/// Raised when a consumer is about to be recovered. This event raises when topology recovery
@@ -225,9 +226,24 @@ public interface IConnection : INetworkConnection, IDisposable
/// Whether or not this close is an abort (ignores certain exceptions).
void Close(ushort reasonCode, string reasonText, TimeSpan timeout, bool abort);
+ ///
+ /// Asynchronously close this connection and all its channels
+ /// and wait with a timeout for all the in-progress close operations to complete.
+ ///
+ /// The close code (See under "Reply Codes" in the AMQP 0-9-1 specification).
+ /// A message indicating the reason for closing the connection.
+ /// Operation timeout.
+ /// Whether or not this close is an abort (ignores certain exceptions).
+ ValueTask CloseAsync(ushort reasonCode, string reasonText, TimeSpan timeout, bool abort);
+
///
/// 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/IConnectionExtensions.cs b/projects/RabbitMQ.Client/client/api/IConnectionExtensions.cs
index 0457caa184..fecb606b78 100644
--- a/projects/RabbitMQ.Client/client/api/IConnectionExtensions.cs
+++ b/projects/RabbitMQ.Client/client/api/IConnectionExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading;
+using System.Threading.Tasks;
namespace RabbitMQ.Client
{
@@ -22,6 +23,22 @@ public static void Close(this IConnection connection)
connection.Close(Constants.ReplySuccess, "Goodbye", InternalConstants.DefaultConnectionCloseTimeout, false);
}
+ ///
+ /// Asynchronously close this connection and all its channels.
+ ///
+ ///
+ /// Note that all active channels and sessions will be
+ /// closed if this method is called. It will wait for the in-progress
+ /// close operation to complete. This method will not return to the caller
+ /// until the shutdown is complete. If the connection is already closed
+ /// (or closing), then this method will do nothing.
+ /// It can also throw when socket was closed unexpectedly.
+ ///
+ public static ValueTask CloseAsync(this IConnection connection)
+ {
+ return connection.CloseAsync(Constants.ReplySuccess, "Goodbye", InternalConstants.DefaultConnectionCloseTimeout, false);
+ }
+
///
/// Close this connection and all its channels.
///
@@ -40,6 +57,24 @@ public static void Close(this IConnection connection, ushort reasonCode, string
connection.Close(reasonCode, reasonText, InternalConstants.DefaultConnectionCloseTimeout, false);
}
+ ///
+ /// Asynchronously close this connection and all its channels.
+ ///
+ ///
+ /// The method behaves in the same way as , with the only
+ /// difference that the connection is closed with the given connection close code and message.
+ ///
+ /// The close code (See under "Reply Codes" in the AMQP specification).
+ ///
+ ///
+ /// A message indicating the reason for closing the connection.
+ ///
+ ///
+ public static ValueTask CloseAsync(this IConnection connection, ushort reasonCode, string reasonText)
+ {
+ return connection.CloseAsync(reasonCode, reasonText, InternalConstants.DefaultConnectionCloseTimeout, false);
+ }
+
///
/// Close this connection and all its channels
/// and wait with a timeout for all the in-progress close operations to complete.
@@ -60,6 +95,26 @@ public static void Close(this IConnection connection, TimeSpan timeout)
connection.Close(Constants.ReplySuccess, "Goodbye", timeout, false);
}
+ ///
+ /// Asynchronously close this connection and all its channels
+ /// and wait with a timeout for all the in-progress close operations to complete.
+ ///
+ ///
+ /// Note that all active channels and sessions will be
+ /// closed if this method is called. It will wait for the in-progress
+ /// close operation to complete with a timeout. If the connection is
+ /// already closed (or closing), then this method will do nothing.
+ /// It can also throw when socket was closed unexpectedly.
+ /// If timeout is reached and the close operations haven't finished, then socket is forced to close.
+ ///
+ /// To wait infinitely for the close operations to complete use .
+ ///
+ ///
+ public static ValueTask CloseAsync(this IConnection connection, TimeSpan timeout)
+ {
+ return connection.CloseAsync(Constants.ReplySuccess, "Goodbye", timeout, false);
+ }
+
///
/// Close this connection and all its channels
/// and wait with a timeout for all the in-progress close operations to complete.
@@ -82,6 +137,28 @@ public static void Close(this IConnection connection, ushort reasonCode, string
connection.Close(reasonCode, reasonText, timeout, false);
}
+ ///
+ /// Asynchronously close this connection and all its channels
+ /// and wait with a timeout for all the in-progress close operations to complete.
+ ///
+ ///
+ /// The method behaves in the same way as , with the only
+ /// difference that the connection is closed with the given connection close code and message.
+ ///
+ /// The close code (See under "Reply Codes" in the AMQP 0-9-1 specification).
+ ///
+ ///
+ /// A message indicating the reason for closing the connection.
+ ///
+ ///
+ /// Operation timeout.
+ ///
+ ///
+ public static ValueTask CloseAsync(this IConnection connection, ushort reasonCode, string reasonText, TimeSpan timeout)
+ {
+ return connection.CloseAsync(reasonCode, reasonText, timeout, false);
+ }
+
///
/// Abort this connection and all its channels.
///
@@ -96,6 +173,20 @@ public static void Abort(this IConnection connection)
connection.Close(Constants.ReplySuccess, "Connection close forced", InternalConstants.DefaultConnectionAbortTimeout, true);
}
+ ///
+ /// Asynchronously abort this connection and all its channels.
+ ///
+ ///
+ /// Note that all active channels and sessions will be closed if this method is called.
+ /// In comparison to normal method, will not throw
+ /// during closing connection.
+ ///This method waits infinitely for the in-progress close operation to complete.
+ ///
+ public static ValueTask AbortAsync(this IConnection connection)
+ {
+ return connection.CloseAsync(Constants.ReplySuccess, "Connection close forced", InternalConstants.DefaultConnectionAbortTimeout, true);
+ }
+
///
/// Abort this connection and all its channels.
///
@@ -114,6 +205,24 @@ public static void Abort(this IConnection connection, ushort reasonCode, string
connection.Close(reasonCode, reasonText, InternalConstants.DefaultConnectionAbortTimeout, true);
}
+ ///
+ /// Asynchronously abort this connection and all its channels.
+ ///
+ ///
+ /// The method behaves in the same way as , with the only
+ /// difference that the connection is closed with the given connection close code and message.
+ ///
+ /// The close code (See under "Reply Codes" in the AMQP 0-9-1 specification)
+ ///
+ ///
+ /// A message indicating the reason for closing the connection
+ ///
+ ///
+ public static ValueTask AbortAsync(this IConnection connection, ushort reasonCode, string reasonText)
+ {
+ return connection.CloseAsync(reasonCode, reasonText, InternalConstants.DefaultConnectionAbortTimeout, true);
+ }
+
///
/// Abort this connection and all its channels and wait with a
/// timeout for all the in-progress close operations to complete.
@@ -132,6 +241,24 @@ public static void Abort(this IConnection connection, TimeSpan timeout)
connection.Close(Constants.ReplySuccess, "Connection close forced", timeout, true);
}
+ ///
+ /// Asynchronously abort this connection and all its channels and wait with a
+ /// timeout for all the in-progress close operations to complete.
+ ///
+ ///
+ /// This method, behaves in a similar way as method with the
+ /// only difference that it explicitly specifies a timeout given
+ /// for all the in-progress close operations to complete.
+ /// If timeout is reached and the close operations haven't finished, then socket is forced to close.
+ ///
+ /// To wait infinitely for the close operations to complete use .
+ ///
+ ///
+ public static ValueTask AbortAsync(this IConnection connection, TimeSpan timeout)
+ {
+ return connection.CloseAsync(Constants.ReplySuccess, "Connection close forced", timeout, true);
+ }
+
///
/// Abort this connection and all its channels and wait with a
/// timeout for all the in-progress close operations to complete.
@@ -150,5 +277,24 @@ public static void Abort(this IConnection connection, ushort reasonCode, string
{
connection.Close(reasonCode, reasonText, timeout, true);
}
+
+ ///
+ /// Asynchronously abort this connection and all its channels and wait with a
+ /// timeout for all the in-progress close operations to complete.
+ ///
+ ///
+ /// The method behaves in the same way as , with the only
+ /// difference that the connection is closed with the given connection close code and message.
+ ///
+ /// The close code (See under "Reply Codes" in the AMQP 0-9-1 specification).
+ ///
+ ///
+ /// A message indicating the reason for closing the connection.
+ ///
+ ///
+ public static ValueTask AbortAsync(this IConnection connection, ushort reasonCode, string reasonText, TimeSpan timeout)
+ {
+ return connection.CloseAsync(reasonCode, reasonText, timeout, true);
+ }
}
}
diff --git a/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
index b1480cf887..56c21d29db 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
@@ -95,13 +95,18 @@ public interface IConnectionFactory
/// Given a list of mechanism names supported by the server, select a preferred mechanism,
/// or null if we have none in common.
///
- IAuthMechanismFactory AuthMechanismFactory(IList mechanismNames);
+ IAuthMechanismFactory AuthMechanismFactory(IEnumerable mechanismNames);
///
/// Create a connection to the specified endpoint.
///
IConnection CreateConnection();
+ ///
+ /// Asynchronously create a connection to the specified endpoint.
+ ///
+ ValueTask CreateConnectionAsync();
+
///
/// Create a connection to the specified endpoint.
///
@@ -114,12 +119,31 @@ 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.
///
/// List of host names to use
/// Open connection
- IConnection CreateConnection(IList hostnames);
+ IConnection CreateConnection(IEnumerable hostnames);
+
+ ///
+ /// Asynchronously connects to the first reachable hostname from the list.
+ ///
+ /// List of host names to use
+ /// Open connection
+ ValueTask CreateConnectionAsync(IEnumerable hostnames);
///
/// Connects to the first reachable hostname from the list.
@@ -132,7 +156,20 @@ public interface IConnectionFactory
/// This value is supposed to be human-readable.
///
/// Open connection
- IConnection CreateConnection(IList hostnames, string clientProvidedName);
+ IConnection CreateConnection(IEnumerable 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(IEnumerable hostnames, string clientProvidedName);
///
/// Create a connection using a list of endpoints.
@@ -146,7 +183,21 @@ public interface IConnectionFactory
///
/// When no hostname was reachable.
///
- IConnection CreateConnection(IList endpoints);
+ IConnection CreateConnection(IEnumerable 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(IEnumerable endpoints);
///
/// Create a connection using a list of endpoints.
@@ -166,7 +217,27 @@ public interface IConnectionFactory
///
/// When no hostname was reachable.
///
- IConnection CreateConnection(IList endpoints, string clientProvidedName);
+ IConnection CreateConnection(IEnumerable 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(IEnumerable endpoints, string clientProvidedName);
///
/// Amount of time protocol handshake operations are allowed to take before
diff --git a/projects/RabbitMQ.Client/client/api/Protocols.cs b/projects/RabbitMQ.Client/client/api/Protocols.cs
index 74b3115ec4..507ea25127 100644
--- a/projects/RabbitMQ.Client/client/api/Protocols.cs
+++ b/projects/RabbitMQ.Client/client/api/Protocols.cs
@@ -39,11 +39,11 @@ public static class Protocols
///
/// Protocol version 0-9-1 as modified by Pivotal.
///
- public static IProtocol AMQP_0_9_1 { get; } = new Framing.Protocol();
+ public readonly static IProtocol AMQP_0_9_1 = new Framing.Protocol();
///
/// Retrieve the current default protocol variant (currently AMQP_0_9_1).
///
- public static IProtocol DefaultProtocol => AMQP_0_9_1;
+ public readonly static IProtocol DefaultProtocol = AMQP_0_9_1;
}
}
diff --git a/projects/RabbitMQ.Client/client/api/PublicationAddress.cs b/projects/RabbitMQ.Client/client/api/PublicationAddress.cs
index f35863b551..6af9dce657 100644
--- a/projects/RabbitMQ.Client/client/api/PublicationAddress.cs
+++ b/projects/RabbitMQ.Client/client/api/PublicationAddress.cs
@@ -79,17 +79,17 @@ public PublicationAddress(string exchangeType, string exchangeName, string routi
///
/// Retrieve the exchange name.
///
- public string ExchangeName { get; }
+ public readonly string ExchangeName;
///
/// Retrieve the exchange type string.
///
- public string ExchangeType { get; }
+ public readonly string ExchangeType;
///
///Retrieve the routing key.
///
- public string RoutingKey { get; }
+ public readonly string RoutingKey;
///
/// Parse a out of the given string,
diff --git a/projects/RabbitMQ.Client/client/api/QueueDeclareOk.cs b/projects/RabbitMQ.Client/client/api/QueueDeclareOk.cs
index dd20ebfa33..cfe2f6fa5e 100644
--- a/projects/RabbitMQ.Client/client/api/QueueDeclareOk.cs
+++ b/projects/RabbitMQ.Client/client/api/QueueDeclareOk.cs
@@ -52,17 +52,17 @@ public QueueDeclareOk(string queueName, uint messageCount, uint consumerCount)
///
/// Consumer count.
///
- public uint ConsumerCount { get; }
+ public readonly uint ConsumerCount;
///
/// Message count.
///
- public uint MessageCount { get; }
+ public readonly uint MessageCount;
///
/// Queue name.
///
- public string QueueName { get; }
+ public readonly string QueueName;
public static implicit operator string(QueueDeclareOk declareOk)
{
diff --git a/projects/RabbitMQ.Client/client/api/TcpClientAdapter.cs b/projects/RabbitMQ.Client/client/api/TcpClientAdapter.cs
index faf27dd520..0d0b3797c8 100644
--- a/projects/RabbitMQ.Client/client/api/TcpClientAdapter.cs
+++ b/projects/RabbitMQ.Client/client/api/TcpClientAdapter.cs
@@ -22,14 +22,16 @@ public TcpClientAdapter(Socket socket)
public virtual async Task ConnectAsync(string host, int port)
{
AssertSocket();
- IPAddress[] adds = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
+ IPAddress[] adds = await Dns.GetHostAddressesAsync(host)
+ .ConfigureAwait(false);
IPAddress ep = GetMatchingHost(adds, _sock.AddressFamily);
if (ep == default(IPAddress))
{
throw new ArgumentException($"No ip address could be resolved for {host}");
}
- await ConnectAsync(ep, port).ConfigureAwait(false);
+ await ConnectAsync(ep, port)
+ .ConfigureAwait(false);
}
public virtual Task ConnectAsync(IPAddress ep, int port)
diff --git a/projects/RabbitMQ.Client/client/events/AsyncEventHandler.cs b/projects/RabbitMQ.Client/client/events/AsyncEventHandler.cs
index 65786dfdf1..f172922eda 100644
--- a/projects/RabbitMQ.Client/client/events/AsyncEventHandler.cs
+++ b/projects/RabbitMQ.Client/client/events/AsyncEventHandler.cs
@@ -1,3 +1,34 @@
+// 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;
namespace RabbitMQ.Client.Events
diff --git a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
index 2dc046badc..7264f7e507 100644
--- a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
+++ b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs
@@ -53,25 +53,30 @@ public event AsyncEventHandler Unregistered
///Fires when the server confirms successful consumer cancellation.
public override async Task HandleBasicCancelOk(string consumerTag)
{
- await base.HandleBasicCancelOk(consumerTag).ConfigureAwait(false);
+ await base.HandleBasicCancelOk(consumerTag)
+ .ConfigureAwait(false);
if (!_unregisteredWrapper.IsEmpty)
{
- await _unregisteredWrapper.InvokeAsync(this, new ConsumerEventArgs(new[] { consumerTag })).ConfigureAwait(false);
+ await _unregisteredWrapper.InvokeAsync(this, new ConsumerEventArgs(new[] { consumerTag }))
+ .ConfigureAwait(false);
}
}
///Fires when the server confirms successful consumer registration.
public override async Task HandleBasicConsumeOk(string consumerTag)
{
- await base.HandleBasicConsumeOk(consumerTag).ConfigureAwait(false);
+ await base.HandleBasicConsumeOk(consumerTag)
+ .ConfigureAwait(false);
if (!_registeredWrapper.IsEmpty)
{
- await _registeredWrapper.InvokeAsync(this, new ConsumerEventArgs(new[] { consumerTag })).ConfigureAwait(false);
+ await _registeredWrapper.InvokeAsync(this, new ConsumerEventArgs(new[] { consumerTag }))
+ .ConfigureAwait(false);
}
}
///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, ReadOnlyMemory body)
{
// No need to call base, it's empty.
return _receivedWrapper.InvokeAsync(this, new BasicDeliverEventArgs(consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body));
@@ -80,10 +85,12 @@ public override Task HandleBasicDeliver(string consumerTag, ulong deliveryTag, b
///Fires the Shutdown event.
public override async Task HandleChannelShutdown(object channel, ShutdownEventArgs reason)
{
- await base.HandleChannelShutdown(channel, reason).ConfigureAwait(false);
+ await base.HandleChannelShutdown(channel, reason)
+ .ConfigureAwait(false);
if (!_shutdownWrapper.IsEmpty)
{
- await _shutdownWrapper.InvokeAsync(this, reason).ConfigureAwait(false);
+ await _shutdownWrapper.InvokeAsync(this, reason)
+ .ConfigureAwait(false);
}
}
}
diff --git a/projects/RabbitMQ.Client/client/events/BasicAckEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicAckEventArgs.cs
index 8783e5cc14..b7a7cfe990 100644
--- a/projects/RabbitMQ.Client/client/events/BasicAckEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicAckEventArgs.cs
@@ -37,13 +37,20 @@ namespace RabbitMQ.Client.Events
///from an AMQP broker within the Basic content-class.
public class BasicAckEventArgs : EventArgs
{
+ public BasicAckEventArgs(ulong deliveryTag, bool multiple)
+ : base()
+ {
+ DeliveryTag = deliveryTag;
+ Multiple = multiple;
+ }
+
///The sequence number of the acknowledged message, or
///the closed upper bound of acknowledged messages if multiple
///is true.
- public ulong DeliveryTag { get; set; }
+ public readonly ulong DeliveryTag;
///Whether this acknowledgement applies to one message
///or multiple messages.
- public bool Multiple { get; set; }
+ public readonly bool Multiple;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
index fbd497bb0f..9d0585cc24 100644
--- a/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicDeliverEventArgs.cs
@@ -37,11 +37,6 @@ namespace RabbitMQ.Client.Events
///from an AMQP broker within the Basic content-class.
public class BasicDeliverEventArgs : EventArgs
{
- ///Default constructor.
- public BasicDeliverEventArgs()
- {
- }
-
///Constructor that fills the event's properties from
///its arguments.
public BasicDeliverEventArgs(string consumerTag,
@@ -50,7 +45,7 @@ public BasicDeliverEventArgs(string consumerTag,
string exchange,
string routingKey,
in ReadOnlyBasicProperties properties,
- ReadOnlyMemory body)
+ ReadOnlyMemory body) : base()
{
ConsumerTag = consumerTag;
DeliveryTag = deliveryTag;
@@ -62,28 +57,28 @@ public BasicDeliverEventArgs(string consumerTag,
}
///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 ReadOnlyMemory Body;
///The consumer tag of the consumer that the message
///was delivered to.
- public string ConsumerTag { get; set; }
+ public readonly string ConsumerTag;
///The delivery tag for this delivery. See
///IChannel.BasicAck.
- public ulong DeliveryTag { get; set; }
+ public readonly ulong DeliveryTag;
///The exchange the message was originally published
///to.
- public string Exchange { get; set; }
+ public readonly string Exchange;
///The AMQP "redelivered" flag.
- public bool Redelivered { get; set; }
+ public readonly bool Redelivered;
///The routing key used when the message was
///originally published.
- public string RoutingKey { get; set; }
+ public readonly string RoutingKey;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/BasicNackEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicNackEventArgs.cs
index 4c6b863e91..60f4d9cfc9 100644
--- a/projects/RabbitMQ.Client/client/events/BasicNackEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicNackEventArgs.cs
@@ -37,17 +37,25 @@ namespace RabbitMQ.Client.Events
///from an AMQP broker within the Basic content-class.
public class BasicNackEventArgs : EventArgs
{
+ public BasicNackEventArgs(ulong deliveryTag, bool multiple, bool requeue)
+ : base()
+ {
+ DeliveryTag = deliveryTag;
+ Multiple = multiple;
+ Requeue = requeue;
+ }
+
///The sequence number of the nack'd message, or the
///closed upper bound of nack'd messages if multiple is
///true.
- public ulong DeliveryTag { get; set; }
+ public readonly ulong DeliveryTag;
///Whether this nack applies to one message or
///multiple messages.
- public bool Multiple { get; set; }
+ public readonly bool Multiple;
///Ignore
///Clients should ignore this field.
- public bool Requeue { get; set; }
+ public readonly bool Requeue;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs b/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
index 67220f1244..2a0bea1e13 100644
--- a/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/BasicReturnEventArgs.cs
@@ -37,26 +37,42 @@ namespace RabbitMQ.Client.Events
///from an AMQP broker within the Basic content-class.
public class BasicReturnEventArgs : EventArgs
{
+ public BasicReturnEventArgs(
+ ushort replyCode,
+ string replyText,
+ string exchange,
+ string routingKey,
+ ReadOnlyBasicProperties basicProperties,
+ ReadOnlyMemory body) : base()
+ {
+ ReplyCode = replyCode;
+ ReplyText = replyText;
+ Exchange = exchange;
+ RoutingKey = routingKey;
+ BasicProperties = basicProperties;
+ Body = body;
+ }
+
///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 ReadOnlyMemory 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;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs b/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs
index 61d47b8190..145e4255c5 100644
--- a/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs
@@ -45,13 +45,12 @@ protected BaseExceptionEventArgs(IDictionary detail, Exception e
///Access helpful information about the context in
///which the wrapped exception was thrown.
- public IDictionary Detail { get; }
+ public readonly IDictionary Detail;
///Access the wrapped exception.
- public Exception Exception { get; }
+ public readonly Exception Exception;
}
-
///Describes an exception that was thrown during the
///library's invocation of an application-supplied callback
///handler.
diff --git a/projects/RabbitMQ.Client/client/events/ConnectionBlockedEventArgs.cs b/projects/RabbitMQ.Client/client/events/ConnectionBlockedEventArgs.cs
index ca8ef8aeea..9ea17c6d9f 100644
--- a/projects/RabbitMQ.Client/client/events/ConnectionBlockedEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/ConnectionBlockedEventArgs.cs
@@ -46,6 +46,6 @@ public ConnectionBlockedEventArgs(string reason)
///
/// Access the reason why connection is blocked.
///
- public string Reason { get; }
+ public readonly string Reason;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/ConnectionRecoveryErrorEventArgs.cs b/projects/RabbitMQ.Client/client/events/ConnectionRecoveryErrorEventArgs.cs
index 2d6284c102..8252036a84 100644
--- a/projects/RabbitMQ.Client/client/events/ConnectionRecoveryErrorEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/ConnectionRecoveryErrorEventArgs.cs
@@ -40,6 +40,6 @@ public ConnectionRecoveryErrorEventArgs(Exception ex)
Exception = ex;
}
- public Exception Exception { get; }
+ public readonly Exception Exception;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/ConsumerEventArgs.cs b/projects/RabbitMQ.Client/client/events/ConsumerEventArgs.cs
index 9669a8b81e..13b354174c 100644
--- a/projects/RabbitMQ.Client/client/events/ConsumerEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/ConsumerEventArgs.cs
@@ -46,6 +46,6 @@ public ConsumerEventArgs(string[] consumerTags)
///Access the consumer-tags of the consumer the event
///relates to.
- public string[] ConsumerTags { get; }
+ public readonly string[] ConsumerTags;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/ConsumerTagChangedAfterRecoveryEventArgs.cs b/projects/RabbitMQ.Client/client/events/ConsumerTagChangedAfterRecoveryEventArgs.cs
index 10e88cd6d6..864b89cd14 100644
--- a/projects/RabbitMQ.Client/client/events/ConsumerTagChangedAfterRecoveryEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/ConsumerTagChangedAfterRecoveryEventArgs.cs
@@ -49,11 +49,11 @@ public ConsumerTagChangedAfterRecoveryEventArgs(string tagBefore, string tagAfte
///
/// Gets the tag before.
///
- public string TagBefore { get; }
+ public readonly string TagBefore;
///
/// Gets the tag after.
///
- public string TagAfter { get; }
+ public readonly string TagAfter;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs
index 681247ea56..4a6bad730b 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, ReadOnlyMemory body)
{
base.HandleBasicDeliver(consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body);
Received?.Invoke(
diff --git a/projects/RabbitMQ.Client/client/events/FlowControlEventArgs.cs b/projects/RabbitMQ.Client/client/events/FlowControlEventArgs.cs
index b8c95a218f..2d3ecd156b 100644
--- a/projects/RabbitMQ.Client/client/events/FlowControlEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/FlowControlEventArgs.cs
@@ -46,6 +46,6 @@ public FlowControlEventArgs(bool active)
///
/// Access the flow control setting.
///
- public bool Active { get; }
+ public readonly bool Active;
}
}
diff --git a/projects/RabbitMQ.Client/client/events/QueueNameChangedAfterRecoveryEventArgs.cs b/projects/RabbitMQ.Client/client/events/QueueNameChangedAfterRecoveryEventArgs.cs
index ead403354f..3edc891368 100644
--- a/projects/RabbitMQ.Client/client/events/QueueNameChangedAfterRecoveryEventArgs.cs
+++ b/projects/RabbitMQ.Client/client/events/QueueNameChangedAfterRecoveryEventArgs.cs
@@ -49,11 +49,11 @@ public QueueNameChangedAfterRecoveryEventArgs(string nameBefore, string nameAfte
///
/// Gets the name before.
///
- public string NameBefore { get; }
+ public readonly string NameBefore;
///
/// Gets the name after.
///
- public string NameAfter { get; }
+ public readonly string NameAfter;
}
}
diff --git a/projects/RabbitMQ.Client/client/framing/Channel.cs b/projects/RabbitMQ.Client/client/framing/Channel.cs
index a59cceb1e3..9b7246799e 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));
@@ -105,26 +100,6 @@ public override void _Private_ConnectionCloseOk()
ChannelSend(new ConnectionCloseOk());
}
- public override void _Private_ConnectionOpen(string virtualHost)
- {
- ChannelSend(new ConnectionOpen(virtualHost));
- }
-
- public override ValueTask _Private_ConnectionOpenAsync(string virtualHost)
- {
- return ModelSendAsync(new ConnectionOpen(virtualHost));
- }
-
- public override void _Private_ConnectionSecureOk(byte[] response)
- {
- ChannelSend(new ConnectionSecureOk(response));
- }
-
- public override void _Private_ConnectionStartOk(IDictionary clientProperties, string mechanism, byte[] response, string locale)
- {
- ChannelSend(new ConnectionStartOk(clientProperties, mechanism, response, locale));
- }
-
public override void _Private_UpdateSecret(byte[] newSecret, string reason)
{
ChannelRpc(new ConnectionUpdateSecret(newSecret, reason), ProtocolCommandId.ConnectionUpdateSecretOk);
@@ -234,19 +209,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 +236,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 +283,25 @@ 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();
- return true;
+ return HandleBasicGetEmpty(in cmd);
}
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 +314,7 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd)
}
case ProtocolCommandId.ChannelCloseOk:
{
- cmd.ReturnMethodBuffer();
- HandleChannelCloseOk();
+ HandleChannelCloseOk(in cmd);
return true;
}
case ProtocolCommandId.ChannelFlow:
@@ -373,8 +349,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..04e25b2e18
--- /dev/null
+++ b/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs
@@ -0,0 +1,484 @@
+// 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 _cancellationTokenSource;
+ private readonly ConfiguredTaskAwaitable _taskAwaitable;
+
+ protected readonly TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private bool _disposedValue;
+
+ public AsyncRpcContinuation(TimeSpan continuationTimeout)
+ {
+ /*
+ * Note: we can't use an ObjectPool for these because the netstandard2.0
+ * version of CancellationTokenSource can't be reset prior to checking
+ * in to the ObjectPool
+ */
+ _cancellationTokenSource = new CancellationTokenSource(continuationTimeout);
+
+ _cancellationTokenSource.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();
+ }
+
+ 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)
+ {
+ _cancellationTokenSource.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ internal class ConnectionSecureOrTuneAsyncRpcContinuation : AsyncRpcContinuation
+ {
+ public ConnectionSecureOrTuneAsyncRpcContinuation(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.ToArray());
+
+ _tcs.TrySetResult(result);
+ }
+ else if (cmd.CommandId == ProtocolCommandId.BasicGetEmpty)
+ {
+ _tcs.TrySetResult(null);
+ }
+ else
+ {
+ _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
+ }
+ }
+ finally
+ {
+ // Note: since we copy the body buffer above, 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..3c58f3b09c 100644
--- a/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs
+++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs
@@ -45,7 +45,7 @@ internal sealed class AutorecoveringChannel : IChannel, IRecoverable
private AutorecoveringConnection _connection;
private RecoveryAwareChannel _innerChannel;
private bool _disposed;
- private List _recordedConsumerTags = new List();
+ private readonly List _recordedConsumerTags = new();
private ushort _prefetchCountConsumer;
private ushort _prefetchCountGlobal;
@@ -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;
@@ -128,98 +122,66 @@ public IEnumerable ConsumerTags
get
{
ThrowIfDisposed();
+ // TODO should this be copied "on its way out"?
return _recordedConsumerTags;
}
}
- public int ChannelNumber
- {
- get
- {
- ThrowIfDisposed();
- return InnerChannel.ChannelNumber;
- }
- }
+ public int ChannelNumber => InnerChannel.ChannelNumber;
- public ShutdownEventArgs CloseReason
- {
- get
- {
- ThrowIfDisposed();
- return InnerChannel.CloseReason;
- }
- }
+ public ShutdownEventArgs CloseReason => InnerChannel.CloseReason;
public IBasicConsumer DefaultConsumer
{
- get
- {
- ThrowIfDisposed();
- return InnerChannel.DefaultConsumer;
- }
-
- set
- {
- ThrowIfDisposed();
- InnerChannel.DefaultConsumer = value;
- }
+ get => InnerChannel.DefaultConsumer;
+ set => InnerChannel.DefaultConsumer = value;
}
public bool IsClosed => !IsOpen;
- public bool IsOpen
- {
- get
- {
- ThrowIfDisposed();
- return _innerChannel != null && _innerChannel.IsOpen;
- }
- }
+ public bool IsOpen => _innerChannel != null && _innerChannel.IsOpen;
- public ulong NextPublishSeqNo
- {
- get
- {
- ThrowIfDisposed();
- return InnerChannel.NextPublishSeqNo;
- }
- }
+ public ulong NextPublishSeqNo => InnerChannel.NextPublishSeqNo;
+
+ public string CurrentQueue => InnerChannel.CurrentQueue;
- public string CurrentQueue
+ internal async ValueTask AutomaticallyRecoverAsync(AutorecoveringConnection conn, bool recoverConsumers,
+ bool recordedEntitiesSemaphoreHeld = false)
{
- get
+ if (false == recordedEntitiesSemaphoreHeld)
{
- ThrowIfDisposed();
- return InnerChannel.CurrentQueue;
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
}
- }
- internal void AutomaticallyRecover(AutorecoveringConnection conn, bool recoverConsumers)
- {
ThrowIfDisposed();
_connection = conn;
- var newChannel = conn.CreateNonRecoveringChannel();
+ RecoveryAwareChannel newChannel = await conn.CreateNonRecoveringChannelAsync()
+ .ConfigureAwait(false);
newChannel.TakeOver(_innerChannel);
if (_prefetchCountConsumer != 0)
{
- newChannel.BasicQos(0, _prefetchCountConsumer, false);
+ await newChannel.BasicQosAsync(0, _prefetchCountConsumer, false)
+ .ConfigureAwait(false);
}
if (_prefetchCountGlobal != 0)
{
- newChannel.BasicQos(0, _prefetchCountGlobal, true);
+ await newChannel.BasicQosAsync(0, _prefetchCountGlobal, true)
+ .ConfigureAwait(false);
}
if (_usesPublisherConfirms)
{
- newChannel.ConfirmSelect();
+ await newChannel.ConfirmSelectAsync()
+ .ConfigureAwait(false);
}
if (_usesTransactions)
{
- newChannel.TxSelect();
+ await newChannel.TxSelectAsync()
+ .ConfigureAwait(false);
}
/*
@@ -232,7 +194,8 @@ internal void AutomaticallyRecover(AutorecoveringConnection conn, bool recoverCo
if (recoverConsumers)
{
- _connection.RecoverConsumers(this, newChannel);
+ await _connection.RecoverConsumersAsync(this, newChannel, recordedEntitiesSemaphoreHeld)
+ .ConfigureAwait(false);
}
_innerChannel.RunRecoveryEventHandlers(this);
@@ -247,7 +210,31 @@ public void Close(ushort replyCode, string replyText, bool abort)
}
finally
{
- _connection.DeleteRecordedChannel(this);
+ _connection.DeleteRecordedChannel(this,
+ channelsSemaphoreHeld: false, recordedEntitiesSemaphoreHeld: false);
+ }
+ }
+
+ public ValueTask CloseAsync(ushort replyCode, string replyText, bool abort)
+ {
+ ThrowIfDisposed();
+ var args = new ShutdownEventArgs(ShutdownInitiator.Library, replyCode, replyText);
+ return CloseAsync(args, abort);
+ }
+
+ public async ValueTask CloseAsync(ShutdownEventArgs args, bool abort)
+ {
+ ThrowIfDisposed();
+ try
+ {
+ await _innerChannel.CloseAsync(args, abort)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ await _connection.DeleteRecordedChannelAsync(this,
+ channelsSemaphoreHeld: false, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
}
}
@@ -264,7 +251,6 @@ public void Dispose()
this.Abort();
_recordedConsumerTags.Clear();
- _recordedConsumerTags = null;
_connection = null;
_innerChannel = null;
_disposed = true;
@@ -273,33 +259,49 @@ 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();
- _connection.DeleteRecordedConsumer(consumerTag);
+ _connection.DeleteRecordedConsumer(consumerTag, recordedEntitiesSemaphoreHeld: false);
_innerChannel.BasicCancel(consumerTag);
}
+ public ValueTask BasicCancelAsync(string consumerTag)
+ {
+ ThrowIfDisposed();
+ _connection.DeleteRecordedConsumer(consumerTag, recordedEntitiesSemaphoreHeld: false);
+ return _innerChannel.BasicCancelAsync(consumerTag);
+ }
+
public void BasicCancelNoWait(string consumerTag)
{
ThrowIfDisposed();
- _connection.DeleteRecordedConsumer(consumerTag);
+ _connection.DeleteRecordedConsumer(consumerTag, recordedEntitiesSemaphoreHeld: false);
_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,
queue: queue, autoAck: autoAck, exclusive: exclusive, arguments: arguments);
- _connection.RecordConsumer(rc);
+ _connection.RecordConsumer(rc, recordedEntitiesSemaphoreHeld: false);
+ _recordedConsumerTags.Add(resultConsumerTag);
+ 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);
+ await _connection.RecordConsumerAsync(rc, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
_recordedConsumerTags.Add(resultConsumerTag);
return resultConsumerTag;
}
@@ -307,9 +309,15 @@ public string BasicConsume(
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 +337,7 @@ public ValueTask BasicPublishAsync(CachedString exchange, CachedStr
public void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
{
ThrowIfDisposed();
+
if (global)
{
_prefetchCountGlobal = prefetchCount;
@@ -337,46 +346,89 @@ 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 async ValueTask ConfirmSelectAsync()
+ {
+ await InnerChannel.ConfirmSelectAsync()
+ .ConfigureAwait(false);
+ _usesPublisherConfirms = true;
+ }
+
public void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments)
{
ThrowIfDisposed();
- _connection.RecordBinding(new RecordedBinding(false, destination, source, routingKey, arguments));
+ var recordedBinding = new RecordedBinding(false, destination, source, routingKey, arguments);
+ _connection.RecordBinding(recordedBinding, recordedEntitiesSemaphoreHeld: false);
_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)
+ .ConfigureAwait(false);
+ var recordedBinding = new RecordedBinding(false, destination, source, routingKey, arguments);
+ await _connection.RecordBindingAsync(recordedBinding, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ }
+
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);
- _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments));
+ InnerChannel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments);
+ var recordedExchange = new RecordedExchange(exchange, type, durable, autoDelete, arguments);
+ _connection.RecordExchange(recordedExchange, recordedEntitiesSemaphoreHeld: false);
+ }
+
+ 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)
+ .ConfigureAwait(false);
+ if (false == passive)
+ {
+ var recordedExchange = new RecordedExchange(exchange, type, durable, autoDelete, arguments);
+ await _connection.RecordExchangeAsync(recordedExchange, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ }
}
public void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- _innerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments);
- _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments));
+ InnerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments);
+ var recordedExchange = new RecordedExchange(exchange, type, durable, autoDelete, arguments);
+ _connection.RecordExchange(recordedExchange, recordedEntitiesSemaphoreHeld: false);
}
public void ExchangeDeclarePassive(string exchange)
@@ -385,21 +437,42 @@ public void ExchangeDeclarePassive(string exchange)
public void ExchangeDelete(string exchange, bool ifUnused)
{
InnerChannel.ExchangeDelete(exchange, ifUnused);
- _connection.DeleteRecordedExchange(exchange);
+ _connection.DeleteRecordedExchange(exchange, recordedEntitiesSemaphoreHeld: false);
+ }
+
+ public async ValueTask ExchangeDeleteAsync(string exchange, bool ifUnused)
+ {
+ await InnerChannel.ExchangeDeleteAsync(exchange, ifUnused)
+ .ConfigureAwait(false);
+ await _connection.DeleteRecordedExchangeAsync(exchange, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
}
public void ExchangeDeleteNoWait(string exchange, bool ifUnused)
{
InnerChannel.ExchangeDeleteNoWait(exchange, ifUnused);
- _connection.DeleteRecordedExchange(exchange);
+ _connection.DeleteRecordedExchange(exchange, recordedEntitiesSemaphoreHeld: false);
}
public void ExchangeUnbind(string destination, string source, string routingKey, IDictionary arguments)
{
ThrowIfDisposed();
- _connection.DeleteRecordedBinding(new RecordedBinding(false, destination, source, routingKey, arguments));
+ var recordedBinding = new RecordedBinding(false, destination, source, routingKey, arguments);
+ _connection.DeleteRecordedBinding(recordedBinding, recordedEntitiesSemaphoreHeld: false);
_innerChannel.ExchangeUnbind(destination, source, routingKey, arguments);
- _connection.DeleteAutoDeleteExchange(source);
+ _connection.DeleteAutoDeleteExchange(source, recordedEntitiesSemaphoreHeld: false);
+ }
+
+ public async ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments)
+ {
+ ThrowIfDisposed();
+ var recordedBinding = new RecordedBinding(false, destination, source, routingKey, arguments);
+ await _connection.DeleteRecordedBindingAsync(recordedBinding, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ await InnerChannel.ExchangeUnbindAsync(destination, source, routingKey, arguments)
+ .ConfigureAwait(false);
+ await _connection.DeleteAutoDeleteExchangeAsync(source, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
}
public void ExchangeUnbindNoWait(string destination, string source, string routingKey, IDictionary arguments)
@@ -408,7 +481,8 @@ public void ExchangeUnbindNoWait(string destination, string source, string routi
public void QueueBind(string queue, string exchange, string routingKey, IDictionary arguments)
{
ThrowIfDisposed();
- _connection.RecordBinding(new RecordedBinding(true, queue, exchange, routingKey, arguments));
+ var recordedBinding = new RecordedBinding(true, queue, exchange, routingKey, arguments);
+ _connection.RecordBinding(recordedBinding, recordedEntitiesSemaphoreHeld: false);
_innerChannel.QueueBind(queue, exchange, routingKey, arguments);
}
@@ -417,27 +491,35 @@ 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);
- _connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments));
+ QueueDeclareOk result = InnerChannel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments);
+ var recordedQueue = new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments);
+ _connection.RecordQueue(recordedQueue, recordedEntitiesSemaphoreHeld: false);
return result;
}
public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments)
{
- ThrowIfDisposed();
- _innerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments);
- _connection.RecordQueue(new RecordedQueue(queue, queue.Length == 0, durable, exclusive, autoDelete, arguments));
+ InnerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments);
+ var recordedQueue = new RecordedQueue(queue, queue.Length == 0, durable, exclusive, autoDelete, arguments);
+ _connection.RecordQueue(recordedQueue, recordedEntitiesSemaphoreHeld: false);
}
- 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)
+ .ConfigureAwait(false);
+ if (false == passive)
+ {
+ var recordedQueue = new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments);
+ await _connection.RecordQueueAsync(recordedQueue, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ }
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,41 +531,76 @@ public uint ConsumerCount(string queue)
public uint QueueDelete(string queue, bool ifUnused, bool ifEmpty)
{
- ThrowIfDisposed();
- uint result = _innerChannel.QueueDelete(queue, ifUnused, ifEmpty);
- _connection.DeleteRecordedQueue(queue);
+ uint result = InnerChannel.QueueDelete(queue, ifUnused, ifEmpty);
+ _connection.DeleteRecordedQueue(queue, recordedEntitiesSemaphoreHeld: false);
+ return result;
+ }
+
+ public async ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty)
+ {
+ uint result = await InnerChannel.QueueDeleteAsync(queue, ifUnused, ifEmpty);
+ await _connection.DeleteRecordedQueueAsync(queue, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
return result;
}
public void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty)
{
InnerChannel.QueueDeleteNoWait(queue, ifUnused, ifEmpty);
- _connection.DeleteRecordedQueue(queue);
+ _connection.DeleteRecordedQueue(queue, recordedEntitiesSemaphoreHeld: false);
}
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();
- _connection.DeleteRecordedBinding(new RecordedBinding(true, queue, exchange, routingKey, arguments));
+ var recordedBinding = new RecordedBinding(true, queue, exchange, routingKey, arguments);
+ _connection.DeleteRecordedBinding(recordedBinding, recordedEntitiesSemaphoreHeld: false);
_innerChannel.QueueUnbind(queue, exchange, routingKey, arguments);
- _connection.DeleteAutoDeleteExchange(exchange);
+ _connection.DeleteAutoDeleteExchange(exchange, recordedEntitiesSemaphoreHeld: false);
+ }
+
+ public async ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments)
+ {
+ ThrowIfDisposed();
+ var recordedBinding = new RecordedBinding(true, queue, exchange, routingKey, arguments);
+ await _connection.DeleteRecordedBindingAsync(recordedBinding, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ await _innerChannel.QueueUnbindAsync(queue, exchange, routingKey, arguments)
+ .ConfigureAwait(false);
+ await _connection.DeleteAutoDeleteExchangeAsync(exchange, recordedEntitiesSemaphoreHeld: false)
+ .ConfigureAwait(false);
}
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.Recording.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recording.cs
index 56d5027be8..c3c3e11f40 100644
--- a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recording.cs
+++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recording.cs
@@ -31,6 +31,8 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using RabbitMQ.Client.Impl;
namespace RabbitMQ.Client.Framing.Impl
@@ -38,7 +40,8 @@ namespace RabbitMQ.Client.Framing.Impl
#nullable enable
internal sealed partial class AutorecoveringConnection
{
- private readonly object _recordedEntitiesLock = new object();
+ private readonly SemaphoreSlim _recordedEntitiesSemaphore = new SemaphoreSlim(1, 1);
+ private readonly SemaphoreSlim _channelsSemaphore = new SemaphoreSlim(1, 1);
private readonly Dictionary _recordedExchanges = new Dictionary();
private readonly Dictionary _recordedQueues = new Dictionary();
private readonly HashSet _recordedBindings = new HashSet();
@@ -47,17 +50,90 @@ internal sealed partial class AutorecoveringConnection
internal int RecordedExchangesCount => _recordedExchanges.Count;
- internal void RecordExchange(in RecordedExchange exchange)
+ internal void RecordExchange(in RecordedExchange exchange,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
{
- _recordedExchanges[exchange.Name] = exchange;
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoRecordExchange(exchange);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoRecordExchange(exchange);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ internal async ValueTask RecordExchangeAsync(RecordedExchange exchange,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoRecordExchange(exchange);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoRecordExchange(exchange);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
}
}
- internal void DeleteRecordedExchange(string exchangeName)
+ private void DoRecordExchange(in RecordedExchange exchange)
+ {
+ _recordedExchanges[exchange.Name] = exchange;
+ }
+
+ internal void DeleteRecordedExchange(string exchangeName,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedExchange(exchangeName);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoDeleteRecordedExchange(exchangeName);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+
+ void DoDeleteRecordedExchange(string exchangeName)
{
_recordedExchanges.Remove(exchangeName);
@@ -66,26 +142,126 @@ internal void DeleteRecordedExchange(string exchangeName)
{
if (binding.Destination == exchangeName)
{
- DeleteRecordedBinding(binding);
- DeleteAutoDeleteExchange(binding.Source);
+ DeleteRecordedBinding(binding,
+ recordedEntitiesSemaphoreHeld: true);
+ DeleteAutoDeleteExchange(binding.Source,
+ recordedEntitiesSemaphoreHeld: true);
}
}
}
}
- internal void DeleteAutoDeleteExchange(string exchangeName)
+ internal async ValueTask DeleteRecordedExchangeAsync(string exchangeName,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
{
- if (_recordedExchanges.TryGetValue(exchangeName, out var recordedExchange) && recordedExchange.AutoDelete)
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ await DoDeleteRecordedExchangeAsync(exchangeName)
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ await DoDeleteRecordedExchangeAsync(exchangeName)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+
+ async Task DoDeleteRecordedExchangeAsync(string exchangeName)
+ {
+ _recordedExchanges.Remove(exchangeName);
+
+ // find bindings that need removal, check if some auto-delete exchanges might need the same
+ foreach (RecordedBinding binding in _recordedBindings.ToArray())
{
- if (!AnyBindingsOnExchange(exchangeName))
+ if (binding.Destination == exchangeName)
{
- // last binding where this exchange is the source is gone, remove recorded exchange if it is auto-deleted.
- _recordedExchanges.Remove(exchangeName);
+ await DeleteRecordedBindingAsync(binding,
+ recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
+ await DeleteAutoDeleteExchangeAsync(binding.Source,
+ recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
}
}
}
+ }
+
+ internal void DeleteAutoDeleteExchange(string exchangeName,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteAutoDeleteExchange(exchangeName);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoDeleteAutoDeleteExchange(exchangeName);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ internal async ValueTask DeleteAutoDeleteExchangeAsync(string exchangeName,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteAutoDeleteExchange(exchangeName);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoDeleteAutoDeleteExchange(exchangeName);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoDeleteAutoDeleteExchange(string exchangeName)
+ {
+ if (_recordedExchanges.TryGetValue(exchangeName, out var recordedExchange) && recordedExchange.AutoDelete)
+ {
+ if (!AnyBindingsOnExchange(exchangeName))
+ {
+ // last binding where this exchange is the source is gone, remove recorded exchange if it is auto-deleted.
+ _recordedExchanges.Remove(exchangeName);
+ }
+ }
bool AnyBindingsOnExchange(string exchange)
{
@@ -103,163 +279,583 @@ bool AnyBindingsOnExchange(string exchange)
internal int RecordedQueuesCount => _recordedQueues.Count;
- internal void RecordQueue(in RecordedQueue queue)
+ internal void RecordQueue(in RecordedQueue queue,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
{
- _recordedQueues[queue.Name] = queue;
+ DoRecordQueue(queue);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoRecordQueue(queue);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
}
}
- internal void DeleteRecordedQueue(string queueName)
+ internal async ValueTask RecordQueueAsync(RecordedQueue queue,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoRecordQueue(queue);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoRecordQueue(queue);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoRecordQueue(RecordedQueue queue)
+ {
+ _recordedQueues[queue.Name] = queue;
+ }
+
+ internal void DeleteRecordedQueue(string queueName,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedQueue(queueName);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoDeleteRecordedQueue(queueName);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+
+ void DoDeleteRecordedQueue(string queueName)
{
_recordedQueues.Remove(queueName);
// find bindings that need removal, check if some auto-delete exchanges might need the same
- foreach (var binding in _recordedBindings.ToArray())
+ foreach (RecordedBinding binding in _recordedBindings.ToArray())
{
if (binding.Destination == queueName)
{
- DeleteRecordedBinding(binding);
- DeleteAutoDeleteExchange(binding.Source);
+ DeleteRecordedBinding(binding,
+ recordedEntitiesSemaphoreHeld: true);
+ DeleteAutoDeleteExchange(binding.Source,
+ recordedEntitiesSemaphoreHeld: true);
}
}
}
}
- private void UpdateBindingsDestination(string oldName, string newName)
+ internal async ValueTask DeleteRecordedQueueAsync(string queueName,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ await DoDeleteRecordedQueueAsync(queueName)
+ .ConfigureAwait(false);
+ }
+ else
{
- foreach (RecordedBinding b in _recordedBindings.ToArray())
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
{
- if (b.Destination == oldName)
+ await DoDeleteRecordedQueueAsync(queueName)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+
+ async ValueTask DoDeleteRecordedQueueAsync(string queueName)
+ {
+ _recordedQueues.Remove(queueName);
+
+ // find bindings that need removal, check if some auto-delete exchanges might need the same
+ foreach (RecordedBinding binding in _recordedBindings.ToArray())
+ {
+ if (binding.Destination == queueName)
{
- _recordedBindings.Remove(b);
- _recordedBindings.Add(new RecordedBinding(newName, b));
+ await DeleteRecordedBindingAsync(binding,
+ recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
+ await DeleteAutoDeleteExchangeAsync(binding.Source,
+ recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
}
}
}
+
}
- private void UpdateConsumerQueue(string oldName, string newName)
+ internal void RecordBinding(in RecordedBinding binding,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoRecordBinding(binding);
+ }
+ else
{
- foreach (RecordedConsumer consumer in _recordedConsumers.Values.ToArray())
+ _recordedEntitiesSemaphore.Wait();
+ try
{
- if (consumer.Queue == oldName)
- {
- _recordedConsumers[consumer.ConsumerTag] = RecordedConsumer.WithNewQueueName(newName, consumer);
- }
+ DoRecordBinding(binding);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
}
}
}
- internal void RecordBinding(in RecordedBinding rb)
+ internal async ValueTask RecordBindingAsync(RecordedBinding binding,
+ bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
{
- _recordedBindings.Add(rb);
+ DoRecordBinding(binding);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoRecordBinding(binding);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
}
}
- internal void DeleteRecordedBinding(in RecordedBinding rb)
+ private void DoRecordBinding(in RecordedBinding binding)
{
- lock (_recordedEntitiesLock)
+ _recordedBindings.Add(binding);
+ }
+
+ internal void DeleteRecordedBinding(in RecordedBinding rb,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
{
- _recordedBindings.Remove(rb);
+ DoDeleteRecordedBinding(rb);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoDeleteRecordedBinding(rb);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
}
}
- internal void RecordConsumer(in RecordedConsumer consumer)
+ internal async ValueTask DeleteRecordedBindingAsync(RecordedBinding rb,
+ bool recordedEntitiesSemaphoreHeld)
{
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedBinding(rb);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoDeleteRecordedBinding(rb);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoDeleteRecordedBinding(in RecordedBinding rb)
+ {
+ _recordedBindings.Remove(rb);
+ }
+
+ internal void RecordConsumer(in RecordedConsumer consumer,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
if (!_config.TopologyRecoveryEnabled)
{
return;
}
- lock (_recordedEntitiesLock)
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoRecordConsumer(consumer);
+ }
+ else
{
- _recordedConsumers[consumer.ConsumerTag] = consumer;
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoRecordConsumer(consumer);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
}
}
- internal void DeleteRecordedConsumer(string consumerTag)
+ internal async ValueTask RecordConsumerAsync(RecordedConsumer consumer,
+ bool recordedEntitiesSemaphoreHeld)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (!_config.TopologyRecoveryEnabled)
{
return;
}
- lock (_recordedEntitiesLock)
+ if (recordedEntitiesSemaphoreHeld)
{
- if (_recordedConsumers.Remove(consumerTag, out var recordedConsumer))
+ DoRecordConsumer(consumer);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
{
- DeleteAutoDeleteQueue(recordedConsumer.Queue);
+ DoRecordConsumer(consumer);
}
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoRecordConsumer(in RecordedConsumer consumer)
+ {
+ _recordedConsumers[consumer.ConsumerTag] = consumer;
+ }
+
+ internal void DeleteRecordedConsumer(string consumerTag,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
}
- void DeleteAutoDeleteQueue(string queue)
+ if (!_config.TopologyRecoveryEnabled)
{
- if (_recordedQueues.TryGetValue(queue, out var recordedQueue) && recordedQueue.AutoDelete)
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedConsumer(consumerTag);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
{
- // last consumer on this connection is gone, remove recorded queue if it is auto-deleted.
- if (!AnyConsumersOnQueue(queue))
- {
- _recordedQueues.Remove(queue);
- }
+ DoDeleteRecordedConsumer(consumerTag);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ internal async ValueTask DeleteRecordedConsumerAsync(string consumerTag,
+ bool recordedEntitiesSemaphoreHeld)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (!_config.TopologyRecoveryEnabled)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedConsumer(consumerTag);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoDeleteRecordedConsumer(consumerTag);
}
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoDeleteRecordedConsumer(string consumerTag)
+ {
+ if (_recordedConsumers.Remove(consumerTag, out var recordedConsumer))
+ {
+ DeleteAutoDeleteQueue(recordedConsumer.Queue);
}
+ }
- bool AnyConsumersOnQueue(string queue)
+ private void DeleteAutoDeleteQueue(string queue)
+ {
+ if (_recordedQueues.TryGetValue(queue, out var recordedQueue) && recordedQueue.AutoDelete)
{
- foreach (var pair in _recordedConsumers)
+ // last consumer on this connection is gone, remove recorded queue if it is auto-deleted.
+ if (!AnyConsumersOnQueue(queue))
{
- if (pair.Value.Queue == queue)
- {
- return true;
- }
+ _recordedQueues.Remove(queue);
}
+ }
+ }
- return false;
+ private bool AnyConsumersOnQueue(string queue)
+ {
+ foreach (var pair in _recordedConsumers)
+ {
+ if (pair.Value.Queue == queue)
+ {
+ return true;
+ }
}
+
+ return false;
}
- private void UpdateConsumer(string oldTag, string newTag, in RecordedConsumer consumer)
+ private void RecordChannel(in AutorecoveringChannel channel,
+ bool channelsSemaphoreHeld = false)
{
- lock (_recordedEntitiesLock)
+ if (channelsSemaphoreHeld)
{
- // make sure server-generated tags are re-added
- _recordedConsumers.Remove(oldTag);
- _recordedConsumers.Add(newTag, consumer);
+ DoAddRecordedChannel(channel);
}
+ else
+ {
+ _channelsSemaphore.Wait();
+ try
+ {
+ DoAddRecordedChannel(channel);
+ }
+ finally
+ {
+ _channelsSemaphore.Release();
+ }
+ }
+ }
+
+ private async Task RecordChannelAsync(AutorecoveringChannel channel,
+ bool channelsSemaphoreHeld = false)
+ {
+ if (channelsSemaphoreHeld)
+ {
+ DoAddRecordedChannel(channel);
+ }
+ else
+ {
+ await _channelsSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoAddRecordedChannel(channel);
+ }
+ finally
+ {
+ _channelsSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoAddRecordedChannel(AutorecoveringChannel channel)
+ {
+ _channels.Add(channel);
}
- private void RecordChannel(AutorecoveringChannel m)
+ internal void DeleteRecordedChannel(in AutorecoveringChannel channel,
+ bool channelsSemaphoreHeld, bool recordedEntitiesSemaphoreHeld)
{
- lock (_channels)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedConsumers(channel);
+ }
+ else
+ {
+ _recordedEntitiesSemaphore.Wait();
+ try
+ {
+ DoDeleteRecordedConsumers(channel);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
+ }
+ }
+
+ if (channelsSemaphoreHeld)
{
- _channels.Add(m);
+ DoDeleteRecordedChannel(channel);
+ }
+ else
+ {
+ _channelsSemaphore.Wait();
+ try
+ {
+ DoDeleteRecordedChannel(channel);
+ }
+ finally
+ {
+ _channelsSemaphore.Release();
+ }
}
}
- internal void DeleteRecordedChannel(AutorecoveringChannel channel)
+ internal async Task DeleteRecordedChannelAsync(AutorecoveringChannel channel,
+ bool channelsSemaphoreHeld, bool recordedEntitiesSemaphoreHeld)
{
- lock (_recordedEntitiesLock)
+ if (_disposed)
{
- foreach (string ct in channel.ConsumerTags)
+ return;
+ }
+
+ if (recordedEntitiesSemaphoreHeld)
+ {
+ DoDeleteRecordedConsumers(channel);
+ }
+ else
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
{
- DeleteRecordedConsumer(ct);
+ DoDeleteRecordedConsumers(channel);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
}
}
- lock (_channels)
+ if (channelsSemaphoreHeld)
{
- _channels.Remove(channel);
+ DoDeleteRecordedChannel(channel);
}
+ else
+ {
+ await _channelsSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ DoDeleteRecordedChannel(channel);
+ }
+ finally
+ {
+ _channelsSemaphore.Release();
+ }
+ }
+ }
+
+ private void DoDeleteRecordedConsumers(in AutorecoveringChannel channel)
+ {
+ foreach (string ct in channel.ConsumerTags)
+ {
+ DeleteRecordedConsumer(ct, recordedEntitiesSemaphoreHeld: true);
+ }
+ }
+
+ private void DoDeleteRecordedChannel(in AutorecoveringChannel channel)
+ {
+ _channels.Remove(channel);
}
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recovery.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recovery.cs
index 733de82c78..668d5fc951 100644
--- a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recovery.cs
+++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.Recovery.cs
@@ -46,6 +46,7 @@ internal sealed partial class AutorecoveringConnection
private Task? _recoveryTask;
private CancellationTokenSource? _recoveryCancellationTokenSource;
+ // TODO dispose the CTS
private CancellationTokenSource RecoveryCancellationTokenSource => _recoveryCancellationTokenSource ??= new CancellationTokenSource();
private void HandleConnectionShutdown(object _, ShutdownEventArgs args)
@@ -70,12 +71,14 @@ private async Task RecoverConnectionAsync()
{
try
{
- var token = RecoveryCancellationTokenSource.Token;
+ CancellationToken token = RecoveryCancellationTokenSource.Token;
bool success;
do
{
- await Task.Delay(_config.NetworkRecoveryInterval, token).ConfigureAwait(false);
- success = TryPerformAutomaticRecovery();
+ await Task.Delay(_config.NetworkRecoveryInterval, token)
+ .ConfigureAwait(false);
+ success = await TryPerformAutomaticRecoveryAsync(token)
+ .ConfigureAwait(false);
} while (!success && !token.IsCancellationRequested);
}
catch (OperationCanceledException)
@@ -86,9 +89,11 @@ private async Task RecoverConnectionAsync()
{
ESLog.Error("Main recovery loop threw unexpected exception.", e);
}
-
- // clear recovery task
- _recoveryTask = null;
+ finally
+ {
+ // clear recovery task
+ _recoveryTask = null;
+ }
}
///
@@ -97,7 +102,7 @@ private async Task RecoverConnectionAsync()
///
private void StopRecoveryLoop()
{
- var task = _recoveryTask;
+ Task? task = _recoveryTask;
if (task is null)
{
return;
@@ -111,6 +116,28 @@ private void StopRecoveryLoop()
}
}
+ ///
+ /// Async cancels the main recovery loop and will block until the loop finishes, or the timeout
+ /// expires, to prevent Close operations overlapping with recovery operations.
+ ///
+ private async ValueTask StopRecoveryLoopAsync()
+ {
+ Task? task = _recoveryTask;
+ if (task is not null)
+ {
+ RecoveryCancellationTokenSource.Cancel();
+ try
+ {
+ await task.TimeoutAfter(_config.RequestedConnectionTimeout)
+ .ConfigureAwait(false);
+ }
+ catch (TimeoutException)
+ {
+ ESLog.Warn("Timeout while trying to stop background AutorecoveringConnection recovery loop.");
+ }
+ }
+ }
+
private static void HandleTopologyRecoveryException(TopologyRecoveryException e)
{
ESLog.Error("Topology recovery exception", e);
@@ -123,16 +150,19 @@ private static void HandleTopologyRecoveryException(TopologyRecoveryException e)
ESLog.Info($"Will not retry recovery because of {e.InnerException?.GetType().FullName}: it's not a known problem with connectivity, ignoring it", e);
}
- private bool TryPerformAutomaticRecovery()
+ // TODO propagate token
+ private async ValueTask TryPerformAutomaticRecoveryAsync(CancellationToken token)
{
ESLog.Info("Performing automatic recovery");
try
{
ThrowIfDisposed();
- if (TryRecoverConnectionDelegate())
+ if (await TryRecoverConnectionDelegate().ConfigureAwait(false))
{
- lock (_recordedEntitiesLock)
+ await _recordedEntitiesSemaphore.WaitAsync(token)
+ .ConfigureAwait(false);
+ try
{
ThrowIfDisposed();
if (_config.TopologyRecoveryEnabled)
@@ -145,13 +175,21 @@ private bool TryPerformAutomaticRecovery()
// 4. Recover consumers
using (var recoveryChannelFactory = new RecoveryChannelFactory(_innerConnection))
{
- RecoverExchanges(recoveryChannelFactory);
- RecoverQueues(recoveryChannelFactory);
- RecoverBindings(recoveryChannelFactory);
+ await RecoverExchangesAsync(recoveryChannelFactory, recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
+ await RecoverQueuesAsync(recoveryChannelFactory, recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
+ await RecoverBindingsAsync(recoveryChannelFactory, recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
}
}
- RecoverChannelsAndItsConsumers();
+ await RecoverChannelsAndItsConsumersAsync(recordedEntitiesSemaphoreHeld: true)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Release();
}
ESLog.Info("Connection recovery completed");
@@ -188,13 +226,16 @@ private bool TryPerformAutomaticRecovery()
return false;
}
- private bool TryRecoverConnectionDelegate()
+ private async ValueTask TryRecoverConnectionDelegate()
{
try
{
- var defunctConnection = _innerConnection;
+ Connection defunctConnection = _innerConnection;
IFrameHandler fh = _endpoints.SelectOne(_config.FrameHandlerFactory);
_innerConnection = new Connection(_config, fh);
+ // TODO cancellation token
+ await _innerConnection.OpenAsync()
+ .ConfigureAwait(false);
_innerConnection.TakeOver(defunctConnection);
return true;
}
@@ -204,6 +245,7 @@ private bool TryRecoverConnectionDelegate()
// Trigger recovery error events
if (!_connectionRecoveryErrorWrapper.IsEmpty)
{
+ // Note: recordedEntities semaphore is _NOT_ held at this point
_connectionRecoveryErrorWrapper.Invoke(this, new ConnectionRecoveryErrorEventArgs(e));
}
}
@@ -211,20 +253,44 @@ private bool TryRecoverConnectionDelegate()
return false;
}
- private void RecoverExchanges(RecoveryChannelFactory recoveryChannelFactory)
+ private async ValueTask RecoverExchangesAsync(RecoveryChannelFactory recoveryChannelFactory,
+ bool recordedEntitiesSemaphoreHeld = false)
{
- foreach (var recordedExchange in _recordedExchanges.Values.Where(x => _config.TopologyRecoveryFilter?.ExchangeFilter(x) ?? true))
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (false == recordedEntitiesSemaphoreHeld)
+ {
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
+ }
+
+ foreach (RecordedExchange recordedExchange in _recordedExchanges.Values.Where(x => _config.TopologyRecoveryFilter?.ExchangeFilter(x) ?? true))
{
try
{
- recordedExchange.Recover(recoveryChannelFactory.RecoveryChannel);
+ using (IChannel ch = await recoveryChannelFactory.CreateRecoveryChannelAsync().ConfigureAwait(false))
+ {
+ await recordedExchange.RecoverAsync(ch)
+ .ConfigureAwait(false);
+ }
}
catch (Exception ex)
{
if (_config.TopologyRecoveryExceptionHandler.ExchangeRecoveryExceptionHandler != null
&& _config.TopologyRecoveryExceptionHandler.ExchangeRecoveryExceptionCondition(recordedExchange, ex))
{
- _config.TopologyRecoveryExceptionHandler.ExchangeRecoveryExceptionHandler(recordedExchange, ex, this);
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _config.TopologyRecoveryExceptionHandler.ExchangeRecoveryExceptionHandler(recordedExchange, ex, this);
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
else
{
@@ -234,14 +300,30 @@ private void RecoverExchanges(RecoveryChannelFactory recoveryChannelFactory)
}
}
- private void RecoverQueues(RecoveryChannelFactory recoveryChannelFactory)
+ private async Task RecoverQueuesAsync(RecoveryChannelFactory recoveryChannelFactory,
+ bool recordedEntitiesSemaphoreHeld = false)
{
- foreach (var recordedQueue in _recordedQueues.Values.Where(x => _config.TopologyRecoveryFilter?.QueueFilter(x) ?? true).ToArray())
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (false == recordedEntitiesSemaphoreHeld)
+ {
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
+ }
+
+ foreach (RecordedQueue recordedQueue in _recordedQueues.Values.Where(x => _config.TopologyRecoveryFilter?.QueueFilter(x) ?? true).ToArray())
{
try
{
- var newName = recordedQueue.Recover(recoveryChannelFactory.RecoveryChannel);
- var oldName = recordedQueue.Name;
+ string newName = string.Empty;
+ using (IChannel ch = await recoveryChannelFactory.CreateRecoveryChannelAsync().ConfigureAwait(false))
+ {
+ newName = await recordedQueue.RecoverAsync(ch)
+ .ConfigureAwait(false);
+ }
+ string oldName = recordedQueue.Name;
if (oldName != newName)
{
@@ -255,13 +337,24 @@ private void RecoverQueues(RecoveryChannelFactory recoveryChannelFactory)
// see rabbitmq/rabbitmq-dotnet-client#43
if (recordedQueue.IsServerNamed)
{
- DeleteRecordedQueue(oldName);
+ DeleteRecordedQueue(oldName,
+ recordedEntitiesSemaphoreHeld: recordedEntitiesSemaphoreHeld);
}
- RecordQueue(new RecordedQueue(newName, recordedQueue));
+ RecordQueue(new RecordedQueue(newName, recordedQueue),
+ recordedEntitiesSemaphoreHeld: recordedEntitiesSemaphoreHeld);
- if (!_queueNameChangeAfterRecoveryWrapper.IsEmpty)
+ if (!_queueNameChangedAfterRecoveryWrapper.IsEmpty)
{
- _queueNameChangeAfterRecoveryWrapper.Invoke(this, new QueueNameChangedAfterRecoveryEventArgs(oldName, newName));
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _queueNameChangedAfterRecoveryWrapper.Invoke(this, new QueueNameChangedAfterRecoveryEventArgs(oldName, newName));
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
}
}
@@ -270,30 +363,86 @@ private void RecoverQueues(RecoveryChannelFactory recoveryChannelFactory)
if (_config.TopologyRecoveryExceptionHandler.QueueRecoveryExceptionHandler != null
&& _config.TopologyRecoveryExceptionHandler.QueueRecoveryExceptionCondition(recordedQueue, ex))
{
- _config.TopologyRecoveryExceptionHandler.QueueRecoveryExceptionHandler(recordedQueue, ex, this);
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _config.TopologyRecoveryExceptionHandler.QueueRecoveryExceptionHandler(recordedQueue, ex, this);
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
else
{
HandleTopologyRecoveryException(new TopologyRecoveryException($"Caught an exception while recovering queue '{recordedQueue}'", ex));
}
}
+
+ void UpdateBindingsDestination(string oldName, string newName)
+ {
+ foreach (RecordedBinding b in _recordedBindings.ToArray())
+ {
+ if (b.Destination == oldName)
+ {
+ _recordedBindings.Remove(b);
+ _recordedBindings.Add(new RecordedBinding(newName, b));
+ }
+ }
+ }
+
+ void UpdateConsumerQueue(string oldName, string newName)
+ {
+ foreach (RecordedConsumer consumer in _recordedConsumers.Values.ToArray())
+ {
+ if (consumer.Queue == oldName)
+ {
+ _recordedConsumers[consumer.ConsumerTag] = RecordedConsumer.WithNewQueueName(newName, consumer);
+ }
+ }
+ }
}
}
- private void RecoverBindings(RecoveryChannelFactory recoveryChannelFactory)
+ private async ValueTask RecoverBindingsAsync(RecoveryChannelFactory recoveryChannelFactory,
+ bool recordedEntitiesSemaphoreHeld = false)
{
- foreach (var binding in _recordedBindings.Where(x => _config.TopologyRecoveryFilter?.BindingFilter(x) ?? true))
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (false == recordedEntitiesSemaphoreHeld)
+ {
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
+ }
+
+ foreach (RecordedBinding binding in _recordedBindings.Where(x => _config.TopologyRecoveryFilter?.BindingFilter(x) ?? true))
{
try
{
- binding.Recover(recoveryChannelFactory.RecoveryChannel);
+ using (IChannel ch = await recoveryChannelFactory.CreateRecoveryChannelAsync().ConfigureAwait(false))
+ {
+ await binding.RecoverAsync(ch)
+ .ConfigureAwait(false);
+ }
}
catch (Exception ex)
{
if (_config.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionHandler != null
&& _config.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionCondition(binding, ex))
{
- _config.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionHandler(binding, ex, this);
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _config.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionHandler(binding, ex, this);
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
else
{
@@ -303,26 +452,55 @@ private void RecoverBindings(RecoveryChannelFactory recoveryChannelFactory)
}
}
- internal void RecoverConsumers(AutorecoveringChannel channelToRecover, IChannel channelToUse)
+ internal async ValueTask RecoverConsumersAsync(AutorecoveringChannel channelToRecover, IChannel channelToUse,
+ bool recordedEntitiesSemaphoreHeld = false)
{
- foreach (var consumer in _recordedConsumers.Values.Where(x => _config.TopologyRecoveryFilter?.ConsumerFilter(x) ?? true).ToArray())
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (false == recordedEntitiesSemaphoreHeld)
+ {
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
+ }
+
+ foreach (RecordedConsumer consumer in _recordedConsumers.Values.Where(x => _config.TopologyRecoveryFilter?.ConsumerFilter(x) ?? true).ToArray())
{
if (consumer.Channel != channelToRecover)
{
continue;
}
- _consumerAboutToBeRecovered.Invoke(this, new RecoveringConsumerEventArgs(consumer.ConsumerTag, consumer.Arguments));
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _consumerAboutToBeRecovered.Invoke(this, new RecoveringConsumerEventArgs(consumer.ConsumerTag, consumer.Arguments));
+ }
+ finally
+ {
+ _recordedEntitiesSemaphore.Wait();
+ }
- var oldTag = consumer.ConsumerTag;
+ string oldTag = consumer.ConsumerTag;
try
{
- var newTag = consumer.Recover(channelToUse);
- UpdateConsumer(oldTag, newTag, RecordedConsumer.WithNewConsumerTag(newTag, consumer));
+ string newTag = await consumer.RecoverAsync(channelToUse);
+ RecordedConsumer consumerWithNewConsumerTag = RecordedConsumer.WithNewConsumerTag(newTag, consumer);
+ UpdateConsumer(oldTag, newTag, consumerWithNewConsumerTag);
if (!_consumerTagChangeAfterRecoveryWrapper.IsEmpty)
{
- _consumerTagChangeAfterRecoveryWrapper.Invoke(this, new ConsumerTagChangedAfterRecoveryEventArgs(oldTag, newTag));
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _consumerTagChangeAfterRecoveryWrapper.Invoke(this, new ConsumerTagChangedAfterRecoveryEventArgs(oldTag, newTag));
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
}
catch (Exception ex)
@@ -330,7 +508,16 @@ internal void RecoverConsumers(AutorecoveringChannel channelToRecover, IChannel
if (_config.TopologyRecoveryExceptionHandler.ConsumerRecoveryExceptionHandler != null
&& _config.TopologyRecoveryExceptionHandler.ConsumerRecoveryExceptionCondition(consumer, ex))
{
- _config.TopologyRecoveryExceptionHandler.ConsumerRecoveryExceptionHandler(consumer, ex, this);
+ try
+ {
+ _recordedEntitiesSemaphore.Release();
+ _config.TopologyRecoveryExceptionHandler.ConsumerRecoveryExceptionHandler(consumer, ex, this);
+ }
+ finally
+ {
+ await _recordedEntitiesSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ }
}
else
{
@@ -338,16 +525,27 @@ internal void RecoverConsumers(AutorecoveringChannel channelToRecover, IChannel
}
}
}
+
+ void UpdateConsumer(string oldTag, string newTag, in RecordedConsumer consumer)
+ {
+ // make sure server-generated tags are re-added
+ _recordedConsumers.Remove(oldTag);
+ _recordedConsumers.Add(newTag, consumer);
+ }
}
- private void RecoverChannelsAndItsConsumers()
+ private async ValueTask RecoverChannelsAndItsConsumersAsync(bool recordedEntitiesSemaphoreHeld = false)
{
- lock (_channels)
+ if (false == recordedEntitiesSemaphoreHeld)
{
- foreach (AutorecoveringChannel m in _channels)
- {
- m.AutomaticallyRecover(this, _config.TopologyRecoveryEnabled);
- }
+ throw new InvalidOperationException("recordedEntitiesSemaphore must be held");
+ }
+
+ foreach (AutorecoveringChannel channel in _channels)
+ {
+ await channel.AutomaticallyRecoverAsync(this, _config.TopologyRecoveryEnabled,
+ recordedEntitiesSemaphoreHeld: recordedEntitiesSemaphoreHeld)
+ .ConfigureAwait(false);
}
}
@@ -361,23 +559,22 @@ public RecoveryChannelFactory(IConnection connection)
_connection = connection;
}
- public IChannel RecoveryChannel
+ public async ValueTask CreateRecoveryChannelAsync()
{
- get
+ if (_recoveryChannel == null)
{
- if (_recoveryChannel == null)
- {
- _recoveryChannel = _connection.CreateChannel();
- }
-
- if (_recoveryChannel.IsClosed)
- {
- _recoveryChannel.Dispose();
- _recoveryChannel = _connection.CreateChannel();
- }
+ _recoveryChannel = await _connection.CreateChannelAsync()
+ .ConfigureAwait(false);
+ }
- return _recoveryChannel;
+ if (_recoveryChannel.IsClosed)
+ {
+ _recoveryChannel.Dispose();
+ _recoveryChannel = await _connection.CreateChannelAsync()
+ .ConfigureAwait(false);
}
+
+ return _recoveryChannel;
}
public void Dispose()
diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs
index 625ba1b2d4..8746730a1f 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;
@@ -72,11 +73,24 @@ public AutorecoveringConnection(ConnectionConfig config, IEndpointResolver endpo
_recoverySucceededWrapper = new EventingWrapper("OnConnectionRecovery", onException);
_connectionRecoveryErrorWrapper = new EventingWrapper("OnConnectionRecoveryError", onException);
_consumerTagChangeAfterRecoveryWrapper = new EventingWrapper("OnConsumerRecovery", onException);
- _queueNameChangeAfterRecoveryWrapper = new EventingWrapper("OnQueueRecovery", onException);
+ _queueNameChangedAfterRecoveryWrapper = new EventingWrapper("OnQueueRecovery", onException);
ConnectionShutdown += HandleConnectionShutdown;
}
+ internal IConnection Open()
+ {
+ InnerConnection.Open();
+ return this;
+ }
+
+ internal async ValueTask OpenAsync()
+ {
+ await InnerConnection.OpenAsync()
+ .ConfigureAwait(false);
+ return this;
+ }
+
public event EventHandler RecoverySucceeded
{
add => _recoverySucceededWrapper.AddHandler(value);
@@ -122,12 +136,12 @@ public event EventHandler ConsumerTagC
}
private EventingWrapper _consumerTagChangeAfterRecoveryWrapper;
- public event EventHandler QueueNameChangeAfterRecovery
+ public event EventHandler QueueNameChangedAfterRecovery
{
- add => _queueNameChangeAfterRecoveryWrapper.AddHandler(value);
- remove => _queueNameChangeAfterRecoveryWrapper.RemoveHandler(value);
+ add => _queueNameChangedAfterRecoveryWrapper.AddHandler(value);
+ remove => _queueNameChangedAfterRecoveryWrapper.RemoveHandler(value);
}
- private EventingWrapper _queueNameChangeAfterRecoveryWrapper;
+ private EventingWrapper _queueNameChangedAfterRecoveryWrapper;
public event EventHandler RecoveringConsumer
{
@@ -158,7 +172,7 @@ public event EventHandler RecoveringConsumer
public IDictionary ServerProperties => InnerConnection.ServerProperties;
- public IList ShutdownReport => InnerConnection.ShutdownReport;
+ public IEnumerable ShutdownReport => InnerConnection.ShutdownReport;
public IProtocol Protocol => Endpoint.Protocol;
@@ -170,10 +184,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)
@@ -194,12 +218,37 @@ public void Close(ushort reasonCode, string reasonText, TimeSpan timeout, bool a
}
}
+ ///Asynchronous API-side invocation of connection.close with timeout.
+ public async ValueTask CloseAsync(ushort reasonCode, string reasonText, TimeSpan timeout, bool abort)
+ {
+ ThrowIfDisposed();
+ await StopRecoveryLoopAsync()
+ .ConfigureAwait(false);
+ if (_innerConnection.IsOpen)
+ {
+ await _innerConnection.CloseAsync(reasonCode, reasonText, timeout, abort)
+ .ConfigureAwait(false);
+ }
+ }
+
public IChannel CreateChannel()
{
EnsureIsOpen();
- AutorecoveringChannel m = new AutorecoveringChannel(this, CreateNonRecoveringChannel());
- RecordChannel(m);
- return m;
+ RecoveryAwareChannel recoveryAwareChannel = CreateNonRecoveringChannel();
+ AutorecoveringChannel channel = new AutorecoveringChannel(this, recoveryAwareChannel);
+ RecordChannel(channel);
+ return channel;
+ }
+
+ public async ValueTask CreateChannelAsync()
+ {
+ EnsureIsOpen();
+ RecoveryAwareChannel recoveryAwareChannel = await CreateNonRecoveringChannelAsync()
+ .ConfigureAwait(false);
+ AutorecoveringChannel channel = new AutorecoveringChannel(this, recoveryAwareChannel);
+ await RecordChannelAsync(channel, channelsSemaphoreHeld: false)
+ .ConfigureAwait(false);
+ return channel;
}
public void Dispose()
@@ -222,6 +271,8 @@ public void Dispose()
_channels.Clear();
_innerConnection = null;
_disposed = true;
+ _recordedEntitiesSemaphore.Dispose();
+ _channelsSemaphore.Dispose();
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs
index decf6ac84f..624fee7b7c 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);
@@ -64,19 +64,18 @@ internal abstract class ChannelBase : IChannel, IRecoverable
private ShutdownEventArgs _closeReason;
public ShutdownEventArgs CloseReason => Volatile.Read(ref _closeReason);
- internal IConsumerDispatcher ConsumerDispatcher { get; }
+ internal readonly IConsumerDispatcher ConsumerDispatcher;
protected ChannelBase(ConnectionConfig config, ISession session)
{
ContinuationTimeout = config.ContinuationTimeout;
ConsumerDispatcher = config.DispatchConsumersAsync ?
- (IConsumerDispatcher)new AsyncConsumerDispatcher(this, config.DispatchConsumerConcurrency) :
+ new AsyncConsumerDispatcher(this, config.DispatchConsumerConcurrency) :
new ConsumerDispatcher(this, config.DispatchConsumerConcurrency);
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,24 +184,80 @@ 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);
+ _Private_ChannelClose(reason.ReplyCode, reason.ReplyText, reason.ClassId, reason.MethodId);
}
k.Wait(TimeSpan.FromMilliseconds(10000));
- await ConsumerDispatcher.WaitForShutdownAsync().ConfigureAwait(false);
+
+ ConsumerDispatcher.WaitForShutdown();
+ }
+ catch (AlreadyClosedException)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ catch (IOException)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ catch (Exception)
+ {
+ if (!abort)
+ {
+ throw;
+ }
+ }
+ finally
+ {
+ ChannelShutdown -= k.OnConnectionShutdown;
+ }
+ }
+
+ public ValueTask CloseAsync(ushort replyCode, string replyText, bool abort)
+ {
+ var args = new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText);
+ return CloseAsync(args, abort);
+ }
+
+ public async ValueTask CloseAsync(ShutdownEventArgs args, bool abort)
+ {
+ using var k = new ChannelCloseAsyncRpcContinuation(ContinuationTimeout);
+ await _rpcSemaphore.WaitAsync()
+ .ConfigureAwait(false);
+ try
+ {
+ ChannelShutdown += k.OnConnectionShutdown;
+ Enqueue(k);
+ ConsumerDispatcher.Quiesce();
+
+ if (SetCloseReason(args))
+ {
+ var method = new ChannelClose(
+ args.ReplyCode, args.ReplyText, args.ClassId, args.MethodId);
+ await ModelSendAsync(method)
+ .ConfigureAwait(false);
+ }
+
+ bool result = await k;
+ Debug.Assert(result);
+
+ await ConsumerDispatcher.WaitForShutdownAsync()
+ .ConfigureAwait(false);
}
catch (AlreadyClosedException)
{
@@ -235,25 +282,33 @@ private async Task CloseAsync(ShutdownEventArgs reason, bool abort)
}
finally
{
+ _rpcSemaphore.Release();
ChannelShutdown -= k.OnConnectionShutdown;
}
}
internal async ValueTask ConnectionOpenAsync(string virtualHost)
{
- await _Private_ConnectionOpenAsync(virtualHost).TimeoutAfter(HandshakeContinuationTimeout);
+ var m = new ConnectionOpen(virtualHost);
+ await ModelSendAsync(m)
+ .TimeoutAfter(HandshakeContinuationTimeout)
+ .ConfigureAwait(false);
}
internal async ValueTask ConnectionSecureOkAsync(byte[] response)
{
- var k = new ConnectionSecureOrTuneContinuation();
- await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
- Enqueue(k);
+ await _rpcSemaphore.WaitAsync()
+ .ConfigureAwait(false);
try
{
+ using var k = new ConnectionSecureOrTuneAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
try
{
- _Private_ConnectionSecureOk(response);
+ var method = new ConnectionSecureOk(response);
+ await ModelSendAsync(method)
+ .ConfigureAwait(false);
}
catch (AlreadyClosedException)
{
@@ -273,14 +328,18 @@ 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);
+ await _rpcSemaphore.WaitAsync()
+ .ConfigureAwait(false);
try
{
+ using var k = new ConnectionSecureOrTuneAsyncRpcContinuation(ContinuationTimeout);
+ Enqueue(k);
+
try
{
- _Private_ConnectionStartOk(clientProperties, mechanism, response, locale);
+ var method = new ConnectionStartOk(clientProperties, mechanism, response, locale);
+ await ModelSendAsync(method)
+ .ConfigureAwait(false);
}
catch (AlreadyClosedException)
{
@@ -311,6 +370,29 @@ 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 +417,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)]
@@ -463,12 +548,13 @@ private void OnSessionShutdown(object sender, ShutdownEventArgs reason)
ConsumerDispatcher.Quiesce();
SetCloseReason(reason);
OnChannelShutdown(reason);
- ConsumerDispatcher.ShutdownAsync(reason).GetAwaiter().GetResult();
+ ConsumerDispatcher.Shutdown(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 +571,8 @@ protected virtual void Dispose(bool disposing)
{
// dispose managed resources
this.Abort();
+ ConsumerDispatcher.Dispose();
+ _rpcSemaphore.Dispose();
}
// dispose unmanaged resources
@@ -494,37 +582,41 @@ 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(ack._deliveryTag, 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(
+ nack._deliveryTag, nack._multiple, 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 +666,352 @@ 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.ToArray());
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ // Note: since we copy the body buffer above, 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 bool HandleBasicGetEmpty(in IncomingCommand cmd)
{
- var k = (SimpleBlockingRpcContinuation)_continuationQueue.Next();
- _basicRecoverOkWrapper.Invoke(this, EventArgs.Empty);
- k.HandleCommand(IncomingCommand.Empty);
+ if (_continuationQueue.TryPeek(out var k))
+ {
+ try
+ {
+ _continuationQueue.Next();
+ k.m_result = null;
+ k.HandleCommand(IncomingCommand.Empty); // release the continuation.
+ return true;
+ }
+ finally
+ {
+ cmd.ReturnBuffers();
+ }
+ }
+ else
+ {
+ return false;
+ }
}
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.Memory);
+ _basicReturnWrapper.Invoke(this, e);
+ }
+ }
+ finally
+ {
+ // Note: we can return all the buffers here since the event has been invoked and has returned
+ 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 = (ConnectionSecureOrTuneAsyncRpcContinuation)_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 = (ConnectionSecureOrTuneAsyncRpcContinuation)_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 +1025,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();
@@ -829,19 +1037,11 @@ protected bool HandleQueueDeclareOk(in IncomingCommand cmd)
public abstract void _Private_ConnectionCloseOk();
- public abstract void _Private_ConnectionOpen(string virtualHost);
+ public abstract void _Private_UpdateSecret(byte[] @newSecret, string @reason);
- public abstract ValueTask _Private_ConnectionOpenAsync(string virtualHost);
+ public abstract void _Private_ExchangeBind(string destination, string source, string routingKey, bool nowait, IDictionary arguments);
- public abstract void _Private_ConnectionSecureOk(byte[] response);
-
- public abstract void _Private_ConnectionStartOk(IDictionary clientProperties, string mechanism, byte[] response, string locale);
-
- public abstract void _Private_UpdateSecret(byte[] @newSecret, string @reason);
-
- public abstract void _Private_ExchangeBind(string destination, string source, string routingKey, bool nowait, IDictionary arguments);
-
- public abstract void _Private_ExchangeDeclare(string exchange, string type, bool passive, bool durable, bool autoDelete, bool @internal, bool nowait, IDictionary arguments);
+ public abstract void _Private_ExchangeDeclare(string exchange, string type, bool passive, bool durable, bool autoDelete, bool @internal, bool nowait, IDictionary arguments);
public abstract void _Private_ExchangeDelete(string exchange, bool ifUnused, bool nowait);
@@ -857,10 +1057,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 +1075,37 @@ public void BasicCancel(string consumerTag)
}
}
+ public async ValueTask BasicCancelAsync(string 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 +1117,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 +1125,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 +1138,38 @@ 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)
+ {
+ // TODO: Replace with flag
+ 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 +1189,31 @@ 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 +1291,22 @@ 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 +1314,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 +1329,64 @@ 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 +1397,29 @@ 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 +1435,29 @@ 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 +1468,29 @@ 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 +1508,56 @@ 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 +1567,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 +1587,27 @@ 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 +1618,127 @@ public uint QueuePurge(string queue)
return _Private_QueuePurge(queue, false);
}
+ public async ValueTask